feat(sat): factura global + fecha_efectiva, fallback tenant-contribuyente, fix anio_global typo

Factura Global & fecha_efectiva:
- Migracion 045_factura_global.sql: periodicidad, meses_global, año_global, fecha_efectiva
- sat-parser.service.ts: extrae InformacionGlobal del XML
- sat.service.ts: calcFechaEfectiva con soporte bimestral (periodicidad 05)
- metricas-compute, dashboard, impuestos, cfdi, export, conciliacion, alertas:
  reemplaza fecha_emision-1h por COALESCE(fecha_efectiva, fecha_emision-1h)
- Script recalc-metricas.ts para recalculo manual

Fallback datos fiscales tenant → contribuyente:
- contribuyente.service.ts: fetchTenantFiscalData + mergeContribuyenteWithTenant
  rellena regimenFiscal, codigoPostal y domicilio cuando el contribuyente
  tiene el mismo RFC que el tenant y sus campos estan vacios
- contribuyente.controller.ts y contribuyente-config.controller.ts:
  pasan req.user!.tenantId al servicio

Fix critico SAT sync:
- sat.service.ts: anio_global → año_global en INSERT/UPDATE de CFDIs
  (la migracion creo 'año_global' con tilde; el codigo usaba 'anio_global',
   causando fallo en 100% de inserciones de CFDI)
- determineChunkMonths: salta sondeo si existe job previo con requestIds
- MAX_POLL_ATTEMPTS: 45 → 500 (~8h) para syncs iniciales grandes

Docs:
- docs/sessions/2026-05-22-factura-global-contribuyente-fallback.md
This commit is contained in:
Horux Dev
2026-05-22 15:52:10 +00:00
parent ba6004ebd6
commit 46846200da
33 changed files with 1128 additions and 171 deletions

View File

@@ -17,6 +17,7 @@ import type { InvoiceData, InvoiceLineItem, RfcSearchResult, CfdiPpdPendiente, C
import { searchRfcs, getCfdisPpd, searchConceptos, getCfdisRelacionables, downloadPdf, downloadXml } from '@/lib/api/facturacion';
import { Plus, Trash2, Send, Receipt, Search, Check, X, FileSearch, AlertTriangle } from 'lucide-react';
import { cn } from '@horux/shared-ui';
import { toCfdiDate } from '@/lib/utils';
interface TaxLine {
category: 'traslado' | 'retencion';
@@ -843,7 +844,7 @@ export default function FacturacionPage() {
<p className="text-xs text-muted-foreground mt-0.5">
{cp.tipoCfdi === 'EMITIDO' ? cp.nombreReceptor : cp.nombreEmisor}
{' · '}
{new Date(cp.fechaEmision).toLocaleDateString('es-MX')}
{toCfdiDate(cp.fechaEmision).toLocaleDateString('es-MX')}
</p>
</div>
<div className="text-right flex-shrink-0 ml-3">
@@ -1237,7 +1238,7 @@ export default function FacturacionPage() {
<span className="font-bold">${Number(c.saldoPendiente).toLocaleString('es-MX', { minimumFractionDigits: 2 })}</span>
</div>
<div className="text-xs text-muted-foreground mt-0.5">
{new Date(c.fechaEmision).toLocaleDateString('es-MX')} · Total: ${Number(c.totalMxn).toLocaleString('es-MX', { minimumFractionDigits: 2 })}
{toCfdiDate(c.fechaEmision).toLocaleDateString('es-MX')} · Total: ${Number(c.totalMxn).toLocaleString('es-MX', { minimumFractionDigits: 2 })}
</div>
</button>
))}
@@ -1540,7 +1541,7 @@ export default function FacturacionPage() {
<span className="px-1.5 py-0.5 rounded bg-muted">{c.tipoComprobante}</span>
{c.serie || c.folio ? <span>{c.serie || ''}{c.folio ? `-${c.folio}` : ''}</span> : null}
<span>${Number(c.totalMxn).toLocaleString('es-MX', { minimumFractionDigits: 2 })}</span>
<span>{new Date(c.fechaEmision).toLocaleDateString('es-MX')}</span>
<span>{toCfdiDate(c.fechaEmision).toLocaleDateString('es-MX')}</span>
</div>
</button>
))}