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