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); });