import { jsPDF } from 'jspdf' import autoTable from 'jspdf-autotable' import type { AplicacionPago, Pago, PlanPago } from '../../types' import { round2, saldoPlan } from '../cobranza' import { formatoFechaCorta, formatoFechaLarga, formatoMoneda } from '../formatters' import type { DocumentoPdf } from './estadoCuenta' const MARGEN = 14 /** Folio corto del recibo: primeros 8 caracteres del id de la aplicación, en mayúsculas */ export function folioRecibo(aplicacion: AplicacionPago): string { return aplicacion.id.slice(0, 8).toUpperCase() } /** * Saldos que imprime el recibo, tomados de los TOTALES del plan y del pago * (suma de montos − suma de pagado), no del historial de `AplicacionPago`: * el enganche/apartado histórico vive en `Pago.montoPagado` sin fila de * aplicación, así que acumular solo aplicaciones daba saldos falsos * (incluso negativos). El saldo nunca puede ser negativo porque un abono * jamás excede el restante de su pago. */ export function saldosDelRecibo(plan: PlanPago, pago: Pago): { restantePago: number; saldoPlan: number } { return { restantePago: round2(pago.monto - pago.montoPagado), saldoPlan: saldoPlan(plan), } } /** * Construye el recibo de pago de una aplicación (abono) ya registrada. * El restante del pago y el saldo del plan son los totales vigentes del plan * (ver `saldosDelRecibo`). */ export function crearDocumento( plan: PlanPago, pago: Pago, aplicacion: AplicacionPago, hoy: Date = new Date(), ): DocumentoPdf { const folio = folioRecibo(aplicacion) const doc = new jsPDF() let y = MARGEN const { restantePago, saldoPlan: saldo } = saldosDelRecibo(plan, pago) // 1. Encabezado doc.setFontSize(14) doc.setFont('helvetica', 'bold') doc.text('FRASA DESARROLLOS', 105, y, { align: 'center' }) y += 7 doc.setFontSize(11) doc.text('RECIBO DE PAGO', 105, y, { align: 'center' }) y += 6 doc.setFontSize(9) doc.setFont('helvetica', 'normal') doc.text(`Folio: ${folio} · Fecha de emisión: ${formatoFechaLarga(hoy)}`, 105, y, { align: 'center' }) y += 8 // 2. Datos del recibo autoTable(doc, { startY: y, margin: { left: MARGEN, right: MARGEN }, styles: { fontSize: 9 }, body: [ ['CLIENTE', plan.clienteNombre], ['UNIDAD', plan.unidadCodigo], ['PROYECTO', plan.proyecto], ['PAGO', `#${pago.numPago} (vencimiento ${formatoFechaCorta(pago.fechaVencimiento)})`], ['FECHA DE PAGO', formatoFechaCorta(aplicacion.fechaPago)], ['MONTO DEL ABONO', formatoMoneda(aplicacion.monto)], ['REFERENCIA', aplicacion.referencia || '—'], ['RESTANTE DEL PAGO', formatoMoneda(restantePago)], ['SALDO DEL PLAN (a la fecha de emisión)', formatoMoneda(saldo)], ], theme: 'plain', columnStyles: { 0: { fontStyle: 'bold', cellWidth: 70 }, 1: { halign: 'left' } }, }) y = (doc as jsPDF & { lastAutoTable: { finalY: number } }).lastAutoTable.finalY + 10 // 3. Leyenda informativa doc.setFontSize(8) doc.text( 'Recibo informativo del abono aplicado al plan de enganche. No sustituye la factura ni el comprobante fiscal correspondiente.', MARGEN, y, { maxWidth: 180 }, ) y += 24 // 4. Línea de firma doc.setFontSize(9) doc.text('_______________________________', 105, y, { align: 'center' }) y += 5 // Quien recibe el pago es FRASA (el cliente es quien paga) doc.text('Recibió — FRASA DESARROLLOS S.A.P.I. DE C.V.', 105, y, { align: 'center' }) return { doc, nombreArchivo: `recibo_${plan.unidadCodigo}_${folio}.pdf` } } /** Genera y descarga el recibo de pago en PDF */ export function descargarRecibo(plan: PlanPago, pago: Pago, aplicacion: AplicacionPago): void { const { doc, nombreArchivo } = crearDocumento(plan, pago, aplicacion) doc.save(nombreArchivo) }