- Agrega tabla sat_proxy_errors con proxy_usado, error_code, stage, message. - sat-client.service.ts expone proxyInfo usado en cada conexión SAT. - sat.service.ts registra errores de bloqueo/devolución del SAT en sat_proxy_errors y guarda proxyUsed en sat_sync_jobs. - Nuevo job sat-proxy-report.job.ts: cron 8 AM CDMX, envía email a ADMIN_EMAIL con errores de las últimas 24h agrupados por proxy/error_code. - Template de email sat-proxy-report.ts y método sendSatProxyReport. - Registra el cron en src/index.ts. - Actualiza docs/SAT-SYNC-IMPLEMENTATION.md.
107 lines
4.3 KiB
TypeScript
107 lines
4.3 KiB
TypeScript
/**
|
|
* Prueba del sharding: relanza el daily SOLO para los tenants del grupo 0
|
|
* (mismo hash que usa sat-sync.job.ts para la ventana de la 1 AM).
|
|
*
|
|
* Uso: node --import <tsx-loader> scripts/test-daily-group.ts [GRUPO]
|
|
*/
|
|
import { prisma } from '../src/config/database.js';
|
|
import { tenantDb } from '../src/config/database.js';
|
|
import { startSync, getSyncStatus } from '../src/services/sat/sat.service.js';
|
|
|
|
const DAILY_GROUPS = 5;
|
|
const GROUP = Number(process.argv[2] ?? 0);
|
|
|
|
function tenantGroup(tenantId: string): number {
|
|
let h = 0;
|
|
for (let i = 0; i < tenantId.length; i++) h = (h * 31 + tenantId.charCodeAt(i)) >>> 0;
|
|
return h % DAILY_GROUPS;
|
|
}
|
|
|
|
async function main() {
|
|
// Población daily real: tenants con job daily de hoy (fallaron a las 3 AM)
|
|
const jobs = await prisma.satSyncJob.findMany({
|
|
where: { type: 'daily', createdAt: { gte: new Date('2026-07-12T06:00:00Z') } },
|
|
select: { tenantId: true },
|
|
distinct: ['tenantId'],
|
|
});
|
|
const allTenants = jobs.map(j => j.tenantId);
|
|
const tenantArg = process.argv.find(a => a.startsWith('tenant:'));
|
|
const groupTenants = tenantArg
|
|
? [tenantArg.slice('tenant:'.length)]
|
|
: allTenants.filter(id => tenantGroup(id) === GROUP);
|
|
|
|
console.log(`[Test] ${allTenants.length} tenants daily de hoy; ${tenantArg ? 'tenant manual' : `grupo ${GROUP}`}: ${groupTenants.length} tenants`);
|
|
console.log(`[Test] Tenants: ${groupTenants.join(', ')}`);
|
|
|
|
const launchedJobIds: string[] = [];
|
|
|
|
for (const tenantId of groupTenants) {
|
|
try {
|
|
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);
|
|
}
|
|
|
|
if (contribuyenteIds.length === 0) {
|
|
const status = await getSyncStatus(tenantId);
|
|
if (status.hasActiveSync) {
|
|
console.log(`[Test] ${tenantId}: sync activo, omitido`);
|
|
continue;
|
|
}
|
|
const completed = await prisma.satSyncJob.findFirst({
|
|
where: { tenantId, type: 'initial', status: 'completed' },
|
|
});
|
|
const syncType = completed ? 'daily' : 'initial';
|
|
const jobId = await startSync(tenantId, syncType);
|
|
launchedJobIds.push(jobId);
|
|
console.log(`[Test] ${tenantId}: job ${jobId} (${syncType}, sin contribuyentes)`);
|
|
continue;
|
|
}
|
|
|
|
for (const contribuyenteId of contribuyenteIds) {
|
|
const status = await getSyncStatus(tenantId, contribuyenteId);
|
|
if (status.hasActiveSync) {
|
|
console.log(`[Test] ${tenantId}/${contribuyenteId}: sync activo, omitido`);
|
|
continue;
|
|
}
|
|
const completed = await prisma.satSyncJob.findFirst({
|
|
where: { tenantId, contribuyenteId, type: 'initial', status: 'completed' },
|
|
});
|
|
const syncType = completed ? 'daily' : 'initial';
|
|
const jobId = await startSync(tenantId, syncType, undefined, undefined, contribuyenteId);
|
|
launchedJobIds.push(jobId);
|
|
console.log(`[Test] ${tenantId}/${contribuyenteId}: job ${jobId} (${syncType})`);
|
|
}
|
|
} catch (error: any) {
|
|
console.error(`[Test] Error en ${tenantId}:`, error.message);
|
|
}
|
|
}
|
|
|
|
console.log('[Test] Lanzamiento del grupo terminado. Esperando a que los jobs terminen...');
|
|
|
|
// Esperar a que todos los jobs alcancen estado terminal (el sync corre en
|
|
// ESTE proceso — si salimos antes, matamos el trabajo en vuelo).
|
|
const maxWaitMs = 45 * 60 * 1000;
|
|
const start = Date.now();
|
|
while (Date.now() - start < maxWaitMs) {
|
|
const jobsStatus = await prisma.satSyncJob.findMany({
|
|
where: { id: { in: launchedJobIds } },
|
|
select: { id: true, status: true, progressPercent: true },
|
|
});
|
|
const pending = jobsStatus.filter(j => j.status === 'running' || j.status === 'queued');
|
|
console.log(`[Test] ${new Date().toISOString()} — ${pending.length}/${jobsStatus.length} en vuelo: ` +
|
|
jobsStatus.map(j => `${j.id.slice(0, 8)}=${j.status}(${j.progressPercent}%)`).join(', '));
|
|
if (pending.length === 0) break;
|
|
await new Promise(r => setTimeout(r, 30000));
|
|
}
|
|
}
|
|
|
|
main().then(() => process.exit(0)).catch(e => { console.error(e); process.exit(1); });
|