fix(sat,conciliacion): propagar contribuyenteId en sync SAT y campos faltantes en visor de conciliacion
- sat-sync.job.ts: cron diario e incremental ahora iteran contribuyentes por tenant y pasan contribuyenteId a startSync(). Evita que CFDIs importados del SAT queden con contribuyente_id = NULL. - sat.service.ts: retryJob() ahora reintenta con job.contribuyenteId. - conciliacion.service.ts: agrega campos faltantes al SELECT de CFDIs: status, formaPago, serie, folio, usoCfdi, subtotal, descuento, moneda, tipoCambio, ivaTraslado, ivaRetencion, isrRetencion, fechaCertSat. Antes el visor mostraba 'CANCELADO' para todos los CFDIs (status era undefined) y faltaban datos de forma de pago, impuestos, serie/folio, etc. Refs: docs/CAMBIOS-2026-05-09.md secciones 6 y 7
This commit is contained in:
@@ -58,24 +58,56 @@ async function needsInitialSync(tenantId: string): Promise<boolean> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ejecuta sincronización para un tenant
|
||||
* Ejecuta sincronización para un tenant y sus contribuyentes
|
||||
*/
|
||||
async function syncTenant(tenantId: string): Promise<void> {
|
||||
try {
|
||||
// Verificar si hay sync activo
|
||||
const status = await getSyncStatus(tenantId);
|
||||
if (status.hasActiveSync) {
|
||||
console.log(`[SAT Cron] Tenant ${tenantId} ya tiene sync activo, omitiendo`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determinar tipo de sync
|
||||
const needsInitial = await needsInitialSync(tenantId);
|
||||
const syncType = needsInitial ? 'initial' : 'daily';
|
||||
|
||||
console.log(`[SAT Cron] Iniciando sync ${syncType} para tenant ${tenantId}`);
|
||||
const jobId = await startSync(tenantId, syncType);
|
||||
console.log(`[SAT Cron] Job ${jobId} iniciado para tenant ${tenantId}`);
|
||||
// Obtener contribuyentes del tenant
|
||||
const tenant = await prisma.tenant.findUnique({
|
||||
where: { id: tenantId },
|
||||
select: { databaseName: true },
|
||||
});
|
||||
|
||||
let contribuyenteIds: string[] = [];
|
||||
if (tenant?.databaseName) {
|
||||
const pool = await tenantDb.getPool(tenantId, tenant.databaseName);
|
||||
const { rows } = await pool.query('SELECT entidad_id FROM contribuyentes');
|
||||
contribuyenteIds = rows.map((r: any) => r.entidad_id);
|
||||
}
|
||||
|
||||
// Si no hay contribuyentes, sincronizar a nivel tenant (legacy Horux 360)
|
||||
if (contribuyenteIds.length === 0) {
|
||||
const status = await getSyncStatus(tenantId);
|
||||
if (status.hasActiveSync) {
|
||||
console.log(`[SAT Cron] Tenant ${tenantId} ya tiene sync activo, omitiendo`);
|
||||
return;
|
||||
}
|
||||
console.log(`[SAT Cron] Iniciando sync ${syncType} para tenant ${tenantId} (sin contribuyentes)`);
|
||||
const jobId = await startSync(tenantId, syncType);
|
||||
console.log(`[SAT Cron] Job ${jobId} iniciado para tenant ${tenantId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sincronizar cada contribuyente
|
||||
for (const contribuyenteId of contribuyenteIds) {
|
||||
try {
|
||||
const status = await getSyncStatus(tenantId, contribuyenteId);
|
||||
if (status.hasActiveSync) {
|
||||
console.log(`[SAT Cron] Tenant ${tenantId} contribuyente ${contribuyenteId} ya tiene sync activo, omitiendo`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`[SAT Cron] Iniciando sync ${syncType} para tenant ${tenantId} contribuyente ${contribuyenteId}`);
|
||||
const jobId = await startSync(tenantId, syncType, undefined, undefined, contribuyenteId);
|
||||
console.log(`[SAT Cron] Job ${jobId} iniciado para tenant ${tenantId} contribuyente ${contribuyenteId}`);
|
||||
} catch (error: any) {
|
||||
console.error(`[SAT Cron] Error sincronizando tenant ${tenantId} contribuyente ${contribuyenteId}:`, error.message);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`[SAT Cron] Error sincronizando tenant ${tenantId}:`, error.message);
|
||||
}
|
||||
@@ -150,19 +182,11 @@ async function getTenantsConSatIncremental(): Promise<string[]> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispara una sincronización incremental (ventana de 6 horas) para un tenant.
|
||||
* Si el tenant ya tiene un sync activo, omite para no solapar solicitudes al SAT.
|
||||
* Si el tenant nunca ha hecho `initial`, omite: el incremental no debe actuar
|
||||
* como primera descarga — la inicial requiere correrse aparte.
|
||||
* Dispara una sincronización incremental (ventana de 6 horas) para un tenant
|
||||
* y sus contribuyentes.
|
||||
*/
|
||||
async function incrementalSyncTenant(tenantId: string): Promise<void> {
|
||||
try {
|
||||
const status = await getSyncStatus(tenantId);
|
||||
if (status.hasActiveSync) {
|
||||
console.log(`[SAT Cron Inc] Tenant ${tenantId} con sync activo, omitiendo`);
|
||||
return;
|
||||
}
|
||||
|
||||
const completedInitial = await prisma.satSyncJob.findFirst({
|
||||
where: { tenantId, type: 'initial', status: 'completed' },
|
||||
});
|
||||
@@ -171,9 +195,48 @@ async function incrementalSyncTenant(tenantId: string): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[SAT Cron Inc] Iniciando incremental para tenant ${tenantId}`);
|
||||
const jobId = await startSync(tenantId, 'incremental');
|
||||
console.log(`[SAT Cron Inc] Job ${jobId} iniciado`);
|
||||
// Obtener contribuyentes del tenant
|
||||
const tenant = await prisma.tenant.findUnique({
|
||||
where: { id: tenantId },
|
||||
select: { databaseName: true },
|
||||
});
|
||||
|
||||
let contribuyenteIds: string[] = [];
|
||||
if (tenant?.databaseName) {
|
||||
const pool = await tenantDb.getPool(tenantId, tenant.databaseName);
|
||||
const { rows } = await pool.query('SELECT entidad_id FROM contribuyentes');
|
||||
contribuyenteIds = rows.map((r: any) => r.entidad_id);
|
||||
}
|
||||
|
||||
// Si no hay contribuyentes, sincronizar a nivel tenant (legacy)
|
||||
if (contribuyenteIds.length === 0) {
|
||||
const status = await getSyncStatus(tenantId);
|
||||
if (status.hasActiveSync) {
|
||||
console.log(`[SAT Cron Inc] Tenant ${tenantId} con sync activo, omitiendo`);
|
||||
return;
|
||||
}
|
||||
console.log(`[SAT Cron Inc] Iniciando incremental para tenant ${tenantId} (sin contribuyentes)`);
|
||||
const jobId = await startSync(tenantId, 'incremental');
|
||||
console.log(`[SAT Cron Inc] Job ${jobId} iniciado`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sincronizar cada contribuyente
|
||||
for (const contribuyenteId of contribuyenteIds) {
|
||||
try {
|
||||
const status = await getSyncStatus(tenantId, contribuyenteId);
|
||||
if (status.hasActiveSync) {
|
||||
console.log(`[SAT Cron Inc] Tenant ${tenantId} contribuyente ${contribuyenteId} con sync activo, omitiendo`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`[SAT Cron Inc] Iniciando incremental para tenant ${tenantId} contribuyente ${contribuyenteId}`);
|
||||
const jobId = await startSync(tenantId, 'incremental', undefined, undefined, contribuyenteId);
|
||||
console.log(`[SAT Cron Inc] Job ${jobId} iniciado`);
|
||||
} catch (error: any) {
|
||||
console.error(`[SAT Cron Inc] Error para tenant ${tenantId} contribuyente ${contribuyenteId}:`, error.message);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`[SAT Cron Inc] Error para tenant ${tenantId}:`, error.message);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ export interface ConciliacionCfdi {
|
||||
id: number;
|
||||
uuid: string;
|
||||
type: string;
|
||||
serie: string | null;
|
||||
folio: string | null;
|
||||
fechaEmision: string;
|
||||
rfcEmisor: string;
|
||||
nombreEmisor: string;
|
||||
@@ -13,7 +15,19 @@ export interface ConciliacionCfdi {
|
||||
nombreReceptor: string;
|
||||
total: number;
|
||||
totalMxn: number;
|
||||
subtotal: number;
|
||||
descuento: number;
|
||||
moneda: string;
|
||||
tipoCambio: number;
|
||||
tipoComprobante: string | null;
|
||||
metodoPago: string | null;
|
||||
formaPago: string | null;
|
||||
usoCfdi: string | null;
|
||||
status: string | null;
|
||||
fechaCertSat: string | null;
|
||||
ivaTraslado: number;
|
||||
ivaRetencion: number;
|
||||
isrRetencion: number;
|
||||
conciliado: string | null;
|
||||
idConciliacion: number | null;
|
||||
conciliacion: {
|
||||
@@ -78,13 +92,23 @@ export async function getCfdisConConciliacion(
|
||||
const { rows } = await pool.query(`
|
||||
SELECT
|
||||
c.id, c.uuid, c.type,
|
||||
c.serie, c.folio,
|
||||
c.fecha_emision as "fechaEmision",
|
||||
c.rfc_emisor as "rfcEmisor", c.nombre_emisor as "nombreEmisor",
|
||||
c.rfc_receptor as "rfcReceptor", c.nombre_receptor as "nombreReceptor",
|
||||
c.total, c.total_mxn as "totalMxn",
|
||||
c.subtotal, c.descuento,
|
||||
c.moneda, c.tipo_cambio as "tipoCambio",
|
||||
c.tipo_comprobante as "tipoComprobante",
|
||||
c.monto_pago_mxn as "montoPagoMxn",
|
||||
c.metodo_pago as "metodoPago",
|
||||
c.forma_pago as "formaPago",
|
||||
c.uso_cfdi as "usoCfdi",
|
||||
c.status,
|
||||
c.fecha_cert_sat as "fechaCertSat",
|
||||
c.iva_traslado as "ivaTraslado",
|
||||
c.iva_retencion as "ivaRetencion",
|
||||
c.isr_retencion as "isrRetencion",
|
||||
c.conciliado,
|
||||
c.id_conciliacion as "idConciliacion",
|
||||
con.id as "conId",
|
||||
@@ -102,6 +126,8 @@ export async function getCfdisConConciliacion(
|
||||
id: r.id,
|
||||
uuid: r.uuid,
|
||||
type: r.type,
|
||||
serie: r.serie,
|
||||
folio: r.folio,
|
||||
fechaEmision: r.fechaEmision,
|
||||
rfcEmisor: r.rfcEmisor,
|
||||
nombreEmisor: r.nombreEmisor,
|
||||
@@ -109,6 +135,10 @@ export async function getCfdisConConciliacion(
|
||||
nombreReceptor: r.nombreReceptor,
|
||||
total: Number(r.total),
|
||||
totalMxn: Number(r.totalMxn),
|
||||
subtotal: Number(r.subtotal || 0),
|
||||
descuento: Number(r.descuento || 0),
|
||||
moneda: r.moneda || 'MXN',
|
||||
tipoCambio: Number(r.tipoCambio || 1),
|
||||
tipoComprobante: r.tipoComprobante,
|
||||
montoPagoMxn: Number(r.montoPagoMxn || 0),
|
||||
// P usa monto_pago_mxn, PPD conciliada no suma (evitar duplicar con su P), resto usa total_mxn
|
||||
@@ -116,6 +146,13 @@ export async function getCfdisConConciliacion(
|
||||
? Number(r.montoPagoMxn || 0)
|
||||
: (r.metodoPago === 'PPD' && r.conciliado === 'true') ? 0 : Number(r.totalMxn || 0),
|
||||
metodoPago: r.metodoPago,
|
||||
formaPago: r.formaPago,
|
||||
usoCfdi: r.usoCfdi,
|
||||
status: r.status,
|
||||
fechaCertSat: r.fechaCertSat,
|
||||
ivaTraslado: Number(r.ivaTraslado || 0),
|
||||
ivaRetencion: Number(r.ivaRetencion || 0),
|
||||
isrRetencion: Number(r.isrRetencion || 0),
|
||||
conciliado: r.conciliado,
|
||||
idConciliacion: r.idConciliacion,
|
||||
conciliacion: r.conId ? {
|
||||
|
||||
@@ -1511,5 +1511,5 @@ export async function retryJob(jobId: string): Promise<string> {
|
||||
throw new Error('Solo se pueden reintentar jobs fallidos');
|
||||
}
|
||||
|
||||
return startSync(job.tenantId, job.type, job.dateFrom, job.dateTo);
|
||||
return startSync(job.tenantId, job.type, job.dateFrom, job.dateTo, job.contribuyenteId ?? undefined);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user