Plan de Pruebas — Módulo de Rendiciones de Cobranza

---

Conceptos clave antes de empezar

┌──────────────────────┬──────────────────────────────────────────────────────────────────────────────────────┐
│ Término              │ Qué es en la práctica                                                                │
├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ Rendición            │ Conjunto de recibos cobrados que un cobrador entrega a Tesorería para verificar      │
├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ Cobrador             │ Usuario que sale a cobrar en la calle y rinde lo recaudado                           │
├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ Tesorería            │ Área que recibe, cuenta y verifica medio por medio antes de aprobar la rendición     │
├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ Medio de pago        │ Cada forma con la que el cliente pagó: EFECTIVO, CHEQUE, TRANSFERENCIA, TARJETA…     │
├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ Total declarado      │ Lo que el cobrador dice traer en la mano                                             │
├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ Total verificado     │ Lo que Tesorería contó al revisar                                                    │
├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ Diferencia           │ Declarado − Verificado. Positiva = faltante; negativa = sobrante                     │
├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ Código de rendición  │ Correlativo por empresa asignado automáticamente al crear (REN-2026-00001…)          │
└──────────────────────┴──────────────────────────────────────────────────────────────────────────────────────┘

---

Estados de la rendición

```
   ┌──────────┐    enviar a    ┌──────────┐   aprobar   ┌──────────┐
   │ BORRADOR │ ──tesorería──▶ │ PENDIENTE│ ──────────▶ │ APROBADO │
   └────┬─────┘                └─────┬────┘             └──────────┘
        │                            │
        │                       observar / rechazar
        ▼                            ▼
   ┌──────────┐                 ┌──────────┐
   │ ANULADO  │                 │ OBSERVADO│ ──reenviar──▶ PENDIENTE
   └──────────┘                 │RECHAZADO │
                                └──────────┘
```

- BORRADOR  → la creó el cobrador, todavía edita
- PENDIENTE → enviada, Tesorería debe verificar
- OBSERVADO → Tesorería detectó diferencia leve y la devuelve para corrección
- APROBADO  → todo verificado, comisiones quedan liquidables
- RECHAZADO → diferencia mayor / fraude; queda como historial
- ANULADO   → cancelada antes de cualquier verificación

---

Flujo obligatorio antes de cualquier prueba

1. Tener al menos un cobrador activo con permiso `COBRADOR`
2. Tener al menos un cliente con cuentas a cobrar
3. Tener emitidos algunos recibos de cobro de ese cobrador SIN rendición asignada
4. Tener un usuario con permiso `RENDICIONES_VERIFICAR` (rol Tesorería)
5. (Opcional) Tener configuradas las comisiones del cobrador para validar la liquidación

---

PRUEBA 1 — Crear Rendición en estado BORRADOR

Dónde: Finanzas → tab Rendiciones → botón "Nueva Rendición"

Escenario: Un cobrador llega de la calle con 3 recibos cobrados (efectivo + cheque + transferencia)
por un total de Gs. 2.500.000 y declara que ese es el monto que trae.

Pasos:

1. Hacer clic en "Nueva Rendición"
2. Completar el wizard:
   - Cobrador: seleccionar al cobrador del día
   - Fecha de cobranza: hoy
   - Recibos: marcar los 3 recibos disponibles
   - Total declarado: 2.500.000
   - Observaciones cobrador: (opcional)
3. Dejar el toggle "Enviar al crear" en OFF
4. Hacer clic en "Crear borrador"

Resultado esperado:

- Aparece toast "Rendición creada exitosamente"
- En la lista aparece la rendición con:
  - Código correlativo (ej: REN-2026-00001)
  - Badge gris "Borrador"
  - Declarado: Gs. 2.500.000
  - Acción visible: "Enviar a tesorería"
- KPI "Borradores" del panel sube en 1

Resultado negativo (debe fallar):

- Crear otra rendición para el mismo cobrador con estado BORRADOR/PENDIENTE/OBSERVADO →
  error "El cobrador ya tiene una rendición activa"

Verificar en BD:
SELECT codigo, estado, total_declarado FROM rendiciones WHERE cobrador_id = '<id>' ORDER BY created_at DESC;

---

PRUEBA 2 — Desglose visual de medios de pago al crear

Dónde: Diálogo "Nueva Rendición" → step 2

Escenario: Validar que el wizard muestra automáticamente cuánto suma cada medio según los recibos
seleccionados, para que el cobrador detecte rápido si el total declarado cuadra.

Pasos:

1. Abrir "Nueva Rendición"
2. Seleccionar recibos que contengan mezcla de medios de pago (ej: efectivo + cheque)
3. Observar la sección "Desglose por medio de pago"

Resultado esperado:

- Aparecen los chips por medio con icono lucide + monto sumado:
  - 💵 EFECTIVO Gs. 1.500.000
  - 📜 CHEQUE Gs. 700.000
  - ↔ TRANSFERENCIA Gs. 300.000
- La diferencia entre Total declarado y total esperado se muestra coloreada:
  - Verde si = 0
  - Ámbar si |dif| < 50.000
  - Rojo si |dif| ≥ 50.000

---

PRUEBA 3 — Enviar a Tesorería

Escenario: El cobrador ya armó el borrador y lo envía para que lo verifiquen.

Pasos:

1. En la lista de rendiciones, ubicar la rendición en BORRADOR
2. Hacer clic en la acción "Enviar a tesorería"
3. Confirmar

Resultado esperado:

- El estado cambia a PENDIENTE (badge ámbar con icono reloj)
- Se registra entrada en `rendicion_historial`: BORRADOR → PENDIENTE
- Aparece la acción "Revisar / Verificar" para Tesorería
- KPI "Pendientes (Tesorería)" sube en 1
- KPI "Borradores" baja en 1

Variante: en el wizard de creación, activar el toggle "Enviar al crear" → debe hacer ambos pasos
seguidos (crear + enviar) y la rendición nace directamente en PENDIENTE.

Verificar en BD:
SELECT estado_anterior, estado_nuevo, motivo FROM rendicion_historial
WHERE rendicion_id = '<id>' ORDER BY created_at;

---

PRUEBA 4 — Verificación granular por medio de pago

Dónde: Finanzas → tab Rendiciones → acción "Revisar / Verificar"

Escenario: Tesorería abre la rendición en PENDIENTE y revisa cada medio de pago. El cheque de
Gs. 700.000 está bien, pero al contar el efectivo descubre que faltan Gs. 50.000.

Pasos:

1. Hacer clic en "Revisar / Verificar"
2. Verificar que el header muestra:
   - Código de la rendición
   - Nombre del cobrador
   - Contador "X/Y verificados" (ej: 0/3)
3. Verificar que los 4 KPIs están visibles: Declarado, Cobrado, Verificado, Dif. Verif.
4. Para cada medio de pago listado:
   - Icono + label (Efectivo, Cheque, Transferencia, etc.)
   - Monto declarado por el cobrador (read-only)
   - Input "Importe verificado" (editable)
   - Select estado: OK | DIFERENCIA | RECHAZADO
   - Campo "Observación" (visible solo si estado ≠ OK)
   - Botón "Verificar" / "Reverificar"

Verificar EFECTIVO con diferencia:

5. Importe verificado: 1.450.000 (en vez de 1.500.000)
6. Estado: DIFERENCIA
7. Observación: "Faltan Gs. 50.000 al contar"
8. Hacer clic en "Verificar"

Resultado esperado:

- El medio queda con badge ámbar "Diferencia"
- El contador pasa a 1/3
- El KPI "Verificado" se actualiza en vivo
- El botón "Aprobar" del footer sigue deshabilitado

Verificar CHEQUE OK:

9. Importe verificado: 700.000
10. Estado: OK
11. "Verificar" → badge verde

Verificar TRANSFERENCIA OK:

12. Misma operación
13. Contador llega a 3/3

Verificar en BD:
SELECT medio_pago, importe_declarado, importe_verificado, verificacion_estado, verificacion_observacion
FROM recibo_cobro_medios_pago
WHERE recibo_id IN (SELECT id FROM recibos_cobro WHERE rendicion_id = '<id>');

---

PRUEBA 5 — Aprobar rendición con diferencia menor

Escenario: La rendición tiene una diferencia de Gs. 50.000 (faltante) en efectivo pero todos los
medios están verificados y ninguno fue marcado como RECHAZADO. Tesorería decide aprobarla.

Pasos:

1. Estando en el diálogo de revisión con 3/3 verificados
2. Verificar que el botón "Aprobar" del footer ya está habilitado (verde)
3. (Opcional) Completar "Observaciones supervisor": "Diferencia descontada del próximo turno"
4. Hacer clic en "Aprobar"

Resultado esperado:

- Toast verde "Rendición aprobada"
- Estado pasa a APROBADO (badge verde)
- `total_verificado` = suma de importes verificados (ej: Gs. 2.450.000)
- `fecha_aprobacion` y `aprobado_por` quedan registrados
- Las comisiones asociadas pasan de `pendiente` → `aprobada` (entran en la liquidación)
- Se registra en historial: PENDIENTE → APROBADO
- El diálogo se cierra y la lista se refresca

Verificar en BD:
SELECT estado, total_declarado, total_verificado, fecha_aprobacion, aprobado_por
FROM rendiciones WHERE id = '<id>';

SELECT estado FROM comisiones WHERE recibo_id IN (SELECT id FROM recibos_cobro WHERE rendicion_id = '<id>');
-- Debe mostrar 'aprobada'

---

PRUEBA 6 — Bloqueo al aprobar sin verificación completa

Escenario: Intentar aprobar antes de verificar todos los medios, o con un medio en RECHAZADO.

Pasos A — verificación incompleta:

1. Crear y enviar una rendición a tesorería
2. Abrir el diálogo de revisión
3. Verificar solo 1 de los 3 medios
4. Intentar hacer clic en "Aprobar"

Resultado esperado:

- El botón "Aprobar" está deshabilitado
- Si se llama directamente al endpoint POST `/rendiciones/:id/aprobar` → 400 con
  mensaje "Falta verificar 2 medio(s) de pago"

Pasos B — medio RECHAZADO:

1. En otra rendición, marcar un cheque como RECHAZADO ("cheque sin fondo")
2. Verificar los otros medios como OK
3. Intentar Aprobar

Resultado esperado:

- Botón deshabilitado
- Endpoint retorna 400 "No se puede aprobar: hay medios rechazados. Use Observar/Rechazar"

---

PRUEBA 7 — Observar rendición (devolver al cobrador)

Escenario: La diferencia es importante y Tesorería quiere que el cobrador revise antes de aprobar.

Pasos:

1. Abrir el diálogo de revisión de una rendición PENDIENTE
2. Hacer clic en "Observar"
3. Aparece campo de motivo obligatorio
4. Motivo: "Falta Gs. 150.000 en efectivo, revisar bolsillo"
5. Confirmar

Resultado esperado:

- Toast "Rendición observada"
- Estado pasa a OBSERVADO (badge azul con icono alert-circle)
- Aparece acción "Enviar a tesorería" disponible para el cobrador (puede corregir y reenviar)
- Las comisiones permanecen en estado `pendiente` (no se liberan todavía)
- Historial registra: PENDIENTE → OBSERVADO con el motivo

Resultado negativo:

- Intentar observar sin motivo → 400 "Motivo es obligatorio"

---

PRUEBA 8 — Rechazar rendición (caso grave)

Escenario: Cheque sin fondo, monto faltante muy alto, o sospecha de mal manejo.

Pasos:

1. En revisión, marcar un medio como RECHAZADO con observación
2. Hacer clic en "Rechazar"
3. Motivo: "Cheque rebotado y faltante de Gs. 300.000 en efectivo"
4. Confirmar

Resultado esperado:

- Estado pasa a RECHAZADO (badge rojo)
- Comisiones de esos recibos pasan a `rechazada` o quedan en `pendiente` según política
- Los recibos NO se liberan automáticamente — requieren acción manual del supervisor
- Historial registra: PENDIENTE → RECHAZADO con motivo

Nota: Una rendición RECHAZADA es definitiva — no se puede reenviar. Si el cobrador necesita
intentar de nuevo, debe crear una rendición nueva con los recibos que aún estén libres.

---

PRUEBA 9 — Re-envío después de OBSERVADO

Escenario: El cobrador encuentra los Gs. 150.000 que faltaban y reenvía la rendición.

Pasos:

1. Con rendición en estado OBSERVADO, el cobrador abre el panel
2. Hace clic en "Enviar a tesorería" sobre la rendición observada
3. Ajusta el total declarado si corresponde
4. Confirma

Resultado esperado:

- Estado vuelve a PENDIENTE
- Las verificaciones previas se conservan (no se borran)
- Tesorería ve el medio que tenía diferencia con su estado anterior + observación,
  pero puede "Reverificar" para corregir
- Historial registra: OBSERVADO → PENDIENTE

---

PRUEBA 10 — Numeración correlativa por empresa

Escenario: Validar que el campo `codigo` se asigna por trigger y no se duplica entre empresas.

Pasos:

1. Crear 3 rendiciones seguidas en la empresa A
2. Verificar que los códigos son consecutivos (REN-2026-00001, 00002, 00003)
3. Crear 1 rendición en la empresa B (otro tenant)
4. Verificar que en B también arranca en 00001, no en 00004

Verificar en BD:
SELECT empresa_id, codigo, created_at FROM rendiciones ORDER BY created_at;

---

PRUEBA 11 — Reporte: Cobranzas del día

Dónde: Finanzas → tab Reportes Rendiciones → "Cobranzas del día"

Pasos:

1. Asegurarse de tener al menos 2 cobradores con rendiciones del día
2. Abrir el tab
3. Seleccionar la fecha de hoy

Resultado esperado:

- 4 KPIs en la parte superior:
  - Cobradores (número de cobradores que rindieron)
  - Cobrado (total verificado del día)
  - Recibos (cantidad total)
  - Rendiciones (cantidad de rendiciones)
- Tabla agrupada por cobrador con columnas:
  Cobrador | Rendiciones | Recibos | Declarado | Verificado | Diferencia
- Diferencia coloreada (verde / ámbar / rojo) según los umbrales del estándar

Verificar en BD (cuadre):
SELECT cobrador_id, COUNT(*) rends, SUM(total_declarado) decl, SUM(total_verificado) verif
FROM rendiciones
WHERE empresa_id = 'tu-id' AND fecha_rendicion = CURRENT_DATE AND estado IN ('APROBADO','PENDIENTE','OBSERVADO')
GROUP BY cobrador_id;
-- Debe coincidir con lo mostrado en pantalla

---

PRUEBA 12 — Reporte: Pendientes con antigüedad

Dónde: Finanzas → tab Reportes Rendiciones → "Pendientes"

Contexto: identifica rendiciones que se demoran en Tesorería. Sirve para detectar cuello de
botella y rendiciones olvidadas.

Pasos:

1. Crear una rendición y enviarla a tesorería
2. Esperar / simular fecha de envío
3. Abrir el reporte

Resultado esperado:

- Tabla con columnas: Código | Cobrador | Estado | Enviada | Antigüedad | Declarado | Recibos
- Badge de antigüedad coloreado:
  - Verde < 24h
  - Ámbar 24h – 72h
  - Rojo ≥ 72h
- La consulta se refresca automáticamente cada 60s
- Solo aparecen estados PENDIENTE y OBSERVADO

---

PRUEBA 13 — Reporte: Diferencias detectadas

Dónde: Finanzas → tab Reportes Rendiciones → "Diferencias"

Contexto: lista todas las rendiciones donde declarado ≠ verificado, para análisis de calidad
del cobrador o detección de problemas operativos.

Pasos:

1. Aprobar varias rendiciones con diferencias (algunas < 50.000, otras > 50.000)
2. Abrir el reporte
3. Probar los filtros: Nivel (menor/mayor), Desde, Hasta

Resultado esperado:

- Tabla ordenada por |diferencia| descendente
- Columnas: Código | Fecha | Cobrador | Declarado | Verificado | Diferencia | Nivel
- Filtro "menor" solo muestra |dif| < 50.000
- Filtro "mayor" solo muestra |dif| ≥ 50.000
- Datos provienen de la vista SQL `vw_rendiciones_diferencias`

Verificar en BD:
SELECT * FROM vw_rendiciones_diferencias WHERE empresa_id = 'tu-id' ORDER BY ABS(diferencia) DESC LIMIT 10;
-- Debe coincidir con la tabla en pantalla

---

PRUEBA 14 — Reporte: Comisiones a liquidar

Dónde: Finanzas → tab Reportes Rendiciones → "Comisiones a liquidar"

Contexto: lista las comisiones que ya fueron APROBADAS (porque la rendición se aprobó) pero
todavía no se incluyeron en una liquidación de pago al cobrador.

Pasos:

1. Aprobar una rendición con recibos que tengan comisión configurada
2. Abrir el tab "Comisiones a liquidar"
3. Verificar:
   - KPI "Total a liquidar" = suma de monto_comision
   - KPI "Comisiones" = cantidad de filas
4. Filtrar por cobrador (si aplica)

Resultado esperado:

- Tabla con columnas: Aprobada | Cobrador | Rendición | Recibo | Base | % | Comisión
- Ordenado por fecha_aprobacion DESC
- Una comisión que ya fue incluida en una liquidación NO debe aparecer aquí
- Datos provienen de la vista SQL `vw_comisiones_a_liquidar`

Verificar en BD:
SELECT * FROM vw_comisiones_a_liquidar WHERE empresa_id = 'tu-id' ORDER BY fecha_aprobacion DESC;

---

PRUEBA 15 — Resumen diario (KPIs del panel)

Dónde: Finanzas → tab Rendiciones (panel principal) → KPIs superiores

Pasos:

1. Cambiar la fecha en el input "Fecha"
2. Observar los 5 KPIs

Resultado esperado — KPIs:

┌──────────────────────────┬──────────────────────────────────────────────┐
│ KPI                      │ Qué muestra                                  │
├──────────────────────────┼──────────────────────────────────────────────┤
│ Total Cobrado            │ Suma de total_verificado del día (verde)     │
├──────────────────────────┼──────────────────────────────────────────────┤
│ Rendiciones              │ Cantidad total de rendiciones del día        │
├──────────────────────────┼──────────────────────────────────────────────┤
│ Pendientes (Tesorería)   │ PENDIENTE + OBSERVADO (ámbar)                │
├──────────────────────────┼──────────────────────────────────────────────┤
│ Borradores               │ Rendiciones aún sin enviar (gris)            │
├──────────────────────────┼──────────────────────────────────────────────┤
│ Diferencia               │ Suma de diferencias del día (verde/rojo)     │
└──────────────────────────┴──────────────────────────────────────────────┘

- El KPI se refresca automáticamente cada 60s
- Cambiar la fecha re-consulta inmediatamente

---

PRUEBA 16 — Filtros y paginación de la lista

Dónde: Finanzas → tab Rendiciones → botón "Filtros"

Pasos:

1. Abrir el panel de filtros
2. Probar cada filtro individualmente:
   - Cobrador: selector con cobradores activos
   - Fecha desde / hasta
   - Estado: dropdown con los 6 estados
3. Cambiar de página con los botones de paginación (límite 15 por página)

Resultado esperado:

- Cada filtro re-consulta y la página vuelve a 1
- El contador de paginación se actualiza
- Al combinar filtros, la condición es AND
- Botón "Limpiar filtros" deja todos en blanco

---

PRUEBA 17 — ScreenGuia colapsable

Dónde: Finanzas → tab Rendiciones (arriba de todo) Y tab Reportes Rendiciones (arriba)

Pasos:

1. Verificar que cada panel inicia con un acordeón "¿Cómo funciona…?"
2. Hacer clic en "Ver guía"
3. Verificar el contenido:

Panel Rendiciones — 4 pasos:
- 1. Crear rendición (azul)
- 2. Enviar a tesorería (ámbar)
- 3. Verificar y aprobar (verde)
- 4. Observar / Rechazar (rojo)

Panel Reportes — 4 pasos describiendo cada tab.

4. Verificar que hay sección "Consideraciones importantes" con 2-3 notas
5. Hacer clic en "Ocultar" → el acordeón se cierra
6. Verificar que NO inicia abierto por defecto (`defaultOpen=false`)

---

PRUEBA 18 — Tour automático de Finanzas

Dónde: Página Finanzas → primera vez que entra un usuario

Prerrequisito: Limpiar `localStorage.tour_finanzas` (o usar un usuario nuevo).

Pasos:

1. Entrar a Finanzas por primera vez
2. Esperar 500ms

Resultado esperado:

- Se dispara automáticamente el tour `driver.js` con 3 pasos:
  - Header "Módulo Finanzas"
  - Tabs operativas
  - Botón "Panel Supervisor" (solo si el usuario tiene permiso APERTURA_CIERRE)
- Al completar el tour, `localStorage.tour_finanzas = "completed"`
- Recargar la página → el tour NO se vuelve a disparar
- El botón "?" siempre queda visible al lado de "Panel Supervisor" para abrirlo manualmente

---

PRUEBA 19 — Restablecer tutoriales desde Mi Perfil

Dónde: Mi Perfil → sección "Tutoriales"

Contexto: El usuario quiere volver a ver el tutorial de Finanzas porque lo cerró sin leer.

Pasos:

1. Ir a Mi Perfil
2. Bajar hasta la tarjeta "Tutoriales"
3. Verificar que los 12 tutoriales están agrupados por módulo (Inicio, Ventas, Inventario,
   Contactos, Compras, RRHH, Finanzas, etc.)
4. Cada tutorial muestra chip "Visto" (verde) o "Pendiente" (gris)
5. Botón "Reiniciar" deshabilitado si está "Pendiente", habilitado si está "Visto"
6. Hacer clic en "Reiniciar" sobre "Finanzas (Tesorería)"
7. Toast: "Tutorial reiniciado — se mostrará la próxima vez que entres al módulo"
8. Volver a Finanzas → el tour se dispara de nuevo

Variante "Restablecer todos":
- Botón al lado del título → resetea las 12 entradas de localStorage en una pasada
- Toast verde

---

PRUEBA 20 — Permisos (control de acceso)

Escenario: Verificar que los botones de acción respetan los permisos asignados al perfil del usuario.

Roles relevantes:
- COBRADOR (rol del cobrador): puede crear, enviar y ver sus propias rendiciones
- TESORERIA / SUPERVISOR: puede verificar, aprobar, observar, rechazar
- AUDITOR / LECTOR: solo lee

Pasos:

1. Iniciar sesión como cobrador puro

Resultado esperado:

- Tab Rendiciones visible, solo ve sus propias rendiciones
- Botón "Nueva Rendición" visible
- Acción "Enviar a tesorería" visible sobre sus borradores
- Acción "Revisar / Verificar" NO visible
- Botones "Aprobar / Observar / Rechazar" NO accesibles
- Tab Reportes Rendiciones visible (lectura)

2. Iniciar sesión como tesorería

- Ve todas las rendiciones de la empresa (no solo las propias)
- Acción "Revisar / Verificar" visible sobre PENDIENTES
- Dentro del diálogo: botones Aprobar / Observar / Rechazar visibles
- Botón "Nueva Rendición" puede estar oculto (operación del cobrador)

3. Iniciar sesión como lector

- Solo ve la lista en modo lectura
- Ningún botón de acción visible
- Llamar directamente al endpoint POST /rendiciones → debe retornar 403 Forbidden

---

PRUEBA 21 — Validación de tema claro y oscuro

Dónde: Cualquier panel del módulo

Contexto: Los componentes deben usar tokens del theme (`theme.bg/bg2/bg6/primary/text/...`) y
verse bien tanto en claro como en oscuro. Solo los 5 colores semánticos son constantes.

Pasos:

1. Estando en tema claro, recorrer:
   - Panel Rendiciones (KPIs, tabla, filtros, badges)
   - Diálogo "Nueva Rendición"
   - Diálogo "Revisar / Verificar"
   - Panel Reportes (4 tabs)
2. Cambiar a tema oscuro (toggle global del sistema)
3. Recorrer los mismos elementos

Resultado esperado:

- En ambos temas: contraste correcto en texto, bordes visibles, hover sutil
- Los colores semánticos (verde / ámbar / rojo / azul / gris) se mantienen IDÉNTICOS en ambos temas
- No hay zonas en blanco roto ni en negro absoluto que descalcen del fondo
- Los inputs respetan `theme.inputBg` e `inputBorder`
- Los iconos lucide se ven sobre el fondo (color `theme.text` o variantes)

Si algo se ve mal en algún tema, está mal usado el token (o falta token nuevo) — ver
`docs/ui-standards.md` sección "Estilo visual estándar".

---

Checklist de "todo funciona"

MÓDULO RENDICIONES

[ ] Cobrador puede crear rendición en BORRADOR con recibos disponibles
[ ] Wizard muestra desglose por medio de pago automático
[ ] Diferencia entre declarado y esperado se colorea verde/ámbar/rojo
[ ] Toggle "Enviar al crear" encadena create + enviarTesoreria
[ ] Validación: un cobrador no puede tener dos rendiciones activas simultáneas
[ ] Código correlativo se asigna automáticamente (REN-AAAA-NNNNN)
[ ] Estado pasa BORRADOR → PENDIENTE al enviar a tesorería
[ ] Diálogo de revisión muestra X/Y verificados y 4 KPIs
[ ] Cada medio de pago se verifica individualmente (OK/DIFERENCIA/RECHAZADO)
[ ] Estado del medio guarda importe_verificado y observación
[ ] Botón Aprobar deshabilitado hasta verificar todos los medios
[ ] Botón Aprobar deshabilitado si algún medio está RECHAZADO
[ ] Aprobar registra fecha_aprobacion y aprobado_por
[ ] Aprobar pasa comisiones de 'pendiente' a 'aprobada'
[ ] Observar requiere motivo y deja la rendición en OBSERVADO
[ ] Rechazar requiere motivo y es definitivo
[ ] Rendición OBSERVADA se puede reenviar a tesorería
[ ] Historial registra cada transición de estado con usuario y motivo
[ ] KPIs del panel se refrescan cada 60s
[ ] Filtros (cobrador/fecha/estado) re-consultan correctamente
[ ] Reporte "Cobranzas del día" agrupa por cobrador con diferencia coloreada
[ ] Reporte "Pendientes" muestra antigüedad coloreada (verde<24h, ámbar<72h, rojo)
[ ] Reporte "Diferencias" filtra por nivel menor/mayor y rango de fechas
[ ] Reporte "Comisiones a liquidar" muestra total + cantidad y desaparece al liquidar
[ ] ScreenGuia colapsable presente arriba del panel y del reporte
[ ] Tour automático se dispara una vez por usuario y queda persistido
[ ] Mi Perfil permite reiniciar el tour de Finanzas individualmente
[ ] Mi Perfil tiene botón "Restablecer todos" que limpia los 12 tours
[ ] Panel se ve correctamente en tema claro Y oscuro
[ ] Diálogos abren con overlay `rgba(0,0,0,0.65) + backdrop-filter: blur(4px)`
[ ] Cobrador solo ve sus propias rendiciones; Tesorería ve todas
[ ] Endpoints retornan 403 sin permiso adecuado
[ ] Vista `vw_rendiciones_diferencias` coincide con el reporte en pantalla
[ ] Vista `vw_comisiones_a_liquidar` coincide con el reporte en pantalla

---

⚠️ Nota crítica — bloqueo de aprobación: Si el botón "Aprobar" sigue deshabilitado aun con
todos los medios verificados, revisar que ninguno haya quedado en RECHAZADO. Un solo medio
rechazado bloquea la aprobación y obliga a usar "Observar" o "Rechazar".

⚠️ Nota — comisiones: La comisión pasa a `aprobada` SOLO cuando la rendición se aprueba. Si
la rendición queda en OBSERVADO o RECHAZADO, las comisiones permanecen en `pendiente` o pasan
a `rechazada` y NO aparecen en el reporte "Comisiones a liquidar".

⚠️ Nota — recibos: Un recibo solo puede estar en UNA rendición. Mientras la rendición esté
en BORRADOR/PENDIENTE/OBSERVADO/APROBADO, el recibo no aparecerá en "Recibos disponibles"
para otra rendición. Si la rendición se ANULA o RECHAZA, evaluar manualmente liberar el recibo.

⚠️ Nota — verificación parcial: Las verificaciones que Tesorería va guardando son persistentes.
Si Tesorería interrumpe la revisión y vuelve más tarde, encuentra los medios ya verificados
con su estado anterior y puede "Reverificar" si necesita corregir.

⚠️ Nota — tema oscuro: Si un componente se ve descalzado en tema oscuro, casi siempre es
porque tiene un `#hex` hardcodeado en lugar de un `theme.X`. Buscar en el componente y
reemplazar por el token del theme correspondiente (ver `docs/ui-standards.md`).

⚠️ Nota — diferencia coloreada: El umbral por defecto para "menor vs mayor" es Gs. 50.000.
Si el negocio quiere otro umbral, hay que parametrizarlo (TODO futuro). Por ahora está
hardcodeado en `colorDif()` del panel.

---

ESTADO DE IMPLEMENTACIÓN CONSOLIDADO (actualizado 2026-05-23)

┌─────────────────────────────────────────────────────┬──────────────────┬────────────────────────────────────────────────────────┐
│ Funcionalidad                                       │ Estado           │ Detalle                                                │
├─────────────────────────────────────────────────────┼──────────────────┼────────────────────────────────────────────────────────┤
│ Schema + migración + trigger correlativo            │ ✅ Implementado  │ F1: tablas rendiciones, historial, vistas SQL         │
│ DTOs + estados + transiciones                       │ ✅ Implementado  │ F2: ESTADOS_RENDICION + VERIFICACION_ESTADOS          │
│ Service: crear / enviar / verificar / aprobar       │ ✅ Implementado  │ F2-F4: logHistorial + validaciones                    │
│ Controller: endpoints REST                          │ ✅ Implementado  │ POST/PATCH/GET para todo el flujo                     │
│ Wizard de creación con desglose medios pago         │ ✅ Implementado  │ F5: CrearRendicionDialog + toggle "Enviar al crear"  │
│ Panel principal con KPIs + lista + filtros          │ ✅ Implementado  │ F5: RendicionesPanel theme-agnostic                   │
│ Diálogo de verificación granular                    │ ✅ Implementado  │ F6: RevisarRendicionDialog medio por medio            │
│ Reportes (4): día / pendientes / dif / comisiones   │ ✅ Implementado  │ F7: backend + service + hooks + ReportesPanel         │
│ Vistas SQL para reportes                            │ ✅ Implementado  │ vw_rendiciones_diferencias, vw_comisiones_a_liquidar  │
│ ScreenGuia colapsable                               │ ✅ Implementado  │ F8: en panel y en reportes                            │
│ Tour de Finanzas (driver.js)                        │ ✅ Implementado  │ finanzasTour.js + TourTrigger autoStart               │
│ Registro central de tutoriales                      │ ✅ Implementado  │ src/tours/registry.js + Mi Perfil reset por tour      │
│ UI tema claro + oscuro (theme-agnostic)             │ ✅ Implementado  │ Tokens del theme, semánticos constantes               │
│ Parametrización del umbral menor/mayor              │ ⏳ Pendiente     │ Hoy hardcodeado Gs. 50.000 en frontend                │
│ Reapertura automática de recibos al anular rendición│ ⏳ Pendiente     │ Hoy requiere acción manual del supervisor             │
│ Notificación push al cobrador cuando se observa     │ ⏳ Pendiente     │ Por ahora se entera al abrir el panel                 │
└─────────────────────────────────────────────────────┴──────────────────┴────────────────────────────────────────────────────────┘

---

DECISIÓN ARQUITECTÓNICA — Verificación granular por medio

La verificación se hace sobre la fila `recibo_cobro_medios_pago` (NO sobre el recibo entero).

Razón: un cobrador puede traer un recibo con efectivo OK pero el cheque rechazado. Si la
verificación fuera por recibo, no podríamos representar este caso real. Modelar al nivel del
medio permite que la observación / rechazo sea quirúrgica y que el reporte de diferencias
muestre exactamente dónde está el problema.

Control de consistencia: `total_verificado` de la rendición = SUM(`importe_verificado`) de
todos los medios cuyo estado sea OK o DIFERENCIA. Los medios RECHAZADO no suman al verificado.

---

DECISIÓN ARQUITECTÓNICA — Comisiones acopladas al estado de la rendición

Las comisiones nacen en estado `pendiente` al crearse el recibo. Solo pasan a `aprobada`
cuando la rendición que contiene ese recibo se aprueba. Esto evita pagar comisiones sobre
cobros que después resulten ser cheques rebotados o efectivo faltante.

Implicación: si la rendición queda en RECHAZADO, la política actual deja las comisiones en
`pendiente` (no en `rechazada` automático) para que un supervisor decida caso por caso.

---

ESCENARIO — Cobro diferido en ruta (factura contado cobrada por cobrador)

Algunas empresas emiten facturas CONTADO en mostrador pero el pago lo hace el cobrador en
campo (le entregan al cobrador la factura impresa y él la cobra en visita posterior). El
cobrador debe poder rendir ese cobro como cualquier otro.

Modelo: bandera `factura_cab.cobro_diferido = TRUE` reusa toda la maquinaria de cartera y
rendición. La factura sigue siendo contado fiscalmente pero queda con saldo pendiente.

Pre-condiciones
- Cliente nominado (no "Sin nombre")
- Cobrador asignado activo y vinculado al cliente o autorizado
- Condición de operación = CONTADO

Flujo end-to-end

1. POSAdminTemplate → factura nueva
   - Cliente nominado, productos cargados, condición CONTADO
   - Asignar cobrador (campo "Cobrador" en cabecera)
   - Tildar checkbox "Cobro en ruta"
   - Verificar: PaymentPanel queda deshabilitado, no se exigen pagos
   - Guardar → backend recibe `cabecera.cobro_diferido = true`, `pagos = []`

2. Backend (facturas.service.create)
   - Si `cobro_diferido && !cobrador_id` → BadRequest "Se requiere asignar un cobrador"
   - No exige pagos para contado
   - `factura_cab` se crea con `cobro_diferido = TRUE`, `saldo_pendiente = total_general_operacion`
   - NO crea filas en `factura_forma_pagos`
   - NO toca `sesiones_caja` ni `movimiento_cajas`
   - SI emite SIFEN normal (condición CONTADO, sin afectar timbrado)

3. Cartera del cobrador
   - La factura aparece en la lista de facturas a cobrar del cobrador asignado
   - El saldo a cobrar es el total completo (no hay entrega inicial)

4. Cobro en campo
   - Cobrador genera `recibo_cobro` normal → estado `PENDIENTE_RENDICION`
   - El recibo descuenta el saldo de la factura como cualquier recibo

5. Rendición
   - El recibo entra al `CrearRendicionDialog` con todos los demás
   - Sin diferencia con flujos previos: la rendición no distingue origen contado vs crédito

Casos negativos
- Tildar "Cobro en ruta" sin cobrador → checkbox deshabilitado en UI, BadRequest si llega al backend
- Enviar `pagos[]` con `cobro_diferido=true` → backend los ignora (no se insertan)
- Factura crédito con `cobro_diferido=true` → la bandera se persiste pero no afecta la lógica:
  los flujos de crédito (cuentas_cobrar/cuotas) ya manejan el saldo. No es el caso de uso pero
  tampoco rompe nada.

Reportes
- En reporte de ventas, agregar filtro opcional `cobro_diferido` para distinguir CONTADO de
  mostrador vs CONTADO de cobrador en ruta.
- En cartera del cobrador, marcar visualmente las facturas `cobro_diferido` para que el
  cobrador entienda que es contado-en-ruta (no crédito).

Guard de visibilidad
- El checkbox "Cobro en ruta" SOLO aparece si la empresa tiene el módulo `RENDICIONES` activo
  (`hasModule("RENDICIONES")` en frontend). Si la empresa no tiene el módulo, la factura
  contado se comporta exactamente como hoy: se exige `pagos[]` y se registra en caja.

Actualización (revisión SIFEN)

SIFEN exige `gPaConEIni` con al menos un medio de pago en facturas CONTADO. Por lo tanto:

- `pagos[]` SÍ se exige y SÍ se inserta en `factura_forma_pagos` aunque `cobro_diferido=true`.
- Lo que cambia con `cobro_diferido=true` es que:
  1. No se actualiza `sesiones_caja` (efectivo/tarjeta/transferencia).
  2. No se crea `movimiento_cajas`.
  3. Se crea `cuentas_cobrar` con `saldo_pendiente = total`.
  4. Se crea `asignacion_facturas` con el `cobrador_id` de la cabecera.

Consecuencia: en reportes que lean directamente `factura_forma_pagos` la factura "muestra"
el pago declarado. La **fuente de verdad del cobro real** son los `recibos_cobro` que el
cobrador genera al recaudar, no las formas de pago de la factura.
