feat: facturación primer pago, fixes SAT/MP, autocompletado RFCs/conceptos
Backend: - Notificación email al admin cuando llega primer pago aprobado (sin factura auto) - Endpoints GET /pagos-sin-factura y POST /emitir-factura-pago para admin global - Fix vinculación org Facturapi Horux 360 (69f23a5a242e0af47a41fa0d) - Fix webhook MP: validación defensiva de x-signature header - Fix autocompleto RFCs: eliminado filtro por contribuyenteId - Fix autocompleto conceptos: eliminado filtro por contribuyenteId - SAT fixes: anti-bot CSF scraper, request reuse, date range fix, stale job thresholds - SAT sync request reuse across jobs para evitar agotar cuota diaria - Typo fix MP_ACCESS_TOKEN en .env - Trial invitations system backend Frontend: - Nueva página /admin/facturas-pendientes con tabla y emisión manual - Métrica 'Facturas pendientes' en /clientes (clickable) - Navegación onboarding FIEL/CSD corregida - Sidebar themes sincronizados - Fix SAT portal migration scraper (NetIQ) - Trial invitation acceptance pages
This commit is contained in:
@@ -69,7 +69,11 @@ export async function consultarConstancia(tenantId: string): Promise<ConstanciaR
|
||||
// caso donde el click sintético no dispara el handler del SAT. Si algún
|
||||
// ambiente necesita ver el browser (debug), setear SAT_HEADLESS=false.
|
||||
const headless = process.env.SAT_HEADLESS !== 'false';
|
||||
const browser = await chromium.launch({ headless });
|
||||
const browser = await chromium.launch({
|
||||
headless,
|
||||
args: ['--disable-blink-features=AutomationControlled'],
|
||||
ignoreDefaultArgs: ['--enable-automation'],
|
||||
});
|
||||
try {
|
||||
const timeoutPromise = new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout: proceso de CSF excedió 3 minutos')), PROCESS_TIMEOUT),
|
||||
@@ -171,6 +175,28 @@ async function matchRegimenesToCatalogo(regimenesCsf: RegimenCsf[]): Promise<num
|
||||
return [...new Set(ids)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Límites de longitud en el schema Prisma de Tenant (defensivo para
|
||||
* evitar P2000 cuando el SAT devuelve valores más largos de lo esperado).
|
||||
*/
|
||||
const TENANT_FIELD_LIMITS: Record<string, number> = {
|
||||
codigoPostal: 5,
|
||||
calle: 255,
|
||||
numExterior: 20,
|
||||
numInterior: 20,
|
||||
colonia: 255,
|
||||
ciudad: 100,
|
||||
municipio: 100,
|
||||
estado: 100,
|
||||
telefono: 20,
|
||||
};
|
||||
|
||||
function truncateToLimit(key: string, value: string): string {
|
||||
const limit = TENANT_FIELD_LIMITS[key];
|
||||
if (!limit || value.length <= limit) return value;
|
||||
return value.slice(0, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aplica el domicilio + regímenes activos de la CSF al tenant. Idempotente:
|
||||
* se puede llamar N veces, el resultado final refleja el último CSF.
|
||||
@@ -183,7 +209,9 @@ export async function sincronizarDatosFiscales(
|
||||
const fields = domicilioToTenantFields(csf.domicilio);
|
||||
const updates: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(fields)) {
|
||||
if (v && v.trim().length > 0) updates[k] = v.trim();
|
||||
if (v && v.trim().length > 0) {
|
||||
updates[k] = truncateToLimit(k, v.trim());
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user