fix: facturapi onboarding, CSF scraper, SAT sync initial, doc notifications

- Auto-update fiscal data on org creation via updateOrgLegalOnCreate
- Add Carta Manifiesto embedded iframe in CSD config page
- Fix CSF scraper: 60s timeout + manual RFC fallback when SAT doesn't auto-populate
- Fix contribuyenteId propagation in constancia frontend hooks/API
- Fix needsInitialSync to check per-contribuyente, not just per-tenant
- Fix documento notifications for global_admin using viewingTenantId
- Extract CSF manually for Carlos Husberto Torres Romero
- Trigger initial SAT sync for Carlos Husberto Torres Romero
- Update org legal data in Facturapi for Carlos Husberto (tax_system 612 + address)

Files changed:
- apps/api/src/controllers/documentos.controller.ts
- apps/api/src/jobs/sat-sync.job.ts
- apps/api/src/services/constancia.service.ts
- apps/api/src/services/contribuyente-facturapi.service.ts
- apps/api/src/services/sat/sat-csf-login.ts
- apps/web/app/(dashboard)/configuracion/csd/page.tsx
- apps/web/lib/api/constancias.ts
- apps/web/lib/hooks/use-constancias.ts
- docs/sessions/2026-05-17-facturapi-csf-sync-notifications.md
This commit is contained in:
Horux Dev
2026-05-17 04:28:32 +00:00
parent 1c92b8eaf1
commit 44d7c796c9
9 changed files with 292 additions and 84 deletions

View File

@@ -43,16 +43,19 @@ async function getTenantsWithFiel(): Promise<string[]> {
}
/**
* Verifica si un tenant necesita sincronización inicial
* Verifica si un tenant (o un contribuyente específico dentro del tenant)
* necesita sincronización inicial.
*/
async function needsInitialSync(tenantId: string): Promise<boolean> {
const completedSync = await prisma.satSyncJob.findFirst({
where: {
tenantId,
type: 'initial',
status: 'completed',
},
});
async function needsInitialSync(tenantId: string, contribuyenteId?: string): Promise<boolean> {
const where: any = {
tenantId,
type: 'initial',
status: 'completed',
};
if (contribuyenteId) {
where.contribuyenteId = contribuyenteId;
}
const completedSync = await prisma.satSyncJob.findFirst({ where });
return !completedSync;
}
@@ -62,10 +65,6 @@ async function needsInitialSync(tenantId: string): Promise<boolean> {
*/
async function syncTenant(tenantId: string): Promise<void> {
try {
// Determinar tipo de sync
const needsInitial = await needsInitialSync(tenantId);
const syncType = needsInitial ? 'initial' : 'daily';
// Obtener contribuyentes del tenant
const tenant = await prisma.tenant.findUnique({
where: { id: tenantId },
@@ -81,6 +80,8 @@ async function syncTenant(tenantId: string): Promise<void> {
// Si no hay contribuyentes, sincronizar a nivel tenant (legacy Horux 360)
if (contribuyenteIds.length === 0) {
const needsInitial = await needsInitialSync(tenantId);
const syncType = needsInitial ? 'initial' : 'daily';
const status = await getSyncStatus(tenantId);
if (status.hasActiveSync) {
console.log(`[SAT Cron] Tenant ${tenantId} ya tiene sync activo, omitiendo`);
@@ -92,7 +93,7 @@ async function syncTenant(tenantId: string): Promise<void> {
return;
}
// Sincronizar cada contribuyente
// Sincronizar cada contribuyente (cada uno puede necesitar su propio initial)
for (const contribuyenteId of contribuyenteIds) {
try {
const status = await getSyncStatus(tenantId, contribuyenteId);
@@ -101,6 +102,8 @@ async function syncTenant(tenantId: string): Promise<void> {
continue;
}
const needsInitial = await needsInitialSync(tenantId, contribuyenteId);
const syncType = needsInitial ? 'initial' : 'daily';
console.log(`[SAT Cron] Iniciando sync ${syncType} para tenant ${tenantId} contribuyente ${contribuyenteId}`);
const jobId = await startSync(tenantId, syncType, undefined, undefined, contribuyenteId);
console.log(`[SAT Cron] Job ${jobId} iniciado para tenant ${tenantId} contribuyente ${contribuyenteId}`);
@@ -187,14 +190,6 @@ async function getTenantsConSatIncremental(): Promise<string[]> {
*/
async function incrementalSyncTenant(tenantId: string): Promise<void> {
try {
const completedInitial = await prisma.satSyncJob.findFirst({
where: { tenantId, type: 'initial', status: 'completed' },
});
if (!completedInitial) {
console.log(`[SAT Cron Inc] Tenant ${tenantId} sin sync inicial completado, omitiendo incremental`);
return;
}
// Obtener contribuyentes del tenant
const tenant = await prisma.tenant.findUnique({
where: { id: tenantId },
@@ -210,6 +205,13 @@ async function incrementalSyncTenant(tenantId: string): Promise<void> {
// Si no hay contribuyentes, sincronizar a nivel tenant (legacy)
if (contribuyenteIds.length === 0) {
const completedInitial = await prisma.satSyncJob.findFirst({
where: { tenantId, type: 'initial', status: 'completed' },
});
if (!completedInitial) {
console.log(`[SAT Cron Inc] Tenant ${tenantId} sin sync inicial completado, omitiendo incremental`);
return;
}
const status = await getSyncStatus(tenantId);
if (status.hasActiveSync) {
console.log(`[SAT Cron Inc] Tenant ${tenantId} con sync activo, omitiendo`);
@@ -221,9 +223,17 @@ async function incrementalSyncTenant(tenantId: string): Promise<void> {
return;
}
// Sincronizar cada contribuyente
// Sincronizar cada contribuyente solo si ya tiene su initial completado
for (const contribuyenteId of contribuyenteIds) {
try {
const hasInitial = await prisma.satSyncJob.findFirst({
where: { tenantId, contribuyenteId, type: 'initial', status: 'completed' },
});
if (!hasInitial) {
console.log(`[SAT Cron Inc] Tenant ${tenantId} contribuyente ${contribuyenteId} sin sync inicial, omitiendo incremental`);
continue;
}
const status = await getSyncStatus(tenantId, contribuyenteId);
if (status.hasActiveSync) {
console.log(`[SAT Cron Inc] Tenant ${tenantId} contribuyente ${contribuyenteId} con sync activo, omitiendo`);