# PLAN FUNCIONAL Y TÉCNICO — Cobranzas: Gestión Integral

**Fecha:** 2026-06-12
**Stack:** NestJS + PostgreSQL + Prisma ORM + React + MUI
**Multiempresa:** `empresa_id` en todas las tablas
**Notificaciones:** Twilio ya integrado (SMS/WhatsApp disponible)
**Arquitectura:** todos los nuevos submódulos viven bajo el **módulo de permisos `COBRANZAS`** ya existente (no en `VENTAS`)

---

## 0.1 ESTADO DE IMPLEMENTACIÓN (actualizado 2026-06-15)

### ✅ Fase A.3 — Intereses moratorios con comprobante fiscal: **núcleo COMPLETO**

**Backend (`/var/www/html/proyectos/smartfactvoice-backend`)**
- ✅ `prisma/schema.prisma`: agregadas tablas `cob_config_intereses` y `cob_interes_cobrado`; columna `autorizaciones_descuento.tipo` (`QUITA_CAPITAL` | `EXONERACION_INTERES`).
- ✅ Migración SQL: `prisma/migrations/20260614_cob_intereses_fiscal/migration.sql` con FKs + CHECK constraints + índices.
- ✅ `src/cobranzas/intereses-moratorios.service.ts` — `getConfig`, `upsertConfig`, `previewParaCuota`, `registrarCobro(tx, ...)`, `listarPorRecibo`. Reusa `ConfigMoraService.calcularMora`. Política de pausa por gestión DEMANDA/INCOBRABLE incluida.
- ✅ `src/cobranzas/intereses-moratorios.controller.ts` — endpoints: `GET/PUT /intereses-moratorios/config`, `GET /intereses-moratorios/preview`, `GET /intereses-moratorios/recibo/:id`.
- ✅ `src/cobranzas/cobranzas.module.ts` — controller + service registrados/exportados.
- ✅ `src/cobros/cobros.service.ts` — `createFromDistribucion` acepta `exoneracionesIntereses[]` y llama `procesarInteresesMoratorios` (resiliente: errores log-only, no rompen cobro).
- ✅ `src/cobros/cobros.controller.ts` — propaga `exoneraciones_intereses` desde body.
- ✅ `src/seguridad/seeds/seguridad.seed-data.ts` — submódulo `COB_INTERESES_MORATORIOS` con 5 privilegios (`COB_INT_CONFIG_VER/EDITAR`, `COB_INT_PREVIEW`, `COB_INT_VER`, `COB_INT_EXONERAR`).

**Frontend (`/var/www/html/proyectos/pos-ventas`)**
- ✅ `src/api/cobros.service.js` — 4 helpers: `getConfigIntereses`, `updateConfigIntereses`, `previewInteresPorCuota`, `getInteresesPorRecibo`.
- ✅ `src/pages/ConfigIntereses.jsx` — pantalla completa de config (tipo comprobante, IVA, producto/servicio, pausa por gestión, orden de imputación) con `ScreenGuia` colapsable y permisos.
- ✅ `src/routers/routes.jsx` — ruta `/cobranzas/config-intereses` protegida por `COB_INT_CONFIG_VER`.
- ✅ `src/pages/ConfiguracionNew.jsx` — entry en menú Cobranzas.

### 🚧 Pasos pendientes para activar en runtime

1. **Backend** — ejecutar en `/var/www/html/proyectos/smartfactvoice-backend`:
   ```bash
   npx prisma migrate deploy   # aplica 20260614_cob_intereses_fiscal
   npx prisma generate         # regenera tipos → desaparecen los TS errors esperados en intereses-moratorios.service.ts
   ```
2. **Seeder de seguridad** — re-ejecutar para crear privilegios `COB_INT_*` en BD.

### 🔜 TODO Fase A.3 (deferidos, atacar en próxima sesión)

| # | Item | Archivo / dónde | Nota |
|---|---|---|---|
| A.3.6 | ✅ **Emisión real de factura-e por intereses** — implementada en `InteresesMoratoriosService.emitirFacturaParaRecibo` (llamada post-TX desde `procesarInteresesMoratorios`). Una línea (`factura_det`) por cuota cobrada con mora; cada `cob_interes_cobrado` queda linkeado vía `factura_det_id` para trazabilidad ante disputas. SIFEN se encola con `queuesService.enqueueSifenFactura`. Si SET falla, las filas quedan con `error_emision`/`intento_emision` para reintento por job. Endpoint de desglose: `GET /intereses-moratorios/factura/:factura_cab_id/desglose`. Migration: `20260616_cob_intereses_emision_traz`. | `src/cobranzas/intereses-moratorios.service.ts`, `src/cobros/cobros.service.ts` | **HECHO** |
| A.3.7 | ✅ **Backend exoneración mora vía autorización del supervisor (atada a cuota)** — Implementado en Paso 1: migration `20260616_autorizaciones_descuento_factura_cuota` agrega `factura_cuota_id` FK; DTOs (`Create...`/`CrearDescuentoAdmin`) aceptan `tipo` y `factura_cuota_id`; `AutorizacionesService` valida coherencia cuota↔cliente; nuevo método `getExoneracionesActivasPorCuotas`; `createFromDistribucion` autobuildea `exoneraciones_intereses[]` desde autorizaciones EXONERACION_INTERES vigentes y luego las marca `usado`; nuevo endpoint `POST /autorizaciones-descuento/exoneraciones-activas` para preview en el wizard. **Pendiente Paso 2/3**: UI Panel Supervisor (tipo + selector de cuota) + badge read-only en `CobrosTemplateV2.jsx`. | `src/cobranzas/autorizaciones.service.ts`, `src/cobros/cobros.service.ts`, `pos-ventas/src/components/.../CobrosTemplateV2.jsx` | Decisión: NO se solicita inline — la autorización se crea desde el Panel Supervisor. El wizard la consume read-only. |
| A.3.8 | ✅ **Job de reintento de emisión SIFEN** de facturas por intereses moratorios que quedaron con error. Cada 30 min, lista recibos con `error_emision IS NOT NULL AND factura_cab_id IS NULL AND intento_emision < 5`, reusa `emitirFacturaParaRecibo` (que ya incrementa intento_emision y limpia error_emision al éxito). El cap de 5 intentos evita loop infinito sobre errores permanentes. Devengo "sin cobro" se difiere: la mora se calcula on-demand en cada preview (no requiere snapshot). | `src/cobranzas/cobranzas-jobs.service.ts`, método nuevo `listarRecibosConEmisionPendiente` en `intereses-moratorios.service.ts` | **HECHO** |
| A.3.9 | ✅ **Reporte de intereses cobrados** — endpoints `GET /reporte-intereses-cobrados/resumen` y `/detalle` (controller `reporte-intereses-cobrados.controller.ts`, permiso `COB_INT_VER`). Página `ReporteInteresesCobrados.jsx` con filtros, KPIs (bruto/exonerado/cobrado/IVA/tasa exoneración) en layout flex (todo el ancho, equal-width), ranking top clientes + top cobradores en stack flex, detalle paginado con link al recibo asociado. Wireado en `Reportes.jsx` (sección Cobranzas) y ruta `/reportes/cobranzas/intereses`. Además: `cobros.service.findOne` ahora devuelve `intereses_cobrados[]` para que el detalle del recibo (fuente de verdad para reclamos del cliente) exponga el desglose por cuota. `RecibosPanel` acepta deep-link `?recibo=<id>` para auto-abrir el modal desde el reporte. **Nota:** el `ReportesHub` de cobranzas fue eliminado posteriormente — los reportes viven sólo en `/reportes`. | `src/cobranzas/reporte-intereses-cobrados.controller.ts`, `src/cobros/cobros.service.ts` (findOne extendido), `pos-ventas/src/pages/ReporteInteresesCobrados.jsx` | **HECHO** |

### Decisiones A.3.6 (2026-06-16)

- **Una línea de factura por cuota** (no agrupada): mapeo 1:1 con `cob_interes_cobrado`. El PDF al cliente muestra el detalle ("Interés mora cuota N°X fact. YYY — Z días @ tasa%"), evitando disputas por opacidad.
- **Link bidireccional**: campo `factura_det_id` agregado a `cob_interes_cobrado` además del existente `factura_cab_id`. Desde la factura emitida se puede reconstruir la traza completa.
- **Trazabilidad de fallos**: `error_emision` (TEXT), `intento_emision` (INT), `emitido_at` (TIMESTAMPTZ) + índice parcial para job de reintento.
- **Emisión post-TX**: el cobro core nunca se pierde si SET cae. La emisión es un paso aparte; si falla, las filas quedan con `error_emision` para reintento por job.
- **Reutilizar `FacturasService.create()`** (no armar `factura_cab`/`factura_det` a mano): hereda el `SELECT FOR UPDATE` sobre `numeraciones_documento`, generación de CDC, validación de datos de cliente, encolado SIFEN según config de sucursal y hooks de contabilidad/comisión. Cero divergencia con el flujo de factura manual y cero colisiones de numeración bajo concurrencia.
- **Mapeo det → cob_interes_cobrado**: se inyecta `info_adicional_item = "INTMORA:{cob_interes_cobrado.id}"` por línea y se matchea post-create para popular `factura_det_id`.
- **Selección de timbrado/punto de expedición**: nuevo campo `cob_config_intereses.numeracion_id` (FK a `numeraciones_documento`). El supervisor de cobranzas elige cuál se usa para emitir las facturas de intereses (la sucursal queda implícita). `upsertConfig` valida que sea de la empresa y esté activa.
- **NOTA_DEBITO sigue bloqueada**: el método no emite si `config.tipo_comprobante !== 'FACTURA'`. Esperando plan ND-e separado.
- **IVA incluido** por convención de retail en PY: `precio_unitario = neto + iva`, `liquidacion_iva = iva`, `base_gravada = neto` (o `base_gravada_exenta` si IVA=0).

### Decisiones A.3.7 (2026-06-16)

- **La solicitud NO es inline en el wizard**: el cobrador llama al supervisor; el supervisor entra a *Panel Supervisor → Autorizaciones* y crea la autorización de tipo `EXONERACION_INTERES` atada a la cuota específica. El wizard sólo la consume read-only (badge "Mora exonerada Gs X por autorización #N"). Justificación: fuerza disciplina, deja trazabilidad antes de tocar caja, evita que el cobrador "se auto-perdone" mora.
- **Autorización atada a cuota** (no a factura ni a cliente). Razones: auditoría limpia 1:1 con la decisión, permite parciales sin ambigüedad de imputación, encaja con la máquina de estados de Gestión de Mora (Demanda/Incobrable pueden bloquear o requerir doble firma).
- **Monto exonerado**: si la autorización es por `descuento_porcentaje`, se aplica sobre la mora calculada al momento del cobro; si es por `descuento_monto`, se topea con la mora actual de la cuota (no se exonera más que la deuda real).
- **Auto-aplicación en `createFromDistribucion`**: el backend busca las autorizaciones activas para las cuotas con mora > 0 y construye `exoneraciones_intereses[]` automáticamente. Si vino una lista explícita en el body, no la sobrescribe (compatibilidad con clientes que arman el array manualmente). Las autorizaciones aplicadas quedan en estado `usado` con `recibo_cobro_id`.
- **`getActivaParaUsuario` filtra por tipo**: ahora solo devuelve `QUITA_CAPITAL` (o tipo null por compat), para que el descuento general no colisione con la exoneración de mora.

### A.3.7 Paso 2 (2026-06-16) — HECHO

UI Panel Supervisor (`pos-ventas/src/pages/PanelSupervisor.jsx` tab Descuentos):
- `ToggleButtonGroup` con `QUITA_CAPITAL` (Descuento capital) | `EXONERACION_INTERES` (Exoneración mora).
- Cuando es exoneración: la factura es obligatoria y aparece cascada `Cuota *` cargada con `getCuotasPorFactura` (sólo cuotas no pagadas).
- Validación en botón: si tipo=exoneración exige factura+cuota; el payload manda `tipo` + `factura_cuota_id`.
- Cards "Activos" / "Pendientes" / "Historial" muestran `(mora)` y `Cuota N` cuando aplica.
- Backend `findAll` ahora incluye `factura_cuota` para que los listados puedan mostrarla.

### A.3.7 Paso 3 (2026-06-16) — HECHO

Wizard `pos-ventas/src/components/templates/CobrosTemplateV2.jsx`:
- Al recibir el preview de distribución, llama a `POST /autorizaciones-descuento/exoneraciones-activas` con las cuotas con mora > 0 y persiste el resultado en `exoneracionesMora` (Map por `factura_cuota_id`).
- Helper `getExoneracionPorCuota` calcula monto exonerado por cuota (porcentaje sobre mora o monto fijo topeado, lógica espejo del backend).
- Por cuota: si hay exoneración vigente, badge verde "Mora exonerada Gs X" + (si queda mora neta) "+ mora Gs Y". Si no, comportamiento clásico.
- Resumen de distribución y panel de confirmación muestran "Mora exonerada: -Gs X" y "Mora a cobrar: +Gs Y" separados.
- `totalACobrar`, `totalPagado`, `totalCobrar`, monto Bancard, todos descuentan `totalExoneradoMora` para que el cliente no pague la mora exonerada.
- El backend (Paso 1) ya autobuildea `exoneraciones_intereses[]` en `createFromDistribucion` y marca las autorizaciones como `usado` — el wizard NO envía el array, sólo consume read-only.

API helper agregado: `pos-ventas/src/api/cobranzas.service.js` → `getExoneracionesActivasPorCuotas`.

### Decisiones A.3.8 (2026-06-16)

- **Solo retry de emisión, no devengo separado**: la mora se computa on-demand al construir el preview (`procesarInteresesMoratorios` ya lo hace al momento del cobro). No hay valor en pre-snapshotear interés diariamente si el cálculo es determinístico y barato; agregaría tabla espejo + drift potencial. Si en el futuro se requiere reporte de "intereses devengados aún no cobrados", se resuelve como query sobre `factura_cuotas` vencidas + `cob_config_intereses`, no como job.
- **Cap de 5 reintentos**: errores SIFEN suelen ser permanentes (datos de cliente inválidos, timbrado agotado, RUC incorrecto). Loopear infinito enmascara el problema. Tras 5 intentos queda como evidencia para soporte y requiere intervención manual del operador (reset de `intento_emision` o emisión manual).
- **Cron 30 min**: balance entre latencia de recuperación ante outage transitorio de SET y carga sobre la cola.
- **Dedup por `recibo_cobro_id`**: aunque un recibo tenga N filas de `cob_interes_cobrado` (una por cuota), `emitirFacturaParaRecibo` emite UNA factura con N líneas — basta con disparar una vez por recibo.

### A.3.7 Paso 4 — Reorganización Sidebar (2026-06-16) — HECHO

**Decisión de arquitectura del menú**: 7 entradas de primer nivel bajo el módulo `COBRANZAS`. Hubs con sub-items se modelan como página única con `MUI <Tabs>` deep-linkeable por `?tab=` (no como sub-entradas del sidebar, para evitar saturar).

```
📂 Cobranzas
  ├─ 💵 Cobros                       → /cobros                       (directa, existe)
  ├─ 🧾 Recibos                      → /cobranzas/recibos            (directa, ruta nueva)
  ├─ 📒 Cuentas por Cobrar           → /cobranzas/cuentas-cobrar     HUB tabs: Revisión CxC | Saldos a Favor
  ├─ ⚖️ Gestión de Mora              → /cobranzas/gestion-mora       (página directa; config de intereses vive en Configuración → Cobranzas)
  ├─ 👥 Cobradores                   → /cobranzas/cobradores         HUB tabs: Hoja de Ruta (Asignación/Comisiones/Liquidaciones permanecen en Finanzas)
  └─ ✅ Autorizaciones               → /cobranzas/autorizaciones     (redirect a PanelSupervisor?tab=descuentos)
```

**Los reportes de Cobranzas NO tienen entrada propia en el sidebar**: viven en el centro de Reportes (`/reportes`, sección Cobranzas) junto con los demás reportes del ERP. Esto evita duplicar el menú y unifica el patrón con Ventas / Inventario / Fiscal.

Reportes disponibles en `/reportes` → Cobranzas:
- Reporte por Cobrador (`/reportes/cobranzas/cobrador`)
- Dashboard de Morosidad (`/reportes/cobranzas/morosidad`)
- Reporte de Rendiciones (`/reportes/cobranzas/rendiciones`)
- Intereses Cobrados (`/reportes/cobranzas/intereses` — A.3.9)
- Libro de Retenciones (`/reportes/cobranzas/libro-retenciones`)
- Morosidad por Zona (próximamente)

**Decisiones**:
- **Tabs vs. sub-entradas del sidebar**: tabs ganan porque (a) un sidebar con 14 hojas satura cognitivamente, (b) los tabs comparten contexto del hub (header, filtros globales, breadcrumb), (c) son deep-linkeables con `?tab=...`, (d) la lectura por permisos se hace una sola vez al entrar al hub.
- **Quitar la entrada suelta "Gestión de Mora"** que hoy está en el sidebar — queda sólo bajo el hub.
- **Promesas de Pago NO es entrada propia**: vive dentro de Gestión de Mora porque conceptualmente una promesa siempre existe en contexto de cuota vencida.
- **Recibos suelto** (no como tab de CxC): es operación (movimiento) vs CxC es estado (saldo); separarlos refleja el modelo del dominio.
- **Autorizaciones de primer nivel**: el supervisor entra varias veces al día, no esconderlo bajo otro hub.
- **Permisos**: cada entrada del sidebar se oculta por `OR` de los permisos de sus tabs; dentro del hub, cada tab se oculta individualmente si falta su permiso. El catálogo backend (`COB_*`) ya está alineado, no se toca.

**Orden de ejecución**:
1. ✅ Sidebar + rutas stub — visibilidad inmediata, deploy-able. (Hecho 2026-06-16)
2. ✅ Página Gestión de Mora (originalmente diseñada como hub Panel Gestión | Config Intereses; la tab "Config Intereses" se removió porque ya vive en Configuración → Cobranzas → Intereses moratorios (fiscal); ahora es una página directa). (Hecho)
3. ✅ Hub Cobradores con tab Hoja de Ruta (Asignación/Comisiones/Liquidaciones permanecen en Finanzas porque comparten administración de vendedores). (Hecho)
4. ✅ Hub Cuentas por Cobrar con tabs Revisión CxC | Saldos a Favor. (Hecho)
5. ✅ Página Recibos (reusa `RecibosPanel`), Autorizaciones (redirect a Panel Supervisor — extracción real diferida). (Hecho)
6. ✅ Limpiar Tesoreria.jsx: removidos tabs migrados (Recibos, Libro Retenciones, Saldos a Favor, Revisión CxC, Hoja de Ruta) y sus imports. Asignación/Comisiones/Liquidaciones se mantienen en Finanzas. (Hecho)
7. ✅ Autorizaciones — solución pragmática: `PanelSupervisor` ahora acepta `?tab=descuentos|historial|pendientes` vía `useSearchParams`; `/cobranzas/autorizaciones` redirige a `/tesoreria/panel-supervisor?tab=descuentos`. Extracción real del UI de descuentos queda como deuda técnica (PanelSupervisor son 1334 líneas mezclando cajas + descuentos). (Hecho)
8. ✅ Borrado `src/pages/GestionMora.jsx` — la ruta ya apunta a `GestionMoraHub`. (Hecho)
9. ✅ **Reportes centralizados en `/reportes`** (2026-06-16, refinamiento post-A.3.9): eliminada la entrada "Reportes Cobranzas" del sidebar; borrado `pages/cobranzas/ReportesHub.jsx`; cada reporte tiene su ruta propia bajo `/reportes/cobranzas/*` y aparece en la sección Cobranzas del centro de reportes. Nueva página `LibroRetencionesPage.jsx` (wrapper sobre `LibroRetencionesPanel`) en `/reportes/cobranzas/libro-retenciones`. (Hecho)
10. ✅ **Gestión de Mora simplificada** (2026-06-16): removida la tab "Config Intereses" del hub — esa config ya existe en Configuración → Cobranzas → Intereses moratorios (fiscal); `GestionMoraHub` ahora renderiza directamente `GestionMoraTab` sin estructura de tabs. (Hecho)
11. ✅ **Back-nav uniforme en reportes** (2026-06-16): botón "← Volver a Reportes" agregado en `ReporteInteresesCobrados`, `LibroRetencionesPage` y `ReporteRendiciones` (este último faltaba). Patrón ya existía en `DashboardMorosidad` y `ReporteCobrador`. (Hecho)

**Decisiones de implementación**:
- **Hubs reusan componentes existentes** (`HojaRutaTab`, `ComisionesWrapper`, `LiquidacionesPanel`, `AsignacionCobranzaPanel`, `RevisionCxCTab`, `SaldosFavorPanel`, `RecibosPanel`, `LibroRetencionesPanel`, `GestionMoraTab`, `ConfigIntereses`, `ReporteCobrador`) — cero duplicación. Cuando se limpie Tesoreria.jsx (paso 6), los componentes mantienen su API.
- **Permisos por tab**: cada hub filtra sus tabs con `usePermission("COBRANZAS").can(...)`. Si el usuario no tiene ninguno → mensaje "sin permisos".
- **URL state**: `useSearchParams("tab")` con `setSearchParams(..., { replace: true })` para no llenar history.
- **Autorizaciones**: redirect temporal a `/tesoreria/panel-supervisor` — la extracción real es trabajo aparte (PanelSupervisor son 1334 líneas mezclando cajas + descuentos + exoneraciones).

### Decisiones A.3.9 (2026-06-16)

**Reparto de responsabilidades de vista** (definido con el usuario al implementar el reporte):

| Vista | Rol | ¿Atiende reclamos del cliente? |
|---|---|---|
| Reporte Intereses Cobrados | Analítica agregada (KPIs, ranking, tendencia) | No — read-only/KPIs. Sirve como índice/buscador. |
| Detalle del Recibo | Documento fuente: desglose por cuota (base, días, tasa, exoneración, autorización, factura SET) | **Sí — fuente de verdad** |
| Cuentas por Cobrar (cliente) | Estado actual de saldos | Parcial — para saldos pendientes |
| Gestión de Mora | Operativa de cobro de cuotas vencidas | No |

Flujo de reclamo: reporte → click en fila → recibo (modal con desglose `intereses_cobrados[]`) → anular/NC o auditar autorización si fue exoneración.

### ✅ Fase B — Gestión de Mora avanzada (`COB_GMR_*`): **COMPLETA** (2026-06-19)

**Backend**
- ✅ Schema/migration `20260612_cob_gestion_mora`: enum `CobMoraEstado` (AL_DIA, GESTION_INTERNA, INFORMCONF, DEMANDA, INCOBRABLE, RECUPERADA, REFINANCIADA), tablas `cob_gestion_mora`, `cob_gestion_mora_historial`, `cob_gestion_mora_factura`.
- ✅ `src/cobranzas/gestion-mora.service.ts` — máquina de estados con `TRANSICIONES`, validación de `ESTADOS_REQUIEREN_DOCUMENTO` y `ESTADOS_DOBLE_AUTORIZACION`. Métodos: `create`, `findAll`, `findOne`, `getHistorial`, `transicionar`, `dashboard`, `getByCliente`, `autoRecuperarPorCliente` (hook fire-and-forget), `autoAbrirGestionesVencidas` (job diario), `buildExpedientePayload`, `generateExpedientePdf`. AuditService en CREATE/TRANSITION/AUTO_*.
- ✅ `gestion-mora.controller.ts` — endpoints: `GET/POST /gestion-mora`, `GET /:id`, `GET /:id/historial`, `GET /dashboard`, `GET /cliente/:clienteId`, `GET /:id/pdf?tipo=view|base64`, `PATCH /:id/transicionar`.
- ✅ Hook en `cobros.service.ts`: tras pagar y actualizar saldo del cliente, dispara `autoRecuperarPorCliente` (no-throw).
- ✅ Cron diario 06:00 `autoAbrirGestionesMora` en `cobranzas-jobs.service.ts` — abre GESTION_INTERNA para clientes con cuotas vencidas > `clientes.dias_mora_maximo` (default 30), idempotente.
- ✅ Permisos en seed (`COB_GMR_GESTION_MORA_VER/REGISTRAR`, `COB_GMR_REPORTAR_INFORMCONF`, `COB_GMR_RETIRAR_INFORMCONF`, `COB_GMR_REGISTRAR_DEMANDA`, `COB_GMR_DECLARAR_INCOBRABLE`, `COB_GMR_REVERTIR_ESTADO`, `COB_GMR_EXPORTAR`).
- ✅ Listados extendidos: `clientes.service.findAll` expone `mora_estado_activo` y soporta filtro `?mora_estado=`. `facturas.service.findAll` expone `cliente_en_gestion_mora: { activa, estado }`. Un único `findMany` agrupado por page para evitar N+1.

**msv-kude (PDF)**
- ✅ Template A4 `src/gestion-mora/expediente_gestion_mora_a4.js` con header, cliente+chip estado, resumen 4-col (apertura/facturas/saldos), tabla facturas (snapshot + actual), timeline visual (marker + chip transición + motivo).
- ✅ Ruta `routes/gestion_mora.js` (`POST /api/gestion-mora/generate-pdf`) registrada en `app.js`.

**Frontend pos-ventas**
- ✅ Helpers en `api/cobranzas.service.js`: `createGestionMora`, `getGestionesMora`, `getGestionMora`, `getHistorialGestionMora`, `transicionarGestionMora`, `getGestionMoraDashboard`, `getGestionMoraByCliente`, `getGestionMoraPdfUrl`, `getGestionMoraPdfBlob`.
- ✅ `ClienteHistorialDrawer.jsx` — card de gestión activa con chip estado, monto, fecha apertura, botón PDF.
- ✅ `ClientesListConfig.jsx` — columna "Estado mora" (Chip por estado) + filtro dropdown `moraEstado` (incluye `ACTIVOS` agrupado), wired a `getClientes({moraEstado})` y `useClientesQuery`.
- ✅ `FacturasTab.jsx` — chip "EN GESTIÓN · {estado}" en card mobile + tabla desktop con helper `moraChipColor`.
- ⚠️ Garantes (Submódulo 2) — diferido a fase exclusiva. PDF expediente deja `garantes: []` placeholder listo.

### ✅ Fase B.2 — Auditoría detallada en Revisión CxC (2026-06-19)

- ✅ `cobros.service.ts` — `updateCuentaCobrar`, `marcarComoPagada`, `regenerarCuotas` ahora aceptan `userId` y `motivo` y registran old/new snapshots (incluye cliente, factura ref, cuotas eliminadas con flag `tenia_pagos`) en AuditService.
- ✅ Controllers actualizados para propagar `user.id` y `motivo` desde body.

### ✅ Submódulo 5 — Refinanciación (`COB_REF_*`): **COMPLETO** (2026-06-19)

**Backend**
- ✅ Schema/migration `20260620_cob_refinanciacion`: enum `CobRefinanciacionEstado` (BORRADOR, ACEPTADA, EJECUTADA, ANULADA), tablas `cob_refinanciacion`, `cob_refinanciacion_cuota`. Campo `tipo_origen` en `solicitudes_credito` (NORMAL | REFINANCIACION).
- ✅ `src/cobranzas/refinanciaciones.service.ts` — `simular`, `create` (borrador), `ejecutar` (transacción: marca cuotas viejas como `REFINANCIADA` saldo 0, recalcula saldo factura, crea solicitud_credito nueva con cronograma y `tipo_origen=REFINANCIACION`, transiciona gestión de mora activa a `REFINANCIADA`), `anular`, `findAll`, `findOne`, `generatePdf`. AuditService en CREATE/EXECUTE/ANULAR. Cálculo de cuotas lineal con interés nominal anual proporcional al plazo.
- ✅ `refinanciaciones.controller.ts` — endpoints `POST /simular`, `POST /`, `GET /`, `GET /:id`, `PATCH /:id/ejecutar`, `PATCH /:id/anular`, `GET /:id/pdf?tipo=view|base64`.
- ✅ Permisos en seed: `COB_REF_REFINANCIACION_VER/CREAR/APROBAR/ANULAR`, `COB_REF_REPORTE`.

**msv-kude (PDF)**
- ✅ Template A4 `src/refinanciacion/acuerdo_refinanciacion_a4.js` con header, cliente+chip estado, resumen 4-col (capital orig/exonerado/nuevo/cuota), política de interés, motivo, tabla cuotas originales reemplazadas, tabla nuevo plan, bloque firmas (cliente + autorizado).
- ✅ Ruta `routes/refinanciacion.js` (`POST /api/refinanciacion/generate-pdf`) registrada en `app.js`.

**Frontend pos-ventas**
- ✅ Helpers en `api/cobranzas.service.js`: `simularRefinanciacion`, `createRefinanciacion`, `getRefinanciaciones`, `getRefinanciacion`, `ejecutarRefinanciacion`, `anularRefinanciacion`, `getRefinanciacionPdfUrl`.
- ✅ `components/cobranzas/RefinanciacionWizard.jsx` — Dialog con Stepper 4 pasos: cuotas → política (`PrereqChecklist`) → simulación (preview de cronograma) → confirmación (motivo + documento). Usa `ScreenGuia`, `InlineValidationBanner`, `LocalizationProvider`+`DatePicker`. Fallback a TextField de IDs si `getCuotasPendientes` no responde.
- ✅ `views/cobranzas/RefinanciacionesPage.jsx` — filtros (cliente_id + estado), tabla con Chip estado, acciones gated por permisos (Ver, PDF, Ejecutar, Anular). `DetalleDialog` muestra cuotas originales + nuevo plan. `useConfirmDialog` para confirmar acciones críticas.
- ✅ Ruta `/cobranzas/refinanciaciones` (lazy, permiso `COB_REF_REFINANCIACION_VER`) + entrada de sidebar bajo Cobranzas.
- ✅ Enums centralizados en `_standards/enums/estados.js`: `COB_REFI_ESTADO`, `COB_REFI_POLITICA` con meta/chip helpers.
- ✅ `ClienteHistorialDrawer.jsx` — sección "Refinanciaciones" (últimas 5, link PDF).
- ✅ `SolicitudesCreditoTab.jsx` — Chip "Refinanciación" cuando `tipo_origen === 'REFINANCIACION'` + toggle "Solo refinanciaciones" (filtro client-side).

### ⏸ Submódulo 5 — Refinanciación: OCULTO TEMPORALMENTE (2026-06-22)

Por decisión del usuario (2026-06-22), la entrada de sidebar **"Refinanciaciones"** queda comentada en `pos-ventas/src/utils/dataEstatica.jsx`. Backend (`COB_REF_*`, schema, servicio, controller, PDF kude) y frontend (wizard, listado, enums) **siguen intactos** y funcionales — sólo se ocultó la entrada de menú. La ruta `/cobranzas/refinanciaciones` continúa registrada y accesible por URL directa para QA. Se retomará en una sesión dedicada para:
- Selector real de cuotas pendientes (sustituir fallback de IDs por coma).
- Filtro server-side `tipo_origen` en `GET /solicitudes-credito`.
- Autocomplete de cliente en `RefinanciacionesPage` (hoy TextField).
- Reactivar entrada en sidebar.

### ✅ Submódulo 4 — Promesas ampliadas (`COB_PRM_*`): **COMPLETO** (2026-06-22)

**Backend (ya estaba)**
- ✅ Modelo `promesas_pago` con estados pendiente | cumplida | cumplida_parcial | incumplida | cancelada | renegociada.
- ✅ Enums `CobPromesaEvidencia` (VERBAL | WHATSAPP | EMAIL | DOCUMENTO_FIRMADO) y `CobPromesaPagador` (CLIENTE | GARANTE | TERCERO) — multi-pagador (garante/tercero) soportado a nivel de DTO/servicio.
- ✅ Endpoints: `POST /promesas-pago`, `POST /promesas-pago/multi-factura`, `GET /promesas-pago`, `GET /promesas-pago/por-factura/:id`, `GET /promesas-pago/vigentes-cliente/:clienteId`, `PATCH /:id/estado`, `PATCH /:id/cancelar`, `PATCH /:id/renegociar`, `GET /reporte/cumplimiento`, `GET /dashboard/cashflow`.
- ✅ `renegociar` deja la original en `renegociada` y crea nueva linkeada por `promesa_origen_id` (historial implícito).
- ✅ Permisos en seed: `COB_PRM_PROMESA_VER/CREAR/EDITAR/CANCELAR/RENEGOCIAR`, `COB_PRM_REPORTE_CUMPLIMIENTO`.

**Frontend pos-ventas (recién hecho 2026-06-22)**
- ✅ `components/cobranzas/promesas/PromesaPagoFormModal.jsx` — modal unificado (reemplaza inline en CobrosTemplateV2 + el viejo NuevaPromesaModal de Gestión de Mora). Soporta cuotaFija o selector multi-cuota; campos: fecha prometida, monto (con prorrateo si se edita), `tipo_evidencia`, `pagador_tipo` (+ Autocomplete de cliente para GARANTE/TERCERO), notas. Crea N promesas (una por cuota) vía `Promise.all`.
- ✅ `components/cobranzas/gestion-mora/NuevaPromesaModal.jsx` — convertido en wrapper retrocompatible que delega en el modal compartido.
- ✅ `components/cobranzas/promesas/CancelarPromesaModal.jsx` — diálogo con motivo obligatorio.
- ✅ `components/cobranzas/promesas/RenegociarPromesaModal.jsx` — diálogo con nueva fecha, nuevo monto, motivo/notas y tipo de evidencia.
- ✅ `views/cobranzas/PromesasListPage.jsx` — **pantalla operativa nueva** (faltaba). Filtros: estado / rango fecha / cobrador / cliente. Columnas: cliente (+pagador alterno), factura/cuota, fecha prometida, monto, estado chip, evidencia (icon+label), cobrador. Acciones por fila (gated por permiso): **Marcar cumplida** (pendiente), **Marcar incumplida** (pendiente), **Renegociar** (pendiente | incumplida), **Cancelar** (pendiente). Total prometido por moneda en header. Usa `StandardTable` con paginación cliente, `useConfirmDialog`, enums `COB_PROMESA_ESTADO/EVIDENCIA/PAGADOR` desde `_standards/enums/estados.js`, `fmtMoneda(value, code)` derivado de `factura.moneda.codigo` (cumple ui-standards multi-moneda + enums).
- ✅ Enums centralizados en `_standards/enums/estados.js`: `COB_PROMESA_ESTADO` (+ meta + helper `getCobPromesaEstadoChip`), `COB_PROMESA_EVIDENCIA` (+ meta con icon), `COB_PROMESA_PAGADOR` (+ meta). Modales (Cancelar/Renegociar) y banner en Cobros también migrados a `fmtMoneda` + moneda derivada de la factura.
- ✅ Backend `PromesasService.findAll` ahora incluye `factura.moneda.codigo` + enriquece con `cuota` (referencia suelta resuelta por separado) y `pagador_cliente`. `getVigentesPorCliente` también devuelve `factura.moneda.codigo` para que el banner en Cobros muestre el código correcto.
- ✅ `views/cobranzas/PromesasReportePage.jsx` — **reescrito 2026-06-22** siguiendo patrón canónico `ReporteVentas.jsx` (Resumen de Ventas): MUI Box/Paper/Card, header con back-arrow + ícono + título + subtítulo + botón `Exportar` outlined, breadcrumb local "Reportes > Cumplimiento de Promesas", filtros en Paper, grid de 5 KPI cards (Total / Cumplidas / Incumplidas / Tasa global / Monto cumplido) con íconos opacos y colores semánticos, tabla con Chip de tasa (verde ≥80% / ámbar ≥50% / rojo), skeleton durante carga, cashflow proyectado en sección separada. Removido el botón "Nueva promesa" — la operación vive sólo en `PromesasListPage`.
- ✅ Reporte **migrado a `/reportes/cobranzas/promesas`** (anteriormente `/cobranzas/promesas-reporte`). Linkeado desde `Reportes.jsx` (sección Cobranzas) y mapeado en `screenSubmoduloMap.js` para breadcrumb global. Patrón consistente con resto de reportes de cobranzas.
- ✅ Ruta nueva `/cobranzas/promesas` (permiso `COB_PRM_PROMESA_VER`) en `routes.jsx`.
- ✅ Sidebar `dataEstatica.jsx`: una sola entrada **"Promesas"** (operación, `/cobranzas/promesas`) bajo `COB_PROMESAS`. La entrada "Reporte de cumplimiento" se retiró del sidebar (los reportes viven sólo en el hub `/reportes`, no duplicados en el módulo). Anula la decisión previa A.3.7 Paso 4 que ocultaba Promesas dentro de Gestión de Mora.
- ✅ `PromesasListPage.jsx` — rediseño UX completo: filtros en Stack flex (no Grid), Autocompletes con `fullWidth` + `filterOptions={(x) => x}` para búsqueda RUC/CI server-side, columna Cobrador colapsada como caption bajo el cliente, columna Evidencia reducida a ícono+tooltip, acciones agrupadas en dropdown `actionsMode="select"`. Patrón documentado en `docs/ui-standards.md` como referencia canónica para listados.
- ✅ `PromesaPagoFormModal.jsx` — mismo fix de búsqueda por RUC/CI aplicado a Autocompletes de cliente y pagador.
- ✅ Banner de promesas en `CobrosTemplateV2.jsx` — rediseñado con tokens de theme (sin colores hardcoded), KPI header (count + "N vencidas" badge + totales por moneda) + chips ordenados (vencidas primero) + link "Ver detalle →". Gated por `COB_PRM_PROMESA_VER` (fetch y render).
- ✅ `docs/ui-standards.md` — agregadas dos secciones de estándares: **"Listados con filtros + tabla"** (a partir de PromesasListPage) y **"Pantallas de reporte"** (a partir de ReporteVentas/PromesasReportePage).

**Decisiones 2026-06-22**
- **Lista operativa = first-class menu entry** (revierte A.3.7 Paso 4 punto 124). Razón: sin pantalla propia, las pruebas D.4 (cancelar) y D.5 (renegociar) no son ejecutables por el usuario final; el reporte analítico no expone acciones por fila.
- **Modal de promesa unificado**: un solo componente para los tres entry-points (cobros wizard, gestión de mora, listado de promesas). Evita drift entre flujos.
- **Acciones "marcar cumplida/incumplida manualmente"**: necesarias para casos donde el cobro entró por otra vía (ej. transferencia directa) o cuando la fecha venció sin pago. Reutilizan `PATCH /:id/estado`.

**Items hechos en Submódulo 4**
- ✅ **Banner en Cobros** (HECHO 2026-06-22): `CobrosTemplateV2` consume `GET /promesas-pago/vigentes-cliente/:clienteId` y renderiza KPI + chips (vencidas primero) con link a `/cobranzas/promesas`. Gated por permiso. Tokens de theme (sin colores hardcoded).
- ✅ **Cierre automático al cobrar** (HECHO 2026-06-22): `PromesasService.cerrarPorCobro` refinado con `cuota_ids` del recibo: saldo cuota=0 → `cumplida`; saldo>0 y cuota recibió cobro → `cumplida_parcial`; saldo>0 sin cobro → no toca. Audita `AUTO_CLOSE`. Promesas factura-nivel usan criterio agregado.

**Pendiente Submódulo 4 (deferido)**
- ⏳ Historial de renegociaciones en el detalle (sigue `promesa_origen_id` recursivamente). UI no priorizada todavía.

### ✅ Submódulo 7 — Workflow de cobranza + Mesa de Gestión (`COB_WFL_*`, `COB_MG_*`): **COMPLETO** (2026-06-22)

Concepto reorganizado tras feedback del usuario: se identificaron **dos personas distintas** en la cobranza:
1. **Gestor de oficina (telemarketing)** — llama / WhatsAppea morosos, registra gestiones digitales, agenda promesas.
2. **Cobrador de ruta** — cobra físicamente; usa app móvil (`novasis-cobros-mobile`), no la web.

La web pos-ventas se enfoca en (1). La hoja de ruta queda como vista de planificación/supervisión, no como punto de carga.

**Backend (`COB_WFL_*` — gestiones)**
- ✅ Modelo `cob_gestion` con `tipo_gestion`, `resultado`, `observacion`, `proxima_accion/fecha`, `promesa_creada_id`, `anulada/motivo`, geo/duracion/adjuntos opcionales. 4 índices (cliente+fecha desc, cobrador+fecha desc, proxima_fecha, promesa_creada_id).
- ✅ Enums `CobTipoGestion` (VISITA | LLAMADA | WHATSAPP | EMAIL | SMS | OTRO) y `CobResultadoGestion` (COBRADO_TOTAL | COBRADO_PARCIAL | PROMESA_PAGO | NO_ATIENDE | CLIENTE_AUSENTE | DIRECCION_INCORRECTA | CLIENTE_DISPUTA | RECHAZO_PAGAR | COMPROMISO_LLAMADA_POSTERIOR | CLIENTE_FALLECIDO | OTRO).
- ✅ Endpoints `cobranzas/gestiones`: `POST /`, `GET /`, `GET /cliente/:id/timeline`, `GET /proximas-acciones/:cobradorId`, `GET /reporte-productividad`, `PATCH /:id`, `PATCH /:id/anular`.
- ✅ Permisos `COB_WFL_GESTION_VER/REGISTRAR/EDITAR/ANULAR`, `COB_WFL_REPORTE_PRODUCTIVIDAD`, `COB_WFL_VER_GESTIONES_AJENAS` en seed.

**Backend (`COB_MG_*` — Mesa de Gestión)**
- ✅ Submódulo nuevo `COB_MESA_GESTION` con privilegios `COB_MG_WORKLIST_VER`, `COB_MG_PASAR_A_RUTA`, `COB_MG_EXPORTAR`.
- ✅ `MesaGestionService.getWorklist` con `$queryRaw` + CTEs (`vencidas`, `ultima_g`, `prom_venc`, `prom_vig`, `mis_g`) → escala a 3000+ clientes con un solo round-trip. Devuelve: cliente + persona, saldo vencido, días máx vencido, última gestión, días sin gestión, promesas vigentes/incumplidas, gestionado_por_mi, prioridad calculada (50% mora + 25% gap gestión + 15% promesas incumplidas + 10% saldo).
- ✅ Filtros: `zona`, `cobrador_id`, `min_dias_vencido`, `sin_gestion_dias`, `solo_promesas_vencidas`, `solo_mis_gestiones`, `estado_gestion` (`todos | por_contactar | en_gestion | con_promesa_vigente`), búsqueda libre, 4 órdenes (`prioridad | vencimiento | saldo | sin_gestion`), paginado.
- ✅ `MesaGestionService.getResumen` — KPIs: total morosos, saldo total, sin gestión, tramos +30/+60/+90 días.

**Backend — Promesa enriquecida desde gestión (Opción A, decisión 2026-06-22)**
- ✅ `CreateGestionDto` extendido con `promesa_cuota_ids[]`, `promesa_tipo_evidencia`, `promesa_pagador_tipo`, `promesa_pagador_cliente_id`, `promesa_notas`.
- ✅ `GestionesService.create` cuando llega `promesa_cuota_ids`: crea **N promesas (una por cuota)** en la misma transacción con **prorrateo del monto** por saldo. Mantiene ruta legacy (`promesa_fecha + monto + factura_cab_id`) para retro-compat con app móvil. Valida que las cuotas pertenezcan al cliente/empresa. Si `pagador_tipo ≠ CLIENTE`, exige `pagador_cliente_id`.

**Frontend pos-ventas**
- ✅ `views/cobranzas/MesaGestionPage.jsx` — pantalla operativa principal del gestor de oficina:
  - Header + breadcrumb + botón "Mora avanzada"
  - `ScreenGuia` con 4 pasos
  - 6 KPIs (Clientes morosos, Saldo vencido, Sin gestión, +30d, +60d, +90d)
  - **Tabs principales**: "Bandeja de gestión" + "Promesas de pago" (esta segunda renderiza `PromesasListPage` embebida → unifica el flujo en una sola pantalla operativa)
  - Filtros con **debounce 400ms** (search/zona/mora/sin-gestión)
  - Tabs internas de estado: Todos / Por contactar / En gestión / Con promesa vigente
  - Botones de filtro: "Promesas incumplidas" + "Mis gestiones"
  - Tabla con chips diferenciados de mora (rojo >90d / naranja >30d), prioridad (rojo ≥70 / naranja ≥40 / azul) y badge "Yo" cuando la última gestión es del usuario actual; promesas vigentes (verde) vs incumplidas (naranja) en chips separados
  - Acciones rápidas por fila: Llamar (`tel:`), WhatsApp (link con saldo precargado), Registrar gestión, Ver historial del cliente
  - "Ver historial" abre `ClienteHistorialDrawer` **in-place** (sin perder contexto)
- ✅ `views/cobranzas/ProductividadCobradoresPage.jsx` — reporte con patrón `ReporteVentas`: KPIs (cobradores activos, total gestiones, efectivas, tasa efectividad), tabla con chip de tasa color-coded, export XLSX. Ruta `/reportes/cobranzas/productividad`.
- ✅ `components/cobranzas/gestiones/RegistrarGestionModal.jsx` — **modal unificado (Opción A)**: cuando resultado = PROMESA_PAGO, expande mostrando selector de cuotas pendientes (con "Solo vencidas" / "Todas" / "Limpiar"), fecha prometida, monto con prorrateo automático, tipo de evidencia, pagador (Cliente/Garante/Tercero) y notas. Una sola acción crea gestión + N promesas atómicas. Botón cambia a "Registrar gestión + promesa".
- ✅ `components/organismos/ClientesDesign/ClienteHistorialDrawer.jsx` — `GestionesTimelineCard` con últimas 8 gestiones y `CreditoDetalle` rediseñado: KPIs de cuotas (Vencido / Por vencer / Pagadas / Al día) + filtro por chips (Todas / Vencidas / Por vencer / Pagadas, pre-selecciona "Vencidas" si las hay) + tabla con scroll y sticky header para mostrar **todas** las cuotas (antes capeado a 5).
- ✅ Enums centralizados en `_standards/enums/estados.js`: `COB_GESTION_TIPO` + META + `getCobGestionTipoChip`, `COB_GESTION_RESULTADO` + META + `getCobGestionResultadoChip`.

**Sidebar y rutas (2026-06-22)**
- ✅ Entrada **"Mesa de Gestión"** (`/cobranzas/mesa-gestion`, ícono `mdi:headset`, submódulo `COB_MESA_GESTION`, permiso `COB_MG_WORKLIST_VER`).
- ✅ **"Gestión de Mora" → renombrada a "Mora avanzada (legal)"** para diferenciar claramente del trabajo diario de la mesa.
- ✅ **"Promesas" removida del sidebar** — ahora vive como tab dentro de Mesa de Gestión. La ruta `/cobranzas/promesas` sigue activa para deep links / retro-compat.
- ✅ `HojaRutaTab.jsx` (en Tesorería) limpiado: se eliminó la columna "Registrar" + state/modal/styled del botón. Mantiene "Últimas gestiones" como info de supervisor solo-lectura.
- ✅ `screenSubmoduloMap.js` mapea `/cobranzas/mesa-gestion` y `/reportes/cobranzas/productividad`.

**Decisiones 2026-06-22**
- **Mesa de Gestión separada de Mora avanzada**: aunque ambas miran morosos, son flujos distintos. Mesa = contacto diario informal (verbal/WhatsApp). Mora avanzada = expediente legal con doble autorización + documentos + estados formales. Conviven como pantallas separadas pero el nombre de cada una hace explícito el rol.
- **Promesas como tab dentro de Mesa de Gestión** (no submenu): el operador trabaja la bandeja y monitorea promesas en el mismo contexto.
- **Modal unificado de gestión + promesa (Opción A)** (vs. abrir dos modales encadenados o mantener mini-form pobre): elimina inconsistencia de datos (todas las promesas quedan con cuotas + evidencia + pagador). La pantalla "Nueva promesa" del tab sigue existiendo para casos donde no hubo gestión previa (p.ej. el cliente avisó por email, carga histórica).
- **No deprecar `PanelCobrador.jsx` aún**: queda como prototipo web del workflow del cobrador de ruta hasta que la app móvil esté en producción para todos los clientes.

### Próximo paso sugerido al retomar (actualizado 2026-06-22)

**Fases cerradas:**
- ✅ Fase A.3 — Intereses moratorios con comprobante fiscal (incluye reporte + exoneración por autorización + retry SIFEN).
- ✅ Fase B — Gestión de Mora avanzada + auditoría detallada CxC.
- ✅ Submódulo 4 — Promesas ampliadas (lista operativa, banner en cobros, cierre automático, reporte bajo `/reportes/cobranzas/promesas`).
- ✅ **Submódulo 7 — Workflow de cobranza + Mesa de Gestión** (gestiones, mesa priorizada con prorrateo de promesas, productividad, sidebar reorganizado, modal unificado gestión+promesa).
- ✅ Estándares de UI documentados (listados + reportes) en `docs/ui-standards.md`.

**Ocultos / deferidos:**
- ⏸ Submódulo 5 — Refinanciación (código intacto, sólo se ocultó la entrada de sidebar).

**Próximos candidatos (por prioridad):**
1. **Submódulo 6 — Línea de crédito y exposición** (`COB_LIC_*`): cálculo dinámico de capacidad disponible (`limite_credito − saldo_pendiente − promesas_vigentes − refinanciaciones_pendientes`); pantalla de exposición por cliente; bloqueo de venta cuando se excede.
2. **Submódulo 7 — Workflow de cobranza** (`COB_WFL_*`): tareas/resultados/calendario del cobrador; integración con hoja de ruta y panel cobrador.
3. **Submódulo 8 — Anticipos y saldos a favor** (`COB_SAF_*`): completar UI sobre seed existente `COB_SALDOS_FAVOR` (modelo ya está).
4. **Submódulo 9 — Disputas / reclamos** (`COB_DSP_*`): congelar factura sin que caiga en mora; flujo de resolución.
5. **Fase D — Reportes ejecutivos**: aging 30/60/90/120+, cobranza vs presupuesto, morosidad por zona.
6. **Submódulo 2 — Garantes** (`COB_GAR_*`): pospuesto explícitamente; integraciones placeholder ya listas (PDF expediente deja `garantes: []`).
7. **Submódulo 5 — Refinanciación**: reactivar sidebar + completar UX (selector real de cuotas, autocomplete cliente, filtro server-side `tipo_origen`).

**Deuda técnica pendiente (sin orden):**
- Extracción de Autorizaciones desde `PanelSupervisor.jsx` (~600 líneas mezcladas) a página propia.
- Migrar retry job de `@nestjs/schedule` a BullMQ o `pg_try_advisory_lock` (multi-instancia).
- Deep-link `?recibo=<id>` en `RecibosPanel`: fetch por id si el recibo no está en la página actual.
- Endpoint dedicado de cuotas pendientes por cliente (para `RefinanciacionWizard` y otros consumidores).
- Filtro `tipo_origen` server-side en `GET /solicitudes-credito` (hoy client-side en `SolicitudesCreditoTab`).
- Autocomplete reutilizable de clientes para sustituir `TextField` de `cliente_id` (`RefinanciacionesPage` y otras).

Deuda técnica registrada:
- Extracción real de Autorizaciones desde `PanelSupervisor.jsx` (~600 líneas de UI de descuentos/exoneraciones) a página propia.
- Migrar retry job de `@nestjs/schedule` a BullMQ o `pg_try_advisory_lock` cuando se pase a multi-instancia.
- Deep-link `?recibo=<id>` en `RecibosPanel` sólo abre el modal si el recibo está en la página actual del listado. Si está fuera del rango/filtro, el usuario debe buscar por número. Mejora: fetch por id si no se encuentra en la página actual.
- `RefinanciacionWizard` usa fallback de IDs separados por coma cuando `getCuotasPendientes` no responde — sustituir por selector real cuando exista endpoint dedicado de cuotas pendientes por cliente.
- Filtro "Solo refinanciaciones" en `SolicitudesCreditoTab` es client-side; ideal que `GET /solicitudes-credito` acepte `tipo_origen` server-side.
- `RefinanciacionesPage` usa TextField para `cliente_id`; sustituir por autocomplete reutilizable de clientes cuando exista.

---

## 0. CONTEXTO Y ALCANCE

Este plan **extiende** `plan-creditos-cobranzas.md` con los submódulos que faltan para tener una gestión completa de cuentas a cobrar a nivel ERP. El plan anterior cubrió:

- ✅ Recibos multifactura
- ✅ Anular/modificar fecha recibo
- ✅ Config mora + cálculo al cobrar
- ✅ Promesas de pago (versión básica)
- ✅ Autorizaciones de descuento
- ✅ Panel cobrador mobile-first
- ✅ Hoja de ruta del cobrador
- ✅ Reporte por cobrador
- ✅ `cobrador_id` en facturas/recibos
- ✅ Solicitud de crédito con workflow de aprobación
- ✅ Historial crediticio (PDF — formato `historial` en estado de cuenta)

Este plan agrega **9 submódulos nuevos** dentro de `COBRANZAS` para cerrar el ciclo completo de cobranzas (desde la concesión del crédito hasta la recuperación/incobrabilidad), respetando lo que ya existe.

### Inventario rápido de lo existente que vamos a tocar/extender

**Backend (modelos Prisma relevantes):**
- `clientes` — ya tiene `limite_credito`, `saldo_pendiente`, `bloqueado_credito`, `cobrador_id`, `dias_mora_maximo`, `fecha_ultimo_pago`
- `factura_cab` — ya tiene `cobrador_id`, `vendedor_id`, `saldo_pendiente`, `saldo_disponible`, `dvencpag`, `icondcred`, `dplazocre`, `dcuotas`
- `factura_cuotas` — `dvenccuo`, `dmoncuota`, `nro_cuota`, `saldo_pendiente`, `estado`
- `cuentas_cobrar` — vista materializada operativa
- `recibos_cobro` — ya tiene `cobrador_id`, `mora_total`, `descuento_porcentaje/monto`, `motivo_descuento`, `total_intereses`
- `recibo_cobro_detalle` — ya tiene `monto_mora`, `dias_mora`, `tasa_mora_aplicada`
- `solicitud_credito` — ya tiene `cobrador_id`, `dia_fijo_pago`, `dia_cobro_semana`, `dias_gracia`, `cronograma`
- `promesas_pago` — versión básica ya existe (factura/cuota, monto, fecha, estado, recordatorio)
- `autorizaciones_descuento` — ya tiene TTL, aprobación, motivo
- `config_mora` — ya tiene tipo_calculo, tasa, base_calculo, periodo_gracia, mínimo, máximo

**Backend (módulos NestJS existentes):**
- `cobros/` — recibos, multifactura, dashboard, estado cuenta, historial crediticio
- `cobranzas/` — autorizaciones, config-mora, panel-cobrador, promesas, reporte-cobrador, jobs
- `recibos/` — gestión de comprobantes
- `clientes/`, `solicitudes-credito/`, `tesoreria/`

**Frontend (pantallas que extenderemos):**
- `organismos/ClientesDesign/` — ClienteHistorialDrawer, ClientesListConfig, ClienteFormDialog
- `ventas/` — FacturasTab, NotasCreditoTab, SolicitudesCreditoTab
- `tesoreria/` — RevisionCxCTab, HojaRutaTab, TesChequesTab
- Standards reutilizables: `MonedaInput`, `EmptyState`, `ScreenGuia`, `EstadoChip`, `StandardTable`, `PrereqChecklist`, etc.

---

## 1. SUBMÓDULOS NUEVOS (resumen ejecutivo)

| # | Submódulo | Código permiso | Propósito |
|---|---|---|---|
| 1 | Gestión de mora avanzada | `COB_GMR_*` | Estados Informconf/demanda/incobrable + timeline |
| 2 | Garantes | `COB_GAR_*` | Avalistas vinculados a cliente + propagación |
| 3 | Intereses con exoneración | `COB_INT_*` | Devengamiento + exoneración auditable |
| 4 | Promesas ampliadas | `COB_PRM_*` | Promesas multi-factura, renegociación, cierre auto |
| 5 | Refinanciación | `COB_REF_*` | Nuevo plan de cuotas que reemplaza el original |
| 6 | Línea de crédito y exposición | `COB_LIC_*` | Cálculo dinámico de capacidad disponible |
| 7 | Workflow de cobranza | `COB_WFL_*` | Tareas, resultados, calendario del cobrador |
| 8 | Anticipos y saldos a favor | `COB_SAF_*` | Ya tiene seed `COB_SALDOS_FAVOR` — completar |
| 9 | Disputas / reclamos | `COB_DSP_*` | Congelar factura sin caer en mora |

---

## 2. SUBMÓDULO 1 — Gestión de mora avanzada (`COB_GMR_*`)

### 2.1 Modelo de datos

```prisma
model cob_gestion_mora {
  id                String   @id @default(uuid())
  empresa_id        String
  cliente_id        String
  estado_actual     CobMoraEstado
  fecha_ingreso     DateTime  // entrada al estado actual
  monto_involucrado Decimal   @db.Decimal(19,4)  // snapshot al ingreso
  observacion       String?
  documento_url     String?   // evidencia (PDF demanda, captura Informconf)
  usuario_id        String
  autorizado_por_id String?   // doble autorización para DEMANDA/INCOBRABLE
  created_at        DateTime  @default(now())
  updated_at        DateTime  @updatedAt

  cliente           clientes  @relation(...)
  historial         cob_gestion_mora_historial[]
  facturas          cob_gestion_mora_factura[]  // N:M con facturas afectadas

  @@index([empresa_id, cliente_id])
  @@index([empresa_id, estado_actual])
}

model cob_gestion_mora_historial {
  id                String   @id @default(uuid())
  gestion_mora_id   String
  estado_anterior   CobMoraEstado?
  estado_nuevo      CobMoraEstado
  fecha             DateTime  @default(now())
  motivo            String
  usuario_id        String
  documento_url     String?
}

model cob_gestion_mora_factura {
  gestion_mora_id   String
  factura_cab_id    String
  saldo_snapshot    Decimal   @db.Decimal(19,4)  // saldo al momento de incluir
  @@id([gestion_mora_id, factura_cab_id])
}

enum CobMoraEstado {
  AL_DIA
  GESTION_INTERNA      // cobranza propia, sin escalado externo
  INFORMCONF           // reportado a buró
  DEMANDA              // judicializado
  INCOBRABLE           // baja contable
  RECUPERADA           // pagó después de estar en mora avanzada
  REFINANCIADA         // se refinanció (ver submódulo 5)
}
```

### 2.2 Máquina de estados (transiciones permitidas)

```
AL_DIA → GESTION_INTERNA (auto al superar X días vencido configurable)
GESTION_INTERNA → INFORMCONF (manual, requiere permiso COB_GMR_REPORTAR_INFORMCONF)
GESTION_INTERNA → DEMANDA (manual, doble autorización)
INFORMCONF → DEMANDA (manual, doble autorización)
INFORMCONF → RECUPERADA (auto cuando saldo = 0)
DEMANDA → INCOBRABLE (manual, doble autorización + documento)
DEMANDA → RECUPERADA (auto cuando saldo = 0 + acuerdo cerrado)
INCOBRABLE → RECUPERADA (excepcional: pago tardío sin proceso)
* → REFINANCIADA (al ejecutar refinanciación — ver submódulo 5)
RECUPERADA / REFINANCIADA → AL_DIA (después de N días sin nueva mora)
```

**Reglas:**
- Las transiciones a `INFORMCONF`/`DEMANDA`/`INCOBRABLE` **propagan al garante** automáticamente (ver submódulo 2).
- Cada transición crea registro en `cob_gestion_mora_historial`.
- `documento_url` obligatorio para `DEMANDA` (carátula) e `INCOBRABLE` (acta directorio o similar).
- Al pasar a `INCOBRABLE`, se genera asiento contable de previsión (futuro módulo contable).

### 2.3 Permisos a agregar en seed

```
COB_GMR_GESTION_MORA_VER
COB_GMR_GESTION_MORA_REGISTRAR
COB_GMR_REPORTAR_INFORMCONF
COB_GMR_RETIRAR_INFORMCONF
COB_GMR_REGISTRAR_DEMANDA
COB_GMR_DECLARAR_INCOBRABLE  ← requiere doble autorización
COB_GMR_REVERTIR_ESTADO
COB_GMR_EXPORTAR
```

### 2.4 Endpoints backend

```
GET    /cobranzas/gestion-mora?estado=&clienteId=&desde=&hasta=
GET    /cobranzas/gestion-mora/:id  (con historial completo)
POST   /cobranzas/gestion-mora  (crear/transicionar)
PATCH  /cobranzas/gestion-mora/:id/transicion  (cambiar estado)
GET    /cobranzas/gestion-mora/cliente/:clienteId  (estado actual + timeline)
GET    /cobranzas/gestion-mora/dashboard  (totales por estado, monto involucrado)
GET    /cobranzas/gestion-mora/:id/pdf  (informe del expediente)
```

### 2.5 Pantallas frontend

**Nueva: `cobranzas/GestionMoraTab.jsx`**
- Listado con filtros por estado, cliente, rango de fechas, cobrador.
- Columnas: cliente, estado actual (chip color), días en estado, monto involucrado, garante (si tiene), última gestión.
- Acciones por fila: ver timeline, transicionar estado, exportar expediente.
- `EmptyState` cuando no hay registros.
- `ScreenGuia` con explicación del flujo y semántica de estados.

**Extensión: `ClienteHistorialDrawer.jsx`**
- Nueva sección "Estado de gestión de mora" con timeline visual de transiciones.
- Si el cliente tiene gestión activa, badge prominente en el header del drawer.

**Extensión: `ClientesListConfig.jsx`**
- Nueva columna opcional "Estado mora" con chip.
- Filtro adicional por estado de mora.

**Extensión: `FacturasTab.jsx`**
- Indicador en la fila si la factura está incluida en una gestión de mora activa (chip mini).

**Modal: `TransicionMoraModal.jsx`**
- Form: nuevo estado (con validación de transiciones permitidas), motivo (obligatorio), documento adjunto (obligatorio para ciertos estados), autorizado_por (combo con permiso `COB_GMR_*_AUTORIZAR` cuando aplica).

### 2.6 Reportes / PDFs

- **Expediente de gestión de mora** (PDF): timeline completo, facturas afectadas, monto, documentos adjuntos. Útil para abogado.
- **Dashboard ejecutivo** (pantalla): clientes por estado, monto en cada estado, evolución mensual.
- Extensión al **historial crediticio PDF** existente (`msv-kude/src/historial-crediticio/`): agregar sección "Estado de gestión de mora" con timeline.

---

## 3. SUBMÓDULO 2 — Garantes (`COB_GAR_*`)

### 3.1 Modelo de datos

```prisma
model cob_garante {
  id                  String   @id @default(uuid())
  empresa_id          String
  cliente_avalado_id  String   // quien recibe el aval
  cliente_garante_id  String   // quien avala (debe ser otro cliente del sistema)
  solicitud_credito_id String? // si fue creado para una solicitud puntual
  tipo                CobGaranteTipo  // SOLIDARIO | SUBSIDIARIO
  monto_max_avalado   Decimal  @db.Decimal(19,4)
  vigente_desde       DateTime
  vigente_hasta       DateTime?  // null = indefinido
  documento_aval_url  String?    // PDF firmado
  activo              Boolean   @default(true)
  motivo_baja         String?
  usuario_id          String
  created_at          DateTime  @default(now())

  avalado   clientes @relation("garante_avalado", fields: [cliente_avalado_id], references: [id])
  garante   clientes @relation("garante_garante", fields: [cliente_garante_id], references: [id])

  @@unique([cliente_avalado_id, cliente_garante_id, vigente_desde])
  @@index([empresa_id, cliente_garante_id])
  @@index([empresa_id, cliente_avalado_id])
}

enum CobGaranteTipo {
  SOLIDARIO    // responde igual que el avalado
  SUBSIDIARIO  // responde solo si avalado no paga
}
```

### 3.2 Reglas de negocio

- El garante es **otro cliente del sistema** (no entidad aparte). Permite reutilizar score, dirección, contacto.
- Un cliente puede ser garante de varios y tener varios garantes.
- **Validación al crear aval**: el garante no debe tener score crediticio bajo (configurable, ej. ≥ 50 puntos).
- **Exposición del garante** se suma a su utilización de línea de crédito (no le impide comprar, pero reduce su capacidad disponible).
- **Propagación de mora**: cuando el avalado pasa a `INFORMCONF`/`DEMANDA`/`INCOBRABLE`, se crea automáticamente registro en `cob_gestion_mora` para el garante con `motivo = "AVAL_ACTIVADO"` referenciando al avalado.
- **Score del garante**: las facturas avaladas impagas afectan su score con peso reducido (50% del impacto que tienen sobre el avalado).
- **Pago del garante**: si el garante paga por el avalado, se registra en `recibos_cobro.pagador_id` (nuevo campo opcional) para trazabilidad.

### 3.3 Permisos

```
COB_GAR_GARANTE_VER
COB_GAR_GARANTE_VINCULAR
COB_GAR_GARANTE_DESVINCULAR
COB_GAR_GARANTE_EXPORTAR
```

### 3.4 Endpoints

```
GET    /cobranzas/garantes/avalado/:clienteId       (quiénes lo avalan)
GET    /cobranzas/garantes/garante/:clienteId       (a quiénes avala)
GET    /cobranzas/garantes/exposicion/:clienteId    (monto total avalado vigente)
POST   /cobranzas/garantes                          (vincular)
PATCH  /cobranzas/garantes/:id/baja                 (desvincular con motivo)
GET    /cobranzas/garantes/reporte-exposicion       (ranking por garante)
```

### 3.5 Pantallas frontend

**Extensión: `ClienteFormDialog.jsx`**
- Nueva pestaña/sección "Garantes" con dos listas:
  - "Quiénes me avalan" (con datos del garante, monto, vigencia).
  - "A quiénes avalo" (con datos del avalado, monto, vigencia, estado mora del avalado).

**Extensión: `SolicitudCreditoDetalleDialog.jsx`**
- Si la solicitud exige garante (regla configurable por monto/score), bloquear aprobación hasta que se vincule.
- Botón "Vincular garante" abre `VincularGaranteModal`.

**Nueva: `VincularGaranteModal.jsx`**
- Selector de cliente (autocomplete `ClienteSearch` existente).
- Tipo (solidario/subsidiario), monto máximo, vigencia, documento adjunto.
- Validación de score mínimo del garante con `InlineValidationBanner` si no cumple.

**Extensión: `ClienteHistorialDrawer.jsx`**
- Sección "Garantes y avales" con resumen + link al detalle.

**Extensión: `RevisionCxCTab.jsx`**
- Nueva columna "Garante" en el listado de cuentas a cobrar.

**Nueva: `cobranzas/ExposicionGarantesReport.jsx`**
- Ranking de garantes por exposición total + riesgo (avalados con mora).

### 3.6 PDFs

- **Aval firmado** (template): generar PDF con datos del aval para que se imprima y firme.
- Extensión al **historial crediticio**: sección "Aval y garantías" tanto en el reporte del avalado como del garante.

### 3.7 Integraciones con submódulos existentes

- **Solicitud de crédito**: agregar paso "Definir garantes" en el wizard. Configurable cuándo es obligatorio (por empresa: por monto, por plazo, por score).
- **Score crediticio** (cálculo ya implementado en `getHistorialCrediticio`): agregar componente "facturas avaladas en mora" con peso 0.5x.

---

## 4. SUBMÓDULO 3 — Intereses con exoneración y comprobante fiscal (`COB_INT_*`)

> ⚠️ **Decisiones tomadas (2026-06-12)** — ver §4.7 para el detalle.
> - Devengamiento **on-demand al cobrar** (no cron nocturno). El interés no devengado no figura en CxC.
> - **Comprobante fiscal por interés cobrado**: configurable por empresa (Factura | Nota de Débito | Ninguno).
> - **Nota de Débito-e NO está implementada** todavía en el ERP — la opción ND queda **stub** hasta el plan separado de ND-e. Mientras tanto, default empresa = Factura.
> - **IVA aplicado al interés**: configurable por empresa (0%, 5%, 10% o exento).
> - **Exoneración**: reusa el flujo de `autorizaciones_descuento` con nuevo campo `tipo`.
> - **Política para clientes en DEMANDA/INCOBRABLE**: TBD (ver §4.7).

### 4.1 Modelo de datos

```prisma
// Configuración fiscal por empresa (una fila por empresa)
model cob_config_intereses {
  id                      String   @id @default(uuid())
  empresa_id              String   @unique
  tipo_comprobante        CobComprobanteIntereses @default(FACTURA)
  // ⚠️ NOTA_DEBITO queda stub hasta el plan ND-e
  iva_porcentaje          Decimal  @db.Decimal(5,2) @default(10.00)
  // 0 = exento; 5/10 = tasa IVA aplicada al interés
  producto_servicio_id    String?   // FK al ítem "Intereses moratorios" en catálogo
  // necesario para emitir el DE; se crea al instalar la empresa o en config
  pausar_en_demanda       Boolean   @default(true)
  pausar_en_incobrable    Boolean   @default(true)
  imputacion_orden        CobImputacionOrden @default(INTERES_PRIMERO)
  // INTERES_PRIMERO | CAPITAL_PRIMERO
  actualizado_por_id      String?
  updated_at              DateTime  @updatedAt
}

enum CobComprobanteIntereses {
  FACTURA       // default — emite factura electrónica nueva por el interés
  NOTA_DEBITO   // stub hasta plan ND-e
  NINGUNO       // sin comprobante fiscal (uso interno; advertir al usuario)
}

enum CobImputacionOrden {
  INTERES_PRIMERO
  CAPITAL_PRIMERO
}

// Registro del interés cobrado y su comprobante fiscal
model cob_interes_cobrado {
  id                  String   @id @default(uuid())
  empresa_id          String
  factura_cuota_id    String   // cuota origen sobre la que se calculó la mora
  recibo_cobro_id     String   // recibo en el que se cobró
  fecha               DateTime
  dias_mora           Int
  tasa_aplicada       Decimal  @db.Decimal(8,4)
  base_calculo        Decimal  @db.Decimal(19,4)  // saldo capital al momento
  monto_interes_bruto Decimal  @db.Decimal(19,4)  // interés devengado sin descuentos
  monto_exonerado     Decimal  @db.Decimal(19,4)  @default(0)
  monto_iva           Decimal  @db.Decimal(19,4)  @default(0)
  monto_total_cobrado Decimal  @db.Decimal(19,4)  // bruto - exonerado + iva
  // Comprobante fiscal generado
  tipo_comprobante    CobComprobanteIntereses
  factura_cab_id      String?  // si tipo = FACTURA, FK a la factura emitida
  // nota_debito_id   String?  // futuro, cuando exista ND-e
  autorizacion_descuento_id String?  // si hubo exoneración, FK al descuento autorizado
  usuario_id          String
  created_at          DateTime  @default(now())

  cuota               factura_cuotas @relation(...)
  recibo              recibos_cobro  @relation(...)

  @@index([empresa_id, factura_cuota_id])
  @@index([empresa_id, recibo_cobro_id])
  @@index([empresa_id, fecha])
}
```

> **Nota sobre `cob_interes_exoneracion`**: ya **no se crea esta tabla**. Las exoneraciones se canalizan por `autorizaciones_descuento` (ya existente) extendido con campo `tipo` (ver §4.2). Esto cumple el pedido del usuario de "reusar autorización de descuento" y evita duplicar el flujo de doble autorización.

### 4.2 Extensión a `autorizaciones_descuento` (existente)

```prisma
model autorizaciones_descuento {
  // ... campos existentes
  // NUEVOS:
  tipo              CobAutorizacionTipo @default(QUITA_CAPITAL)
  // INTERES_MORA  → reduce el interés moratorio al cobrar (NO toca capital)
  // QUITA_CAPITAL → reduce el saldo de la cuota (comportamiento histórico)
  // OTRO          → uso libre
}

enum CobAutorizacionTipo {
  INTERES_MORA
  QUITA_CAPITAL
  OTRO
}
```

**Compatibilidad**: registros existentes se asumen `QUITA_CAPITAL` (preserva comportamiento). Migración: `UPDATE autorizaciones_descuento SET tipo = 'QUITA_CAPITAL' WHERE tipo IS NULL`.

### 4.3 Reglas operativas

#### 4.3.1 Devengamiento (cuándo se calcula el interés)

- **On-demand al cobrar** (opción B confirmada): el sistema calcula el interés moratorio **en el instante del cobro**, usando `config_mora` y el saldo pendiente al momento.
- **No** se devenga nocturnamente — el reporte de CxC NO muestra interés acumulado (sólo días de mora). Esto evita la obligación de emitir DEs nocturnos por intereses que el cliente quizás nunca pague.
- **Pausa por estado de mora**: si el cliente tiene `cob_gestion_mora` activa en `DEMANDA` o `INCOBRABLE` (y la config lo indica), el interés calculado es 0 — la mora se congela.

#### 4.3.2 Cobro con interés (flujo nuevo)

Reemplaza el flujo actual de `cobros.service.ts:945-973` (que sumaba `total_mora` al recibo sin generar DE):

```
1. Cobrador inicia cobro de cuota X
2. Sistema calcula interés moratorio bruto (config_mora)
3. UI muestra al cajero: capital | interés bruto | descuento aplicable | a cobrar
4. Si cajero exonera (parcial o total): se crea autorizacion_descuento con tipo=INTERES_MORA
   (si el monto supera umbral → se solicita autorización al supervisor, igual flujo actual)
5. Se calcula IVA sobre (interés bruto − exonerado), según cob_config_intereses.iva_porcentaje
6. ANTES de generar el recibo, se emite el comprobante fiscal por el interés neto + IVA:
   - tipo_comprobante = FACTURA → emite factura electrónica nueva al cliente
     · Un único ítem: "Intereses moratorios factura {nro}" (producto/servicio configurado)
     · IVA según config
     · Misma sucursal/timbrado/punto que el recibo
   - tipo_comprobante = NOTA_DEBITO → ⚠️ no implementado; bloquear con mensaje claro
   - tipo_comprobante = NINGUNO → no emite (advertir al cajero "no se emitirá comprobante fiscal")
7. Se crea registro en cob_interes_cobrado con todos los snapshots + FK al comprobante
8. Se aplica el pago al recibo, que ahora distribuye contra:
   · cuotas originales (capital)
   · factura nueva de intereses (interés + IVA)
9. Asiento contable resultante (cuando exista módulo contable):
   - Debe: Caja/Banco                         total
   - Haber: Cuentas por Cobrar (capital)      capital
   - Haber: Ingresos por intereses moratorios interés neto
   - Haber: IVA Débito Fiscal 10%             iva
```

#### 4.3.3 Imputación

- Configurable por empresa: `INTERES_PRIMERO` (default) o `CAPITAL_PRIMERO`.
- Si pago parcial: respeta el orden y deja saldo pendiente en la cuota correspondiente.

#### 4.3.4 Refinanciación

Al refinanciar (submódulo 5), por cada cuota refinanciada el sistema ofrece:
- **Capitalizar intereses** → emite factura de intereses (igual flujo §4.3.2) y suma su saldo al capital del nuevo plan.
- **Exonerar** → registra `autorizacion_descuento tipo=INTERES_MORA` y no emite nada.
- **Mantener pendiente** → no emite ahora; al pago futuro se vuelve a calcular.

#### 4.3.5 Anulación

- Recibo anulado → si emitió factura por intereses, se debe emitir Nota de Crédito Electrónica de esa factura (flujo NC-e ya existe).
- Exoneración anulada (autorización revertida) → el monto exonerado vuelve como deuda cobrable; queda en `cob_interes_cobrado.estado = REVERTIDA` para auditoría.

### 4.4 Permisos

```
COB_INT_INTERES_VER
COB_INT_CONFIG_FISCAL_EDITAR           ← edita cob_config_intereses
COB_INT_EXPORTAR
COB_INT_REVERTIR_EXONERACION
COB_INT_VER_REPORTE_EXONERACIONES
```

> La autorización para exonerar reusa los permisos existentes de `autorizaciones_descuento` (`COB_AUT_*`). El campo `tipo = INTERES_MORA` no requiere permiso adicional — quien autoriza descuento puede autorizar exoneración.

### 4.5 Endpoints

```
GET    /cobranzas/intereses/config                  (config fiscal de la empresa)
PATCH  /cobranzas/intereses/config                  (editar tipo_comprobante, iva, producto_servicio_id, etc.)
GET    /cobranzas/intereses/calcular?cuotaId=&fecha= (preview interés al cobrar — no persiste)
POST   /cobranzas/intereses/cobrar                  (emite comprobante + genera registro cob_interes_cobrado)
GET    /cobranzas/intereses/cliente/:clienteId      (historial de intereses cobrados/exonerados)
GET    /cobranzas/intereses/reporte-cobrados?desde=&hasta=
GET    /cobranzas/intereses/reporte-exoneraciones?desde=&hasta=&usuario=
```

### 4.6 Pantallas frontend

**Extensión: `cobros/CobrarMultifacturaWizard.jsx`**
- Step de imputación: columnas "Interés bruto" + "Exonerado" + "IVA" + "A cobrar".
- Banner informativo: "Se emitirá {tipo_comprobante} por Gs. X de intereses" según config empresa.
- Si `tipo_comprobante = NINGUNO` → banner warning "Esta empresa no emite comprobante fiscal por intereses".
- Botón "Exonerar" inline en cada cuota → abre modal de descuento existente (con `tipo=INTERES_MORA` preseleccionado).

**Nueva: `configuracion/ConfigInteresesEmpresa.jsx`**
- Tipo de comprobante (radio: Factura | ND | Ninguno; ND deshabilitado con tooltip "Próximamente").
- IVA aplicable (combo: 0% / 5% / 10% / Exento).
- Producto/servicio "Intereses moratorios" (autocomplete de catálogo; CTA para crearlo si no existe).
- Pausar devengamiento en DEMANDA / INCOBRABLE (switches).
- Orden de imputación (radio).

**Extensión: modal de autorización de descuento existente**
- Nuevo campo "Tipo": Interés mora | Quita capital | Otro.
- Al seleccionar "Interés mora": el monto se aplica al interés calculado; al seleccionar "Quita capital": comportamiento histórico.

**Nueva: `cobranzas/ReporteInteresesPage.jsx`**
- Tab 1 — Cobrados: fecha, cliente, cuota origen, interés bruto, exonerado, IVA, comprobante emitido (link).
- Tab 2 — Exonerados: fecha, cliente, monto, motivo, usuario que solicitó, autorizador.
- Export Excel + PDF.

**Extensión: `RevisionCxCTab.jsx`**
- ❌ No agregar columna "Interés devengado pendiente" (no se devenga hasta cobrar).
- ✅ Sí agregar columna "Días de mora" (ya existe) y opcional "Interés estimado al día" (cálculo on-the-fly, sólo display).

### 4.7 Decisiones tomadas (2026-06-12) y dependencias

| Pregunta | Decisión | Implicancia |
|---|---|---|
| ¿Cuándo se emite el comprobante por interés? | **Al cobrar** (opción B) | El interés no aparece en CxC hasta que el cliente lo paga. Reportes muestran días de mora, no monto interés. |
| ¿IVA aplicado al interés? | **Configurable por empresa** (`cob_config_intereses.iva_porcentaje`) | Algunos rubros lo dejan exento; otros al 10%. Cada empresa decide en su pantalla de configuración. |
| ¿Tipo de comprobante? | **Configurable por empresa**: Factura \| ND \| Ninguno | ND queda **stub** hasta plan ND-e separado. Default `FACTURA` para no bloquear adopción. |
| ¿Cómo se exonera el interés? | **Reusa `autorizaciones_descuento`** con nuevo `tipo` enum | No duplicamos la tabla ni el flujo de doble autorización ya implementado. |
| Política intereses en DEMANDA/INCOBRABLE | **TBD** — campos `pausar_en_demanda/incobrable` en config, default `true` | Definir con el contador del cliente piloto antes del go-live. |

#### Dependencias técnicas

- **Bloqueante**: el flujo §4.3.2 requiere que `facturacion_electronica.service.ts` exponga método `emitirFacturaIntereses(payload)` que reuse la emisión actual de facturas-e (mismo timbrado, mismo envío a SET). Estimado: ½ día de adaptación.
- **No bloqueante**: la opción `NOTA_DEBITO` queda con código stub que tira `NotImplementedException` con mensaje claro al usuario. Se activa cuando el plan separado de **ND-e** entregue `emitirNotaDebitoElectronica()`.
- **Dato maestro**: cada empresa debe tener un producto/servicio "Intereses moratorios" en su catálogo. El instalador del módulo (`POST /cobranzas/intereses/config` la primera vez) ofrece crearlo automáticamente si no existe.

#### Política diferenciada por estado de mora (a confirmar con contador)

Defaults propuestos (override por empresa):

| Estado del cliente | ¿Se devenga interés? | ¿Se emite factura? |
|---|---|---|
| AL_DIA / GESTION_INTERNA / INFORMCONF | Sí | Sí, según config empresa |
| DEMANDA | **No** (config `pausar_en_demanda=true`) | N/A |
| INCOBRABLE | **No** (config `pausar_en_incobrable=true`) | N/A |
| RECUPERADA | Sí (vuelve a calcular sobre pagos futuros) | Sí |
| REFINANCIADA | No sobre cuotas viejas (se cancelan); sí sobre cuotas nuevas | Sí, sobre las nuevas |

---

## 5. SUBMÓDULO 4 — Promesas ampliadas (`COB_PRM_*`)

### 5.1 Qué falta a la versión actual

La tabla `promesas_pago` ya existe pero es 1 promesa = 1 cuota/factura. Lo nuevo:

```prisma
model promesas_pago {
  // ... campos existentes
  // NUEVOS:
  facturas_objetivo cob_promesa_factura[]  // N:M con facturas (no solo una)
  tipo_evidencia    CobPromesaEvidencia    @default(VERBAL)
  promesa_origen_id String?                 // si renegocia otra
  pagador_tipo      CobPromesaPagador       @default(CLIENTE)  // CLIENTE | GARANTE | TERCERO
  pagador_cliente_id String?                // si pagador != CLIENTE titular
  monto_cumplido    Decimal @db.Decimal(19,4) @default(0)
  fecha_cumplimiento DateTime?
  cumplida_recibo_id String?                // recibo que la cerró
}

model cob_promesa_factura {
  promesa_pago_id String
  factura_cab_id  String
  monto_asignado  Decimal @db.Decimal(19,4)
  @@id([promesa_pago_id, factura_cab_id])
}

enum CobPromesaEvidencia {
  VERBAL
  WHATSAPP
  EMAIL
  DOCUMENTO_FIRMADO
}

enum CobPromesaPagador {
  CLIENTE
  GARANTE
  TERCERO
}
```

### 5.2 Reglas operativas

- **Cierre automático**: cuando entra recibo con monto + facturas + fechas que matchean una promesa vigente, se marca `cumplida` y se vincula `cumplida_recibo_id`. Lógica en `CobrosService.create()`.
- **Cumplimiento parcial**: si el cobro cubre solo parte → `cumplida_parcial` + nueva promesa por el saldo (si el cobrador confirma).
- **Incumplimiento automático**: cron diario marca `incumplida` las promesas con `fecha_prometida < today - 1` sin cobro asociado. Dispara alerta al cobrador.
- **Renegociación**: al crear nueva promesa con `promesa_origen_id`, la anterior pasa a `renegociada`.
- **Banner al cobrar** (lo más importante): pantalla de cobro muestra arriba banner con promesas vigentes/incumplidas del cliente. Si el monto a cobrar matchea → pre-vincula.
- **Score**: agregar componente "cumplimiento de promesas" (peso ~10%) al cálculo del score.
- **Bloqueo de venta**: cliente con N promesas incumplidas vigentes (configurable) → requiere autorización para nueva venta.
- **Garante puede prometer**: registrar quién promete (afecta su historial si incumple).

### 5.3 Permisos (extender los existentes)

```
COB_PRM_PROMESA_VER             (ya existe COB_PROMESAS.VER)
COB_PRM_PROMESA_CREAR           (ya existe)
COB_PRM_PROMESA_EDITAR          (ya existe)
COB_PRM_PROMESA_CANCELAR        ← nuevo
COB_PRM_PROMESA_RENEGOCIAR      ← nuevo
COB_PRM_AUTORIZAR_VENTA_CON_INCUMPLIMIENTOS  ← nuevo
COB_PRM_REPORTE_CUMPLIMIENTO    ← nuevo
```

### 5.4 Endpoints (extender)

```
POST   /cobranzas/promesas/multi-factura       (con array de facturas)
POST   /cobranzas/promesas/:id/renegociar      (crea nueva, marca origen)
PATCH  /cobranzas/promesas/:id/cancelar
GET    /cobranzas/promesas/cliente/:clienteId/vigentes  (banner cobro)
GET    /cobranzas/promesas/reporte-cumplimiento?desde=&hasta=&cobrador=
GET    /cobranzas/promesas/dashboard-cashflow  (proyección por fecha prometida)
```

### 5.5 Pantallas

**Extensión: `CobrarMultifacturaWizard.jsx`**
- Banner superior con promesas vigentes/incumplidas del cliente.
- Si match: chip "Vincular a promesa #X" (auto-marcado).
- Botón "Crear promesa al saldo restante" si quedó saldo.

**Extensión: `PanelCobrador.jsx`**
- Chip "Promesa vigente" en cada cuota con promesa (ya planeado en F3.5, marcar como completo o validar).
- Acción "Registrar promesa multi-factura" desde detalle de cliente.

**Extensión: `FacturasTab.jsx` / `RevisionCxCTab.jsx`**
- Columna "Promesa" con fecha próxima.

**Nueva: `cobranzas/PromesasReportPage.jsx`**
- Cumplimiento por cobrador, por cliente.
- Cashflow proyectado por semana (KPI gerencial).

### 5.6 Integraciones

- **Historial crediticio PDF**: agregar "Cumplimiento de promesas: X/Y (Z%)".
- **Recordatorios automáticos** (submódulo 7): la promesa dispara recordatorio el día anterior.
- **Cobranza workflow** (submódulo 7): "registrar promesa" es un resultado posible de la tarea de cobranza.

---

## 6. SUBMÓDULO 5 — Refinanciación (`COB_REF_*`)

### 6.1 Modelo de datos

```prisma
model cob_refinanciacion {
  id                  String   @id @default(uuid())
  empresa_id          String
  cliente_id          String
  fecha               DateTime
  motivo              String
  monto_capital_orig  Decimal  @db.Decimal(19,4)
  monto_interes_orig  Decimal  @db.Decimal(19,4)
  monto_interes_exon  Decimal  @db.Decimal(19,4)  // exonerado en la refinanciación
  monto_capital_nuevo Decimal  @db.Decimal(19,4)  // puede incluir capitalización de interés
  cantidad_cuotas     Int
  monto_cuota         Decimal  @db.Decimal(19,4)
  tasa_interes        Decimal  @db.Decimal(8,4)
  fecha_primera_cuota DateTime
  solicitud_credito_nueva_id String?  // crea nueva solicitud bajo el hood
  documento_url       String?  // acuerdo firmado
  usuario_id          String
  autorizado_por_id   String?
  estado              CobRefinanciacionEstado
  created_at          DateTime  @default(now())

  cuotas_originales   cob_refinanciacion_cuota[]  // qué cuotas viejas se reemplazan
}

model cob_refinanciacion_cuota {
  refinanciacion_id   String
  factura_cuota_id    String   // cuota original
  saldo_capital       Decimal  @db.Decimal(19,4)
  saldo_interes       Decimal  @db.Decimal(19,4)
  @@id([refinanciacion_id, factura_cuota_id])
}

enum CobRefinanciacionEstado {
  BORRADOR
  ACEPTADA
  EJECUTADA   // cuotas viejas marcadas + nueva solicitud emitida
  ANULADA
}
```

### 6.2 Reglas operativas

- Una refinanciación **no edita las cuotas originales** — las marca como `estado = REFINANCIADA` (nuevo valor del enum existente) con saldo 0, y crea una nueva solicitud de crédito con nuevo cronograma.
- El **link** entre cuotas viejas y nuevas vive en `cob_refinanciacion_cuota` para auditoría perpetua.
- Al ejecutar refinanciación, la gestión de mora del cliente pasa a `REFINANCIADA`.
- Decisión por refinanciación: **capitalizar interés** (sumar al nuevo capital), **exonerar** (registra exoneración formal), o **mantener pendiente** aparte.
- Requiere autorización si supera umbral o si el cliente ya refinanció N veces (configurable).
- **Limita refinanciaciones**: máximo X por año por cliente (configurable, default 2).

### 6.3 Permisos

```
COB_REF_REFINANCIACION_VER
COB_REF_REFINANCIACION_CREAR
COB_REF_REFINANCIACION_APROBAR
COB_REF_REFINANCIACION_ANULAR
COB_REF_REPORTE
```

### 6.4 Endpoints

```
GET    /cobranzas/refinanciaciones?clienteId=&estado=
POST   /cobranzas/refinanciaciones/simular  (devuelve cuotas propuestas sin guardar)
POST   /cobranzas/refinanciaciones          (crea borrador)
PATCH  /cobranzas/refinanciaciones/:id/ejecutar  (marca cuotas viejas + crea solicitud)
PATCH  /cobranzas/refinanciaciones/:id/anular
GET    /cobranzas/refinanciaciones/:id/pdf  (acuerdo)
```

### 6.5 Pantallas

**Nueva: `cobranzas/RefinanciacionWizard.jsx`**
- Step 1: seleccionar cuotas a refinanciar (checklist de cuotas vencidas/pendientes del cliente).
- Step 2: decisión sobre intereses (capitalizar / exonerar / mantener) — usa `PrereqChecklist` para explicar implicancias.
- Step 3: simular nuevo plan (cantidad cuotas, tasa, fecha primera cuota) → preview de cronograma.
- Step 4: documentos + autorización.
- Confirmar → ejecuta.

**Extensión: `ClienteHistorialDrawer.jsx`**
- Sección "Refinanciaciones" con histórico.

**Extensión: `SolicitudesCreditoTab.jsx`**
- Filtro/badge "Originada por refinanciación" para distinguir las nuevas solicitudes que salen de refinanciaciones.

### 6.6 PDFs

- **Acuerdo de refinanciación** (nuevo PDF en msv-kude): texto legal con datos del cliente, cuotas anteriores, nuevo plan, firmas.

### 6.7 Integraciones

- **Solicitud de crédito**: la refinanciación crea una solicitud con `tipo_origen = "REFINANCIACION"` (nuevo campo).
- **Gestión de mora**: transición automática a `REFINANCIADA` al ejecutar.
- **Score**: una refinanciación reduce el score (peso pequeño, ~5%) — señal de incumplimiento previo.

---

## 7. SUBMÓDULO 6 — Línea de crédito y exposición (`COB_LIC_*`)

### 7.1 Qué falta

El campo `clientes.limite_credito` existe pero el **cálculo de exposición real** no está formalizado. Implementar:

```prisma
// Extensión a clientes (campos calculados — vista materializada o cache):
// clientes.deuda_directa          (sum saldos facturas pendientes)
// clientes.monto_avalado_vigente  (sum cob_garante donde es garante)
// clientes.interes_devengado_pend (sum cob_interes_mora estado PENDIENTE)
// clientes.promesas_vigentes_monto

// Para evitar denormalizar, mejor crear vista:
CREATE MATERIALIZED VIEW cob_cliente_exposicion AS
SELECT
  c.id AS cliente_id,
  c.empresa_id,
  c.limite_credito,
  COALESCE(SUM(DISTINCT f.saldo_pendiente), 0) AS deuda_directa,
  COALESCE(SUM(DISTINCT g.monto_max_avalado), 0) AS monto_avalado,
  COALESCE(SUM(DISTINCT i.monto_devengado), 0) AS interes_pendiente,
  c.limite_credito
    - COALESCE(SUM(DISTINCT f.saldo_pendiente), 0)
    - COALESCE(SUM(DISTINCT g.monto_max_avalado), 0) AS disponible
FROM clientes c
LEFT JOIN factura_cab f ON f.cliente_id = c.id AND f.estado IN ('emitida','vencida')
LEFT JOIN cob_garante g ON g.cliente_garante_id = c.id AND g.activo = true
LEFT JOIN cob_interes_mora i ON ... AND i.estado = 'PENDIENTE'
GROUP BY c.id;

-- refresh nocturno + on-demand al cobrar
```

### 7.2 Reglas

- **Bloqueo de venta**: al facturar a crédito, validar que `disponible >= monto_nueva_factura`. Si no, bloquear o pedir autorización.
- **Autorización por excedente**: similar al flujo de descuentos — solicitud asíncrona al supervisor con TTL.
- **Configuración por empresa**: porcentaje de utilización máxima antes de alerta (ej. 80% → warning, 100% → bloqueo).
- **Historial de cambios de línea de crédito**: nueva tabla `cob_linea_credito_historial` (anterior, nuevo, motivo, usuario, fecha).

### 7.3 Permisos

```
COB_LIC_LINEA_CREDITO_VER
COB_LIC_LINEA_CREDITO_EDITAR
COB_LIC_AUTORIZAR_EXCEDENTE
COB_LIC_REPORTE_EXPOSICION
```

### 7.4 Endpoints

```
GET    /cobranzas/linea-credito/cliente/:clienteId  (limite, deuda, avalado, disponible, %util)
PATCH  /cobranzas/linea-credito/cliente/:clienteId  (editar límite con motivo)
GET    /cobranzas/linea-credito/historial/:clienteId
POST   /cobranzas/linea-credito/autorizar-excedente (similar a descuento)
GET    /cobranzas/linea-credito/reporte-utilizacion (ranking por % uso)
POST   /cobranzas/linea-credito/refresh-cache       (manual)
```

### 7.5 Pantallas

**Extensión: `ClienteFormDialog.jsx`**
- Widget "Línea de crédito" con barra de utilización tipo termómetro (verde < 50%, amarillo 50-80%, rojo > 80%).
- Botón "Editar línea" con modal que pide motivo + opcional documento.
- Subsección "Historial de cambios de línea".

**Extensión: pantalla de facturación a crédito**
- Validación en tiempo real: si excede, mostrar `InlineValidationBanner` con monto excedido + botón "Solicitar autorización".

**Nueva: `cobranzas/ExposicionDashboard.jsx`**
- KPI gerencial: top clientes por exposición, concentración de cartera, % utilización promedio.

### 7.6 Integraciones

- **Garantes**: al crear/borrar aval, refresh del cache del garante.
- **Gestión de mora**: al pasar cliente a `INFORMCONF` o peor, sugerir reducir línea de crédito a 0 (configurable).
- **Solicitud de crédito**: al aprobar, validar contra disponible del cliente.

---

## 8. SUBMÓDULO 7 — Workflow de cobranza (`COB_WFL_*`)

> **✅ IMPLEMENTADO 2026-06-22.** Ver bloque "✅ Submódulo 7 — Workflow de cobranza + Mesa de Gestión" en §0.1 para detalle final de scope, decisiones de UX (personas: gestor de oficina vs cobrador de ruta), permisos del submódulo `COB_MESA_GESTION` y modal unificado gestión+promesa (Opción A). La especificación de 8.x abajo es el diseño original; los desvíos (Mesa de Gestión como pantalla principal + Promesas como tab embebido + rename Mora → "Mora avanzada (legal)") están en §0.1.

### 8.1 Concepto

Hoy el cobrador trabaja con la **hoja de ruta** (existe) y el **panel cobrador** (existe). Falta la capa de **gestiones registradas**: cada vez que el cobrador interactúa con el cliente (visita, llamada, WhatsApp), debe quedar registro con resultado.

### 8.2 Modelo de datos

```prisma
model cob_gestion {
  id                String   @id @default(uuid())
  empresa_id        String
  cliente_id        String
  factura_cab_id    String?  // si la gestión es por factura puntual
  cobrador_id       String?
  fecha             DateTime
  tipo_gestion      CobTipoGestion
  resultado         CobResultadoGestion
  observacion       String?
  proxima_accion    String?   // texto libre
  proxima_fecha     DateTime?
  geo_lat           Decimal? @db.Decimal(10,7)
  geo_lng           Decimal? @db.Decimal(10,7)
  duracion_seg      Int?      // útil para llamadas
  adjuntos          Json?     // array de URLs (foto puerta, captura wsp)
  promesa_creada_id String?
  created_at        DateTime  @default(now())

  @@index([empresa_id, cliente_id, fecha])
  @@index([empresa_id, cobrador_id, fecha])
}

enum CobTipoGestion {
  VISITA
  LLAMADA
  WHATSAPP
  EMAIL
  SMS
  OTRO
}

enum CobResultadoGestion {
  COBRADO_TOTAL
  COBRADO_PARCIAL
  PROMESA_PAGO
  NO_ATIENDE
  CLIENTE_AUSENTE
  DIRECCION_INCORRECTA
  CLIENTE_DISPUTA
  RECHAZO_PAGAR
  COMPROMISO_LLAMADA_POSTERIOR
  CLIENTE_FALLECIDO
  OTRO
}
```

### 8.3 Reglas

- Una gestión con resultado `PROMESA_PAGO` debe crear/vincular registro en `promesas_pago`.
- Una gestión con resultado `CLIENTE_DISPUTA` debe disparar flujo de disputa (submódulo 9).
- Una gestión con resultado `COBRADO_*` debe vincular el recibo creado.
- **Productividad**: dashboard gerencial mide gestiones por cobrador, ratio cobranza/gestión, tiempo entre gestiones.
- **Próxima acción**: si tiene `proxima_fecha`, aparece como tarea en panel cobrador en esa fecha.

### 8.4 Permisos

```
COB_WFL_GESTION_VER
COB_WFL_GESTION_REGISTRAR
COB_WFL_GESTION_EDITAR        ← solo el día de creación, después solo supervisor
COB_WFL_GESTION_ANULAR
COB_WFL_REPORTE_PRODUCTIVIDAD
COB_WFL_VER_GESTIONES_AJENAS  ← solo supervisor (ve todas)
```

### 8.5 Endpoints

```
GET    /cobranzas/gestiones?clienteId=&cobradorId=&desde=&hasta=
POST   /cobranzas/gestiones
PATCH  /cobranzas/gestiones/:id
GET    /cobranzas/gestiones/cliente/:clienteId/timeline
GET    /cobranzas/gestiones/reporte-productividad?desde=&hasta=
GET    /cobranzas/gestiones/proximas-acciones/:cobradorId  (alimentando panel cobrador)
```

### 8.6 Pantallas

**Extensión: `PanelCobrador.jsx`**
- Botón flotante "Registrar gestión" → modal con form rápido (tipo, resultado, observación, foto).
- Vista "Próximas acciones" arriba del listado.

**Extensión: `HojaRutaTab.jsx`**
- Por cada cliente en ruta, ver últimas 3 gestiones colapsables.

**Extensión: `ClienteHistorialDrawer.jsx`**
- Pestaña "Timeline de gestiones" con todas las interacciones.

**Nueva: `cobranzas/ProductividadCobradoresPage.jsx`**
- KPI por cobrador: gestiones/día, % éxito, monto cobrado/gestión, tiempo promedio.

### 8.7 Integraciones

- **Promesas**: gestión con resultado `PROMESA_PAGO` → crear promesa automáticamente.
- **Disputas**: resultado `CLIENTE_DISPUTA` → crear disputa.
- **Recordatorios**: gestión con `proxima_fecha` → notificación al cobrador.

---

## 9. SUBMÓDULO 8 — Anticipos y saldos a favor (`COB_SAF_*`)

### 9.1 Estado actual

Los permisos `COB_SALDOS_FAVOR` (VER, APLICAR, AJUSTAR) ya están sembrados pero no está claro si la lógica está implementada. Validar y completar.

### 9.2 Modelo (si no existe)

```prisma
model cob_saldo_favor {
  id                String   @id @default(uuid())
  empresa_id        String
  cliente_id        String
  origen            CobSaldoFavorOrigen  // ANTICIPO | PAGO_EXCEDENTE | NOTA_CREDITO | AJUSTE
  fecha             DateTime
  monto             Decimal  @db.Decimal(19,4)
  saldo_actual      Decimal  @db.Decimal(19,4)  // se reduce al aplicar
  moneda_id         String
  recibo_cobro_id   String?   // si vino de un cobro
  nota_credito_id   String?
  observacion       String?
  usuario_id        String
  estado            CobSaldoFavorEstado  // ACTIVO | AGOTADO | DEVUELTO | ANULADO
  created_at        DateTime  @default(now())

  aplicaciones      cob_saldo_favor_aplicacion[]
}

model cob_saldo_favor_aplicacion {
  id                String   @id @default(uuid())
  saldo_favor_id    String
  recibo_cobro_id   String   // recibo en el que se aplicó
  monto_aplicado    Decimal  @db.Decimal(19,4)
  fecha             DateTime
  usuario_id        String
}
```

### 9.3 Reglas

- Cliente paga de más → genera saldo a favor automáticamente.
- Anticipos: cobro sin factura asociada (registrar `recibos_cobro` con tipo `ANTICIPO`) → genera saldo a favor.
- Al cobrar futuras facturas, banner sugerir aplicar saldo a favor.
- Devolución de saldo a favor (cliente pide reembolso): permiso especial + asiento contable.

### 9.4 Pantallas

**Extensión: `CobrarMultifacturaWizard.jsx`**
- Banner si cliente tiene saldo a favor → opción "Aplicar X de saldo".
- Si cobro excede deuda total → preguntar "¿registrar como anticipo?".

**Nueva: `cobranzas/SaldosFavorTab.jsx`**
- Listado por cliente, monto, fecha origen, aplicaciones realizadas.

**Extensión: `ClienteHistorialDrawer.jsx`**
- Widget "Saldo a favor disponible: Gs. X" en header.

---

## 10. SUBMÓDULO 9 — Disputas / reclamos (`COB_DSP_*`)

### 10.1 Modelo de datos

```prisma
model cob_disputa {
  id                String   @id @default(uuid())
  empresa_id        String
  cliente_id        String
  factura_cab_id    String
  motivo            CobDisputaMotivo
  monto_disputado   Decimal  @db.Decimal(19,4)
  estado            CobDisputaEstado
  observacion       String
  documento_url     String?
  resolucion        String?
  usuario_origen_id String
  resuelto_por_id   String?
  fecha_apertura    DateTime  @default(now())
  fecha_resolucion  DateTime?

  @@index([empresa_id, factura_cab_id])
}

enum CobDisputaMotivo {
  PRECIO_INCORRECTO
  CANTIDAD_INCORRECTA
  PRODUCTO_NO_RECIBIDO
  PRODUCTO_DEFECTUOSO
  COBRO_DUPLICADO
  ERROR_DATOS_FISCALES
  OTRO
}

enum CobDisputaEstado {
  ABIERTA
  EN_ANALISIS
  RESUELTA_A_FAVOR_CLIENTE    // generar NC
  RESUELTA_A_FAVOR_EMPRESA    // cliente debe pagar
  ANULADA
}
```

### 10.2 Reglas

- Factura con disputa abierta **no devenga interés de mora** (pausa el job).
- Factura con disputa abierta no entra en gestión de mora avanzada.
- Factura con disputa abierta **no se incluye en aging vencido** del reporte de cartera (se reporta aparte como "en disputa").
- Si resolución es a favor del cliente → genera nota de crédito (link al módulo existente).

### 10.3 Permisos

```
COB_DSP_DISPUTA_VER
COB_DSP_DISPUTA_REGISTRAR
COB_DSP_DISPUTA_RESOLVER
COB_DSP_REPORTE
```

### 10.4 Endpoints

```
GET    /cobranzas/disputas?estado=&clienteId=
POST   /cobranzas/disputas
PATCH  /cobranzas/disputas/:id/resolver
GET    /cobranzas/disputas/factura/:facturaId
```

### 10.5 Pantallas

**Extensión: `FacturasTab.jsx`**
- Chip "En disputa" si la factura tiene disputa activa.
- Botón "Abrir disputa" en menú de acciones.

**Nueva: `cobranzas/DisputasTab.jsx`**
- Listado por estado, cliente, motivo, monto.

**Extensión: `RevisionCxCTab.jsx`**
- Filtro "Incluir/excluir disputas" — por default excluye.

---

## 11. EXTENSIÓN A REPORTES EXISTENTES

### 11.1 Reporte de cuentas a cobrar (`RevisionCxCTab.jsx`)

Columnas nuevas:
- Estado de gestión de mora (chip).
- Garante (nombre del primero, badge si hay varios).
- Interés devengado.
- Promesa vigente (fecha + monto).
- En disputa (badge).

Filtros nuevos:
- Por estado de mora.
- Solo con garante / sin garante.
- Con promesa vigente / sin promesa.
- Excluir disputas (default ON).

### 11.2 Historial crediticio (PDF `msv-kude/src/historial-crediticio/`)

Secciones a agregar:
- **Timeline de gestión de mora**: ingresos/salidas de Informconf/Demanda/Incobrable.
- **Garantes y avales**: quiénes lo avalan, a quiénes avala.
- **Intereses exonerados acumulados**.
- **Cumplimiento de promesas**: X/Y.
- **Refinanciaciones históricas**: cantidad + fechas.
- **Saldo a favor disponible**.

### 11.3 Estado de cuenta (PDF formato `a4` y `a4-detallado`)

- ❌ **No** agregar columna "Interés devengado pendiente" (no se devenga hasta cobrar — decisión §4.7).
- ✅ Sí agregar columna "Días de mora" por cuota (calculado al momento de generar el PDF).
- ✅ Sí agregar sección "Intereses moratorios cobrados YTD" con total + cantidad de facturas emitidas por intereses.
- ✅ Sí agregar sección "Intereses exonerados YTD" con total + cantidad de autorizaciones tipo `INTERES_MORA`.

### 11.4 Dashboard de morosidad (existente, extender)

- KPI nuevos: clientes en Informconf, en Demanda, en disputa, refinanciados último mes.
- Proyección de cashflow basada en promesas vigentes.
- DSO (Days Sales Outstanding) por empresa/sucursal/cobrador/vendedor.
- Concentración: top 20 clientes y % del total a cobrar.

### 11.5 Reporte por cobrador (`ReporteCobrador.jsx`)

- Columnas nuevas: gestiones realizadas, promesas conseguidas, promesas cumplidas (%), monto exonerado autorizado por él.

---

## 12. PERMISOS — Resumen consolidado para `seguridad.seed-data.ts`

Agregar bajo módulo `COBRANZAS` (extender el array existente):

```typescript
// Submódulos NUEVOS
{ codigo: "COB_GMR", nombre: "Gestión de mora avanzada", privilegios: [
  "COB_GMR_GESTION_MORA_VER", "COB_GMR_GESTION_MORA_REGISTRAR",
  "COB_GMR_REPORTAR_INFORMCONF", "COB_GMR_RETIRAR_INFORMCONF",
  "COB_GMR_REGISTRAR_DEMANDA", "COB_GMR_DECLARAR_INCOBRABLE",
  "COB_GMR_REVERTIR_ESTADO", "COB_GMR_EXPORTAR"
]},
{ codigo: "COB_GAR", nombre: "Garantes", privilegios: [
  "COB_GAR_GARANTE_VER", "COB_GAR_GARANTE_VINCULAR",
  "COB_GAR_GARANTE_DESVINCULAR", "COB_GAR_GARANTE_EXPORTAR"
]},
{ codigo: "COB_INT", nombre: "Intereses moratorios y comprobante fiscal", privilegios: [
  "COB_INT_INTERES_VER", "COB_INT_CONFIG_FISCAL_EDITAR",
  "COB_INT_REVERTIR_EXONERACION", "COB_INT_VER_REPORTE_EXONERACIONES",
  "COB_INT_EXPORTAR"
  // La exoneración como tal reusa permisos de COB_AUT_* (autorizaciones_descuento) — no se duplican.
]},
{ codigo: "COB_PRM", nombre: "Promesas de pago (ampliado)", privilegios: [
  "COB_PRM_PROMESA_CANCELAR", "COB_PRM_PROMESA_RENEGOCIAR",
  "COB_PRM_AUTORIZAR_VENTA_CON_INCUMPLIMIENTOS", "COB_PRM_REPORTE_CUMPLIMIENTO"
]},
{ codigo: "COB_REF", nombre: "Refinanciación", privilegios: [
  "COB_REF_REFINANCIACION_VER", "COB_REF_REFINANCIACION_CREAR",
  "COB_REF_REFINANCIACION_APROBAR", "COB_REF_REFINANCIACION_ANULAR", "COB_REF_REPORTE"
]},
{ codigo: "COB_LIC", nombre: "Línea de crédito y exposición", privilegios: [
  "COB_LIC_LINEA_CREDITO_VER", "COB_LIC_LINEA_CREDITO_EDITAR",
  "COB_LIC_AUTORIZAR_EXCEDENTE", "COB_LIC_REPORTE_EXPOSICION"
]},
{ codigo: "COB_WFL", nombre: "Workflow de cobranza", privilegios: [
  "COB_WFL_GESTION_VER", "COB_WFL_GESTION_REGISTRAR", "COB_WFL_GESTION_EDITAR",
  "COB_WFL_GESTION_ANULAR", "COB_WFL_REPORTE_PRODUCTIVIDAD", "COB_WFL_VER_GESTIONES_AJENAS"
]},
{ codigo: "COB_DSP", nombre: "Disputas / reclamos", privilegios: [
  "COB_DSP_DISPUTA_VER", "COB_DSP_DISPUTA_REGISTRAR",
  "COB_DSP_DISPUTA_RESOLVER", "COB_DSP_REPORTE"
]},
```

---

## 13. ROADMAP PRIORIZADO POR FASES

### **FASE A — Fundación (sprint 1-2, ~6 semanas)** 🔴 BLOQUEANTE

Sin esto, los demás submódulos se construyen sobre datos sucios.

| # | Item | Submódulo |
|---|---|---|
| A.1 | Modelo + endpoints + UI gestión de mora con estados completos | 1 |
| A.2 | Modelo + endpoints + UI garantes (vinculación básica) | 2 |
| A.3 | Intereses: `cob_config_intereses` + `cob_interes_cobrado` + extensión `autorizaciones_descuento.tipo` + cálculo on-demand al cobrar + emisión factura-e por intereses + UI config empresa | 3 | ⚠️ **Parcial** — ver §0.1 |
| A.4 | Vista materializada `cob_cliente_exposicion` + UI línea crédito | 6 |
| A.5 | Extensión historial crediticio PDF con timeline mora + garantes | 1, 2 |
| A.6 | Seeds permisos nuevos | todos |

**Entregable de la fase**: cuentas a cobrar muestra columna estado mora + garante; cliente puede ser puesto en Informconf/Demanda; intereses se devengan correctamente; línea de crédito tiene cálculo real.

### **FASE B — Operación diaria (sprint 3-4, ~4 semanas)**

| # | Item | Submódulo |
|---|---|---|
| B.1 | Workflow de gestión (cob_gestion) + integración panel cobrador | 7 |
| B.2 | Promesas ampliadas: multi-factura, banner cobro, cierre automático | 4 |
| B.3 | UI exoneración inline al cobrar (reusa `autorizaciones_descuento` con `tipo=INTERES_MORA`) + reporte de exoneraciones | 3 |
| B.4 | Propagación de mora al garante (auto) | 1, 2 |
| B.5 | Disputas básicas (abrir/resolver/pausar mora) | 9 |

**Entregable de la fase**: cobradores trabajan dentro del sistema; el cobro contempla promesas y permite exonerar; disputas no contaminan el aging.

### **FASE C — Recuperación y negociación (sprint 5-6, ~4 semanas)**

| # | Item | Submódulo |
|---|---|---|
| C.1 | Wizard de refinanciación completo | 5 |
| C.2 | Anticipos y saldos a favor (completar lo sembrado) | 8 |
| C.3 | Renegociación de promesas | 4 |
| C.4 | Constancia de exoneración PDF + acuerdo refinanciación PDF | 3, 5 |

**Entregable de la fase**: cliente que viene a negociar tiene flujo completo (refinanciar, exonerar, dejar saldo a favor).

### **FASE D — Analítica gerencial (sprint 7, ~2 semanas)**

| # | Item | Submódulo |
|---|---|---|
| D.1 | Dashboard exposición y concentración de cartera | 6 |
| D.2 | DSO por dimensión (empresa/sucursal/cobrador/vendedor) | reportes |
| D.3 | Reporte productividad por cobrador (extender existente) | 7 |
| D.4 | Reporte de exoneraciones (ranking por usuario y por cliente) | 3 |
| D.5 | Proyección de cashflow basada en promesas vigentes | 4 |

### **FASE E — Cliente-facing y automatización (sprint 8-9, ~4 semanas)**

| # | Item | Origen |
|---|---|---|
| E.1 | Recordatorios automáticos vía Twilio (cliente + garante) | extender jobs |
| E.2 | Link de pago por WhatsApp (integración payments-gateway) | nuevo |
| E.3 | Portal cliente: estado de cuenta autoservicio + descarga | nuevo |
| E.4 | Portal garante: ver lo que avala + alertas | nuevo |

### **FASE F — Avanzado (sin sprint definido, on-demand)**

- Integración API Informconf (alta/baja automática).
- Multi-moneda con diferencia de cambio.
- Compensación cliente ↔ proveedor.
- Factoring / cesión de cartera.
- Nota de débito fiscal por intereses (con timbrado).
- ML para predicción de morosidad.

---

## 14. DEPENDENCIAS CRÍTICAS

```
FASE A (Fundación)
  ├── A.1 (gestión mora) → habilita A.5, B.4, C.1
  ├── A.2 (garantes)     → habilita B.4, ampliación reportes
  ├── A.3 (intereses)    → habilita B.3, C.4
  │     ⚠️ requiere facturación-e existente (✅ disponible)
  │     ⚠️ tipo_comprobante=NOTA_DEBITO bloqueado hasta plan ND-e (otro plan)
  └── A.4 (exposición)   → habilita D.1, validación facturación

FASE B
  ├── B.1 (workflow)     → habilita D.3
  ├── B.2 (promesas)     → habilita C.3, D.5, E.1
  └── B.3 (exoneración)  → habilita D.4, C.4
        ⚠️ requiere extensión autorizaciones_descuento.tipo (parte de A.3)

FASE C
  └── C.1 (refinanciación) requiere A.3 + A.4
        ⚠️ "capitalizar intereses" reusa flujo de emisión de A.3

FASE E
  └── E.2 (link pago)    requiere payments-gateway (otro plan)

PLAN SEPARADO — Nota de Débito Electrónica (ND-e)
  └── desbloquea cob_config_intereses.tipo_comprobante = NOTA_DEBITO
        (mientras tanto: stub con NotImplementedException)
```

---

## 15. RIESGOS Y CONSIDERACIONES

### 15.1 Migración de datos legacy

- Facturas históricas no tienen `cobrador_id` directo. Backfill con `asignacion_facturas` y luego null para huérfanas.
- Saldos pendientes: validar contra suma de cuotas (problema ya encontrado y resuelto con COALESCE cascade en `getHistorialCrediticio`).
- Cliente sin línea de crédito definida: usar default por categoría (configurable).

### 15.2 Performance

- La vista `cob_cliente_exposicion` debe ser **materializada** con refresh nocturno + refresh on-demand al cobrar/facturar/vincular garante. NO calcular on-the-fly por request.
- ~~Job de devengamiento de intereses~~ (eliminado por decisión §4.7: cálculo on-demand al cobrar). Si en el futuro se quiere mostrar interés devengado en CxC, se reactivaría el cron — entonces sí paginar y usar transacción por chunk de 1000 cuotas.
- El cron de "incumplidas" debe correr una vez al día y filtrar solo promesas con `fecha_prometida < CURRENT_DATE`.

### 15.3 Auditoría

- **Todas** las transiciones de estado de mora, exoneraciones, refinanciaciones, cambios de línea de crédito y resoluciones de disputa deben pasar por `AuditoriaService` con: usuario, fecha, IP, valores anterior/nuevo, motivo.
- Las exoneraciones e incobrables tienen implicancia legal/contable: documento adjunto obligatorio.

### 15.4 UI/UX

- Todas las pantallas nuevas siguen `docs/ui-standards.md` y reutilizan `src/components/_standards/`.
- Cada pantalla nueva arranca con `ScreenGuia` colapsable explicando el flujo.
- Estados de mora/disputa/promesa → usar `EstadoChip` con colores semánticos consistentes.
- Montos → siempre `MonedaInput`.
- Fechas → siempre `src/utils/fecha.js` (nunca `new Date().toLocaleDateString`).
- Errores multi-campo → `InlineValidationBanner`.

### 15.5 Multi-empresa y multi-moneda

- Toda tabla con `empresa_id` y filtros respetados en queries.
- Montos en moneda original + cotización al momento (igual que se hace en recibos).
- Configuraciones (umbral exoneración, máx refinanciaciones/año, % utilización línea para alerta) por empresa en `config_mora` o nueva tabla `cob_config_empresa`.

### 15.6 Compatibilidad con plan-creditos-cobranzas.md

- **No tocar** lo ya implementado: panel cobrador, hoja ruta, autorizaciones descuento, config mora, promesas básicas siguen funcionando.
- **Extender, no duplicar**: por ej. promesas extendidas usan misma tabla con campos nuevos nullable.
- **Permisos coexisten**: los permisos viejos (`COB_PROMESAS.VER`) siguen activos; los nuevos (`COB_PRM_*`) suman granularidad.

---

## 16. PRÓXIMOS PASOS PARA APROBACIÓN

Antes de empezar Fase A:

1. **Validar nombres de tablas y enums** con stakeholder técnico (usé prefijo `cob_` para todos los nuevos — confirmar).
2. **Definir umbrales por empresa**: monto para doble autorización en exoneración/refinanciación, máx refinanciaciones/año, % utilización para alerta. Documentar valores default.
3. **Decidir backfill de datos legacy**: cómo poblar `cob_cliente_exposicion` la primera vez, qué hacer con facturas sin cobrador, etc.
4. **Wireframes**: aprobar wireframes funcionales de las 4 pantallas principales (GestionMoraTab, RefinanciacionWizard, ExposicionDashboard, extensión ClienteFormDialog con tab Garantes).
5. **Sprint planning**: estimar puntos por item de Fase A y armar sprint 1 concreto.

---

## 17. ADENDA — INTERESES MORATORIOS (CIERRE FASE A.3)

Esta sección documenta decisiones y ajustes realizados durante la implementación
del cobro con interés moratorio, posteriores al diseño original de la Fase A.3.

### 17.1 IVA configurable por empresa: `INCLUIDO` vs `POR_FUERA`

**Decisión**: el modo de aplicación del IVA sobre la tasa de mora es una
**decisión contractual de la empresa**, no del ERP. Depende exclusivamente del
texto del pagaré/contrato firmado con el cliente.

- `INCLUIDO`: la tasa configurada (ej: 3%/mes) ya contiene el IVA. El cliente
  paga exactamente lo que dice el recibo; la factura desglosa el IVA "por
  dentro" (`monto / 1.10` → neto, resto → IVA 10%).
- `POR_FUERA`: la tasa se aplica sobre capital y luego se le suma el IVA. El
  cliente paga `mora + IVA`. La factura desglosa con IVA "por arriba".

**Persistencia**: nueva columna `cob_config_intereses.iva_modo VARCHAR(20)
DEFAULT 'INCLUIDO'` (migración `20260622_cob_config_intereses_iva_modo`).

**Impacto en cálculo**:
- `cobros.service.ts::previewDistribucion::calcularMoraCuota`: si `iva_modo =
  POR_FUERA` y `iva_porcentaje > 0`, el preview suma el IVA al monto que el
  cobrador debe cobrar, para que el recibo y la factura cuadren.
- `intereses-moratorios.service.ts::previewParaCuota` y `registrar`: branching
  por `iva_modo` para devolver `neto / iva / total` coherentes con la emisión.

**UI**: `ConfigIntereses.jsx` expone un toggle "IVA incluido / IVA por fuera"
debajo del selector de porcentaje, con alerta de fondo amarillo recordando que
debe coincidir con el pagaré firmado. Simulación dinámica con desglose.

### 17.2 Unificación de fórmula de mora (proporcional, no `Math.ceil`)

**Bug detectado**: para un día de mora más allá de un mes calendario,
`config-mora.service.ts::calcularMora` con `tipo_calculo='mensual'` usaba
`Math.ceil(diasEfectivos / 30)`, cobrando 2 meses por 1 día extra. El preview
del cobro (`cobros.service.ts`) usaba prorrateo `diasEfectivos / 30`. Resultado:
el cobrador veía Gs 517 en el recibo y la factura se emitía por Gs 1.100.

**Fix**: `calcularMora` ahora usa `mora = base * (tasa/100) * (diasEfectivos/30)`,
idéntica al preview. Fórmula única en todo el sistema.

### 17.3 Persistencia de `mora_total` en `recibos_cobro`

**Bug detectado**: `recibos_cobro.create` ignoraba `cabeceraData.mora_total`,
quedando guardado en 0 y rompiendo el desglose en recibos PDF / drawer / KUDE.

**Fix**: `cobros.service.ts` calcula `montoTotal = sum(detalles) + mora_total` y
persiste `mora_total` explícitamente al crear el recibo.

### 17.4 Línea "Mora cobrada" en KUDE de recibo (A4 y ticket)

- `msv-kude/src/recibos/v2/recibo_a4.js`: nueva línea "Mora cobrada" antes del
  box de Total recibido, sólo si `data.mora_total > 0`.
- `recibo_ticket.js`: ya tenía el bloque; el bug estaba en el payload
  (`generateReciboPdf` no pasaba `mora_total`). Corregido.

### 17.5 Vínculos bidireccionales recibo ↔ factura de intereses

**Objetivo**: trazabilidad y seguimiento. Desde el recibo se puede llegar al
documento fiscal emitido por la mora; desde la factura de mora se puede llegar
al recibo de origen.

**Persistencia**: se reutiliza la tabla existente `cob_interes_cobrado`, que
ya enlaza `recibo_cobro_id ↔ factura_cab_id` (sin migración adicional).

**Backend**:
- `cobros.service.ts::findOne` enriquece cada `intereses_cobrados[]` con
  `factura_emitida` (id, número formateado `dest-dpunexp-dnumdoc`, estado
  SIFEN, CDC).
- `facturas.service.ts::findOne` busca reverso en `cob_interes_cobrado` y
  cuando la factura es de origen mora devuelve `origen_intereses = {
  tipo_origen: 'INTERES_MORATORIO', interes: { dias_mora, tasa_aplicada,
  monto_interes_cobrado }, recibo: { id, nro_recibo, fecha_emision, mora_total,
  cliente } }`.

**UI**:
- `RecibosPanel.jsx` (drawer del recibo): nueva sección "Documento de intereses
  emitido" con número fiscal, estado SIFEN, monto, exoneración aplicada y
  error de emisión si lo hubiera (para casos `tipo_comprobante=FACTURA` que
  fallaron y necesitan reintento).
- `FacturaDetalleDialog.jsx`: chip "Origen: Mora" (warning) en el título y
  bloque destacado "Recibo de origen (interés moratorio)" con Nº recibo,
  fecha, días de mora y tasa aplicada.

### 17.6 Auditoría completa del flujo de intereses (mandato "todo auditable")

- `procesarInteresesMoratorios` registra en `AuditService`:
  - `CREATE` con count/total cuando se registran intereses cobrados.
  - `SKIP` con reason diagnóstico cuando ninguna cuota acumuló mora
    (chequeo de `config_mora.activo / tasa / periodo_gracia / diasMaxAtraso`).
  - `SKIP` con motivo cuando se pausa por estado de gestión (DEMANDA/INCOBRABLE).
  - `ERROR` con stack al fallar la TX.
- Bloque de emisión de factura:
  - `CREATE factura_intereses_moratorios` en éxito.
  - `ERROR` con el array `errores` del `BadRequestException` (extraído
    correctamente, no truncado) al fallar la emisión.
  - `SKIP` cuando `tipo_comprobante != FACTURA`.

### 17.7 Limpieza UX: no usar `info_adicional_item` como marcador interno

Se removió el prefijo `INTMORA:<uuid>` de `info_adicional_item` en la línea de
factura de mora (filtraba al KUDE como "Info. Adic."). El mapeo
`cob_interes_cobrado.det ↔ factura_det` ahora es por orden de creación
(`orderBy: { created_at: 'asc' }`) en lugar de marcador textual.

### 17.8 IVA Exento: corrección de `afectacion_iva` SET

Cuando el usuario configura `iva_porcentaje = 0` para mora, la línea de factura
debe emitirse como **Exento (código SET 3)**, no como **Exonerado (código 2)**.

- `intereses-moratorios.service.ts`:
  ```ts
  // SET: 1=Gravado, 2=Exonerado (por decreto), 3=Exento (no alcanzado), 4=Gravado parcial
  afectacion_iva: ivaPct === 0 ? 3 : 1,
  ```
- Aplica también para mora con IVA 5% / 10% (siempre `1`).

### 17.9 KUDE — bug de doble conteo en columna Exentas

Bug cross-cutting (no exclusivo de intereses): el KUDE sumaba
`format_exenta + format_base_exenta`, duplicando el valor cuando la línea era
puramente exenta (mostraba 1.034 para un ítem de Gs 517).

Archivos corregidos en `msv-kude/src/invoice/`:
- `estandar_plantilla/kude_factura.js`
- `estandar_plantilla/kude_factura_jspdf.js`
- `estandar_plantilla/kude_factura_bk.js`
- `estandar/kude_facturaNew.js`
- `estandar/kude_factura.js`
- `estandar/kude_ticket.js`

Lógica nueva:
```js
let suma_exenta;
if (afectacion_iva == "4") {
  // Gravado parcial: suma ambos
  suma_exenta = format_base_exenta;
} else {
  // Exento puro: uno u otro, no ambos
  suma_exenta = format_exenta > 0 ? format_exenta : format_base_exenta;
}
```

### 17.10 Coherencia de montos en drawer de recibo

Cuando `mora_total > 0`, "Cuotas incluidas" y "Formas de pago" mostraban solo
el capital y no cuadraban con "Total cobrado". Causa: `recibo_cobro_detalle.mora_monto`
viene en 0/null — la mora vive en `cob_interes_cobrado`.

Fix en `RecibosPanel.jsx`:
- **Cuotas incluidas**: se construye `moraPorCuota: Map<factura_cuota_id, monto>`
  desde `intereses_cobrados` y se suma a cada cuota. Si no hay desglose pero sí
  `mora_total`, se acumula a la primera cuota como fallback.
- **Formas de pago**: el `mora_total` se suma al método de pago dominante (el
  de mayor monto), para que la suma cuadre con "Total cobrado".

### 17.11 Auto-enriquecimiento del drawer/dialog

El endpoint de listado de recibos no incluye `intereses_cobrados` ni los
documentos vinculados. Para no exigir refresh manual:

- `RecibosPanel.jsx`: `useEffect` que llama `getCobro(id)` al abrir el drawer y
  reemplaza el row de lista por el detalle completo.
- `FacturaDetalleDialog.jsx`: `useEffect` análogo que llama `getFactura(id)`
  cuando `origen_intereses` es `undefined`, para resolver el bloque "Recibo de
  origen" en facturas de mora abiertas desde listados ligeros.

### 17.12 Correcciones de nombres de campos Prisma

Errores de runtime `Unknown field …` revelaron divergencias entre el código y
el schema:

- `recibos_cobro`: `numero_recibo` (no `nro_recibo`)
- `factura_cab`: `estado_sifen` / `enlace_qr` / `total_factura` (no
  `destado_sifen` / `dfeemide` / `cdc` / `total_general`)

Archivos ajustados:
- `cobros.service.ts` (select de `factura_cab` en `findOne`)
- `facturas.service.ts` (select de `recibos_cobro` para `origen_intereses`)
- `RecibosPanel.jsx` y `FacturaDetalleDialog.jsx` consumiendo los nuevos campos.

---

**Fin del plan.**
