# Plan Cobranzas H2 2026 — Roadmap Vigente

**Última actualización:** 2026-06-29
**Estado:** 🚧 EN DESARROLLO (Sprint 0 ✅ + Sprint 1 Slices 2A→2N ✅ + IA-1→IA-4 ✅)
**Naturaleza:** módulo del ERP Novasis vendible aparte a entidades crediticias (cooperativas, financieras, casas de crédito, fintechs)
**Reemplaza:** todas las versiones previas (`plan-modulo-mora.md`, `plan-scoring-concesion-credito.md`, `plan-cobranzas-modulo-vendible-2026-h2.md`)

---

## 0. Cómo leer este documento

Este es **el plan vigente** que el equipo va a ejecutar. Está consolidado tras una sesión de discovery del 2026-06-25 donde se cerraron 23 decisiones de producto y arquitectura.

- **Sección 1**: las 23 decisiones cerradas. Si alguien pregunta "¿por qué tomamos X?", está acá.
- **Sección 2**: inventario de lo que ya está construido (no se duplica).
- **Sección 3**: arquitectura técnica del módulo.
- **Sección 4**: monetización.
- **Sección 5**: roadmap mes a mes (10 meses).
- **Sección 6**: Sprint 0 detallado — **arrancar acá**.
- **Sección 7+**: métricas, riesgos, pendientes, próximos pasos.

---

## 1. Decisiones cerradas (2026-06-25)

| # | Decisión | Valor |
|---|---|---|
| 1 | Arquitectura agnóstica de origen | `IFuenteCartera` con N implementaciones |
| 2 | Multi-tenant | Aprovecha el modelo del ERP |
| 3 | Estructura de proyecto | Dentro del monorepo del ERP (`src/cartera/`) |
| 4 | Stack | React + MUI (consistente con ERP) |
| 5 | Nombre del producto | ⏸ Pendiente — definir antes del mes 2 |
| 6 | Orden de features | Cobranza primero, scoring concesión después |
| 7 | Modelo de pricing | Tiered (Starter / Pro / Enterprise) |
| 8 | Moneda | USD facturado en PYG |
| 9 | Ciclo de cobro | Mensual + descuento 20% si anual |
| 10 | Trial | 14 días con tarjeta |
| 11 | Onboarding | Híbrido (Excel + API REST + conectores futuros) |
| 12 | Sincronización inicial | Snapshot manual periódico |
| 13 | Manejo de errores import | Importar válidas + reporte de errores |
| 14 | Infraestructura | Aprovecha la del ERP |
| 15 | Multi-tenant DB | Modelo del ERP (no DB-per-customer separada) |
| 16 | Región | La del ERP |
| 17 | IA Provider | Multi-provider (Claude + GPT + Gemini) |
| 17b | Gateway IA | OpenRouter / LiteLLM |
| 18 | Política IA | Estricta: IA recomienda, reglas calculan, humanos deciden |
| 19 | Límites IA | Por plan (Starter limit, Pro mid, Enterprise unlimited) |
| 20 | Scope MVP | CORE + DIFERENCIADORES + PREMIUM (10 meses) |
| 21 | Modo operativo | Configurable: Operacional / Co-pilot / Híbrido |
| 22 | API + Webhooks | En CORE (no Premium) |
| 23 | Naturaleza producto | Módulo del ERP **vendible aparte** |

### Lo más importante que estas decisiones cambian

1. **"No reemplazás el sistema del cliente, lo hacés más inteligente con IA"** — es la propuesta de valor única. El modo Co-pilot (#21) es el game-changer.
2. **El módulo vive en el monorepo del ERP** (#3) pero **se vende aparte** (#23). Captás clientes del ERP por upsell + clientes que no usan el ERP por nuevo mercado.
3. **`IFuenteCartera`** (#1) hace que el código no sepa si los datos vienen de `factura_cab`, Excel, API o conector nativo. Eso permite vender a cualquier entidad sin refactorizar.
4. **IA con responsabilidad acotada** (#18): nunca decide nada que afecte plata. Solo recomienda, informa, sugiere.

---

## 2. Estado actual del módulo COBRANZAS (no duplicar)

### Backend implementado en el ERP
- ✅ Submódulo 7 — Workflow de gestiones (`cob_gestion`, enums, endpoints CRUD, productividad)
- ✅ Mesa de Gestión — worklist priorizada CTE-based, filtros, KPIs, export Excel
- ✅ Mora Avanzada (legal) — `cob_gestion_mora` con estados, doble autorización, PDFs expediente
- ✅ Promesas Opción A — modal unificado gestión+promesa con prorrateo N cuotas + evidencia + pagador
- ✅ Refinanciaciones (código completo, oculto del sidebar)
- ✅ Intereses moratorios con comprobante fiscal (Fase A.3)
- ✅ Autorizaciones de descuento + exoneración
- ✅ Mobile: rendiciones, gestiones, cobros, alertas cliente

### Infra y permisos
- ✅ Permisos sembrados (`COB_WORKFLOW`, `COB_MESA_GESTION`, `COB_GMR`, `COB_PROMESAS`, `COB_INTERESES_MORATORIOS`)
- ✅ Twilio integrado (recordatorios D-1 de promesas)
- ✅ AuditService para auditoría
- ✅ Multi-empresa nativo

**Conclusión:** la base operativa interna del ERP está madura. El trabajo nuevo es construir las capas que faltan: agnosticidad de origen, API/Webhooks, modos co-pilot, recomendaciones IA, branding del módulo separable, billing tiered, onboarding standalone.

---

## 3. Arquitectura técnica

### 3.1 `IFuenteCartera` — capa agnóstica de origen

```typescript
interface IFuenteCartera {
  // Lectura
  listarCuentasPendientes(filtros: CuentasFilter): Promise<CuentaPendiente[]>
  obtenerCuenta(id: string): Promise<CuentaDetalle>
  listarCuotas(cuentaId: string): Promise<Cuota[]>
  obtenerSaldoVencido(clienteId: string): Promise<Decimal>
  obtenerHistorialCobros(clienteId: string): Promise<Recibo[]>

  // Escritura (solo si modo === 'operacional' || modo === 'híbrido' con feature habilitada)
  registrarCobro(input: RegistrarCobroInput): Promise<ReciboCobro>
  marcarCobroEnCuotas(cobroId: string, distribución: Distribución[]): Promise<void>

  // Metadatos
  getModo(): 'operacional' | 'co-pilot' | 'híbrido'
  getOrigen(): 'erp-novasis' | 'cartera-importada' | 'api-externa' | `conector-${string}`
}
```

### 3.2 Implementaciones planificadas

```
src/cartera/fuentes/
├── i-fuente-cartera.interface.ts
├── erp-novasis-fuente.service.ts        ← cliente que usa ERP completo (mes 1-4)
├── cartera-importada-fuente.service.ts  ← Excel/CSV importado (mes 3-4)
├── api-externa-fuente.service.ts        ← cliente envía via API REST (mes 5-8)
└── conector-fitbank-fuente.service.ts   ← nativo sistema FITBANK (mes 9+, si demanda)
```

### 3.3 Modos de operación

| Modo | Cómo opera | Cliente típico |
|---|---|---|
| **Operacional** | Cliente trabaja todo desde el módulo. Source of truth es el módulo. | Cooperativa con sistema viejo o Excel |
| **Co-pilot** | Solo recomendaciones. Cliente trabaja en su sistema, módulo le da worklist + sugerencias IA. Source of truth es su sistema. | Cooperativa con sistema robusto que NO quiere migrar |
| **Híbrido** | Mix configurable feature-por-feature (ej. campañas masivas WhatsApp acá, cobros en su sistema). | Cliente con sistema parcial |

Configurable en `tenant_cartera_config.modo_operativo`. Cambia comportamiento de UI (esconde botones de cobro en modo co-pilot) y de servicios (no permite escritura si modo === 'co-pilot').

### 3.4 API REST + Webhooks bidireccionales

#### Entrante (cliente → módulo)
```
POST /api/v1/cartera/sync               → snapshot full o delta
POST /api/v1/cartera/cuotas/cobro       → notificar cobro registrado en sistema cliente
GET  /api/v1/cartera/recomendaciones    → ?cliente_id=X
GET  /api/v1/cartera/worklist           → ?filtros
POST /api/v1/cartera/gestiones          → opcional, si cliente registra desde su sistema
```

#### Saliente (módulo → cliente)
```
gestion.registrada               → al registrar gestión en el módulo
promesa.creada                   → al crear promesa
promesa.cumplida                 → al cerrar promesa
cobro.registrado                 → al registrar cobro (modo operacional)
campana.enviada                  → al terminar campaña masiva
recomendacion.alta_prioridad     → cliente crítico que requiere atención
```

Configurables en `tenant_webhook_endpoints`. Retry exponencial 3x con DLQ.

### 3.5 AI Gateway (OpenRouter)

```typescript
// src/cartera/ai-gateway/ai-gateway.service.ts
async generarInformeCliente(clienteId: string, contexto: ClienteContexto) {
  const prompt = await this.prompts.get('informe-cliente', tenant.plan)
  const modelo = await this.selectModel(tenant.plan, 'informe')
  // selectModel: starter → haiku, pro → sonnet, enterprise → opus

  const respuesta = await openRouter.chat({
    model: modelo,
    messages: [
      {role: 'system', content: prompt.system},
      {role: 'user', content: prompt.user.fill({contexto})},
    ],
    fallback: ['gpt-4o-mini', 'gemini-1.5-flash'],
  })

  await this.aiCalls.audit({
    tenant_id,
    prompt_version: prompt.version,
    modelo,
    input,
    output,
    costo_usd,
    latencia_ms,
    fallback_usado,
  })

  return respuesta
}
```

**Política estricta IA vs reglas:**
> Cualquier cosa que afecte plata o decisión auditable → reglas en código. Cualquier cosa narrativa, sugerencia o asistencia → IA.

---

## 4. Modelo de monetización

### 4.1 Planes

#### **Starter** — USD $99/mes
- Hasta 500 cuentas activas
- 3 usuarios
- Importación Excel (snapshot manual)
- Worklist priorizada con score por reglas
- Gestiones + Promesas + Cobros manuales
- 200 SMS/mes incluidos
- IA Haiku — máx 50 calls/día
- Soporte por email

#### **Pro** — USD $399/mes
- Hasta 2.500 cuentas activas
- 10 usuarios
- **API REST + Webhooks** (modo Co-pilot habilitado)
- Todo Starter +
- WhatsApp Business + SMS ilimitados (fair use 5k/mes)
- Sincronización diaria automática
- Reglas de priorización configurables
- IA Sonnet — máx 500 calls/día
- Recomendaciones IA: canal sugerido, mensaje sugerido, "a quién llamar hoy"
- Reportes avanzados + export Excel
- Soporte prioritario

#### **Enterprise** — desde USD $999/mes (custom)
- Cuentas y usuarios ilimitados
- Todo Pro +
- **Scoring crediticio (concesión)** — Fase 2
- Integración Informconf u otra central de riesgo
- IA Opus + límite por contrato
- Conectores nativos (FITBANK, SISTEMAS B&M, etc.)
- White-label (logo + colores)
- Onboarding asistido
- Account Manager dedicado
- SLA 99.9% + soporte 24/7

### 4.2 Add-ons (todos los planes)

| Add-on | Precio | Tipo |
|---|---|---|
| Consultas Informconf | USD $0.50/consulta | Pass-through con margen 100% |
| Mensajes WhatsApp extra | USD $25/mil | Pay-as-you-go |
| Storage extra documentos | USD $5/GB | Mensual |
| Onboarding express | USD $499 | One-time |

### 4.3 Billing

- USD facturado en PYG al tipo de cambio del día
- Mensual recurrente con Stripe
- Descuento anual: 20% off si paga 12 meses por adelantado
- Trial: 14 días con tarjeta (no se cobra hasta día 15)

---

## 5. Roadmap mes a mes (10 meses)

### 🟦 Mes 1-2 — Fundación (Sprint 0 + Sprint 1)

**Objetivo:** módulo activable por flag, sin features. Estructura técnica lista.

**Backend:**
- Estructura `src/cartera/` + módulo NestJS
- `IFuenteCartera` interface + `ErpNovasisFuente` (lee de `factura_cab`)
- Migración: `tenant_cartera_config`, `cuenta_pendiente_cache`, `tenant_webhook_endpoints`
- Permisos `COB_CART_*` sembrados
- Audit log enriquecido con `tenant_id` + `cartera_origen`

**Frontend:**
- Layout `/cartera-inteligente/*`
- Página "Activar Cartera Inteligente" en módulo COBRANZAS
- Pantalla configuración (modo operativo + fuente)
- Sidebar item visible solo si activado

**Producto / Marketing:**
- Definir nombre del producto (pendiente decisión #5)
- Lista de 3-5 cooperativas piloto target
- Asesoría legal sobre compliance financiero

**Entregable:** activable, sin features todavía.

---

### 🟦 Mes 3-4 — CORE operativo

**Objetivo:** un cliente puede importar Excel, ver su cartera, registrar gestiones, cobros y promesas.

**Backend:**
- `ImportadorExcelService` con wizard de mapeo de columnas
- Validaciones automáticas + reporte de errores
- `WorklistService.getPriorizada` (CTE based, similar a `MesaGestionService.getWorklist`)
- `GestionService.create` adapter (reusa lógica del módulo COBRANZAS existente)
- `PromesaService.create` adapter (reusa Opción A)
- `CobroService.registrar` (modo operacional)
- API REST entrante: `POST /api/v1/cartera/sync`
- Webhooks salientes: `gestion.registrada`, `promesa.creada`, `cobro.registrado`
- Historial cliente: timeline unificado

**Frontend:**
- Wizard onboarding: importar Excel → mapear columnas → preview → import
- Worklist principal (similar a `MesaGestionPage` pero con `IFuenteCartera`)
- Detalle de cliente con timeline
- Registrar gestión / promesa / cobro (componentes refactorizados)

**Entregable mes 4:** Cliente piloto #1 importa y trabaja worklist.

---

### 🟦 Mes 5-6 — DIFERENCIADORES + IA

**Objetivo:** producto vendible con propuesta única. Recomendaciones IA + campañas + reportes.

**Backend:**
- `AIGatewayService` con OpenRouter
- Prompt registry versionado en BD
- Recomendaciones IA:
  - `recomendarCanal(cliente)` → "WhatsApp porque históricamente responde por ahí"
  - `recomendarMensaje(cliente, canal)` → mensaje personalizado
  - `recomendarPrioridad(cliente)` → explica por qué este cliente es prioritario
- `CampanaService` masivas WhatsApp + SMS con BullMQ (reusa Redis del ERP)
- `RecordatorioPromesaScheduler` (cron diario)
- API:
  - `GET /api/v1/cartera/recomendaciones?cliente_id=X`
  - `GET /api/v1/cartera/worklist`
- Auditoría completa de cada acción

**Frontend:**
- Componente "Recomendaciones IA" en detalle cliente
- Tooltip explicativo del canal sugerido
- Mensaje sugerido con botón "Editar" + "Enviar"
- Wizard de campañas masivas (preview → confirmar → enviar)
- Reporte productividad por gestor
- Reporte cumplimiento promesas
- Multi-usuario con roles (admin / supervisor / gestor)

**Entregable mes 6:** producto vendible. 3 clientes piloto activos.

---

### 🟦 Mes 7-8 — Modo Co-pilot + API completa

**Objetivo:** clientes con sistema propio pueden usar el producto sin migrar nada.

**Backend:**
- `ApiExternaFuente` para clientes que envían via API
- Modo Co-pilot: deshabilitar UIs de cobro/gestión cuando modo activado
- Webhooks inversos: cliente notifica desde su sistema (gestión registrada, cobro hecho)
- Sincronización delta diaria (solo cambios desde último sync)
- `ConectorFitbankFuente` (si algún piloto usa FITBANK)
- Modo Híbrido configurable feature-por-feature

**Frontend:**
- Settings tenant: configurar modo operativo
- Settings: webhook endpoints + API keys (UI de gestión)
- Modo Co-pilot UI: solo lectura, botones "Sugerir contactar" en vez de "Llamar"
- Documentación API pública (Swagger/OpenAPI)

**Entregable mes 8:** primer cliente Co-pilot activo. Pilotos 4 y 5 activos.

---

### 🟦 Mes 9-10 — PREMIUM + Lanzamiento comercial

**Objetivo:** producto comercializable. 5+ clientes pagando.

**Backend:**
- Asignación automática de cartera a gestores (algoritmo balance + zona)
- Dashboard supervisor con polling 30s (KPIs live)
- White-label: subida de logo + paleta + custom domain
- Analytics avanzados: tendencias, comparativos
- Forecast simple (media móvil 30 días)
- Reportes para compliance: exportar para SUPERINTENDENCIA, BCP, etc.

**Frontend:**
- Dashboard supervisor
- Pantalla de white-label
- Pantalla de configuración avanzada
- Landing page pública con login para clientes nuevos
- Stripe checkout integrado
- Onboarding self-service (signup + trial 14 días + tarjeta)

**Sales / Marketing:**
- Landing page propia (subdominio o sección dedicada)
- Material de venta: deck, video demo, casos de éxito pilotos
- Lanzamiento público
- Tarifario público
- Documentación de onboarding

**Entregable mes 10:** producto comercializable. 5 clientes pagando. Campaña de adquisición lanzada.

---

### 🟩 Mes 11-12 — Fase 2 (Plan Enterprise + Scoring)

Adelanto para planeación:

- Módulo de scoring crediticio (concesión)
- Adapters de centrales de riesgo (Informconf, BCP CRB manual, Equifax mock)
- Wizard de solicitud de crédito enriquecida
- Motor de scoring por reglas + Claude para informe narrativo
- Dashboard analista

---

## 6. Sprint 0 — Arrancar aquí (2 semanas)

### Objetivo
Dejar el módulo arrancable. Sin features todavía, pero con la arquitectura lista.

### Pre-flight checklist (antes de arrancar)

- [ ] Confirmar que NestJS y Prisma del ERP están en versiones esperadas
- [ ] Confirmar acceso al Redis del ERP (para BullMQ futuro)
- [ ] Crear cuenta OpenRouter + API key (decisión #17b)
- [ ] Variable `OPENROUTER_API_KEY` en `.env.example`
- [ ] Branch `feature/cartera-inteligente-mvp` creado
- [ ] Definir empresa "demo" en el ERP para activar el módulo durante development

### Backend tasks

#### 1. Estructura del módulo
- [ ] Crear `src/cartera/cartera.module.ts`
- [ ] Crear estructura de carpetas:
  ```
  src/cartera/
  ├── cartera.module.ts
  ├── fuentes/
  │   ├── i-fuente-cartera.interface.ts
  │   └── erp-novasis-fuente.service.ts
  ├── config/
  │   ├── cartera-config.controller.ts
  │   └── cartera-config.service.ts
  ├── ai-gateway/
  │   └── (vacío por ahora, mes 5)
  └── dto/
      └── cartera-config.dto.ts
  ```

#### 2. Migración Prisma

Crear migration `20260626_cartera_inteligente_setup`:

```sql
-- Configuración por tenant del módulo
CREATE TABLE tenant_cartera_config (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  empresa_id      UUID NOT NULL REFERENCES empresas(id) ON DELETE CASCADE,
  plan            VARCHAR(20) NOT NULL DEFAULT 'starter',
  modo_operativo  VARCHAR(20) NOT NULL DEFAULT 'operacional',
  fuente_default  VARCHAR(50) NOT NULL DEFAULT 'erp-novasis',
  trial_activado_en TIMESTAMPTZ,
  trial_termina_en  TIMESTAMPTZ,
  suscripcion_activa BOOLEAN DEFAULT false,
  stripe_subscription_id VARCHAR(100),
  activado_en     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  actualizado_en  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  UNIQUE(empresa_id)
);

-- Webhooks salientes configurables
CREATE TABLE tenant_webhook_endpoints (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  empresa_id      UUID NOT NULL REFERENCES empresas(id) ON DELETE CASCADE,
  evento          VARCHAR(50) NOT NULL,
  url             TEXT NOT NULL,
  activo          BOOLEAN DEFAULT true,
  secreto_hmac    VARCHAR(100),
  creado_en       TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_tenant_webhook_empresa ON tenant_webhook_endpoints(empresa_id, evento) WHERE activo = true;

-- Auditoría de llamadas IA
CREATE TABLE ai_calls (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  empresa_id      UUID NOT NULL REFERENCES empresas(id),
  prompt_version  VARCHAR(20) NOT NULL,
  modelo          VARCHAR(50) NOT NULL,
  input           JSONB NOT NULL,
  output          TEXT,
  costo_usd       DECIMAL(10,6),
  latencia_ms     INT,
  fallback_usado  BOOLEAN DEFAULT false,
  creado_en       TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_ai_calls_empresa ON ai_calls(empresa_id, creado_en DESC);
```

#### 3. Privilegios sembrados

Agregar al seed de seguridad:
```
COB_CART_VER            → Ver módulo cartera inteligente
COB_CART_CONFIGURAR     → Configurar modo, fuentes, webhooks
COB_CART_IMPORTAR       → Importar cartera desde Excel (Sprint 1)
COB_CART_API            → Gestionar API keys (mes 7)
COB_CART_ENTERPRISE     → Features Enterprise (white-label, scoring) (mes 9+)
```

#### 4. `IFuenteCartera` interface

```typescript
// src/cartera/fuentes/i-fuente-cartera.interface.ts
export interface IFuenteCartera {
  listarCuentasPendientes(filtros: CuentasFilter): Promise<CuentaPendiente[]>
  obtenerCuenta(id: string): Promise<CuentaDetalle>
  listarCuotas(cuentaId: string): Promise<Cuota[]>
  obtenerSaldoVencido(clienteId: string): Promise<number>
  obtenerHistorialCobros(clienteId: string): Promise<Recibo[]>

  registrarCobro(input: RegistrarCobroInput): Promise<ReciboCobro>
  marcarCobroEnCuotas(cobroId: string, distribución: Distribución[]): Promise<void>

  getModo(): ModoOperativo
  getOrigen(): OrigenCartera
}

export type ModoOperativo = 'operacional' | 'co-pilot' | 'híbrido'
export type OrigenCartera = 'erp-novasis' | 'cartera-importada' | 'api-externa' | string
```

#### 5. `ErpNovasisFuente` implementación

```typescript
// src/cartera/fuentes/erp-novasis-fuente.service.ts
@Injectable()
export class ErpNovasisFuente implements IFuenteCartera {
  constructor(private prisma: PrismaService) {}

  async listarCuentasPendientes(filtros: CuentasFilter): Promise<CuentaPendiente[]> {
    // Reusa la query CTE de MesaGestionService.getWorklist
    // Adapta la salida al formato de CuentaPendiente agnóstico
  }

  // ... resto de métodos delegan a servicios existentes del ERP
}
```

#### 6. Endpoint de configuración

```typescript
// src/cartera/config/cartera-config.controller.ts
@Controller('api/v1/cartera/config')
export class CarteraConfigController {
  @Get()
  async obtener(@Empresa() empresaId: string) { ... }

  @Put()
  async actualizar(@Empresa() empresaId: string, @Body() dto: ActualizarConfigDto) { ... }
}
```

#### 7. Tests unitarios

- [ ] Test que `ErpNovasisFuente.listarCuentasPendientes` devuelve la misma data que `MesaGestionService.getWorklist`
- [ ] Test que `tenant_cartera_config` solo permite valores válidos en `modo_operativo`
- [ ] Test que webhook endpoint requiere URL válida

### Frontend tasks

#### 1. Estructura de rutas

```typescript
// src/router/cartera-inteligente.routes.tsx
const CarteraInteligenteRoutes = lazy(() => import('./CarteraInteligente'))

// Agregar al router principal
<Route path="/cartera-inteligente/*" element={<CarteraInteligenteRoutes />} />
```

#### 2. Páginas

- [ ] `pages/CarteraInteligente/Home.tsx` — landing dentro del ERP
- [ ] `pages/CarteraInteligente/Activar.tsx` — CTA para empresas que no lo tienen
- [ ] `pages/CarteraInteligente/Configuracion.tsx` — modo, fuente, webhooks

#### 3. Sidebar

- [ ] Agregar item "Cartera Inteligente" en `dataEstatica.jsx` (sidebar Cobranzas)
- [ ] Visible solo si `tenant_cartera_config.empresa_id` existe

### Variables de entorno

```
# .env.example
OPENROUTER_API_KEY=
CARTERA_INTELIGENTE_DEFAULT_PLAN=starter
CARTERA_INTELIGENTE_TRIAL_DAYS=14
```

### CI/CD

- [ ] Pipeline pasa con los tests nuevos
- [ ] Migration aplicada en CI

### Criterios de aceptación

1. Un admin de empresa puede entrar a `Cobranzas → Cartera Inteligente` y activar el módulo.
2. El modo operativo se puede cambiar en Configuración.
3. La sidebar muestra "Cartera Inteligente" solo si está activado.
4. `GET /api/v1/cartera/config` responde con la config del tenant.
5. `IFuenteCartera` es testeable con mocks y `ErpNovasisFuente` lee del ERP correctamente.
6. La estructura está lista para que Sprint 1 (importación Excel) empiece sin friction.

---

## 7. Métricas de éxito por fase

| Fase | KPI | Target |
|---|---|---|
| Mes 2 (Fundación) | Módulo activable + 1 empresa demo activada | ✅ activable |
| Mes 4 (Core) | 1 cliente importa Excel y trabaja worklist | 1 cliente activo |
| Mes 6 (Diferenciadores) | 3 clientes piloto + 100 gestiones/semana | 3 pilotos, 80% retención |
| Mes 8 (Co-pilot) | 1 cliente con sistema propio integrado via API | 1 integración API live |
| Mes 10 (Lanzamiento) | 5 clientes pagando, MRR USD $1,500 | 5 paying, USD $1,500 MRR |
| Mes 12 (post-lanzamiento) | 10-15 clientes pagando, MRR USD $4,000 | $4k MRR, churn <5% |

---

## 8. Riesgos y mitigaciones

| Riesgo | Probabilidad | Impacto | Mitigación |
|---|---|---|---|
| Cooperativas desconfían de IA | Media | Alto | Demo guiada + caso de éxito piloto temprano + política estricta IA-recomienda-humano-decide |
| Sistemas legacy sin API | Alta | Medio | Excel import como fallback siempre disponible |
| Compliance/regulación BCP afecta arquitectura | Media | Alto | Asesoría legal mes 1-2; arquitectura de aislamiento ya prevista |
| IA da malas recomendaciones al inicio | Alta | Medio | Baja confianza al inicio, humano siempre decide |
| OpenRouter o algún provider IA cae | Baja | Bajo | Multi-provider con fallback ya en arquitectura |
| Competencia llega antes | Media | Alto | Pilotos firmados con SLA antes de lanzamiento público |
| Costo IA escala más de previsto | Baja | Medio | Límites por plan ya definidos + monitoreo por tenant |
| Equipo se distrae con features no-MVP | Alta | Alto | Política: lo que no está en CORE+DIFERENCIADORES+PREMIUM no se construye hasta mes 11 |

---

## 9. Pendientes / decisiones a tomar

| # | Pendiente | Cuándo decidir |
|---|---|---|
| Nombre del producto | Antes del mes 2 | Próximas semanas |
| Logo + identidad visual | Antes del mes 5 | Cuando esté el nombre |
| Selección de 3-5 cooperativas piloto target | Antes del mes 4 | Cuando arranque outreach |
| Asesoría legal / compliance BCP | Mes 1-2 | Inmediato |
| Documentación API pública (Swagger) | Mes 7 | Cuando arranque Co-pilot |
| Tarifario público y términos de servicio | Antes del mes 10 | Pre-lanzamiento |
| Decisión sobre soporte 24/7 para Enterprise | Mes 9 | Pre-lanzamiento |

---

## 10. Próximos pasos concretos

### Esta semana
1. ✅ Plan consolidado documentado (este archivo)
2. ⏳ Brainstorming de nombres del producto
3. ⏳ Lista de 5 cooperativas target para piloto
4. ⏳ Validar plan con stakeholders

### Próxima semana (semana 1 de desarrollo)
5. ⏳ Sprint 0 arranca (estructura backend + frontend)
6. ⏳ Asesoría legal sobre compliance financiero
7. ⏳ Definir nombre + dominio + redes

### Mes 1-2
8. ⏳ Sprint 0 completo
9. ⏳ Sprint 1: importación Excel + worklist básica
10. ⏳ Outreach a primer piloto

---

## 11. Documentos relacionados

- `plan-cobranzas-gestion-integral.md` — historial técnico de lo construido en cobranzas
- `plan-prueba-usuario-cobranzas.md` — checklist QA
- `plan-novasis-cobros-mobile.md` — app móvil del cobrador de ruta
- `guias/guia-cobranzas.md` — guía operativa del módulo COBRANZAS actual
- `docs/ui-standards.md` — estándares UI del ERP

---

## 12. Glosario

| Término | Definición |
|---|---|
| **Módulo vendible aparte** | Vive en el monorepo del ERP pero se vende a clientes que pueden NO tener el ERP completo |
| **`IFuenteCartera`** | Interface agnóstica: cualquier consumidor lee/escribe via esta interfaz, sin saber si los datos vienen del ERP, Excel, API o conector nativo |
| **Modo Operacional** | Cliente trabaja desde el módulo; somos source of truth |
| **Modo Co-pilot** | Cliente trabaja en su sistema; módulo le da recomendaciones (read-only) |
| **Modo Híbrido** | Mix configurable feature-por-feature |
| **AIGateway** | Servicio único que enruta llamadas IA a OpenRouter con fallback multi-provider |
| **Tenant** | Cliente del módulo (puede ser empresa del ERP o cliente directo) |
| **Worklist** | Bandeja priorizada de clientes morosos por contactar hoy |
| **Source of truth** | Sistema canonical: el que tiene la versión "verdadera" del dato |

---

## 13. Changelog del plan

| Fecha | Versión | Cambios |
|---|---|---|
| 2026-06-29 | v3.2 | Decisión: el módulo NO usa billing standalone; se habilita vía `suscripcion_submodulos` como cualquier submódulo del ERP. Plan/trial/Stripe removidos de la config. |
| 2026-06-25 | v3 (vigente) | Consolidación tras discovery de 23 decisiones. Módulo vendible aparte del ERP. Modo Co-pilot agregado. |
| 2026-06-23 | v2 | Roadmap inicial H2 2026 con 8 fases (A-H). |
| 2026-06-20 | v1 | Plan original `plan-modulo-mora.md` con scoring + módulo de mora. |

---

## 14. Progreso de implementación

### ✅ Sprint 0 — Fundación (2026-06-29)

**Backend:**
- ✅ `src/cartera/` con `CarteraModule` registrado en `app.module.ts`
- ✅ `IFuenteCartera` interface + tipos (`CuentaPendiente`, `CuentaDetalle`, `Cuota`, `Recibo`, etc.)
- ✅ `ErpNovasisFuente` (stub inicial)
- ✅ Migración `20260629_cartera_inteligente_setup` — `tenant_cartera_config`, `tenant_webhook_endpoints`, `ai_calls`
- ✅ Migración `20260629_cartera_config_simplificar` — corrige el modelo dropping plan/trial/stripe
- ✅ Permisos `COB_CART_VER` + `COB_CART_CONFIGURAR` sembrados bajo submódulo `COB_CARTERA_INTELIGENTE`
- ✅ `CarteraConfigController` con `GET /v1/cartera/config` + `PUT /v1/cartera/config`

**Frontend:**
- ✅ Ruta `/cartera-inteligente` + item sidebar bajo COBRANZAS
- ✅ `CarteraInteligenteHub.jsx` con config operativa (modo + fuente)
- ✅ API service `cartera.service.js`

**Decisión clave:** la habilitación del módulo NO usa trial/billing propio; se delega al sistema existente de `plan_submodulos` + `suscripcion_submodulos` del ERP (igual que cualquier otro submódulo). Holding asigna el submódulo `COB_CARTERA_INTELIGENTE` a la suscripción del cliente.

### ✅ Sprint 1 — Slice 2A — Worklist desde ERP (2026-06-29)

**Backend:**
- ✅ `ErpNovasisFuente.listarCuentasPendientes()` — query Prisma SQL sobre `factura_cab` + `factura_cuotas` con joins a `clientes`/`personas`/`moneda`. Ordena por vencimiento más antiguo.
- ✅ `ErpNovasisFuente.obtenerCuenta()` + `listarCuotas()`
- ✅ `WorklistService` agnóstico — resuelve fuente desde `tenant_cartera_config.fuente_default`
- ✅ `WorklistController` — `GET /v1/cartera/worklist` + `GET /v1/cartera/cuentas/:id`
- ✅ `WorklistQueryDto` con filtros (cliente_id, cobrador_id, min_dias_mora, search, paginación)

**Frontend:**
- ✅ `WorklistPage.jsx` — tabla con cliente/doc/saldo/vencimiento/mora, chip de color por gravedad, filtros + paginación
- ✅ Multi-moneda via `fmtMoneda(value, code)` (`_standards/enums/monedas.js`)
- ✅ Fechas via `fmtFechaCalendario` (`utils/fecha.js`)
- ✅ CTA "Abrir worklist" en el Hub
- ✅ Ruta `/cartera-inteligente/worklist`

### ✅ Sprint 1 — Slice 2B — Importación Excel (2026-06-29)

**Backend:**
- ✅ Migración `20260629_cartera_importada` — `cartera_importada_jobs` + `cartera_importada_documentos` con unique `(empresa_id, cliente_externo_id, documento_numero)` para upsert idempotente
- ✅ `ImportadorService` — parsea Excel/CSV con `xlsx`, sugiere mapeo automático por nombre de columna, valida cada fila (montos con tolerancia a separadores PY, fechas `dd/mm/aaaa`), persiste con upsert
- ✅ `ImportadorController` — `POST /v1/cartera/import/subir` (multipart 10MB max), `POST /v1/cartera/import/:jobId/confirmar`, `GET /v1/cartera/import/jobs`
- ✅ `CarteraImportadaFuente` implementa `IFuenteCartera` leyendo de `cartera_importada_documentos`
- ✅ `WorklistService` resuelve dinámicamente entre `ErpNovasisFuente` y `CarteraImportadaFuente`

**Frontend:**
- ✅ `ImportarCarteraPage.jsx` — wizard 3 pasos: subir → mapear → confirmar → resultado con errores
- ✅ Mapeo automático sugerido (cliente_externo_id, monto_total, etc.)
- ✅ Selector de fuente en Hub (erp-novasis ↔ cartera-importada)
- ✅ Ruta `/cartera-inteligente/importar` (requiere `COB_CART_CONFIGURAR`)

### ✅ Sprint 1 — Slice 2C — Gestiones desde worklist (2026-06-29)

**Backend:**
- ✅ Migración `20260629_cartera_gestiones` — tabla `cob_gestion_cartera` con CHECK constraints en tipo_gestion + resultado
- ✅ `GestionesCarteraService` con dispatch transparente por fuente:
  - `erp-novasis` → escribe en `cob_gestion` existente (visible en Mesa de Gestión)
  - `cartera-importada` → escribe en `cob_gestion_cartera`
- ✅ `POST /v1/cartera/cuentas/:id/gestiones` (registrar)
- ✅ `GET /v1/cartera/cuentas/:id/gestiones` (historial top 100)

**Frontend:**
- ✅ `GestionDialog` — modal con tipo (LLAMADA/WHATSAPP/VISITA/EMAIL/SMS/OTRO), resultado, observación, próxima acción + próxima fecha
- ✅ `HistorialGestionesDrawer` — drawer right con timeline de gestiones, chip de color por resultado, icono por tipo
- ✅ Botones "Nueva gestión" + "Ver historial" en cada fila del worklist

### ✅ Sprint 1 — Slice 2D — Detalle de cliente con timeline unificado (2026-06-29)

**Backend:**
- ✅ `ClientesCarteraService` con dispatch transparente: ERP busca por `clientes.id` + verifica que el cliente tenga facturas en la empresa; importada busca por `cliente_externo_id`
- ✅ `GET /v1/cartera/clientes/:id` devuelve `{ cliente, cuentas, gestiones, totales }`
- ✅ Validación de scope multi-empresa (cliente_id sin movimientos en la empresa → 404)

**Frontend:**
- ✅ `ClienteCarteraDrawer` con tarjeta de info del cliente, 3 KPIs (cuentas / saldo / gestiones), tabs "Cuentas" y "Gestiones"
- ✅ Nombre del cliente clickeable en cada fila del worklist
- ✅ Botón "Ver cliente" en columna Acciones
- ✅ Timeline de gestiones unificado (across todas las cuentas del cliente)

**Migración aplicada:** se renombró `20260629_cartera_gestiones` → `20260630_cartera_gestiones` por orden alfabético (gestiones depende de `cartera_importada_documentos`).

### ✅ Sprint 1 — Slice 2E — Webhooks salientes (2026-06-29)

**Backend:**
- ✅ Migración `20260630_cartera_webhook_deliveries` — tabla `webhook_deliveries` con estados (pendiente/entregando/entregado/falla_temporal/falla_definitiva), intentos, response_code, last_error
- ✅ Nueva queue BullMQ `cartera-webhooks` registrada en `CarteraModule`
- ✅ `WebhookService.publicar(empresa, evento, payload)` — crea deliveries + encola con backoff exponencial (30s/60s/120s, 3 reintentos)
- ✅ `WebhookProcessor` — POST con timeout 10s, firma HMAC SHA256 en header `X-Cartera-Signature`, headers `X-Cartera-Event` + `X-Cartera-Delivery`, persiste resultado
- ✅ `WebhookController` — CRUD endpoints (`GET/POST/PATCH/DELETE /v1/cartera/webhooks/endpoints`) + `GET /v1/cartera/webhooks/deliveries`
- ✅ Validación URL HTTPS-only (excepto localhost), secreto HMAC ≥16 chars
- ✅ Hook en `GestionesCarteraService.registrar()` que emite `gestion.registrada` (fire-and-forget)
- ✅ 7 eventos definidos: `gestion.registrada`, `promesa.{creada,cumplida,incumplida}`, `cobro.registrado`, `campana.enviada`, `recomendacion.alta_prioridad`

**Frontend:**
- ✅ `WebhooksPage.jsx` con dos paneles: endpoints (crear/activar-desactivar/eliminar) + deliveries recientes con refresh automático cada 15s
- ✅ `CrearEndpointDialog` con selector de evento + URL + secreto opcional
- ✅ Chips de color por estado de delivery (entregado=verde, falla=rojo, etc.)
- ✅ Tooltip con error completo en último error
- ✅ CTA "Configurar webhooks" en el Hub
- ✅ Ruta `/cartera-inteligente/webhooks`

**Formato del payload entregado:**
```json
POST https://cliente.com/webhook
Headers:
  X-Cartera-Event: gestion.registrada
  X-Cartera-Delivery: <uuid>
  X-Cartera-Signature: sha256=<hmac>
Body:
  {
    "evento": "gestion.registrada",
    "payload": { gestion_id, cuenta_id, tipo_gestion, resultado, ... },
    "timestamp": "2026-06-29T...",
    "empresa_id": "<uuid>"
  }
```

### ✅ Sprint 1 — Slice 2F — Mi día (2026-06-30)

**Backend:**
- ✅ `MiDiaService` con dispatch transparente por fuente
- ✅ Seguimientos pendientes: gestiones con `proxima_fecha ≤ ahora` ordenadas por fecha asc (top 50)
- ✅ Cuentas críticas: top 10 documentos con más mora (delegado a la fuente activa)
- ✅ KPIs: seguimientos_hoy + seguimientos_atrasados + gestiones_realizadas_hoy
- ✅ `GET /v1/cartera/mi-dia`
- ✅ Funciona en ambos modos: `cob_gestion` para ERP y `cob_gestion_cartera` para fuente importada

**Frontend:**
- ✅ `MiDiaPage` con 3 KPI cards + 2 paneles (seguimientos + top 10 críticas)
- ✅ Tabla seguimientos: cliente clickeable → drawer, chip "Hoy"/"Atrasado Xd" según gravedad, botón "Hacer ahora" → modal de gestión
- ✅ Tabla cuentas críticas: mismo flujo de drawer cliente + botón "Nueva gestión"
- ✅ Refresh automático cada 60s
- ✅ CTA destacado (banner color primary) en el Hub como entry point principal del día
- ✅ Ruta `/cartera-inteligente/mi-dia`

### ✅ Sprint 1 — Slice 2G — Eventos de promesa + cobro (2026-06-30)

**Pattern de acoplamiento (clave):** la integración es **no-invasiva**. `WebhookService`
se inyecta con `@Optional()` en los servicios existentes del ERP. Si la cola
no está disponible o la empresa no tiene endpoints suscritos, el flujo de
cobranzas/cobros sigue funcionando idénticamente. No se rompe nada para
tenants que NO usan Cartera Inteligente.

**Backend:**
- ✅ `PromesasService` (cobranzas) inyecta `WebhookService` con `@Optional()`:
  - `create()` → emite `promesa.creada` fire-and-forget
  - `updateEstado()` → emite `promesa.cumplida` o `promesa.incumplida` según el nuevo estado
  - Helper interno `emitirEvento()` con doble try/catch para garantizar que ninguna excepción de webhook propague
- ✅ `CobrosService` inyecta `WebhookService` con `@Optional()`:
  - `create()` → emite `cobro.registrado` fire-and-forget al final del flujo (después de la integración contable y la generación de comisión)
- ✅ `CobranzasModule` importa `CarteraModule` (que exporta `WebhookService`)
- ✅ `CobrosModule` importa `CarteraModule` directamente (porque `CobranzasModule` no reexporta)

**Por qué es seguro:**
- `@Optional()` permite que NestJS arme el DI graph aunque `WebhookService` no esté disponible
- `WebhookService.publicar()` es idempotente respecto a tenants sin config: `findMany` devuelve `[]` y sale silenciosamente
- Si Redis cae, `queue.add()` ya tiene catch interno que solo loguea warning
- Si Cartera está deshabilitada para una empresa via suscripción, los eventos igual se procesan en backend pero como no hay endpoints suscritos → no-op real

**Eventos emitidos:**
| Evento | Origen | Payload principal |
|---|---|---|
| `promesa.creada` | `PromesasService.create` | promesa_id, cliente_id, factura_cab_id, fecha_prometida, monto |
| `promesa.cumplida` | `PromesasService.updateEstado` | promesa_id, cliente_id, factura_cab_id, notas |
| `promesa.incumplida` | `PromesasService.updateEstado` | promesa_id, cliente_id, factura_cab_id, notas |
| `cobro.registrado` | `CobrosService.create` | recibo_id, numero_recibo, cliente_id, monto_total, fecha |

### ✅ Sprint 1 — Slice 2H — UI Co-pilot indicators (2026-06-30)

**Frontend:**
- ✅ Nuevo componente `ModoCarteraBadge` con 3 variantes (operacional/co-pilot/híbrido) — Chip con ícono + color + tooltip explicativo
- ✅ Badge integrado en headers de Hub, Worklist y Mi día
- ✅ Banner Alert informativo "Modo Co-pilot" en Worklist y Mi día cuando `modo === 'co-pilot'`
- ✅ Copy adaptativo en GestionDialog:
  - Título "Nueva gestión" → "Registrar acción realizada"
  - Alert explicativo de que la acción real ocurre en el sistema original
  - Botón "Registrar gestión" → "Registrar acción"
- ✅ Tooltips de Mi día: "Hacer ahora" → "Registrar lo realizado"; "Nueva gestión" → "Registrar acción"
- ✅ Slice puramente visual — backend igual (las escrituras siguen permitidas en cualquier modo; las restricciones reales del modo se enforzarán cuando agreguemos UI de cobro en slices futuros)

**Decisión clave:** los webhooks se siguen disparando en modo co-pilot (la gestión registrada queda como tracking + notificación al sistema original via webhook). Esto es lo que hace al modo co-pilot funcional como capa de aumento del sistema cliente.

### ✅ IA-1 — IA Gateway Cartera (2026-06-30)

**Decisión arquitectónica clave:** en vez de crear un AI Gateway nuevo (OpenRouter
desde cero), **reusamos toda la infraestructura existente**:
- `AiConfigService` (módulo AI Dashboard) maneja la config LLM **por empresa** (`ai_empresa_config`) — decisión del 2026-06-30: usar la config per-empresa en vez de la del holding (Ayuda IA), porque permite a cada empresa elegir su propio proveedor/key/modelo.
- `AyudaProviderService` (módulo Ayuda IA) abstrae Anthropic / OpenAI / Ollama con la misma API.
- Tabla `ai_calls` (creada en Slice 0) sirve para auditoría + cache 24h.

**Backend:**
- ✅ `AyudaIaModule` ahora exporta `AyudaConfigService` y `AyudaProviderService` (antes solo exportaba `AyudaIaService`).
- ✅ `CarteraModule` importa `AyudaIaModule` para acceder a los servicios.
- ✅ `CarteraIaService` con `@Optional()` inyección de los servicios IA — si Ayuda IA no está cargada/configurada, devuelve 503 con mensaje claro pero no rompe el módulo.
- ✅ `recomendarContacto(empresa, cliente_id, { forceRefresh })`:
  - 1. Busca cache en `ai_calls` (último call para este cliente en últimas 24h).
  - 2. Si miss: carga detalle del cliente vía `ClientesCarteraService` (cuentas + gestiones).
  - 3. Arma prompt sistema con reglas explícitas (mora alta → llamada/visita, sin teléfono → email/visita, etc.) + contexto del cliente como JSON.
  - 4. Llama al LLM configurado por el holding (Claude/OpenAI/Ollama).
  - 5. Parsea JSON estricto: `{ canal_sugerido, mensaje_sugerido, prioridad, razonamiento }`.
  - 6. Persiste en `ai_calls` (auditoría + cache + costo).
- ✅ Tolera envoltorios markdown (\`\`\`json…\`\`\`) que algunos modelos meten.
- ✅ `GET /v1/cartera/clientes/:id/recomendacion?refresh=true` (refresh opcional para regenerar).

**Frontend:**
- ✅ `RecomendacionIaCard` integrado en `ClienteCarteraDrawer` (debajo del header del cliente, arriba de las tabs).
- ✅ Estado inicial: botón "Generar recomendación con IA" + descripción.
- ✅ Estado loaded: chip de canal sugerido (con ícono + color), chip de prioridad (alta/media/baja), mensaje sugerido en quote con botón copiar al portapapeles, Alert con razonamiento.
- ✅ Botón regenerar (icono refresh) con tooltip "consume tokens".
- ✅ Chip "cacheado" si viene del cache.
- ✅ Manejo de error 503: toast claro "el holding debe configurar Ayuda IA primero".
- ✅ Skeleton durante loading.

**Por qué es no-invasivo:**
- Usa los mismos endpoints/config que el chatbot NovaIA y el IA Dashboard → cero duplicación de config.
- Si el holding NO usa IA, el módulo Cartera Inteligente sigue 100% funcional (worklist, gestiones, promesas, mi día, webhooks). Solo el botón "Generar recomendación" devuelve 503.
- Cache 24h evita quemar tokens en cada apertura del drawer.

### ✅ IA-2 — Informe ejecutivo del cliente (2026-06-30)

**Backend:**
- ✅ Nuevo método `CarteraIaService.informeEjecutivo(empresa, cliente_id, {forceRefresh})` reusa la misma infra (AiConfigService + AyudaProviderService + ai_calls cache 24h).
- ✅ Prompt diferenciado (`prompt_version: 'informe-cli-v1'`): pide narrativa estructurada en JSON con shape:
  - `perfil_cumplimiento` (2-3 oraciones)
  - `evolucion_mora` (2-3 oraciones citando datos concretos)
  - `nivel_riesgo` (alto/medio/bajo)
  - `factores_riesgo[]` (2-5 items)
  - `acciones_correctivas[]` (2-5 items concretas, no genéricas)
  - `oportunidades[]` (refinanciación, descuento contado, ajuste plan)
  - `resumen_ejecutivo` (1-2 oraciones de cierre "qué hacer esta semana")
- ✅ Contexto enriquecido al prompt: indicadores derivados (mora_promedio_dias, mora_maxima_dias, cuentas_con_mora) además de cuentas + gestiones.
- ✅ Reglas explícitas: no inventar datos, tono profesional sin tutear, nivel_riesgo se infiere de mora + resultados negativos (CLIENTE_FALLECIDO, RECHAZO_PAGAR, DIRECCION_INCORRECTA).
- ✅ Modelo se ejecuta con `max_tokens: 1200` y `temperatura: 0.4` (más espacio + un toque más creativo que la recomendación).
- ✅ `GET /v1/cartera/clientes/:id/informe-ejecutivo?refresh=true`.

**Frontend:**
- ✅ Nueva tab "Informe IA" (ícono lucide:sparkles) en `ClienteCarteraDrawer`.
- ✅ `InformeEjecutivoIaCard` con accent vertical `secondary.main`:
  - Estado inicial: CTA "Generar informe" con descripción.
  - Loaded: chip de nivel de riesgo (rojo/ámbar/verde con ícono), Alert "Esta semana" con resumen ejecutivo, 2 secciones de párrafo (Perfil + Evolución), divider, 3 listas estructuradas (Factores riesgo / Acciones / Oportunidades) cada una con su ícono y color semántico.
  - Botón "Copiar informe completo" formatea todo a texto plano con headers y lo manda al portapapeles (listo para pegar en email a gerencia).
  - Botón regenerar.
  - Chip "cacheado" + modelo visible.
- ✅ Manejo de errores 503 con mensajes claros.

### ✅ IA-3 — Resumen IA del día (portfolio-level) (2026-06-30)

**Decisión de diseño:** en vez de hacer re-rank IA del worklist (1 llamada por cliente listado = costoso), una sola llamada IA por empresa por día que analiza el portfolio crítico completo.

**Backend:**
- ✅ `CarteraIaService.resumenDia(empresa, {forceRefresh})`:
  - Llama a la fuente activa para obtener top 30 cuentas críticas → ordena por mora + saldo → toma top 20.
  - Si no hay cuentas con mora → devuelve respuesta directa sin llamar al LLM (`modelo: 'sin-llm'`, salud_cartera: 'sana'). Ahorra tokens.
  - Si hay → construye prompt con indicadores (saldo_total, mora_promedio, mora_maxima) y array de cuentas.
  - Cache 24h por empresa (no por cliente, este es portfolio-level) usando nuevo helper `buscarCachePortfolio`.
- ✅ Prompt strict JSON:
  - `salud_cartera`: 'sana' | 'tensionada' | 'critica' (definido por % mora >30d y mora máxima).
  - `resumen_general`: 2-3 oraciones del estado general.
  - `prioridades_dia`: 3-5 clientes priorizados con cliente_id, nombre, razón, accion_sugerida.
  - `alertas`: 1-5 alertas operativas (concentración riesgo, deterioro, etc.).
- ✅ Tono profesional gerencial (escribir PARA el equipo).
- ✅ `GET /v1/cartera/insights/resumen-dia?refresh=true`.
- ✅ Nuevo controller separado (`CarteraInsightsController` bajo path `cartera/insights`) — el de clientes era para per-cliente, este es portfolio-level.

**Frontend:**
- ✅ `ResumenDiaIaCard` con accent vertical de color dinámico (verde/ámbar/rojo según salud).
- ✅ Integrado en Mi día arriba de los KPIs (entrada visual del día).
- ✅ Chip de salud con ícono semántico, resumen general en itálica, lista de prioridades clickeable (abre `ClienteCarteraDrawer`), alertas con `Alert severity="warning"` apilados.
- ✅ Click en cualquier prioridad navega al drawer del cliente — flujo end-to-end: ver resumen → click cliente → ver recomendación + informe ejecutivo IA → registrar gestión/promesa/cobro.

**Costo IA:** 1 llamada por empresa por día (cacheada 24h). Para una empresa con 1.000 cuentas → 1 sola llamada genera el contexto operativo del día completo.

### ⏭ Próximos slices IA propuestos

- **IA-4 — Detección de patrones (cron)**: job cron que ejecuta el resumen automáticamente cada 24h (cada empresa con IA activa + recalculo_automatico) y dispara webhook `recomendacion.alta_prioridad` con cada prioridad del resumen.

### ✅ 2I — Asignación a cobradores (2026-06-30)

**Backend:**
- ✅ DTO `WorklistQueryDto.solo_mis_clientes: boolean` (con `@Transform` para parsear de query string).
- ✅ `WorklistService.resolverCobradorDeUsuario(empresa, user)` — busca `vendedores_cobradores.usuario_id = user.id AND active = true`. Devuelve `null` si el usuario no es cobrador asignado.
- ✅ `WorklistService.getPriorizada(empresa, user_id, query)` — si `solo_mis_clientes`, resuelve el cobrador y lo pasa como filtro. Si el usuario no es cobrador, devuelve `[]` + flag `usuario_no_es_cobrador: true`.
- ✅ Mismo patrón en `MiDiaService.getMiDia(empresa, user_id, { solo_mis_clientes })`:
  - Seguimientos pendientes: filtra `cob_gestion` por `cliente.cobrador_id`.
  - Cuentas críticas: pasa `cobrador_id` a la fuente.
  - KPIs: filtra los 3 counts por `cliente.cobrador_id`.
- ✅ `ErpNovasisFuente` ya tenía el filtro `cobrador_id` en su SQL — solo había que pasarlo desde el caller.
- ✅ `cartera-importada` fuente: el filtro queda no-op porque esa tabla no tiene relación a cobrador (cliente_externo_id es libre).

**Frontend:**
- ✅ Toggle "Solo mis clientes" con switch + label en filtros de Worklist.
- ✅ Mismo toggle en header de Mi día (anclado a la derecha con `ml: auto`).
- ✅ Alert warning cuando el response trae `usuario_no_es_cobrador: true`: "No estás vinculado como cobrador. Pedile al admin que te vincule en Cobranzas → Cobradores".
- ✅ Query key incluye `soloMisClientes` para re-fetch automático.

**Para que un usuario use el filtro:**
1. Admin va a Cobranzas → Cobradores.
2. Edita el cobrador del usuario.
3. Vincula con el `usuario_id` correspondiente.
4. El usuario hace logout/login → el toggle filtra correctamente.

### ✅ 2J — Cobro desde Worklist (2026-06-30)

**Decisión arquitectónica:** en vez de crear un flujo de cobro propio (duplicaría toda la lógica de medios de pago, NCs, retenciones, etc.), **reusamos el `NuevoReciboMultiWizard` existente** del ERP con un cambio mínimo: agregar prop opcional `initialClienteId`.

**Frontend (modificación mínima al wizard existente):**
- ✅ `NuevoReciboMultiWizard` acepta nuevo prop opcional `initialClienteId = null`.
- ✅ Cuando se monta abierto con `initialClienteId`, `useEffect` precarga el cliente vía `getCliente(id)` y lo agrega a `clienteOpts` para que el Autocomplete muestre el nombre.
- ✅ Flujo standalone (sin `initialClienteId`) intacto.

**Frontend WorklistPage:**
- ✅ Nueva IconButton "Registrar cobro" (verde, ícono `lucide:dollar-sign`) en cada fila del worklist.
- ✅ Visibilidad condicional: `fuente === 'erp-novasis' && modo !== 'co-pilot' && canCobranzas('COB_REC_RECIBO_CREAR')`. En co-pilot no aparece porque el cobro real sucede en el sistema del cliente. En cartera-importada no aparece porque esa fuente no tiene infraestructura de cobros del ERP.
- ✅ Click abre el wizard con el cliente preseleccionado → el usuario elige factura(s) y medio de pago → confirma → toast de éxito → wizard se cierra.
- ✅ Al guardar exitosamente, invalida `["cartera", "worklist"]`, `["cartera", "mi-dia"]` y `["cartera", "cliente"]` → la fila se actualiza con saldo nuevo o desaparece si quedó pagada.

**Pipeline end-to-end (no-invasivo):**
1. Worklist Cartera Inteligente → click "Registrar cobro" → wizard se abre con cliente preseleccionado.
2. Wizard usa `useCreateReciboMultiMutation` que llama `POST /recibos-cobro` (servicio existente del ERP).
3. `CobrosService.create()` ejecuta toda la lógica existente del ERP: crea recibo, distribuye en cuotas, crea movimiento de tesorería, contabiliza, genera comisión de cobranza.
4. **Webhook `cobro.registrado` se dispara automáticamente** (integrado en Slice 2G) → llega a los endpoints suscritos en Cartera Inteligente → Webhooks.
5. La fila del worklist se actualiza/desaparece según el saldo nuevo.

**Por qué es safe:**
- El wizard original sigue funcionando idéntico cuando no se pasa `initialClienteId`.
- Toda la lógica de validación, distribución FIFO en cuotas, NCs, retenciones, contabilidad, etc. usa el código existente del ERP sin tocarlo.
- La acción no aparece para tenants en modo co-pilot (la cobranza física sucede en su sistema) ni para fuente cartera-importada (no tiene infra de cobros).
