import { describe, expect, it } from 'vitest' import type { PlanPago } from '../../types' import { generarCalendario, registrarAbono, saldoPlan } from '../cobranza' import { crearDocumento, folioRecibo, saldosDelRecibo } from './reciboAbono' function planConAbono(): PlanPago { const { mensualidad, pagos } = generarCalendario('2026-01-15', 100000, 3) const plan: PlanPago = { id: 'plan-fixture', ventaRef: 'VTA-2026-001', clienteNombre: 'Cliente Fixture', clienteTelefono: '55 0000 0000', unidadCodigo: 'U-101', proyecto: 'SENDA', fechaInicio: '2026-01-15', engancheTotal: 100000, numPagos: 3, mensualidad, status: 'ACTIVO', pagos, notas: [], createdAt: '2026-01-01T00:00:00.000Z', } return registrarAbono(plan, { pagoId: plan.pagos[0].id, fechaPago: '2026-01-15', monto: 5000, referencia: 'SPEI-001', }) } describe('reciboAbono PDF', () => { it('el folio son los primeros 8 chars del id de la aplicación en mayúsculas', () => { const plan = planConAbono() const aplicacion = plan.pagos[0].aplicaciones[0] expect(folioRecibo(aplicacion)).toBe(aplicacion.id.slice(0, 8).toUpperCase()) expect(folioRecibo({ ...aplicacion, id: 'abcdef12-3456-7890' })).toBe('ABCDEF12') }) it('genera el documento con nombre de archivo correcto', () => { const plan = planConAbono() const pago = plan.pagos[0] const aplicacion = pago.aplicaciones[0] const { doc, nombreArchivo } = crearDocumento(plan, pago, aplicacion, new Date('2026-02-20T12:00:00')) expect(nombreArchivo).toBe(`recibo_U-101_${folioRecibo(aplicacion)}.pdf`) expect(doc.getNumberOfPages()).toBeGreaterThanOrEqual(1) // smoke: el contenido interno es un PDF válido expect(doc.output('datauristring')).toContain('application/pdf') }) it('saldosDelRecibo usa los totales del plan, no solo los abonos registrados', () => { const plan = planConAbono() const pago = plan.pagos[0] const { restantePago, saldoPlan: saldo } = saldosDelRecibo(plan, pago) expect(restantePago).toBe(28333.33) expect(saldo).toBe(saldoPlan(plan)) expect(saldo).toBe(95000) }) it('el saldo incluye el apartado/enganche histórico sin fila de AplicacionPago', () => { // Caso real del maestro importado: el pago inicial (numPago 0) trae // montoPagado == monto pero sin aplicaciones. Antes el recibo restaba solo // aplicaciones y mostraba un saldo inflado (o negativo). const base = planConAbono() const pagoInicial = { id: 'pago-0', numPago: 0, fechaVencimiento: '2026-01-01', monto: 20000, montoPagado: 20000, aplicaciones: [], } const plan: PlanPago = { ...base, pagos: [pagoInicial, ...base.pagos], engancheTotal: base.engancheTotal, } const pago = plan.pagos[1] const aplicacion = pago.aplicaciones[0] const { saldoPlan: saldo, restantePago } = saldosDelRecibo(plan, pago) // saldo real = Σ monto − Σ montoPagado = 120000 − 25000 expect(saldo).toBe(95000) expect(restantePago).toBe(28333.33) expect(crearDocumento(plan, pago, aplicacion).doc.getNumberOfPages()).toBeGreaterThanOrEqual(1) }) it('un abono mayor al monto de su pago no produce saldo negativo (dato importado)', () => { // El importador viejo guardaba el valor crudo en AplicacionPago.monto // aunque montoPagado se topara con Math.min: el recibo restaba de más. const base = planConAbono() const pago = base.pagos[0] const inflado: PlanPago = { ...base, pagos: base.pagos.map((p) => p.id === pago.id ? { ...p, aplicaciones: [{ ...p.aplicaciones[0], monto: 500000 }] } : p, ), } expect(saldosDelRecibo(inflado, inflado.pagos[0]).saldoPlan).toBe(95000) }) })