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:
@@ -5,7 +5,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { DashboardShell } from '@/components/layouts/dashboard-shell';
|
||||
import { Card, CardContent, Button, SortableHeader } from '@horux/shared-ui';
|
||||
import { apiClient } from '@/lib/api/client';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import { formatCurrency, toCfdiDate } from '@/lib/utils';
|
||||
import { exportToExcel } from '@/lib/export-excel';
|
||||
import { useTableSort } from '@horux/shared-ui';
|
||||
import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal';
|
||||
@@ -27,8 +27,8 @@ const EXCEL_COLUMNS = [
|
||||
function prepareRows(data: any[]) {
|
||||
return data.map((c) => ({
|
||||
...c,
|
||||
_fechaEmision: new Date(c.fechaEmision).toLocaleDateString('es-MX'),
|
||||
_fechaCancelacion: c.fechaCancelacion ? new Date(c.fechaCancelacion).toLocaleDateString('es-MX') : '',
|
||||
_fechaEmision: toCfdiDate(c.fechaEmision).toLocaleDateString('es-MX'),
|
||||
_fechaCancelacion: c.fechaCancelacion ? toCfdiDate(c.fechaCancelacion).toLocaleDateString('es-MX') : '',
|
||||
_totalMxn: Number(c.totalMxn || 0),
|
||||
}));
|
||||
}
|
||||
@@ -50,8 +50,8 @@ export default function CancelacionesPeriodoAnteriorPage() {
|
||||
const { sortedData, toggleSort, getSortIndicator } = useTableSort<Cfdi, 'fecha' | 'cancelacion' | 'total'>(
|
||||
data,
|
||||
{
|
||||
fecha: (c) => new Date(c.fechaEmision).getTime(),
|
||||
cancelacion: (c: any) => c.fechaCancelacion ? new Date(c.fechaCancelacion).getTime() : 0,
|
||||
fecha: (c) => toCfdiDate(c.fechaEmision).getTime(),
|
||||
cancelacion: (c: any) => c.fechaCancelacion ? toCfdiDate(c.fechaCancelacion).getTime() : 0,
|
||||
total: (c) => Number(c.totalMxn || 0),
|
||||
},
|
||||
'cancelacion',
|
||||
@@ -97,8 +97,8 @@ export default function CancelacionesPeriodoAnteriorPage() {
|
||||
{(sortedData || []).map((cfdi: any) => (
|
||||
<tr key={cfdi.id} className="border-b hover:bg-muted/50">
|
||||
<td className="py-3 font-mono text-xs">{cfdi.uuid?.substring(0, 8)}</td>
|
||||
<td className="py-3">{new Date(cfdi.fechaEmision).toLocaleDateString('es-MX')}</td>
|
||||
<td className="py-3">{cfdi.fechaCancelacion ? new Date(cfdi.fechaCancelacion).toLocaleDateString('es-MX') : '-'}</td>
|
||||
<td className="py-3">{toCfdiDate(cfdi.fechaEmision).toLocaleDateString('es-MX')}</td>
|
||||
<td className="py-3">{cfdi.fechaCancelacion ? toCfdiDate(cfdi.fechaCancelacion).toLocaleDateString('es-MX') : '-'}</td>
|
||||
<td className="py-3 font-mono text-xs">{cfdi.rfcEmisor}</td>
|
||||
<td className="py-3 font-mono text-xs">{cfdi.rfcReceptor}</td>
|
||||
<td className="py-3 text-right font-medium">{formatCurrency(Number(cfdi.totalMxn))}</td>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { DashboardShell } from '@/components/layouts/dashboard-shell';
|
||||
import { Card, CardContent, Button, SortableHeader } from '@horux/shared-ui';
|
||||
import { apiClient } from '@/lib/api/client';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import { formatCurrency, toCfdiDate } from '@/lib/utils';
|
||||
import { exportToExcel } from '@/lib/export-excel';
|
||||
import { useTableSort } from '@horux/shared-ui';
|
||||
import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal';
|
||||
@@ -26,8 +26,8 @@ const EXCEL_COLUMNS = [
|
||||
function prepareRows(data: any[]) {
|
||||
return data.map((c) => ({
|
||||
...c,
|
||||
_fechaEmision: new Date(c.fechaEmision).toLocaleDateString('es-MX'),
|
||||
_fechaCancelacion: c.fechaCancelacion ? new Date(c.fechaCancelacion).toLocaleDateString('es-MX') : '',
|
||||
_fechaEmision: toCfdiDate(c.fechaEmision).toLocaleDateString('es-MX'),
|
||||
_fechaCancelacion: c.fechaCancelacion ? toCfdiDate(c.fechaCancelacion).toLocaleDateString('es-MX') : '',
|
||||
_totalMxn: Number(c.totalMxn || 0),
|
||||
}));
|
||||
}
|
||||
@@ -46,8 +46,8 @@ export default function CancelacionesPage() {
|
||||
const { sortedData, toggleSort, getSortIndicator } = useTableSort<Cfdi, 'fecha' | 'cancelacion' | 'total'>(
|
||||
data,
|
||||
{
|
||||
fecha: (c) => new Date(c.fechaEmision).getTime(),
|
||||
cancelacion: (c: any) => c.fechaCancelacion ? new Date(c.fechaCancelacion).getTime() : 0,
|
||||
fecha: (c) => toCfdiDate(c.fechaEmision).getTime(),
|
||||
cancelacion: (c: any) => c.fechaCancelacion ? toCfdiDate(c.fechaCancelacion).getTime() : 0,
|
||||
total: (c) => Number(c.totalMxn || 0),
|
||||
},
|
||||
'cancelacion',
|
||||
@@ -91,8 +91,8 @@ export default function CancelacionesPage() {
|
||||
{(sortedData || []).map((cfdi: any) => (
|
||||
<tr key={cfdi.id} className="border-b hover:bg-muted/50">
|
||||
<td className="py-3 font-mono text-xs">{cfdi.uuid?.substring(0, 8)}</td>
|
||||
<td className="py-3">{new Date(cfdi.fechaEmision).toLocaleDateString('es-MX')}</td>
|
||||
<td className="py-3">{cfdi.fechaCancelacion ? new Date(cfdi.fechaCancelacion).toLocaleDateString('es-MX') : '-'}</td>
|
||||
<td className="py-3">{toCfdiDate(cfdi.fechaEmision).toLocaleDateString('es-MX')}</td>
|
||||
<td className="py-3">{cfdi.fechaCancelacion ? toCfdiDate(cfdi.fechaCancelacion).toLocaleDateString('es-MX') : '-'}</td>
|
||||
<td className="py-3 font-mono text-xs">{cfdi.rfcEmisor}</td>
|
||||
<td className="py-3 font-mono text-xs">{cfdi.rfcReceptor}</td>
|
||||
<td className="py-3 text-right font-medium">{formatCurrency(Number(cfdi.totalMxn))}</td>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { DashboardShell } from '@/components/layouts/dashboard-shell';
|
||||
import { Card, CardContent, CardHeader, CardTitle, Button, SortableHeader, Input } from '@horux/shared-ui';
|
||||
import { apiClient } from '@/lib/api/client';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import { formatCurrency, toCfdiDate } from '@/lib/utils';
|
||||
import { exportToExcel } from '@/lib/export-excel';
|
||||
import { useTableSort } from '@horux/shared-ui';
|
||||
import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal';
|
||||
@@ -29,7 +29,7 @@ const EXCEL_COLUMNS = [
|
||||
function prepareRows(data: any[]) {
|
||||
return data.map((c) => ({
|
||||
...c,
|
||||
_fecha: new Date(c.fechaEmision).toLocaleDateString('es-MX'),
|
||||
_fecha: toCfdiDate(c.fechaEmision).toLocaleDateString('es-MX'),
|
||||
_totalMxn: Number(c.totalMxn || 0),
|
||||
regimenReceptor: c.regimenReceptor || c.regimenFiscalReceptor || '',
|
||||
}));
|
||||
@@ -91,10 +91,10 @@ export default function DiscrepanciaRegimenPage() {
|
||||
let filtered = data;
|
||||
|
||||
if (fechaDesde) {
|
||||
filtered = filtered.filter(c => c.fechaEmision >= fechaDesde);
|
||||
filtered = filtered.filter(c => toCfdiDate(c.fechaEmision).toISOString() >= fechaDesde);
|
||||
}
|
||||
if (fechaHasta) {
|
||||
filtered = filtered.filter(c => c.fechaEmision <= fechaHasta + 'T23:59:59');
|
||||
filtered = filtered.filter(c => toCfdiDate(c.fechaEmision).toISOString() <= fechaHasta + 'T23:59:59');
|
||||
}
|
||||
if (regimenFilter) {
|
||||
filtered = filtered.filter((c: any) => (c.regimenReceptor || c.regimenFiscalReceptor) === regimenFilter);
|
||||
@@ -106,7 +106,7 @@ export default function DiscrepanciaRegimenPage() {
|
||||
const { sortedData, toggleSort, getSortIndicator } = useTableSort<Cfdi, 'fecha' | 'total'>(
|
||||
visibleData,
|
||||
{
|
||||
fecha: (c) => new Date(c.fechaEmision).getTime(),
|
||||
fecha: (c) => toCfdiDate(c.fechaEmision).getTime(),
|
||||
total: (c) => Number(c.totalMxn || 0),
|
||||
},
|
||||
'fecha',
|
||||
@@ -311,7 +311,7 @@ export default function DiscrepanciaRegimenPage() {
|
||||
</button>
|
||||
</td>
|
||||
<td className="py-3 font-mono text-xs">{cfdi.uuid?.substring(0, 8)}</td>
|
||||
<td className="py-3">{new Date(cfdi.fechaEmision).toLocaleDateString('es-MX')}</td>
|
||||
<td className="py-3">{toCfdiDate(cfdi.fechaEmision).toLocaleDateString('es-MX')}</td>
|
||||
<td className="py-3 font-mono text-xs">{cfdi.rfcEmisor}</td>
|
||||
<td className="py-3 truncate max-w-[200px]">{cfdi.nombreEmisor}</td>
|
||||
<td className="py-3 font-mono font-bold text-destructive">{cfdi.regimenReceptor}</td>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { DashboardShell } from '@/components/layouts/dashboard-shell';
|
||||
import { Card, CardContent, Button, SortableHeader } from '@horux/shared-ui';
|
||||
import { apiClient } from '@/lib/api/client';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import { formatCurrency, toCfdiDate } from '@/lib/utils';
|
||||
import { exportToExcel } from '@/lib/export-excel';
|
||||
import { useTableSort } from '@horux/shared-ui';
|
||||
import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal';
|
||||
@@ -26,7 +26,7 @@ const EXCEL_COLUMNS = [
|
||||
function prepareRows(data: any[]) {
|
||||
return data.map((c) => ({
|
||||
...c,
|
||||
_fecha: new Date(c.fechaEmision).toLocaleDateString('es-MX'),
|
||||
_fecha: toCfdiDate(c.fechaEmision).toLocaleDateString('es-MX'),
|
||||
_totalMxn: Number(c.totalMxn || 0),
|
||||
}));
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export default function EfectivoPage() {
|
||||
const { sortedData, toggleSort, getSortIndicator } = useTableSort<Cfdi, 'fecha' | 'total'>(
|
||||
data,
|
||||
{
|
||||
fecha: (c) => new Date(c.fechaEmision).getTime(),
|
||||
fecha: (c) => toCfdiDate(c.fechaEmision).getTime(),
|
||||
total: (c) => Number(c.totalMxn || 0),
|
||||
},
|
||||
'fecha',
|
||||
@@ -89,7 +89,7 @@ export default function EfectivoPage() {
|
||||
{(sortedData || []).map((cfdi: any) => (
|
||||
<tr key={cfdi.id} className="border-b hover:bg-muted/50">
|
||||
<td className="py-3 font-mono text-xs">{cfdi.uuid?.substring(0, 8)}</td>
|
||||
<td className="py-3">{new Date(cfdi.fechaEmision).toLocaleDateString('es-MX')}</td>
|
||||
<td className="py-3">{toCfdiDate(cfdi.fechaEmision).toLocaleDateString('es-MX')}</td>
|
||||
<td className="py-3 font-mono text-xs">{cfdi.rfcEmisor}</td>
|
||||
<td className="py-3 truncate max-w-[200px]">{cfdi.nombreEmisor}</td>
|
||||
<td className="py-3 font-mono text-xs">{cfdi.rfcReceptor}</td>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { DashboardShell } from '@/components/layouts/dashboard-shell';
|
||||
import { Card, CardContent, CardHeader, CardTitle, Button, SortableHeader, Input } from '@horux/shared-ui';
|
||||
import { apiClient } from '@/lib/api/client';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import { formatCurrency, toCfdiDate } from '@/lib/utils';
|
||||
import { exportToExcel } from '@/lib/export-excel';
|
||||
import { useTableSort } from '@horux/shared-ui';
|
||||
import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal';
|
||||
@@ -30,7 +30,7 @@ const EXCEL_COLUMNS = [
|
||||
function prepareRows(data: any[]) {
|
||||
return data.map((c) => ({
|
||||
...c,
|
||||
_fecha: new Date(c.fechaEmision).toLocaleDateString('es-MX'),
|
||||
_fecha: toCfdiDate(c.fechaEmision).toLocaleDateString('es-MX'),
|
||||
_totalMxn: Number(c.totalMxn || 0),
|
||||
}));
|
||||
}
|
||||
@@ -83,8 +83,8 @@ export default function TipoRelacionSospechosaPage() {
|
||||
const visibleData = useMemo(() => {
|
||||
if (!data) return [];
|
||||
let filtered = data;
|
||||
if (fechaDesde) filtered = filtered.filter(c => c.fechaEmision >= fechaDesde);
|
||||
if (fechaHasta) filtered = filtered.filter(c => c.fechaEmision <= fechaHasta + 'T23:59:59');
|
||||
if (fechaDesde) filtered = filtered.filter(c => toCfdiDate(c.fechaEmision).toISOString() >= fechaDesde);
|
||||
if (fechaHasta) filtered = filtered.filter(c => toCfdiDate(c.fechaEmision).toISOString() <= fechaHasta + 'T23:59:59');
|
||||
if (tipoRelFilter) filtered = filtered.filter((c: any) => c.cfdiTipoRelacion === tipoRelFilter);
|
||||
return filtered;
|
||||
}, [data, fechaDesde, fechaHasta, tipoRelFilter]);
|
||||
@@ -92,7 +92,7 @@ export default function TipoRelacionSospechosaPage() {
|
||||
const { sortedData, toggleSort, getSortIndicator } = useTableSort<Cfdi, 'fecha' | 'total'>(
|
||||
visibleData,
|
||||
{
|
||||
fecha: (c) => new Date(c.fechaEmision).getTime(),
|
||||
fecha: (c) => toCfdiDate(c.fechaEmision).getTime(),
|
||||
total: (c) => Number(c.totalMxn || 0),
|
||||
},
|
||||
'fecha',
|
||||
@@ -296,7 +296,7 @@ export default function TipoRelacionSospechosaPage() {
|
||||
</button>
|
||||
</td>
|
||||
<td className="py-3 font-mono text-xs">{cfdi.uuid?.substring(0, 8)}</td>
|
||||
<td className="py-3">{new Date(cfdi.fechaEmision).toLocaleDateString('es-MX')}</td>
|
||||
<td className="py-3">{toCfdiDate(cfdi.fechaEmision).toLocaleDateString('es-MX')}</td>
|
||||
<td className="py-3 truncate max-w-[180px]">
|
||||
<div className="font-mono text-xs">{cfdi.rfcEmisor}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{cfdi.nombreEmisor}</div>
|
||||
|
||||
Reference in New Issue
Block a user