Files
HoruxDespachosNuevo/apps/api/scripts/launch-pending-daily.ts

143 lines
4.5 KiB
TypeScript

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