feat(cfdi-viewer): mostrar complemento de pago en facturas tipo P

Para CFDIs tipo P (Pago) el visor ahora muestra:
- Monto pagado
- Fecha de pago
- Número de parcialidad
- UUID relacionado (factura pagada)
- Saldo insoluto
- Impuestos del pago (ISR/IVA/IEPS retenciones y traslados)

Además se ocultan para tipo P:
- Tabla de conceptos (dummy)
- Bloque de totales tradicional (subtotal/IVA/total)
- Sección CFDIs relacionados (reemplazada por UUID pagado)

El complemento de pago se renderiza en una card verde destacada.
This commit is contained in:
Horux Dev
2026-05-19 16:15:41 +00:00
parent 0b704e0e27
commit d0174fed3e

View File

@@ -204,8 +204,8 @@ export const CfdiInvoice = forwardRef<HTMLDivElement, CfdiInvoiceProps>(
</div>
</div>
{/* CFDIs Relacionados */}
{(cfdi.cfdiTipoRelacion || cfdi.cfdisRelacionados) && (
{/* CFDIs Relacionados (no-P) */}
{(cfdi.cfdiTipoRelacion || cfdi.cfdisRelacionados) && cfdi.tipoComprobante !== 'P' && (
<div className="bg-amber-50 rounded-lg p-3 mb-5 border border-amber-200">
<div className="flex items-start gap-3">
<svg className="w-5 h-5 text-amber-600 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -239,6 +239,82 @@ export const CfdiInvoice = forwardRef<HTMLDivElement, CfdiInvoiceProps>(
</div>
)}
{/* Complemento de Pago (solo tipo P) */}
{cfdi.tipoComprobante === 'P' && (
<div className="bg-emerald-50 rounded-lg p-4 mb-5 border border-emerald-200">
<div className="flex items-start gap-3">
<svg className="w-5 h-5 text-emerald-600 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-6a1 1 0 11-2 0 1 1 0 012 0z" />
</svg>
<div className="flex-1 min-w-0 space-y-2">
<p className="text-xs font-semibold text-emerald-800 uppercase tracking-wide">Complemento de Pago</p>
{cfdi.montoPago > 0 && (
<div className="flex justify-between items-center">
<span className="text-sm text-gray-700">Monto pagado</span>
<span className="text-sm font-bold text-emerald-700">{formatCurrency(cfdi.montoPago)}</span>
</div>
)}
{cfdi.fechaPagoP && (
<div className="flex justify-between items-center">
<span className="text-sm text-gray-700">Fecha de pago</span>
<span className="text-sm font-medium text-gray-800">{formatDate(cfdi.fechaPagoP)}</span>
</div>
)}
{cfdi.numParcialidad && (
<div className="flex justify-between items-center">
<span className="text-sm text-gray-700">Parcialidad</span>
<span className="text-sm font-medium text-gray-800">{cfdi.numParcialidad}</span>
</div>
)}
{cfdi.uuidRelacionado && (
<div className="pt-1">
<span className="text-sm text-gray-700 font-medium">UUID relacionado (factura pagada):</span>
<div className="flex flex-wrap gap-2 mt-1">
{cfdi.uuidRelacionado.split('|').map((uuid) => uuid.trim()).filter(Boolean).map((uuid, idx) => (
<span
key={idx}
className="inline-block px-2 py-0.5 bg-white border border-emerald-200 rounded text-xs font-mono text-gray-600"
>
{uuid}
</span>
))}
</div>
</div>
)}
{cfdi.saldoInsoluto && (
<div className="flex justify-between items-center">
<span className="text-sm text-gray-700">Saldo insoluto</span>
<span className="text-sm font-medium text-gray-800">{cfdi.saldoInsoluto}</span>
</div>
)}
{/* Impuestos del pago */}
{(cfdi.isrRetencionPago > 0 || cfdi.ivaTrasladoPago > 0 || cfdi.ivaRetencionPago > 0 || cfdi.iepsTrasladoPago > 0 || cfdi.iepsRetencionPago > 0) && (
<div className="pt-2 border-t border-emerald-100">
<p className="text-xs font-medium text-emerald-700 mb-1">Impuestos del pago</p>
<div className="grid grid-cols-2 gap-x-4 gap-y-1 text-sm">
{cfdi.isrRetencionPago > 0 && (
<div className="flex justify-between"><span className="text-gray-600">ISR Ret.</span><span className="text-red-600">-{formatCurrency(cfdi.isrRetencionPago)}</span></div>
)}
{cfdi.ivaTrasladoPago > 0 && (
<div className="flex justify-between"><span className="text-gray-600">IVA Tras.</span><span>{formatCurrency(cfdi.ivaTrasladoPago)}</span></div>
)}
{cfdi.ivaRetencionPago > 0 && (
<div className="flex justify-between"><span className="text-gray-600">IVA Ret.</span><span className="text-red-600">-{formatCurrency(cfdi.ivaRetencionPago)}</span></div>
)}
{cfdi.iepsTrasladoPago > 0 && (
<div className="flex justify-between"><span className="text-gray-600">IEPS Tras.</span><span>{formatCurrency(cfdi.iepsTrasladoPago)}</span></div>
)}
{cfdi.iepsRetencionPago > 0 && (
<div className="flex justify-between"><span className="text-gray-600">IEPS Ret.</span><span className="text-red-600">-{formatCurrency(cfdi.iepsRetencionPago)}</span></div>
)}
</div>
</div>
)}
</div>
</div>
</div>
)}
{/* Datos del Comprobante */}
<div className="grid grid-cols-4 gap-3 mb-5">
<div className="bg-gray-50 rounded-lg p-3 text-center">
@@ -272,8 +348,8 @@ export const CfdiInvoice = forwardRef<HTMLDivElement, CfdiInvoiceProps>(
</div>
</div>
{/* Conceptos */}
{conceptos && conceptos.length > 0 && (
{/* Conceptos — ocultar para tipo P (concepto dummy) */}
{conceptos && conceptos.length > 0 && cfdi.tipoComprobante !== 'P' && (
<div className="mb-5">
<h3 className="text-xs text-gray-500 uppercase tracking-wide font-medium mb-2 flex items-center gap-2">
<span className="w-1 h-4 bg-blue-600 rounded-full"></span>
@@ -323,45 +399,47 @@ export const CfdiInvoice = forwardRef<HTMLDivElement, CfdiInvoiceProps>(
</div>
)}
{/* Totales */}
<div className="flex justify-end mb-5">
<div className="w-80 bg-gray-50 rounded-lg overflow-hidden">
<div className="divide-y divide-gray-200">
<div className="flex justify-between py-2.5 px-4">
<span className="text-gray-600">Subtotal</span>
<span className="font-medium">{formatCurrency(cfdi.subtotal)}</span>
{/* Totales — ocultar bloque tradicional para tipo P */}
{cfdi.tipoComprobante !== 'P' && (
<div className="flex justify-end mb-5">
<div className="w-80 bg-gray-50 rounded-lg overflow-hidden">
<div className="divide-y divide-gray-200">
<div className="flex justify-between py-2.5 px-4">
<span className="text-gray-600">Subtotal</span>
<span className="font-medium">{formatCurrency(cfdi.subtotal)}</span>
</div>
{cfdi.descuento > 0 && (
<div className="flex justify-between py-2.5 px-4">
<span className="text-gray-600">Descuento</span>
<span className="font-medium text-red-600">-{formatCurrency(cfdi.descuento)}</span>
</div>
)}
{cfdi.ivaTraslado > 0 && (
<div className="flex justify-between py-2.5 px-4">
<span className="text-gray-600">IVA (16%)</span>
<span className="font-medium">{formatCurrency(cfdi.ivaTraslado)}</span>
</div>
)}
{cfdi.ivaRetencion > 0 && (
<div className="flex justify-between py-2.5 px-4">
<span className="text-gray-600">IVA Retenido</span>
<span className="font-medium text-red-600">-{formatCurrency(cfdi.ivaRetencion)}</span>
</div>
)}
{cfdi.isrRetencion > 0 && (
<div className="flex justify-between py-2.5 px-4">
<span className="text-gray-600">ISR Retenido</span>
<span className="font-medium text-red-600">-{formatCurrency(cfdi.isrRetencion)}</span>
</div>
)}
</div>
<div className="bg-blue-700 text-white py-3 px-4 flex justify-between items-center">
<span className="font-semibold">TOTAL</span>
<span className="text-xl font-bold">{formatCurrency(cfdi.total)}</span>
</div>
{cfdi.descuento > 0 && (
<div className="flex justify-between py-2.5 px-4">
<span className="text-gray-600">Descuento</span>
<span className="font-medium text-red-600">-{formatCurrency(cfdi.descuento)}</span>
</div>
)}
{cfdi.ivaTraslado > 0 && (
<div className="flex justify-between py-2.5 px-4">
<span className="text-gray-600">IVA (16%)</span>
<span className="font-medium">{formatCurrency(cfdi.ivaTraslado)}</span>
</div>
)}
{cfdi.ivaRetencion > 0 && (
<div className="flex justify-between py-2.5 px-4">
<span className="text-gray-600">IVA Retenido</span>
<span className="font-medium text-red-600">-{formatCurrency(cfdi.ivaRetencion)}</span>
</div>
)}
{cfdi.isrRetencion > 0 && (
<div className="flex justify-between py-2.5 px-4">
<span className="text-gray-600">ISR Retenido</span>
<span className="font-medium text-red-600">-{formatCurrency(cfdi.isrRetencion)}</span>
</div>
)}
</div>
<div className="bg-blue-700 text-white py-3 px-4 flex justify-between items-center">
<span className="font-semibold">TOTAL</span>
<span className="text-xl font-bold">{formatCurrency(cfdi.total)}</span>
</div>
</div>
</div>
)}
{/* Timbre Fiscal Digital */}
<div className="bg-gradient-to-r from-gray-100 to-gray-50 rounded-lg p-4 border border-gray-200">