# Lista de Precios a Cuotas + Solicitud de Crédito

Plan para implementar precios de cuotas manuales por producto y un flujo de solicitud de crédito con aprobación de supervisor, integrado al POS/facturación existente.

---

## Contexto actual

- **`condiciones_pago`**: Ya existe con `cuotas`, `intervalo_dias`, `cod_condicion_venta`, `cod_tipo_credito`. Se usará para definir la frecuencia de pago (semanal=7, quincenal=15, mensual=30).
- **`planes_cuotas` / `cuotas_individuales`**: Existen pero son complejos (requieren plan previo, cálculos automáticos). Se dejan intactos pero **no se usan** en este flujo.
- **`pedidos`** (mayorista): Patrón de referencia para solicitud de crédito (borrador → confirmado → facturado).

---

## FASE 1: Lista de Precios a Cuotas por Producto

### 1.1 Backend — Nuevo modelo `producto_precio_cuota`

**Schema Prisma** — modelo independiente y simple:

```prisma
model producto_precio_cuota {
  id              String    @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  empresa_id      String    @db.Uuid
  producto_id     String    @db.Uuid
  nombre          String    @db.VarChar(100)   // "3 x 100.000", "12 x 300.000"
  cant_cuotas     Int       @db.SmallInt
  monto_cuota     Decimal   @db.Decimal(18, 4)
  monto_total     Decimal   @db.Decimal(18, 4) // cant_cuotas * monto_cuota (calculado)
  cuota_inicial   Decimal?  @db.Decimal(18, 4) // Entrada opcional
  moneda          String    @default("PYG") @db.VarChar(3)
  activo          Boolean   @default(true)
  orden           Int?      @default(0) @db.SmallInt
  created_at      DateTime  @default(now()) @db.Timestamp(6)
  updated_at      DateTime? @db.Timestamp(6)
  usuario         String?   @db.VarChar(100)

  // Relaciones
  empresa   empresas  @relation(fields: [empresa_id], references: [id], onDelete: Cascade)
  producto  productos @relation(fields: [producto_id], references: [id], onDelete: Cascade)

  @@unique([producto_id, nombre, empresa_id], name: "unique_producto_precio_cuota")
  @@index([producto_id])
  @@index([empresa_id])
  @@index([activo])
  @@map("producto_precio_cuotas")
}
```

**Campos clave**:

- `nombre`: descriptivo, ej. "3 x 100.000 mensual"
- `cant_cuotas` + `monto_cuota`: lo que carga el usuario manualmente
- `monto_total`: calculado en backend al guardar (cant_cuotas \* monto_cuota)
- Sin dependencia de `planes_cuotas`

**Migración SQL**: `ALTER TABLE` para crear la tabla `producto_precio_cuotas`.

**Service** (`producto-precio-cuotas.service.ts`):

- `crear(dto)` — valida producto y empresa, calcula monto_total
- `obtenerPorProducto(productoId, empresaId)` — lista cuotas de un producto
- `actualizar(id, dto)`
- `eliminar(id)`
- `obtenerPorProductos(productoIds[], empresaId)` — bulk fetch para el POS

**Controller** (`producto-precio-cuotas.controller.ts`):

- `POST /producto-precio-cuotas` — crear
- `GET /producto-precio-cuotas/producto/:productoId` — listar por producto
- `GET /producto-precio-cuotas/productos?ids=...` — bulk por IDs (para POS)
- `PATCH /producto-precio-cuotas/:id` — actualizar
- `DELETE /producto-precio-cuotas/:id` — eliminar

### 1.2 Frontend — ABM en Producto

**En `RegistrarProductos.jsx` o `PreciosProductoDialog.jsx`**:

- Nueva sección/tab "Precios a Cuotas" en el formulario de producto
- Tabla editable con columnas: Nombre | Cuotas | Monto Cuota | Total | Acciones
- Botón "+ Agregar plan de cuotas" → formulario inline o mini-dialog
- Ejemplo visual:

```
┌──────────────────────────┬────────┬────────────┬─────────────┬──────────┐
│ Nombre                   │ Cuotas │ Monto/Cuota│ Total       │ Acciones │
├──────────────────────────┼────────┼────────────┼─────────────┼──────────┤
│ 3 x 100.000              │ 3      │ 100.000    │ 300.000     │ ✏️ 🗑️    │
│ 5 x 100.000              │ 5      │ 100.000    │ 500.000     │ ✏️ 🗑️    │
│ 12 x 300.000 mensual     │ 12     │ 300.000    │ 3.600.000   │ ✏️ 🗑️    │
└──────────────────────────┴────────┴────────────┴─────────────┴──────────┘
```

### 1.3 Frontend — Selector en POS/Facturación

**En `ProductSearch.jsx` / `POSAdminTemplate.jsx`**:

- Cuando la condición de venta es **Crédito** y se selecciona un producto que tiene precios a cuotas:
  - Mostrar un selector/popup con las opciones de cuotas disponibles
  - Al seleccionar, el precio del item se calcula como `cant_cuotas * monto_cuota`
  - Se guarda la referencia al `producto_precio_cuota_id` en el detalle de la factura

**Cambio en `factura_det`**: Agregar campo opcional `producto_precio_cuota_id` para trazar qué plan de cuotas se usó.

---

## FASE 2: Solicitud de Crédito

### 2.1 Backend — Nuevos modelos

**`solicitud_credito`** (cabecera):

```prisma
model solicitud_credito {
  id                    String    @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  empresa_id            String    @db.Uuid
  sucursal_id           String?   @db.Uuid
  cliente_id            String    @db.Uuid
  vendedor_id           String?   @db.Uuid
  condicion_pago_id     String?   @db.Uuid
  numero_solicitud      String?   @db.VarChar(20)
  // borrador → pendiente_aprobacion → aprobada → facturada | rechazada | cancelada
  estado                String    @default("borrador") @db.VarChar(25)
  fecha_solicitud       DateTime  @default(now()) @db.Timestamp(6)
  fecha_aprobacion      DateTime? @db.Timestamp(6)
  aprobado_por          String?   @db.Uuid
  motivo_rechazo        String?
  // Totales
  moneda                String    @default("PYG") @db.VarChar(3)
  monto_total           Decimal?  @default(0) @db.Decimal(19, 4)
  cant_cuotas_total     Int?      @db.SmallInt
  observaciones         String?
  factura_id            String?   @db.Uuid  // Referencia a la factura generada
  // Auditoría
  creado_por            String?   @db.Uuid
  active                Boolean   @default(true)
  created_at            DateTime  @default(now()) @db.Timestamp(6)
  updated_at            DateTime? @db.Timestamp(6)

  // Relaciones
  empresa           empresas               @relation(...)
  cliente           clientes               @relation(...)
  vendedor          vendedores_cobradores? @relation(...)
  condicion_pago    condiciones_pago?      @relation(...)
  aprobador         usuario?               @relation(...)
  factura           factura_cab?           @relation(...)
  detalle           solicitud_credito_det[]
  historial         solicitud_credito_historial[]
}
```

**`solicitud_credito_det`** (detalle):

```prisma
model solicitud_credito_det {
  id                        String   @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  solicitud_credito_id      String   @db.Uuid
  producto_id               String   @db.Uuid
  producto_precio_cuota_id  String?  @db.Uuid  // Plan de cuotas seleccionado
  descripcion               String   @db.VarChar(2000)
  cantidad                  Decimal  @db.Decimal(14, 4)
  cant_cuotas               Int?     @db.SmallInt
  monto_cuota               Decimal? @db.Decimal(18, 4)
  precio_total              Decimal  @db.Decimal(23, 8)  // cant_cuotas * monto_cuota * cantidad
  porcentaje_iva            Int?
  monto_iva                 Decimal? @db.Decimal(23, 8)
  observacion               String?
  created_at                DateTime @default(now()) @db.Timestamp(6)

  // Relaciones
  solicitud   solicitud_credito       @relation(...)
  producto    productos               @relation(...)
  precio_cuota producto_precio_cuota? @relation(...)
}
```

**`solicitud_credito_historial`**: Mismo patrón que `pedido_historial` (acción, descripción, datos JSON, usuario, fecha).

### 2.2 Backend — Service y Controller

**`solicitud-credito.service.ts`**:

- `crear(dto)` — crea en estado `borrador`
- `enviarAprobacion(id)` — cambia a `pendiente_aprobacion`
- `aprobar(id, aprobadorId)` — valida permisos, cambia a `aprobada`
- `rechazar(id, motivo, aprobadorId)` — cambia a `rechazada`
- `cancelar(id)`
- `listar(empresaId, filtros)` — con paginación y filtros por estado/fecha/cliente
- `obtenerPorId(id)` — detalle completo
- `obtenerAprobadas(empresaId, clienteId?)` — para el selector en POS
- `marcarFacturada(id, facturaId)` — cuando se factura desde POS

**Controller**: CRUD + endpoints de workflow (aprobar, rechazar, enviar).

**Permisos**: Nuevo módulo `SOLICITUD_CREDITO` con acciones: `CREAR`, `APROBAR`, `RECHAZAR`, `VER`.

### 2.3 Frontend — Gestión de Solicitudes

**Nueva página `/ventas/solicitudes-credito`**:

- Tab similar a `FacturasTab.jsx` / `PedidosTab.jsx`
- Listado con filtros: estado, fecha, cliente, vendedor
- Chips de estado con colores (borrador=gris, pendiente=naranja, aprobada=verde, rechazada=rojo, facturada=azul)
- Acciones: Ver detalle, Enviar a aprobación, Aprobar, Rechazar

**Nueva página `/ventas/solicitudes-credito/nueva`**:

- Formulario similar a nuevo pedido mayorista
- Selector de cliente
- Selector de condición de pago
- Buscador de productos → al agregar, si tiene precios a cuotas muestra las opciones
- Resumen: productos, cuotas seleccionadas, total

**Dialog de detalle**: Similar a `PedidoDetalleDialog.jsx` — muestra datos completos + botones de acción según estado y permisos.

### 2.4 Frontend — Integración con POS/Facturación

**En el POS (`POSAdminTemplate.jsx`)**:

- Nuevo botón/selector "Cargar desde solicitud de crédito" (similar al de pedidos pendientes `PedidosPendientesDialog.jsx`)
- Al seleccionar una solicitud aprobada:
  - Precarga cliente, condición de pago, productos con sus cuotas
  - El usuario confirma y factura
  - Al facturar, se marca la solicitud como `facturada` con referencia a la factura

### 2.5 PDF de Solicitud de Crédito (msv-kude)

Generar un PDF imprimible con el detalle completo de la solicitud para entregar al supervisor/aprobador.

**Nuevo archivo**: `src/solicitud-credito/solicitud_credito_a4.js` (usa `pdfkit`, mismo patrón que `recibos/v2/recibo_a4.js`).

**Endpoint**: `POST /solicitud-credito/generate-pdf`

- Recibe: `{ empresa_id, solicitud }` (objeto completo con cabecera + detalle + cliente)
- Retorna: buffer PDF en base64

**Ruta** en `src/routes/` — nuevo archivo `solicitud-credito.js` o agregado a `kude/index.js`:

```js
router.post('/solicitud-credito/generate-pdf', async (req, res) => {
  const { empresa_id, solicitud } = req.body;
  const empresa = await db.pg.table('empresas').where('id', empresa_id).first();
  const result = await createSolicitudCreditoPDF({ empresa, solicitud });
  res.json(result);
});
```

**Diseño del PDF (A4)**:

```
┌─────────────────────────────────────────────────────────┐
│  [LOGO]  Razón Social Empresa                          │
│          RUC: xxx | Tel: xxx | Dirección                │
│                                                         │
│          SOLICITUD DE CRÉDITO Nº 0001                   │
│          Estado: PENDIENTE DE APROBACIÓN                │
│          Fecha: 22/03/2026                              │
├─────────────────────────────────────────────────────────┤
│  DATOS DEL CLIENTE                                      │
│  Nombre: Juan Pérez          RUC/CI: 1234567-8          │
│  Teléfono: 0981 123 456      Dirección: Asunción        │
│  Vendedor: María López                                  │
│  Condición de pago: Crédito 30 días                     │
├─────────────────────────────────────────────────────────┤
│  DETALLE DE PRODUCTOS                                   │
│ ┌────┬──────────────┬─────┬───────┬──────────┬────────┐ │
│ │ #  │ Producto     │Cant.│Cuotas │Mto/Cuota │ Total  │ │
│ ├────┼──────────────┼─────┼───────┼──────────┼────────┤ │
│ │ 1  │ Producto A   │  1  │  3    │ 100.000  │300.000 │ │
│ │ 2  │ Producto B   │  2  │  6    │  50.000  │600.000 │ │
│ └────┴──────────────┴─────┴───────┴──────────┴────────┘ │
│                                                         │
│  Observaciones: Texto libre ingresado por el vendedor   │
├─────────────────────────────────────────────────────────┤
│  RESUMEN                                                │
│  Total productos: 2          Moneda: PYG                │
│  Monto total: Gs. 900.000                               │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ________________          ________________             │
│  Firma Vendedor            Firma Supervisor              │
│  Nombre vendedor           Nombre aprobador             │
│  Fecha:                    Fecha:                       │
│                                                         │
└─────────────────────────────────────────────────────────┘
```

**Secciones del PDF**:

1. **Encabezado**: Logo empresa (si tiene), razón social, RUC, datos de contacto. Número de solicitud y estado con badge de color.
2. **Datos del cliente**: Nombre/razón social, RUC/CI, teléfono, dirección, vendedor asignado, condición de pago.
3. **Tabla de productos**: Nº, Descripción, Cantidad, Cuotas, Monto/Cuota, Cuota inicial (si aplica), Precio total. Filas alternadas con fondo claro.
4. **Observaciones**: Bloque de texto libre si la solicitud tiene observaciones.
5. **Resumen**: Total de productos, monto total formateado, moneda.
6. **Firmas**: Dos bloques con línea para firma — vendedor (izquierda) y supervisor/aprobador (derecha), con espacio para nombre y fecha.

**Frontend — Botón de impresión**:

- En `SolicitudDetalleDialog` y en la fila del listado: botón/ícono `Print` que llama al endpoint, recibe el PDF en base64 y lo abre en nueva pestaña para imprimir.
- Disponible en cualquier estado (para que el vendedor pueda imprimir antes de la aprobación).
- Usa el mismo patrón que la impresión de recibos/facturas existente.

---

## FASE 3: Política de cobranza por cuenta (escalable)

### Contexto

La `solicitud_credito` ya guarda `intervalo_dias`, `dia_fijo_pago`, `dia_cobro_semana`, `dias_gracia` y un `cronograma` JSONB precalculado. **Problema**: al facturar, esos datos NO se propagan a `cuentas_cobrar` ni a `factura_cuotas`. Consecuencias:

- El cobrador no ve la política acordada en ningún lado.
- Si se regeneran cuotas más tarde, se pierde la referencia (cae al hardcode `fecha_base + N*30`).
- No hay forma de armar una agenda de cobro basada en la política del cliente.
- `dias_gracia` está duplicado con la config global de mora (`cob_config_mora.periodo_gracia_dias`) sin regla clara de precedencia.

### 3.1 Schema — extensión de `cuentas_cobrar`

Migración Prisma que agrega:

```prisma
// Política de pago acordada — heredada de solicitud_credito al facturar
intervalo_dias         Int?    @db.SmallInt  // 7 / 15 / 30
dia_fijo_pago_1        Int?    @db.SmallInt  // 1..31 (mensual/quincenal 1ra)
dia_fijo_pago_2        Int?    @db.SmallInt  // 1..31 (quincenal 2da)
dia_cobro_semana       Int?    @db.SmallInt  // 1..7 (semanal, 1=Lunes)
dias_gracia_override   Int?    @db.SmallInt  // null = hereda cob_config_mora
solicitud_credito_id   String? @db.Uuid      // trazabilidad → FK a solicitud_credito
```

Migración equivalente en `solicitud_credito`:

- Renombrar `dia_fijo_pago` → `dia_fijo_pago_1`.
- Agregar `dia_fijo_pago_2` (nullable).
- Renombrar `dias_gracia` → `dias_gracia_override` (nullable, sin default).

Backfill: para quincenales existentes con solo `dia_fijo_pago_1`, dejar `_2` en NULL (el cálculo hará fallback a `_1 + 15`).

### 3.2 Reglas de resolución (función central `calcularVencimiento`)

Nueva función pura en `src/cobros/politica-cobro.util.ts`:

```ts
calcularProximoVencimiento(politica, fechaBase): Date
calcularCronograma(politica, saldo, montoCuota, fechaBase): { nro, dvenccuo, monto }[]
```

Lógica:

1. **`intervalo_dias = 30` (mensual)**: usa `dia_fijo_pago_1` como día del mes. Ignora `_2`.
2. **`intervalo_dias = 15` (quincenal)**:
   - Si hay `dia_fijo_pago_1` **y** `dia_fijo_pago_2` → alternar. Próxima fecha = la más cercana futura entre día_1 y día_2 (mes actual/próximo). Después de cada cuota se conmuta al otro día.
   - Si solo `dia_fijo_pago_1` → segunda quincena = `dia_fijo_pago_1 + 15` (con roll-over si pasa de 31).
   - Si solo `dia_cobro_semana` → cada 15 días el mismo día de la semana.
   - Sin política → `fecha_base + 15 días` (fallback plano).
3. **`intervalo_dias = 7` (semanal)**: usa `dia_cobro_semana`. Próxima fecha = próxima ocurrencia del día en la semana.
4. **Roll-over de días 29/30/31**: si el día > días del mes destino → clamp al último día del mes. Evita "vence el 31 de febrero → 3 de marzo".

Reemplaza el hardcode en:
- `cobros.service.ts:regenerarCuotas` — `venc.setDate(venc.getDate() + i * 30)`.
- `facturas.service.ts` — cálculo de vencimientos por default cuando no viene cronograma explícito.
- `refinanciaciones.service.ts:calcularCuotasPropuestas` — actualmente asume 30 días fijos.

### 3.3 Propagación al facturar

En `solicitudes-credito.service.ts:marcarFacturada` (o en `facturas.service.ts:createFacturaAutomatico` cuando recibe `solicitud_id`):

- Copiar `intervalo_dias`, `dia_fijo_pago_1`, `dia_fijo_pago_2`, `dia_cobro_semana`, `dias_gracia_override` desde `solicitud_credito` a la nueva `cuentas_cobrar`.
- Setear `cuentas_cobrar.solicitud_credito_id` para trazabilidad.

Backfill una-sola-vez para cuentas existentes con solicitud origen:

```sql
UPDATE cuentas_cobrar cc
SET intervalo_dias = sc.intervalo_dias,
    dia_fijo_pago_1 = sc.dia_fijo_pago_1,
    dia_fijo_pago_2 = sc.dia_fijo_pago_2,
    dia_cobro_semana = sc.dia_cobro_semana,
    solicitud_credito_id = sc.id
FROM solicitud_credito sc
WHERE sc.factura_id = cc.factura_venta_id
  AND cc.intervalo_dias IS NULL;
```

### 3.4 Días de gracia — herencia con override

Regla de resolución:

```ts
diasGraciaEfectivo = cuenta.dias_gracia_override ?? configMora.periodo_gracia_dias ?? 0
```

En UI de solicitud/cuenta: badge read-only mostrando el valor efectivo con etiqueta "según configuración de mora" cuando viene del global, o "acuerdo especial" cuando es override.

Auditar cada cambio de override con `AuditService` (`action: 'GRACIA_OVERRIDE'`, `entity_type: 'cuenta_cobrar'`, `new_value.motivo`).

Reporte "Cuentas con días de gracia distintos al estándar" para ver desviaciones.

### 3.5 UI en la solicitud (extensión del form)

Cuando `frecuencia = Quincenal`, reemplazar el `Select` único de "Día fijo de pago" por:

```
Vencimientos quincenales
[Primer día del mes ▾] y [Segundo día del mes ▾]
Chips presets: [15 y último] [5 y 20] [1 y 15] [10 y 25]
```

Validación: día_2 > día_1 (swap automático si no).

Cuando `frecuencia = Mensual`: solo `dia_fijo_pago_1`.

Cuando `frecuencia = Semanal`: solo `dia_cobro_semana`.

Días de gracia: badge derivado de la config global, con botón "✎ ajustar para este cliente" que abre override numérico + motivo obligatorio.

### 3.6 Agenda de cobro derivada (no persistida)

**Resolución del cobrador — precedencia** (más específico → más general):

1. `factura_cab.cobrador_id` — override específico para ese documento.
2. `cliente_direcciones.cobrador_id` — asignado a la **dirección de entrega** de la factura (vía `factura_cab.cliente_direccion_id`). Útil cuando un cliente tiene múltiples sucursales/depósitos con distintos cobradores.
3. `clientes.cobrador_id` — cobrador default del cliente.
4. `asignacion_facturas.cobrador_id` — asignación manual/histórica (retrocompatibilidad).

Cada nivel se activa **solo** cuando el anterior está vacío. La vista lo expone en la columna `cobrador_origen` (`FACTURA | DIRECCION | CLIENTE | NULL`) para trazabilidad y depuración.

Vista SQL `v_agenda_cobrador`:

```sql
CREATE VIEW v_agenda_cobrador AS
SELECT
  cc.id AS cuenta_id,
  cc.cliente_id,
  fc.cobrador_id,
  cc.intervalo_dias,
  cc.dia_fijo_pago_1,
  cc.dia_fijo_pago_2,
  cc.dia_cobro_semana,
  COALESCE(cc.dias_gracia_override, m.periodo_gracia_dias, 0) AS dias_gracia,
  cu.id AS cuota_id,
  cu.dvenccuo,
  cu.saldo_pendiente,
  CURRENT_DATE - cu.dvenccuo AS dias_vencido
FROM cuentas_cobrar cc
JOIN factura_cuotas cu ON cu.cuenta_id = cc.id AND cu.estado = 'pendiente'
JOIN factura_cab fc ON fc.id = cc.factura_venta_id
LEFT JOIN cob_config_mora m ON m.empresa_id = cc.empresa_id
WHERE cc.estado != 'pagada' AND fc.estado != 'Anulado';
```

Endpoint `GET /cobranzas/agenda-hoy?cobrador_id=X` filtra por:

- `dia_cobro_semana = EXTRACT(DOW FROM CURRENT_DATE)` (semanales de hoy), **OR**
- `dia_fijo_pago_1 = EXTRACT(DAY FROM CURRENT_DATE)` **OR** `dia_fijo_pago_2 = EXTRACT(DAY FROM CURRENT_DATE)`, **OR**
- cuotas venciendo hoy / mañana / próximos N días (según `dias_gracia`).

Devuelve la agenda del día del cobrador sin necesidad de tabla adicional.

### 3.7 Rutas de cobro materializadas (opcional, escala mayor)

Reutilizar `rutas_cobranza` existente:

- Job diario (o botón "Generar ruta del día") que arma `rutas_cobranza` para cada cobrador desde `v_agenda_cobrador`.
- Campos a agregar (si escala): `zona_id`, `orden_optimo`, `distancia_km`.
- Al completar la visita, marcar `ruta_facturas.cobrado = true` con timestamp.

### 3.8 UI complementaria

- **Detalle de factura / cuenta**: mostrar `"Vence el {día X} de cada mes · Cobrar los {lunes} · Gracia {N} días"`.
- **Mesa de gestión del cobrador**: filtro "Solo mis clientes de HOY" basado en agenda.
- **Reporte de cartera**: agrupable por día de cobro (para dimensionar carga por día del cobrador).
- **Al regenerar cuotas**: usar la política guardada como default, mostrar advertencia si el usuario la cambia manualmente.

### 3.9 Orden de implementación de Fase 3

| Sub-paso | Qué                                                                              | Dónde                                            |
| -------- | -------------------------------------------------------------------------------- | ------------------------------------------------ |
| 3.1      | ✅ Migración Prisma: columnas nuevas en `cuentas_cobrar` + rename en `solicitud_credito` | Backend (Prisma migrate)                         |
| 3.2      | ✅ Backfill SQL: propagar política de solicitud a cuentas existentes                | Backend (migración SQL adicional)                |
| 3.3      | ✅ Función central `calcularProximoVencimiento` + `calcularCronograma`             | `src/cobros/politica-cobro.util.ts`              |
| 3.4      | ✅ Refactor `regenerarCuotas` y `calcularCuotasPropuestas` (refinanciaciones)      | Backend                                          |
| 3.5      | ✅ Propagación al facturar: copiar política de solicitud a cuenta                   | `facturas.service.ts` (crear cuenta a cobrar)   |
| 3.6      | ✅ UI: dos selectores para quincenal + chips presets                                | `NuevaSolicitudCredito.jsx`                      |
| 3.7      | ✅ UI: badge de días de gracia con override + motivo                                | `NuevaSolicitudCredito.jsx`                      |
| 3.8      | ✅ Vista SQL `v_agenda_cobrador` (con precedencia factura→dirección→cliente) + endpoint `/panel-cobrador/agenda-hoy` | Backend                                          |
| 3.9      | ✅ UI: filtro "Cobros de hoy" en mesa de gestión (server-side vía `solo_agenda_hoy=true`) | `MesaGestionPage.jsx` + `mesa-gestion.service.ts` |
| 3.10     | ✅ UI: sección "Política de Cobranza" en detalle de factura (chips)                | `FacturaDetalleDialog.jsx`                       |
| 3.11     | (Pendiente, opcional) Job diario que materializa `rutas_cobranza` desde `v_agenda_cobrador` | Backend cron                                     |
| 3.12     | ✅ Endpoint `/panel-cobrador/cartera-por-dia-cobro` (distribución mes/semana + sin_politica); UI del reporte queda para siguiente iteración | Backend                                          |

---

## Orden de implementación

| Paso | Qué                                                | Dónde                                                 |
| ---- | -------------------------------------------------- | ----------------------------------------------------- |
| 1    | Schema + migración `producto_precio_cuotas`        | Backend (Prisma)                                      |
| 2    | Service + Controller + DTO producto-precio-cuotas  | Backend (NestJS)                                      |
| 3    | API service frontend                               | `pos-ventas/src/api/`                                 |
| 4    | ABM cuotas en producto                             | Frontend (RegistrarProductos / PreciosProductoDialog) |
| 5    | Selector cuotas en POS                             | Frontend (ProductSearch / POSAdmin)                   |
| 6    | Campo `producto_precio_cuota_id` en `factura_det`  | Backend (migración)                                   |
| 7    | Schema + migración solicitud de crédito            | Backend (Prisma)                                      |
| 8    | Service + Controller solicitud-credito             | Backend (NestJS)                                      |
| 9    | API service frontend solicitudes                   | `pos-ventas/src/api/`                                 |
| 10   | Páginas de solicitudes (listado + nueva + detalle) | Frontend                                              |
| 11   | Integración solicitud → POS facturación            | Frontend (POSAdmin)                                   |
| 12   | Permisos módulo SOLICITUD_CREDITO                  | Backend + Frontend                                    |
| 13   | PDF solicitud de crédito (diseño + endpoint)       | msv-kude (`solicitud_credito_a4.js` + ruta)           |
| 14   | Botón imprimir solicitud en frontend               | Frontend (SolicitudDetalleDialog)                     |

---

## Decisiones tomadas

- **Modelo cuotas**: Nuevo `producto_precio_cuota` independiente de `planes_cuotas`
- **Aprobación**: Un solo nivel (vendedor → supervisor)
- **Datos solicitud**: Básico (sin garantías ni scoring)
- **Facturación**: Desde POS seleccionando solicitud aprobada (no generación automática)
- **Cálculo cuotas**: Manual (sin auto-cálculo semanal/quincenal). La empresa carga sus precios como ya los tiene
- **Condiciones de pago**: Se reutiliza el modelo existente para frecuencia de pago

---

## ISSUES PENDIENTES (Post-implementación)

### Prioridad Alta
1. **Stock: Validación y reserva al aprobar solicitud**
   - Al agregar producto: mostrar stock disponible + warning si insuficiente
   - Al aprobar solicitud: reservar stock (descontar de disponible)
   - Al rechazar/cancelar: liberar stock reservado
   - Al facturar: convertir reserva en salida definitiva
   - Respetar config de stock mínimo por sucursal/producto

### Prioridad Media
2. **"Primera Cuota: Invalid Date" en el detalle**
   - El campo `fecha_primera_cuota` se guarda como Date en Prisma pero se muestra como "Invalid Date" en el frontend
   - Verificar formato de fecha en `findOneInternal` y en el componente `SolicitudCreditoDetalleDialog`

3. **PDF no abre desde el diálogo de detalle**
   - El blob se genera pero `window.open` no abre la pestaña
   - Posible popup blocker o issue con el tipo MIME del blob
   - Verificar con `URL.createObjectURL(new Blob([response], { type: "application/pdf" }))`

4. **Edición de borradores**
   - Permitir guardar solicitud como borrador (estado="borrador") y editarla después
   - Ruta `/solicitudes-credito/:id/editar` que cargue los datos en el formulario
   - Solo editable si estado = "borrador"

### Prioridad Baja
5. **Logo de empresa en PDF**
   - Campo corregido de `logo_url` a `logo` pero verificar que aparece en solicitudes nuevas

6. **Solicitudes antiguas sin número**
   - Las solicitudes creadas antes del fix de numeración muestran "Borrador" como número
   - Opción: migración para asignar números retroactivamente o dejar como está

7. **~~Días de gracia configurable por empresa~~** — **Resuelto en FASE 3** (hereda de `cob_config_mora.periodo_gracia_dias` con override opcional por cuenta en `dias_gracia_override`).
