# Plan Técnico — Módulo de Gestión de Créditos en Mora

**Fecha:** 2026-06-20  
**Autor:** Claude (basado en docs Recuperación Créditos v1.0 + arquitectura Novasis existente)  
**Stack:** NestJS + Prisma + PostgreSQL + React/MUI (monorepo Novasis)  
**Escala objetivo:** 10–100 agentes, hasta 50.000 cuentas en mora  
**Decisiones clave:**
- Monorepo — sin microservicios separados
- Extiende Panel Cobrador y módulo Cobranzas existentes
- Scoring por reglas explicables (TypeScript/SQL puro, sin Python ML inicial)
- WebSockets existentes + BullMQ/Redis para colas async

---

## 1. DIAGNÓSTICO — Qué ya existe vs. qué falta

| Componente | Estado actual | Acción |
|---|---|---|
| `promesas_pago` | Diseñada en plan-creditos-cobranzas, parcialmente implementada | Verificar/completar + extender |
| `config_mora` | Diseñada, puede estar en BD | Verificar + usar |
| `cobros.service.ts` | Existe con lógica de cobro | Extender con scoring |
| `CuentasCobrar.jsx` | Existe parcial | Extender con vista mora |
| Panel Cobrador | Existe (cuotas próximas, promesas, expandible) | Agregar priorización por score |
| `audit_logs` | Existe con AuditInterceptor global | Reutilizar — no cambiar |
| PosGateway (WebSocket) | Existe en `src/pos-gateway/` | Extender con eventos mora |
| Twilio (SMS/WhatsApp) | Integrado | Reutilizar para notificaciones |
| `vendedores_cobradores` | Existe con campo `tipo` | Reutilizar como agentes |
| `factura_cab` + `factura_cuotas` | Existen | Fuente de verdad de deuda |
| Scoring / IA | **No existe** | Nuevo módulo |
| Dashboard supervisor mora | **No existe** | Nuevo módulo |
| Campañas de cobranza | **No existe** | Nuevo módulo |
| Asignación automática | **No existe** | Nuevo módulo |
| Gestiones de cobranza (log) | **No existe** | Tabla nueva `gestiones_cobranza` |

---

## 2. ARQUITECTURA DEL MÓDULO

```
ERP Novasis (monorepo)
└── src/
    └── mora/                           ← módulo nuevo principal
        ├── mora.module.ts
        ├── mora.gateway.ts             ← WebSocket events (extiende patrón PosGateway)
        ├── mora.scheduler.ts           ← @Cron jobs nocturnos y periódicos
        │
        ├── cartera/
        │   ├── cartera.service.ts      ← Vista materializada de cuentas en mora
        │   └── cartera.controller.ts   ← GET /mora/cartera/hoy
        │
        ├── gestiones/
        │   ├── gestiones.service.ts    ← CRUD gestiones_cobranza
        │   └── gestiones.controller.ts ← POST /mora/gestiones
        │
        ├── scoring/
        │   └── scoring.service.ts      ← Cálculo priority_score (reglas TypeScript)
        │
        ├── asignacion/
        │   ├── asignacion.service.ts   ← Motor asignación + balance de carga
        │   └── asignacion.controller.ts
        │
        ├── campanas/
        │   ├── campanas.service.ts     ← CRUD + ejecución campañas
        │   └── campanas.controller.ts
        │
        ├── dashboards/
        │   ├── supervisor.service.ts   ← KPIs en tiempo real
        │   ├── gerencia.service.ts     ← KPIs ejecutivos + forecast
        │   └── dashboards.controller.ts
        │
        └── dto/
            ├── crear-gestion.dto.ts
            ├── crear-promesa.dto.ts
            └── asignar-cartera.dto.ts
```

**Frontend (React/MUI):**
```
src/pages/
├── mora/
│   ├── PanelCobradorMora.jsx     ← Extiende panel cobrador existente
│   ├── PanelSupervisorMora.jsx   ← Dashboard supervisor en tiempo real
│   ├── PanelGerenciaMora.jsx     ← KPIs ejecutivos + forecast
│   ├── FichaCuentaMora.jsx       ← Detalle completo cuenta morosa
│   └── ConfigMora.jsx            ← Admin: reglas scoring, segmentos, campañas
│
src/components/mora/
│   ├── CarteraTable.jsx          ← Tabla priorizada con score visual
│   ├── GestionForm.jsx           ← Form registro gestión rápida
│   ├── PromesaForm.jsx           ← Form promesa de pago
│   ├── ScoreBadge.jsx            ← Badge visual del priority_score
│   ├── KpiCard.jsx               ← Tarjeta KPI reutilizable
│   └── ForecastChart.jsx         ← Gráfico forecast recupero
```

---

## 3. BASE DE DATOS — Tablas nuevas y modificaciones

### 3.1 Tabla: `cuentas_mora`

Vista consolidada de cuentas con cuotas vencidas. Una fila por cliente+cobrador con mora activa.

```sql
CREATE TABLE cuentas_mora (
  id                    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  empresa_id            UUID NOT NULL REFERENCES empresas(id),
  cliente_id            UUID NOT NULL REFERENCES clientes(id),
  cobrador_id           UUID REFERENCES vendedores_cobradores(id),  -- agente asignado
  
  -- Métricas de mora (calculadas y actualizadas por job)
  dias_mora             SMALLINT NOT NULL DEFAULT 0,      -- max días vencidos
  monto_vencido         DECIMAL(19,4) NOT NULL DEFAULT 0, -- capital vencido total
  cuotas_vencidas       SMALLINT NOT NULL DEFAULT 0,      -- cantidad cuotas vencidas
  monto_mora_calculado  DECIMAL(19,4) DEFAULT 0,          -- interés mora acumulado
  
  -- Scoring IA (calculado por scoring.service.ts)
  priority_score        DECIMAL(10,4) DEFAULT 0,          -- 0-100, mayor = más urgente
  score_actualizado_en  TIMESTAMPTZ,
  
  -- Estado operativo
  estado                VARCHAR(30) NOT NULL DEFAULT 'activa',
  -- activa | en_gestion | con_promesa | promesa_vencida | regularizada | incobrable | escalada
  segmento              VARCHAR(20),
  -- temprana (1-30d) | media (31-60d) | critica (61-90d) | judicial (91d+)
  
  -- Contactabilidad
  tiene_telefono        BOOLEAN DEFAULT false,
  tiene_whatsapp        BOOLEAN DEFAULT false,
  tiene_email           BOOLEAN DEFAULT false,
  contactos_semana      SMALLINT DEFAULT 0,  -- para control de saturación
  ultimo_contacto_en    TIMESTAMPTZ,
  
  -- Promesa activa vinculada
  promesa_activa_id     UUID REFERENCES promesas_pago(id),
  
  -- Observaciones del agente
  notas_operativas      TEXT,
  
  creado_en             TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  actualizado_en        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  creado_por            UUID REFERENCES usuarios(id),

  UNIQUE(empresa_id, cliente_id)  -- Una sola cuenta mora activa por cliente/empresa
);

CREATE INDEX idx_cuentas_mora_empresa_cobrador ON cuentas_mora(empresa_id, cobrador_id);
CREATE INDEX idx_cuentas_mora_score ON cuentas_mora(priority_score DESC);
CREATE INDEX idx_cuentas_mora_estado ON cuentas_mora(estado);
CREATE INDEX idx_cuentas_mora_dias ON cuentas_mora(dias_mora DESC);
```

### 3.2 Tabla: `gestiones_cobranza`

Log inmutable de cada gestión realizada por el agente. Es la fuente de verdad operativa.

```sql
CREATE TABLE gestiones_cobranza (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  empresa_id      UUID NOT NULL REFERENCES empresas(id),
  cuenta_mora_id  UUID NOT NULL REFERENCES cuentas_mora(id),
  agente_id       UUID NOT NULL REFERENCES vendedores_cobradores(id),
  
  canal           VARCHAR(30) NOT NULL,
  -- llamada | whatsapp | sms | email | visita | carta | automatico
  resultado       VARCHAR(40) NOT NULL,
  -- contactado | no_contesta | numero_incorrecto | promesa_creada | pago_recibido
  -- rechaza_pagar | buzon | colgó | sin_resultado
  
  notas           TEXT,
  duracion_seg    SMALLINT,           -- duración llamada en segundos
  proxima_accion_en TIMESTAMPTZ,      -- cuándo el agente debe volver a gestionar
  
  -- Referencia a entidades generadas en esta gestión
  promesa_id      UUID REFERENCES promesas_pago(id),
  recibo_id       UUID,               -- si se registró pago
  
  -- Contexto del momento (snapshot para auditoría)
  score_al_momento    DECIMAL(10,4),
  dias_mora_al_momento SMALLINT,
  
  creado_en       TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  creado_por      UUID NOT NULL REFERENCES usuarios(id)
  -- SIN actualizado_en ni eliminado_en: gestiones son inmutables
);

CREATE INDEX idx_gestiones_empresa_cuenta ON gestiones_cobranza(empresa_id, cuenta_mora_id);
CREATE INDEX idx_gestiones_agente ON gestiones_cobranza(agente_id, creado_en DESC);
CREATE INDEX idx_gestiones_canal ON gestiones_cobranza(canal);
```

### 3.3 Tabla: `segmentos_mora` (configuración)

```sql
CREATE TABLE segmentos_mora (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  empresa_id      UUID NOT NULL REFERENCES empresas(id),
  nombre          VARCHAR(50) NOT NULL,   -- "Temprana", "Media", "Crítica", "Judicial"
  dias_desde      SMALLINT NOT NULL,
  dias_hasta      SMALLINT,              -- NULL = sin límite
  color_hex       VARCHAR(7),            -- para UI (ej: "#F59E0B")
  icono           VARCHAR(50),           -- nombre icono MUI
  orden           SMALLINT DEFAULT 0,
  activo          BOOLEAN DEFAULT true,
  creado_en       TIMESTAMPTZ DEFAULT NOW()
);

-- Datos iniciales
INSERT INTO segmentos_mora (nombre, dias_desde, dias_hasta, color_hex) VALUES
  ('Temprana',  1,  30, '#F59E0B'),
  ('Media',    31,  60, '#F97316'),
  ('Crítica',  61,  90, '#EF4444'),
  ('Judicial', 91, NULL,'#7C3AED');
```

### 3.4 Tabla: `campanas_cobranza`

```sql
CREATE TABLE campanas_cobranza (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  empresa_id      UUID NOT NULL REFERENCES empresas(id),
  nombre          VARCHAR(150) NOT NULL,
  canal           VARCHAR(30) NOT NULL,   -- whatsapp | sms | email
  plantilla_msg   TEXT NOT NULL,          -- con variables: {{nombre}}, {{dias_mora}}, {{monto}}
  
  -- Filtros de target
  segmento_id     UUID REFERENCES segmentos_mora(id),
  dias_mora_min   SMALLINT,
  dias_mora_max   SMALLINT,
  monto_min       DECIMAL(19,4),
  cobrador_id     UUID REFERENCES vendedores_cobradores(id),  -- NULL = todos
  
  -- Ejecución
  estado          VARCHAR(20) DEFAULT 'borrador',  -- borrador | activa | pausada | completada
  ejecutar_en     TIMESTAMPTZ,   -- programada o NULL = manual
  ejecutada_en    TIMESTAMPTZ,
  total_enviados  INT DEFAULT 0,
  total_errores   INT DEFAULT 0,
  
  creado_por      UUID REFERENCES usuarios(id),
  creado_en       TIMESTAMPTZ DEFAULT NOW(),
  actualizado_en  TIMESTAMPTZ DEFAULT NOW()
);
```

### 3.5 Tabla: `reglas_scoring_mora` (configuración del motor de scoring)

```sql
CREATE TABLE reglas_scoring_mora (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  empresa_id      UUID NOT NULL UNIQUE REFERENCES empresas(id),
  
  -- Pesos (deben sumar ≤ 100)
  peso_dias_mora          DECIMAL(5,2) DEFAULT 20.0,
  peso_monto_vencido      DECIMAL(5,2) DEFAULT 25.0,
  peso_probabilidad_pago  DECIMAL(5,2) DEFAULT 25.0,
  peso_contactabilidad    DECIMAL(5,2) DEFAULT 10.0,
  peso_promesa_vencida    DECIMAL(5,2) DEFAULT 10.0,
  peso_valor_cliente      DECIMAL(5,2) DEFAULT 5.0,
  peso_riesgo_saturacion  DECIMAL(5,2) DEFAULT 5.0,  -- negativo (penalización)
  
  -- Normalización de días mora
  dias_mora_max_ref       SMALLINT DEFAULT 180,   -- 180 días = score 100% en días
  
  -- Control de saturación
  max_contactos_semana    SMALLINT DEFAULT 5,     -- bloqueo si supera este límite
  
  activo                  BOOLEAN DEFAULT true,
  actualizado_en          TIMESTAMPTZ DEFAULT NOW(),
  actualizado_por         UUID REFERENCES usuarios(id)
);
```

### 3.6 Modificaciones a tablas existentes

```sql
-- promesas_pago: agregar referencia a cuenta_mora y canal
ALTER TABLE promesas_pago
  ADD COLUMN IF NOT EXISTS cuenta_mora_id UUID REFERENCES cuentas_mora(id),
  ADD COLUMN IF NOT EXISTS canal_registro  VARCHAR(30);  -- whatsapp | llamada | visita

-- clientes: campo contactabilidad para scoring
ALTER TABLE clientes
  ADD COLUMN IF NOT EXISTS tiene_whatsapp     BOOLEAN DEFAULT false,
  ADD COLUMN IF NOT EXISTS whatsapp_validado  BOOLEAN DEFAULT false;
  -- telefono y celular ya existen en personas
```

---

## 4. LÓGICA DE SCORING (scoring.service.ts)

Motor de priorización por reglas, calculado en TypeScript. Sin dependencias externas.

```typescript
/**
 * Calcula el priority_score de una cuenta morosa.
 * Score de 0 a 100. Mayor score = mayor urgencia de gestión.
 * Basado en las reglas configuradas en reglas_scoring_mora.
 */
async calcularScore(
  cuentaMora: CuentaMoraConRelaciones,
  reglas: ReglasScoringMora
): Promise<number> {

  // 1. Score por días de mora (normalizado, más días = más urgente hasta el ref)
  const scoreDias = Math.min(cuentaMora.dias_mora / reglas.dias_mora_max_ref, 1) * 100;

  // 2. Score por monto vencido (normalizado contra el máximo de la cartera activa)
  const maxMonto = await this.getMaxMontoCartera(cuentaMora.empresa_id);
  const scoreMonto = (cuentaMora.monto_vencido / maxMonto) * 100;

  // 3. Probabilidad de pago (regla simple inicial):
  //    - Si tiene promesa activa vigente → 80
  //    - Si pagó la última cuota antes de vencer → 60
  //    - Sin historial → 40
  //    - Promesa vencida incumplida → 20
  const scoreProbabilidad = await this.calcularProbabilidadPago(cuentaMora);

  // 4. Contactabilidad: puntos por cada canal disponible
  let scoreContactabilidad = 0;
  if (cuentaMora.tiene_telefono) scoreContactabilidad += 40;
  if (cuentaMora.tiene_whatsapp) scoreContactabilidad += 40;
  if (cuentaMora.tiene_email)    scoreContactabilidad += 20;

  // 5. Promesa vencida incumplida: urgencia inmediata
  const tienePromesaVencida = await this.tienepromesaVencida(cuentaMora.id);
  const scorePromesaVencida  = tienePromesaVencida ? 100 : 0;

  // 6. Valor del cliente (basado en historial de compras)
  const scoreValorCliente = await this.calcularValorCliente(cuentaMora.cliente_id);

  // 7. Riesgo de saturación (penalización si supera contactos máximos)
  const contactosSemana    = cuentaMora.contactos_semana;
  const maxContactos       = reglas.max_contactos_semana;
  const penalizacionSatur  = contactosSemana >= maxContactos ? 100 : (contactosSemana / maxContactos) * 50;

  // Score final ponderado
  const score =
    (scoreDias           * reglas.peso_dias_mora          / 100) +
    (scoreMonto          * reglas.peso_monto_vencido       / 100) +
    (scoreProbabilidad   * reglas.peso_probabilidad_pago   / 100) +
    (scoreContactabilidad* reglas.peso_contactabilidad     / 100) +
    (scorePromesaVencida * reglas.peso_promesa_vencida     / 100) +
    (scoreValorCliente   * reglas.peso_valor_cliente       / 100) -
    (penalizacionSatur   * reglas.peso_riesgo_saturacion   / 100);

  return Math.max(0, Math.min(100, score));
}
```

**Jobs de recálculo (mora.scheduler.ts):**

```typescript
// Recálculo masivo nocturno (2 AM)
@Cron('0 2 * * *')
async recalcularCarteraCompleta(): Promise<void>

// Sincronización de cuentas mora desde factura_cuotas (cada hora)
@Cron('0 * * * *')
async sincronizarCuentasNuevas(): Promise<void>

// Detectar promesas vencidas y actualizar estado (cada 30 min)
@Cron('*/30 * * * *')
async detectarPromesasVencidas(): Promise<void>
```

---

## 5. ENDPOINTS API

### Módulo Cartera
```
GET    /api/v1/mora/cartera/hoy              → cartera priorizada del agente autenticado
GET    /api/v1/mora/cartera/agente/:id       → cartera de agente específico (supervisor)
GET    /api/v1/mora/cuentas/:id              → ficha completa de cuenta morosa
PATCH  /api/v1/mora/cuentas/:id/notas        → actualizar notas operativas
POST   /api/v1/mora/sincronizar              → forzar sync desde factura_cuotas (admin)
```

### Módulo Gestiones
```
POST   /api/v1/mora/gestiones                → registrar gestión de cobranza
GET    /api/v1/mora/gestiones/cuenta/:id     → historial de gestiones de cuenta
GET    /api/v1/mora/gestiones/agente/:id     → gestiones por agente (supervisor)
```

### Módulo Promesas (extiende el existente)
```
POST   /api/v1/mora/promesas                 → registrar promesa de pago con canal
GET    /api/v1/mora/promesas/vencidas        → promesas vencidas sin cumplir (alerta)
GET    /api/v1/mora/promesas/hoy             → promesas que vencen hoy
```

### Módulo Asignación
```
POST   /api/v1/mora/asignacion/ejecutar      → asignación automática completa
POST   /api/v1/mora/asignacion/manual        → asignar cuentas específicas a agente
GET    /api/v1/mora/asignacion/balance       → capacidad y carga por agente
```

### Módulo Dashboards
```
GET    /api/v1/mora/dashboards/cobrador      → KPIs del agente autenticado (hoy)
GET    /api/v1/mora/dashboards/supervisor    → KPIs equipo en tiempo real
GET    /api/v1/mora/dashboards/gerencia      → KPIs ejecutivos + forecast mensual
```

### Módulo Campañas
```
GET    /api/v1/mora/campanas                 → listado campañas
POST   /api/v1/mora/campanas                 → crear campaña
POST   /api/v1/mora/campanas/:id/ejecutar    → ejecutar campaña manualmente
```

### Módulo Scoring
```
GET    /api/v1/mora/scoring/config           → obtener reglas de scoring activas
PUT    /api/v1/mora/scoring/config           → actualizar pesos (admin)
POST   /api/v1/mora/scoring/recalcular/:id   → recalcular score de cuenta puntual
```

---

## 6. EVENTOS WEBSOCKET (extiende PosGateway)

**Nuevo archivo:** `src/mora/mora.gateway.ts`

```typescript
// Eventos emitidos (servidor → cliente)
'mora:cartera_actualizada'    // cuando score de agente cambia
'mora:promesa_vencida'        // cuando una promesa vence sin pago
'mora:pago_recibido'          // cuando llega pago (desde cobros.service)
'mora:cuenta_asignada'        // cuando se asigna/reasigna una cuenta
'mora:alerta_saturacion'      // cuando agente está sobrecargado
'mora:kpi_supervisor'         // actualización KPIs supervisor (cada 5 min)

// Rooms: cada agente se une a su room 'mora:agente:{id}'
// Supervisores se unen a 'mora:supervisor:{empresa_id}'
```

**Integración con flujo de cobros existente:**  
En `cobros.service.ts`, al crear un `recibo_cobro` exitoso, emitir:
```typescript
this.moraGateway.emitirPagoRecibido({
  cuentaMoraId, montoRecibido, agenteId, empresaId
});
```

---

## 7. COLAS ASYNC (BullMQ sobre Redis)

Para escala 10–100 agentes sin bloquear el hilo principal:

```
Cola: mora-scoring
  - Job: recalcular-score-cuenta
  - Trigger: pago recibido, gestión registrada, promesa creada/vencida

Cola: mora-notificaciones  
  - Job: enviar-whatsapp-promesa     → via Twilio (ya integrado)
  - Job: enviar-sms-campana
  - Job: enviar-recordatorio-promesa → 24hs antes del vencimiento

Cola: mora-reportes
  - Job: calcular-forecast-mensual   → proceso pesado, nightly
```

**Instalación:**
```bash
npm install bullmq ioredis
```

**Variable env requerida:**
```env
REDIS_URL=redis://localhost:6379
MORA_SCORING_CONCURRENCY=5
MORA_NOTIF_CONCURRENCY=3
```

---

## 8. FRONTEND — PANTALLAS

### 8.1 Panel Cobrador Mora (`/mora/cobrador`)

Extiende el Panel Cobrador existente con una nueva tab "Mora" o sección priorizada.

```
┌─────────────────────────────────────────────────────────────────┐
│  GESTIÓN DE MORA — Mi cartera                    [Juan Pérez]   │
│  Recuperado hoy: Gs. 2.450.000  |  Promesas: 3  |  Pendientes: 18 │
├─────────────────────────────────────────────────────────────────┤
│  [Filtro: Segmento ▼] [Canal sugerido ▼] [🔍 Buscar cliente]   │
├─────────────────────────────────────────────────────────────────┤
│ Score │ Cliente      │ Mora  │ Monto Vencido │ Canal │ Acción   │
│  🔴94 │ María López  │ 45d   │ Gs. 850.000   │  WA   │[Gestionar]│
│  🟠78 │ Carlos Ruiz  │ 28d   │ Gs. 320.000   │ Call  │[Gestionar]│
│  🟡55 │ Ana Giménez  │ 15d   │ Gs. 180.000   │ WA    │[Gestionar]│
├─────────────────────────────────────────────────────────────────┤
│ [← Expandir ficha]  Historial | Promesas | Gestiones | Pagos   │
└─────────────────────────────────────────────────────────────────┘
```

**Componentes:**
- `CarteraTable.jsx` — tabla con score visual (badge de color), ordenable
- `ScoreBadge.jsx` — círculo con color según segmento + tooltip explicativo
- `GestionDrawer.jsx` — drawer lateral para registrar gestión rápida
- `PromesaModal.jsx` — modal para crear/ver promesa (extiende existente)

### 8.2 Panel Supervisor (`/mora/supervisor`)

```
┌─────────────────────────────────────────────────────────────────┐
│  SUPERVISOR — Equipo Mora                 [Actualizado: 14:32]  │
│  Recuperado hoy: Gs. 12.3M  |  Meta: Gs. 18M  |  68% cumplido  │
├─────────────────────────────────────────────────────────────────┤
│ Agente    │ Cartera │ Contactos │ Promesas │ Recuperado │ Estado │
│ Juan P.   │   47    │    32     │    8     │ Gs. 2.4M   │  🟢OK  │
│ María G.  │   52    │    18     │    3     │ Gs. 0.9M   │  🟡⚠️  │
│ Pedro L.  │   39    │    41     │   11     │ Gs. 3.8M   │  🟢OK  │
├─────────────────────────────────────────────────────────────────┤
│ ⚠️ ALERTAS IA:                                                  │
│ • María G. tiene baja conversión (35% vs 62% promedio)         │
│ • 8 cuentas críticas sin gestionar en últimas 24hs             │
│ • Promesas vencidas: 5 sin seguimiento                          │
├─────────────────────────────────────────────────────────────────┤
│ [Reasignar cuentas] [Activar campaña] [Ver cartera completa]   │
└─────────────────────────────────────────────────────────────────┘
```

### 8.3 Panel Gerencia (`/mora/gerencia`)

KPIs ejecutivos con gráficos (Recharts ya disponible en el stack):

- Tarjetas: Recuperado Mes | Forecast | Mora Total | Recovery Rate
- Gráfico línea: tendencia diaria de recupero (últimos 30 días)
- Gráfico barras: recupero por agente vs. meta
- Gráfico torta: distribución cartera por segmento
- Tabla: conversión por canal (WhatsApp vs. llamada vs. visita)
- Sección: decisiones sugeridas por el sistema

### 8.4 Ficha Cuenta Mora (`/mora/cuentas/:id`)

Vista completa de una cuenta:
- Header: datos cliente + score + segmento + estado
- Tabs: Resumen | Facturas/Cuotas | Historial Gestiones | Promesas | Pagos | Notas
- Acciones flotantes: Registrar gestión | Nueva promesa | Reasignar | Escalar

### 8.5 Configuración Mora (`/mora/config`)

Acceso solo admin/gerente:
- Config mora por empresa (ya existe, extender UI)
- Pesos del scoring (con preview visual)
- Segmentos y colores
- Reglas de asignación automática
- Gestión de campañas

---

## 9. PERMISOS RBAC

Agregar al sistema de permisos existente:

```
MORA
  MORA.VER_CARTERA_PROPIA          ← agente: ver su propia cartera
  MORA.VER_CARTERA_EQUIPO          ← supervisor: ver cartera de su equipo
  MORA.VER_CARTERA_TOTAL           ← gerente/admin: ver toda la cartera
  MORA.REGISTRAR_GESTION           ← agente: crear gestiones_cobranza
  MORA.REGISTRAR_PROMESA           ← agente: crear/modificar promesas
  MORA.REASIGNAR_CUENTAS           ← supervisor: reasignar entre agentes
  MORA.EJECUTAR_ASIGNACION         ← supervisor: correr asignación automática
  MORA.VER_DASHBOARD_SUPERVISOR    ← supervisor: ver KPIs de equipo
  MORA.VER_DASHBOARD_GERENCIA      ← gerente: ver KPIs ejecutivos y forecast
  MORA.GESTIONAR_CAMPANAS          ← supervisor/gerente: crear y ejecutar campañas
  MORA.CONFIGURAR_SCORING          ← admin: modificar pesos del scoring
  MORA.CONFIGURAR_SEGMENTOS        ← admin: crear/editar segmentos
  MORA.ESCALAR_CUENTA              ← supervisor: escalar a judicial/senior
  MORA.VER_AUDIT_MORA              ← compliance/gerente: ver audit de gestiones
```

---

## 10. MIGRACIONES SQL (Prisma)

Crear en orden:

```
migrations/
├── 20260620_001_crear_cuentas_mora.sql
├── 20260620_002_crear_gestiones_cobranza.sql
├── 20260620_003_crear_segmentos_mora.sql
├── 20260620_004_crear_campanas_cobranza.sql
├── 20260620_005_crear_reglas_scoring_mora.sql
├── 20260620_006_modificar_promesas_pago.sql
├── 20260620_007_modificar_clientes_contactabilidad.sql
└── 20260620_008_insertar_segmentos_default.sql
```

**Migración de datos inicial** (job único):

```sql
-- Poblar cuentas_mora desde factura_cuotas existentes
INSERT INTO cuentas_mora (empresa_id, cliente_id, cobrador_id, dias_mora, monto_vencido, cuotas_vencidas)
SELECT 
  fc.empresa_id,
  fc.cliente_id,
  fc.cobrador_id,
  MAX(CURRENT_DATE - fc.fecha_vencimiento) AS dias_mora,
  SUM(fc.saldo_pendiente) AS monto_vencido,
  COUNT(*) AS cuotas_vencidas
FROM factura_cuotas fc
WHERE fc.estado = 'vencida'
  AND fc.saldo_pendiente > 0
  AND fc.fecha_vencimiento < CURRENT_DATE
GROUP BY fc.empresa_id, fc.cliente_id, fc.cobrador_id
ON CONFLICT (empresa_id, cliente_id) DO UPDATE SET
  dias_mora = EXCLUDED.dias_mora,
  monto_vencido = EXCLUDED.monto_vencido,
  cuotas_vencidas = EXCLUDED.cuotas_vencidas,
  actualizado_en = NOW();
```

---

## 11. ROADMAP POR SPRINTS

### Sprint 1 — Base de datos y APIs fundacionales (2 semanas)
**Objetivo:** BD lista, APIs core, sincronización desde factura_cuotas

| Tarea | Tipo | Prioridad |
|---|---|---|
| Crear migraciones 001–008 | Backend | 🔴 Crítico |
| Job `sincronizarCuentasNuevas()` | Backend | 🔴 Crítico |
| GET /mora/cartera/hoy (sin score, orden por días mora) | Backend | 🔴 Crítico |
| GET /mora/cuentas/:id (ficha) | Backend | 🔴 Crítico |
| POST /mora/gestiones (CRUD básico) | Backend | 🔴 Crítico |
| Verificar/completar `promesas_pago` existente | Backend | 🔴 Crítico |
| Permisos MORA en sistema RBAC existente | Backend | 🟠 Alto |
| Pruebas unitarias scoring + sync | Backend | 🟠 Alto |

**Entregable:** BD lista, API funciona, datos de mora visibles (sin UI todavía)

---

### Sprint 2 — Motor de scoring + Panel Cobrador extendido (2 semanas)
**Objetivo:** Agentes ven su cartera priorizada con score visual

| Tarea | Tipo | Prioridad |
|---|---|---|
| `scoring.service.ts` con fórmula completa | Backend | 🔴 Crítico |
| Job nightly de recálculo masivo | Backend | 🔴 Crítico |
| `CarteraTable.jsx` con ScoreBadge y ordenamiento | Frontend | 🔴 Crítico |
| `GestionDrawer.jsx` (registrar gestión rápida) | Frontend | 🔴 Crítico |
| Integrar en Panel Cobrador existente (nueva tab) | Frontend | 🔴 Crítico |
| `FichaCuentaMora.jsx` con tabs completos | Frontend | 🟠 Alto |
| Reconfigurar reglas scoring desde UI | Frontend | 🟡 Medio |

**Entregable:** HU-001 y HU-002 implementadas. Agente puede ver cartera priorizada y registrar gestiones.

---

### Sprint 3 — Dashboard Supervisor + WebSockets (2 semanas)
**Objetivo:** Supervisores controlan su equipo en tiempo real

| Tarea | Tipo | Prioridad |
|---|---|---|
| `mora.gateway.ts` (eventos WebSocket) | Backend | 🔴 Crítico |
| GET /mora/dashboards/supervisor | Backend | 🔴 Crítico |
| `PanelSupervisorMora.jsx` con KPIs live | Frontend | 🔴 Crítico |
| Integración WebSocket en frontend (hook useMoraSocket) | Frontend | 🔴 Crítico |
| Alertas automáticas: baja conversión, cuentas sin tocar | Backend | 🟠 Alto |
| POST /mora/asignacion/manual | Backend | 🟠 Alto |
| UI Reasignación de cuentas | Frontend | 🟠 Alto |

**Entregable:** HU-003 y HU-004 implementadas. Dashboard con latencia < 5 segundos.

---

### Sprint 4 — Asignación automática + Campañas (2 semanas)
**Objetivo:** Motor de asignación inteligente + primer campaña WhatsApp

| Tarea | Tipo | Prioridad |
|---|---|---|
| `asignacion.service.ts` con balance de carga | Backend | 🔴 Crítico |
| POST /mora/asignacion/ejecutar | Backend | 🔴 Crítico |
| `campanas.service.ts` + integración Twilio | Backend | 🔴 Crítico |
| CRUD campañas + UI `ConfigCampanas.jsx` | Frontend | 🟠 Alto |
| BullMQ setup + cola mora-notificaciones | Backend | 🟠 Alto |
| Recordatorios automáticos promesas (D-1) vía Twilio | Backend | 🟠 Alto |
| Segmentos configurables desde UI | Frontend | 🟡 Medio |

**Entregable:** Asignación automática funciona. Primera campaña WhatsApp ejecutable.

---

### Sprint 5 — Dashboard Gerencia + Forecast (2 semanas)
**Objetivo:** Visibilidad ejecutiva completa

| Tarea | Tipo | Prioridad |
|---|---|---|
| GET /mora/dashboards/gerencia | Backend | 🔴 Crítico |
| Algoritmo forecast (rolling average 30d) | Backend | 🔴 Crítico |
| `PanelGerenciaMora.jsx` con Recharts | Frontend | 🔴 Crítico |
| `ForecastChart.jsx` | Frontend | 🟠 Alto |
| KPIs: recovery rate, costo por recupero, conversión canal | Backend | 🟠 Alto |
| Job de consolidación diaria para BI | Backend | 🟡 Medio |
| Export Excel de cartera y gestiones | Frontend | 🟡 Medio |

**Entregable:** HU-005 implementada. Gerencia tiene forecast y KPIs ejecutivos.

---

### Sprint 6 — Compliance, auditoría y hardening (2 semanas)
**Objetivo:** Listo para producción con trazabilidad completa

| Tarea | Tipo | Prioridad |
|---|---|---|
| Control de saturación de contactos (bloqueo automático) | Backend | 🔴 Crítico |
| Control de horarios de contacto permitidos | Backend | 🔴 Crítico |
| Pantalla de auditoría mora (HU-006) | Frontend | 🔴 Crítico |
| Tests de integración endpoints críticos | Backend | 🟠 Alto |
| Pruebas de carga (k6 o Artillery, 100 agentes concurrentes) | DevOps | 🟠 Alto |
| Documentación OpenAPI completa | Backend | 🟡 Medio |
| `ConfigMora.jsx` completa con todas las opciones | Frontend | 🟡 Medio |

**Entregable:** Módulo listo para producción. HU-006 completa.

---

## 12. RECOMENDACIONES TÉCNICAS IMPORTANTES

### ① NO crear un endpoint `/mora/cartera/hoy` que haga JOINs masivos en tiempo real
El `priority_score` y los campos derivados deben estar PRE-CALCULADOS en `cuentas_mora`. El GET retorna desde esa tabla (índice por cobrador + score). Sin joins complejos en el request.

### ② Integración con cobros.service.ts existente
Cuando se registra un pago en `cobros.service.ts`, publicar en BullMQ:
```typescript
await this.scoringQueue.add('recalcular-score-cuenta', { 
  cuentaMoraId, clienteId, empresaId 
});
```
Si el monto vencido llega a 0, cambiar estado de `cuentas_mora` a `'regularizada'`.

### ③ Vista materializada vs. tabla
Se eligió **tabla** (no vista materializada) para `cuentas_mora` porque:
- Necesita escribir `priority_score`, `estado`, `notas_operativas` 
- El job de sync es rápido (upsert por empresa_id/cliente_id)
- Control total sobre cuándo se actualiza

### ④ Real-time con escala media
Para 10–100 agentes, el PosGateway existente con Socket.IO es suficiente. No se necesita Redis Pub/Sub todavía. Si se pasa de 100 agentes concurrentes en WebSocket, agregar Redis adapter: `@nestjs/platform-socket.io` + `socket.io-redis`.

### ⑤ Forecast simple (no ML)
El forecast se calcula con una **media móvil ponderada de 30 días**:
```
forecast_mes = (recupero_promedio_diario_30d × dias_restantes_mes) + recupero_acumulado_mes
```
Suficiente para Sprint 5. En Fase 2 futura se puede reemplazar con un modelo XGBoost entrenado con el histórico acumulado.

### ⑥ Twilio ya integrado — aprovechar
El módulo `campanas.service.ts` debe llamar al servicio Twilio existente, no re-implementarlo. Usar la función de envío de WhatsApp del `CobrosService` como referencia.

---

## 13. CRITERIOS DE ACEPTACIÓN POR HU

| ID | Historia | Criterios clave |
|---|---|---|
| HU-001 | Agente ve cartera priorizada | Cuentas ordenadas por priority_score, color por segmento, explicación del score en tooltip |
| HU-002 | Agente registra gestión | Canal + resultado requeridos, queda en audit, genera job de recálculo de score |
| HU-003 | Supervisor monitorea equipo en tiempo real | Dashboard se actualiza en < 5 segundos vía WebSocket |
| HU-004 | Supervisor reasigna cuentas | Balance de carga visible antes y después, confirmación requerida |
| HU-005 | Gerente ve forecast mensual | Forecast con metodología explícita, comparativo vs. mes anterior |
| HU-006 | Auditor consulta historial | Toda gestión tiene: agente, fecha, canal, resultado, snapshot del score al momento |

---

## 14. VARIABLES DE ENTORNO NUEVAS

```env
# BullMQ / Redis
REDIS_URL=redis://localhost:6379
MORA_SCORING_CONCURRENCY=5
MORA_NOTIF_CONCURRENCY=3

# Control de campañas
MORA_CAMPANA_DELAY_MS=1000       # delay entre envíos para no saturar Twilio
MORA_MAX_CONTACTOS_DIA=3         # límite global diario por cliente
MORA_HORARIO_INICIO=08:00        # hora más temprana para contactar
MORA_HORARIO_FIN=20:00           # hora límite de contacto
```

---

## 15. ARCHIVOS A CREAR (orden recomendado)

```
Sprint 1:
  src/mora/mora.module.ts
  src/mora/cartera/cartera.service.ts
  src/mora/cartera/cartera.controller.ts
  src/mora/gestiones/gestiones.service.ts
  src/mora/gestiones/gestiones.controller.ts
  src/mora/mora.scheduler.ts
  src/mora/dto/*.dto.ts
  prisma/migrations/20260620_*/migration.sql

Sprint 2:
  src/mora/scoring/scoring.service.ts
  src/pages/mora/PanelCobradorMora.jsx
  src/components/mora/CarteraTable.jsx
  src/components/mora/ScoreBadge.jsx
  src/components/mora/GestionDrawer.jsx
  src/pages/mora/FichaCuentaMora.jsx

Sprint 3:
  src/mora/mora.gateway.ts
  src/mora/dashboards/supervisor.service.ts
  src/mora/dashboards/dashboards.controller.ts
  src/pages/mora/PanelSupervisorMora.jsx
  src/hooks/useMoraSocket.js

Sprint 4:
  src/mora/asignacion/asignacion.service.ts
  src/mora/asignacion/asignacion.controller.ts
  src/mora/campanas/campanas.service.ts
  src/mora/campanas/campanas.controller.ts
  src/mora/queues/scoring.queue.ts
  src/mora/queues/notificaciones.queue.ts

Sprint 5:
  src/mora/dashboards/gerencia.service.ts
  src/pages/mora/PanelGerenciaMora.jsx
  src/components/mora/ForecastChart.jsx
  src/components/mora/KpiCard.jsx

Sprint 6:
  src/mora/compliance/compliance.service.ts
  src/pages/mora/ConfigMora.jsx
  tests/mora/*.spec.ts
```

---

## 16. NOTAS FINALES

- **Fase 2 (futura):** Una vez acumulados 6+ meses de gestiones reales, entrenar un modelo XGBoost en Python para reemplazar el scoring por reglas. La estructura de `cuentas_mora.priority_score` y `gestiones_cobranza` ya está preparada para ser la fuente de datos de entrenamiento.
- **Integración buró de crédito:** Agregar campo `score_externo` en `cuentas_mora` para cuando se conecte a un buró (Equifax, Infoconf Paraguay). El campo puede iniciarse en NULL.
- **Multi-empresa:** Todas las consultas deben incluir `empresa_id` en el WHERE. Usar el patrón existente de `req.user.empresa_id`.
- **Naming:** Seguir convención existente: tablas plural español snake_case, servicios TypeScript con comentarios en español, respuestas API con `{ exito: boolean, datos, error }`.
