Some checks failed
Build / Build Backend (push) Has been cancelled
Build / Build Mobile (TypeScript Check) (push) Has been cancelled
Lint / Lint Backend (push) Has been cancelled
Lint / Lint Mobile (push) Has been cancelled
Test / Backend E2E Tests (push) Has been cancelled
Test / Mobile Unit Tests (push) Has been cancelled
Build / Build Docker Image (push) Has been cancelled
- Add exports module with PDF/CSV/Excel generation - Add reports module for inventory analytics - Add POS integrations module - Add database migrations for exports, movements and integrations - Add GitHub Actions CI/CD workflow with Docker support - Add mobile export and reports screens with tests - Update epic documentation with traceability - Add deployment and security guides Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
13 KiB
13 KiB
MII-014: Sistema de Referidos
id: MII-014 type: Epic status: Completado priority: P1 phase: 4 story_points: 21 created_date: 2026-01-10 updated_date: 2026-01-13 simco_version: "4.0.0"
Metadata
| Campo | Valor |
|---|---|
| ID | MII-014 |
| Nombre | Sistema de Referidos |
| Fase | 4 - Crecimiento |
| Prioridad | P1 |
| Story Points | 21 |
| Estado | Completado |
1. Descripcion
Implementar un sistema de referidos multinivel que incentive el crecimiento organico, con recompensas justas y mecanismos anti-fraude.
Objetivo
Crear un motor de crecimiento viral donde usuarios existentes inviten a nuevos usuarios y ambos se beneficien.
2. Requerimientos Relacionados
| RF | Descripcion | Prioridad |
|---|---|---|
| FR-110 | Codigo de referido unico por usuario | P0 |
| FR-111 | Registro atribuido (nuevo usuario con codigo) | P0 |
| FR-112 | Condicion de recompensa (compra + primer inventario) | P0 |
| FR-113 | Recompensa break-even (1 credito por referido) | P0 |
| FR-114 | Multinivel configurable (niveles, %, limites) | P2 |
| FR-115 | Anti-fraude (duplicados, topes, holds) | P0 |
| FR-116 | Panel de referidos (invitaciones, referidos, creditos) | P1 |
3. Criterios de Aceptacion
AC-001: Codigo Unico
DADO que soy usuario registrado
CUANDO veo mi perfil o panel de referidos
ENTONCES tengo un codigo unico para compartir
Y puedo copiarlo o compartirlo facilmente
AC-002: Registro con Codigo
DADO que soy nuevo usuario
CUANDO me registro con un codigo de referido
ENTONCES quedo vinculado a quien me invito
Y ambos podemos ver la relacion
AC-003: Condiciones de Recompensa
DADO que invite a un usuario
CUANDO el referido completa:
1. Primera compra de creditos
2. Primer inventario exitoso
ENTONCES recibo mi recompensa
Y el referido tambien recibe bonus
AC-004: Recompensa de 1 Credito
DADO que mi referido cumplio las condiciones
CUANDO se activa la recompensa
ENTONCES recibo 1 credito en mi wallet
Y veo la transaccion en mi historial
AC-005: Panel de Referidos
DADO que he invitado usuarios
CUANDO veo mi panel de referidos
ENTONCES veo:
- Mi codigo de referido
- Lista de referidos y su status
- Creditos ganados totales
- Referidos pendientes de activacion
AC-006: Anti-fraude
DADO que el sistema detecta comportamiento sospechoso
CUANDO hay indicios de fraude
ENTONCES las recompensas quedan en hold
Y se notifica para revision manual
Y no se entregan creditos hasta verificar
4. Tareas Tecnicas
| ID | Tarea | Estimacion | Estado |
|---|---|---|---|
| T-001 | Crear modulo referrals en NestJS | 1 SP | Completado |
| T-002 | Implementar generador de codigos | 1 SP | Completado |
| T-003 | Crear entidades ReferralCode, ReferralTree | 2 SP | Completado |
| T-004 | Implementar vinculacion en registro | 2 SP | Completado |
| T-005 | Crear motor de condiciones | 3 SP | Completado |
| T-006 | Implementar sistema de recompensas | 2 SP | Completado |
| T-007 | Crear reglas anti-fraude | 3 SP | Completado |
| T-008 | Implementar panel mobile | 3 SP | Completado |
| T-009 | Crear compartir codigo | 1 SP | Completado |
| T-010 | Implementar multinivel (P2) | 3 SP | Completado |
5. Modelo de Datos
Tabla: referral_codes
CREATE TABLE referral_codes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) UNIQUE,
code VARCHAR(10) UNIQUE NOT NULL,
total_referrals INT DEFAULT 0,
total_activated INT DEFAULT 0,
total_rewards DECIMAL(12,4) DEFAULT 0,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW()
);
Tabla: referral_tree
CREATE TABLE referral_tree (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referrer_id UUID REFERENCES users(id),
referred_id UUID REFERENCES users(id) UNIQUE,
referral_code_id UUID REFERENCES referral_codes(id),
level INT DEFAULT 1, -- Para multinivel
status VARCHAR(20) DEFAULT 'PENDING',
first_purchase_at TIMESTAMP,
first_session_at TIMESTAMP,
activated_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW()
);
-- Status: PENDING, PURCHASE_DONE, ACTIVATED, FRAUD_HOLD, REJECTED
Tabla: referral_rewards
CREATE TABLE referral_rewards (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_id UUID REFERENCES referral_tree(id),
beneficiary_id UUID REFERENCES users(id),
reward_type VARCHAR(20), -- 'REFERRER', 'REFERRED', 'LEVEL_2', etc
amount DECIMAL(12,4),
status VARCHAR(20) DEFAULT 'PENDING',
released_at TIMESTAMP,
hold_reason VARCHAR(100),
created_at TIMESTAMP DEFAULT NOW()
);
-- Status: PENDING, RELEASED, HELD, CANCELLED
Tabla: referral_fraud_signals
CREATE TABLE referral_fraud_signals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_id UUID REFERENCES referral_tree(id),
signal_type VARCHAR(50),
signal_data JSONB,
severity VARCHAR(20), -- 'LOW', 'MEDIUM', 'HIGH'
reviewed BOOLEAN DEFAULT false,
reviewed_by UUID REFERENCES users(id),
reviewed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW()
);
6. Generador de Codigos
function generateReferralCode(userId: string): string {
// Formato: 3 letras + 4 numeros (ej: MII-A1B2)
const letters = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; // Sin I, O
const numbers = '0123456789';
let code = '';
for (let i = 0; i < 3; i++) {
code += letters[Math.floor(Math.random() * letters.length)];
}
for (let i = 0; i < 4; i++) {
code += numbers[Math.floor(Math.random() * numbers.length)];
}
return code; // ej: "ABC1234"
}
7. Motor de Condiciones
interface ActivationConditions {
firstPurchase: boolean;
firstSession: boolean;
minPurchaseAmount?: number;
minSessionsCount?: number;
}
async function checkActivation(referralId: string): Promise<boolean> {
const referral = await this.referralRepo.findOne(referralId);
const conditions = getActivationConditions();
const checks = {
firstPurchase: referral.first_purchase_at !== null,
firstSession: referral.first_session_at !== null
};
// Verificar condiciones minimas
if (conditions.minPurchaseAmount) {
const totalPurchased = await this.getTotalPurchased(referral.referred_id);
checks.minPurchase = totalPurchased >= conditions.minPurchaseAmount;
}
const allMet = Object.values(checks).every(v => v);
if (allMet && referral.status === 'PURCHASE_DONE') {
await this.activateReferral(referralId);
}
return allMet;
}
async function activateReferral(referralId: string) {
const referral = await this.referralRepo.findOne(referralId);
// Verificar anti-fraude
const fraudSignals = await this.checkFraudSignals(referral);
if (fraudSignals.length > 0) {
await this.holdReferral(referralId, fraudSignals);
return;
}
// Liberar recompensas
await this.releaseRewards(referralId);
// Actualizar status
await this.referralRepo.update(referralId, {
status: 'ACTIVATED',
activated_at: new Date()
});
}
8. Reglas Anti-fraude
| Senal | Severidad | Accion |
|---|---|---|
| Mismo dispositivo | HIGH | Hold + revision |
| Mismo IP | MEDIUM | Hold si > 3 |
| Email temporal | MEDIUM | Hold |
| Patron de registro | HIGH | Hold + revision |
| Compra minima exacta | LOW | Monitor |
| Sesion artificial | HIGH | Bloquear |
async function checkFraudSignals(referral: ReferralTree): Promise<FraudSignal[]> {
const signals: FraudSignal[] = [];
const referrer = await this.usersRepo.findOne(referral.referrer_id);
const referred = await this.usersRepo.findOne(referral.referred_id);
// Mismo dispositivo
if (referrer.device_fingerprint === referred.device_fingerprint) {
signals.push({
type: 'SAME_DEVICE',
severity: 'HIGH',
data: { fingerprint: referrer.device_fingerprint }
});
}
// Mismo IP
if (referrer.last_ip === referred.registration_ip) {
const sameIpCount = await this.countSameIPReferrals(referrer.id);
if (sameIpCount > 3) {
signals.push({
type: 'SAME_IP_MULTIPLE',
severity: 'MEDIUM',
data: { ip: referrer.last_ip, count: sameIpCount }
});
}
}
// Email temporal
if (isTemporaryEmail(referred.email)) {
signals.push({
type: 'TEMPORARY_EMAIL',
severity: 'MEDIUM',
data: { domain: referred.email.split('@')[1] }
});
}
return signals;
}
9. Endpoints API
| Metodo | Endpoint | Descripcion | Auth |
|---|---|---|---|
| GET | /referrals/my-code | Obtener mi codigo | JWT |
| GET | /referrals/stats | Estadisticas de referidos | JWT |
| GET | /referrals/list | Lista mis referidos | JWT |
| POST | /referrals/validate/:code | Validar codigo existe | No |
| GET | /referrals/rewards | Historial recompensas | JWT |
10. Pantallas Mobile
| Pantalla | Componentes |
|---|---|
| ReferralDashboard | Codigo, stats, CTA compartir |
| ReferralListScreen | Lista referidos, status |
| RewardsHistoryScreen | Recompensas, timeline |
| ShareCodeModal | Codigo, opciones compartir |
11. UI del Panel
┌─────────────────────────────────────────┐
│ INVITA Y GANA │
├─────────────────────────────────────────┤
│ │
│ Tu codigo de invitacion: │
│ ┌─────────────────────────────────┐ │
│ │ ABC1234 │ │
│ │ [📋] │ │
│ └─────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ │
│ │ 📤 COMPARTIR CODIGO │ │
│ └─────────────────────────────────┘ │
│ │
│ ───────────────────────────────────── │
│ │
│ TUS ESTADISTICAS │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 12 │ │ 8 │ │ 8.0 │ │
│ │Invitados│ │Activados│ │Creditos │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ ───────────────────────────────────── │
│ │
│ REFERIDOS RECIENTES │
│ │
│ 👤 Juan P. ✅ Activado +1.0 │
│ Hace 2 dias │
│ │
│ 👤 Maria L. ⏳ Pendiente │
│ Falta: primer inventario │
│ │
│ 👤 Carlos R. 💰 Compro │
│ Falta: primer inventario │
│ │
│ [Ver todos →] │
│ │
└─────────────────────────────────────────┘
12. Configuracion Multinivel (P2)
referral_config:
levels:
- level: 1
reward_percent: 100 # 1 credito completo
max_rewards: null # Sin limite
- level: 2
reward_percent: 25 # 0.25 creditos
max_rewards: 10 # Max 10 nivel 2
- level: 3
reward_percent: 10 # 0.1 creditos
max_rewards: 5 # Max 5 nivel 3
anti_fraud:
same_device_action: "HOLD"
same_ip_threshold: 3
min_session_duration: 30 # segundos
reward_hold_hours: 24
13. Dependencias
Entrada (Requiere)
- MII-002: Autenticacion (registro)
- MII-009: Wallet y Creditos (recompensas)
- MII-011/012/013: Pagos (primera compra)
- MII-005: Procesamiento IA (primera sesion)
Salida (Bloquea)
- MII-015: Admin (moderacion de fraude)
14. Riesgos
| Riesgo | Probabilidad | Impacto | Mitigacion |
|---|---|---|---|
| Fraude masivo | Alta | Alto | Anti-fraude robusto, holds |
| Abuso de creditos | Media | Medio | Limites, topes |
| Experiencia confusa | Media | Medio | UX clara, FAQs |
15. Referencias
- REQUERIMIENTOS-FUNCIONALES.md - Seccion 5.12
- VISION-PROYECTO.md - Modelo crecimiento
Ultima Actualizacion: 2026-01-10