From 683b65b4322c97f57fcbf9a425cdf1436424d8ea Mon Sep 17 00:00:00 2001 From: Horux Dev Date: Wed, 26 Aug 2026 17:15:40 +0000 Subject: [PATCH] feat(sat-sync): script para lanzar daily sync de contribuyentes pendientes --- apps/api/scripts/launch-pending-daily.ts | 142 +++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 apps/api/scripts/launch-pending-daily.ts diff --git a/apps/api/scripts/launch-pending-daily.ts b/apps/api/scripts/launch-pending-daily.ts new file mode 100644 index 0000000..5934dbe --- /dev/null +++ b/apps/api/scripts/launch-pending-daily.ts @@ -0,0 +1,142 @@ +import { Client } from 'pg'; +import { startSync } from '../src/services/sat/sat.service.js'; + +const HORUX_CONN = { + host: 'localhost', + port: 5432, + user: 'postgres', + password: 'ZxHMrmnwanvLfLDdNJdRthFjWF2Lj1Rb', + database: 'horux360', +}; + +const BATCH_SIZE = 10; +const BATCH_DELAY_MS = 60_000; // 1 minuto entre lotes + +const todayStart = new Date('2026-08-26T06:00:00.000Z'); +const todayEnd = new Date('2026-08-27T06:00:00.000Z'); + +async function query(client: Client, sql: string, params: any[] = []) { + const res = await client.query(sql, params); + return res.rows; +} + +async function main() { + const client = new Client(HORUX_CONN); + await client.connect(); + + const excluded = new Set( + (process.env.SAT_SYNC_EXCLUDED_TENANTS || '') + .split(',') + .map(s => s.trim().toUpperCase()) + .filter(Boolean) + ); + + const tenants = await query(client, ` + SELECT t.id, t.rfc, t.nombre, t.database_name, + EXISTS ( + SELECT 1 FROM fiel_credentials fc + WHERE fc.tenant_id = t.id AND fc.is_active = true AND fc.valid_until > NOW() + ) AS has_legacy_fiel + FROM tenants t + WHERE t.active = true + ORDER BY t.rfc + `); + + const dailyJobs = await query(client, ` + SELECT tenant_id, contribuyente_id + FROM sat_sync_jobs + WHERE type = 'daily' + AND created_at >= $1 + AND created_at < $2 + `, [todayStart, todayEnd]); + + const dailySet = new Set( + dailyJobs.map((j: any) => (j.contribuyente_id ? `${j.tenant_id}:${j.contribuyente_id}` : `${j.tenant_id}:`)) + ); + + const pending: { tenantId: string; contribuyenteId: string; rfc: string; name: string }[] = []; + + for (const t of tenants) { + if (excluded.has(t.id) || excluded.has(t.rfc.toUpperCase())) continue; + if (!t.database_name) { + // Legacy tenant-wide: no hay contribuyentes en este proyecto actual + continue; + } + + const tenantClient = new Client({ ...HORUX_CONN, database: t.database_name }); + try { + await tenantClient.connect(); + const contribuyentes = await query(tenantClient, ` + SELECT c.entidad_id, c.rfc, eg.nombre AS razon_social, + EXISTS ( + SELECT 1 FROM fiel_contribuyente f + WHERE f.contribuyente_id = c.entidad_id + AND f.is_active = true AND f.valid_until > NOW() + ) AS has_fiel + FROM contribuyentes c + JOIN entidades_gestionadas eg ON eg.id = c.entidad_id + ORDER BY eg.nombre + `); + + for (const c of contribuyentes) { + const eligible = c.has_fiel || t.has_legacy_fiel; + const key = `${t.id}:${c.entidad_id}`; + if (eligible && !dailySet.has(key)) { + pending.push({ tenantId: t.id, contribuyenteId: c.entidad_id, rfc: c.rfc, name: c.razon_social }); + } + } + } finally { + await tenantClient.end().catch(() => {}); + } + } + + await client.end(); + + console.log(`[Launch Pending Daily] Total pendientes: ${pending.length}`); + + const started: string[] = []; + const failed: { item: typeof pending[0]; error: string }[] = []; + + for (let i = 0; i < pending.length; i += BATCH_SIZE) { + const batch = pending.slice(i, i + BATCH_SIZE); + console.log(`\n[Launch Pending Daily] Lote ${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil(pending.length / BATCH_SIZE)} (${batch.length} items)`); + + const results = await Promise.allSettled( + batch.map(async (item) => { + const jobId = await startSync(item.tenantId, 'daily', undefined, undefined, item.contribuyenteId); + console.log(` - ${item.name} (${item.rfc}) => job ${jobId}`); + return { item, jobId }; + }) + ); + + results.forEach((res, idx) => { + const item = batch[idx]; + if (res.status === 'fulfilled') { + started.push(`${item.name} (${item.rfc})`); + } else { + const error = res.reason?.message || String(res.reason); + console.error(` - ${item.name} (${item.rfc}) => ERROR: ${error}`); + failed.push({ item, error }); + } + }); + + if (i + BATCH_SIZE < pending.length) { + console.log(`[Launch Pending Daily] Esperando ${BATCH_DELAY_MS / 1000}s antes del siguiente lote...`); + await new Promise(resolve => setTimeout(resolve, BATCH_DELAY_MS)); + } + } + + console.log(`\n[Launch Pending Daily] Resumen`); + console.log(` Iniciados: ${started.length}`); + console.log(` Fallidos: ${failed.length}`); + if (failed.length > 0) { + for (const f of failed) { + console.log(` - ${f.item.name} (${f.item.rfc}): ${f.error}`); + } + } +} + +main().catch(err => { + console.error(err); + process.exit(1); +});