feat(sat-sync): script para lanzar daily sync de contribuyentes pendientes
This commit is contained in:
142
apps/api/scripts/launch-pending-daily.ts
Normal file
142
apps/api/scripts/launch-pending-daily.ts
Normal file
@@ -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);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user