Files
HoruxDespachosNuevo/apps/api/scripts/test-daily-group.ts
Horux Dev 099f34c903 feat(sat): reporte diario de errores SAT por proxy
- 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.
2026-08-03 14:27:19 +00:00

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