MII-014: Sistema de Referidos
id: MII-014
type: Epic
status: Pendiente
priority: P1
phase: 4
story_points: 21
created_date: 2026-01-10
updated_date: 2026-01-10
simco_version: "4.0.0"
Metadata
| Campo |
Valor |
| ID |
MII-014 |
| Nombre |
Sistema de Referidos |
| Fase |
4 - Crecimiento |
| Prioridad |
P1 |
| Story Points |
21 |
| Estado |
Pendiente |
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 |
Pendiente |
| T-002 |
Implementar generador de codigos |
1 SP |
Pendiente |
| T-003 |
Crear entidades ReferralCode, ReferralTree |
2 SP |
Pendiente |
| T-004 |
Implementar vinculacion en registro |
2 SP |
Pendiente |
| T-005 |
Crear motor de condiciones |
3 SP |
Pendiente |
| T-006 |
Implementar sistema de recompensas |
2 SP |
Pendiente |
| T-007 |
Crear reglas anti-fraude |
3 SP |
Pendiente |
| T-008 |
Implementar panel mobile |
3 SP |
Pendiente |
| T-009 |
Crear compartir codigo |
1 SP |
Pendiente |
| T-010 |
Implementar multinivel (P2) |
3 SP |
Pendiente |
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
Ultima Actualizacion: 2026-01-10