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>
452 lines
13 KiB
Markdown
452 lines
13 KiB
Markdown
# 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
|
|
```gherkin
|
|
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
|
|
```gherkin
|
|
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
|
|
```gherkin
|
|
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
|
|
```gherkin
|
|
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
|
|
```gherkin
|
|
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
|
|
```gherkin
|
|
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
|
|
```sql
|
|
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
|
|
```sql
|
|
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
|
|
```sql
|
|
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
|
|
```sql
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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 |
|
|
|
|
```typescript
|
|
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)
|
|
|
|
```yaml
|
|
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](../00-vision-general/REQUERIMIENTOS-FUNCIONALES.md) - Seccion 5.12
|
|
- [VISION-PROYECTO.md](../00-vision-general/VISION-PROYECTO.md) - Modelo crecimiento
|
|
|
|
---
|
|
|
|
**Ultima Actualizacion:** 2026-01-10
|