feat(cfdi): agrega C.P. receptor, regimen receptor, no_identificacion, tipo_relacion y CFDIs relacionados al visualizador

Backend:
- Migracion 044: codigo_postal_receptor VARCHAR(5) + indice
- sat-parser: extrae DomicilioFiscalReceptor
- sat.service: persiste codigo_postal_receptor en INSERT/UPDATE
- cfdi.service: incluye codigo_postal_receptor en CFDI_SELECT
- shared/types: codigoPostalReceptor en interfaz Cfdi

Frontend:
- cfdi-invoice: tarjeta receptor con C.P. y regimen (con descripciones)
- cfdi-invoice: seccion CFDI Relacionado (tipo + UUIDs)
- cfdi-invoice: columna No. Identificacion en tabla de conceptos
- cfdi-viewer-modal: mapea noIdentificacion desde DB y XML
This commit is contained in:
Horux Dev
2026-05-16 14:45:00 +00:00
parent 0bde43a309
commit bda0a4e212
8 changed files with 142 additions and 6 deletions

View File

@@ -10,6 +10,7 @@ interface CfdiConcepto {
importe: number;
claveUnidad?: string;
claveProdServ?: string;
noIdentificacion?: string;
}
interface CfdiInvoiceProps {
@@ -66,6 +67,38 @@ const metodoPagoLabels: Record<string, string> = {
PPD: 'Pago en parcialidades o diferido',
};
const regimenFiscalLabels: Record<string, string> = {
'601': 'General de Ley Personas Morales',
'603': 'Personas Morales con Fines no Lucrativos',
'605': 'Sueldos y Salarios e Ingresos Asimilados a Salarios',
'606': 'Arrendamiento',
'608': 'Demás ingresos',
'609': 'Consolidación',
'610': 'Residentes en el Extranjero sin Establecimiento Permanente en México',
'611': 'Ingresos por Dividendos (socios y accionistas)',
'612': 'Personas Físicas con Actividades Empresariales y Profesionales',
'614': 'Ingresos por intereses',
'615': 'Régimen de los ingresos por obtención de premios',
'616': 'Sin obligaciones fiscales',
'620': 'Sociedades Cooperativas de Producción que optan por diferir sus ingresos',
'621': 'Incorporación Fiscal',
'622': 'Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras',
'623': 'Opcional para Grupos de Sociedades',
'624': 'Coordinados',
'625': 'Régimen de las Actividades Empresariales con ingresos a través de Plataformas Tecnológicas',
'626': 'Régimen Simplificado de Confianza',
};
const tipoRelacionLabels: Record<string, string> = {
'01': 'Nota de crédito',
'02': 'Nota de débito',
'03': 'Devolución de mercancía',
'04': 'Sustitución de CFDI previo',
'05': 'Traslados de mercancias facturados previamente',
'06': 'Factura generada por traslados previos',
'07': 'CFDI por aplicación de anticipo',
};
const usoCfdiLabels: Record<string, string> = {
G01: 'Adquisición de mercancías',
G02: 'Devoluciones, descuentos o bonificaciones',
@@ -142,13 +175,26 @@ export const CfdiInvoice = forwardRef<HTMLDivElement, CfdiInvoiceProps>(
{/* Receptor */}
<div className="bg-gray-50 rounded-lg p-4 mb-5 border-l-4 border-blue-600">
<div className="flex items-start justify-between">
<div>
<div className="flex-1 min-w-0">
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Receptor</p>
<p className="text-lg font-semibold text-gray-800 mt-1">{cfdi.nombreReceptor}</p>
<p className="text-gray-600 text-sm">RFC: {cfdi.rfcReceptor}</p>
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 mt-1 text-sm text-gray-600">
<span>RFC: {cfdi.rfcReceptor}</span>
{cfdi.codigoPostalReceptor && (
<span>C.P.: {cfdi.codigoPostalReceptor}</span>
)}
{cfdi.regimenFiscalReceptor && (
<span>
Régimen: {cfdi.regimenFiscalReceptor}
{regimenFiscalLabels[cfdi.regimenFiscalReceptor]
? `${regimenFiscalLabels[cfdi.regimenFiscalReceptor]}`
: ''}
</span>
)}
</div>
</div>
{cfdi.usoCfdi && (
<div className="text-right">
<div className="text-right ml-4 flex-shrink-0">
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Uso CFDI</p>
<p className="text-sm font-medium text-gray-700 mt-1">
{cfdi.usoCfdi} - {usoCfdiLabels[cfdi.usoCfdi] || ''}
@@ -158,6 +204,41 @@ export const CfdiInvoice = forwardRef<HTMLDivElement, CfdiInvoiceProps>(
</div>
</div>
{/* CFDIs Relacionados */}
{(cfdi.cfdiTipoRelacion || cfdi.cfdisRelacionados) && (
<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">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
</svg>
<div className="flex-1 min-w-0">
<p className="text-xs font-semibold text-amber-800 uppercase tracking-wide">CFDI Relacionado</p>
{cfdi.cfdiTipoRelacion && (
<p className="text-sm text-gray-700 mt-1">
<span className="font-medium">Tipo de relación:</span>{' '}
{cfdi.cfdiTipoRelacion} {tipoRelacionLabels[cfdi.cfdiTipoRelacion] || 'Desconocido'}
</p>
)}
{cfdi.cfdisRelacionados && (
<div className="text-sm text-gray-700 mt-1">
<span className="font-medium">UUIDs relacionados:</span>
<div className="flex flex-wrap gap-2 mt-1">
{cfdi.cfdisRelacionados.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-amber-200 rounded text-xs font-mono text-gray-600"
>
{uuid}
</span>
))}
</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">
@@ -203,6 +284,7 @@ export const CfdiInvoice = forwardRef<HTMLDivElement, CfdiInvoiceProps>(
<thead>
<tr className="bg-gray-100">
<th className="text-left py-3 px-4 font-semibold text-gray-700">Descripción</th>
<th className="text-center py-3 px-3 font-semibold text-gray-700 w-24">No. Id.</th>
<th className="text-center py-3 px-3 font-semibold text-gray-700 w-20">Cant.</th>
<th className="text-right py-3 px-4 font-semibold text-gray-700 w-32">P. Unitario</th>
<th className="text-right py-3 px-4 font-semibold text-gray-700 w-32">Importe</th>
@@ -223,6 +305,9 @@ export const CfdiInvoice = forwardRef<HTMLDivElement, CfdiInvoiceProps>(
</p>
)}
</td>
<td className="text-center py-3 px-3 text-gray-700">
{concepto.noIdentificacion || <span className="text-gray-300"></span>}
</td>
<td className="text-center py-3 px-3 text-gray-700">{concepto.cantidad}</td>
<td className="text-right py-3 px-4 text-gray-700">
{formatCurrency(concepto.valorUnitario)}