chore: catálogo obligaciones, cierre automático, fixes SAT y facturación
- Catálogo de obligaciones fiscales expandido a 30 entradas con campo requierePago. - Soporte de frecuencia cuatrimestral en obligaciones y declaraciones. - Automatización de cierre de obligaciones fiscales desde Documentos › Declaraciones. - Nuevas tablas obligacion_evidencias, obligacion_periodos estados y declaracion_obligaciones. - Nuevo servicio obligacion-evidencias.service.ts y endpoints REST. - Refactor de declaraciones.service.ts para vincular obligaciones y crear evidencias. - Notificaciones por email para evidencias de obligaciones. - Adjuntar PDFs en correo de declaración subida. - Fix drill-down de CFDIs: carga completa al visualizar. - Fix sincronización SAT: tipos P/N, UUID case-insensitive, no reutilizar requestId. - Fix suscripciones pending en /configuracion/planes-despacho. - Fix sugerencias de Clave Producto SAT: importar catálogo y robustecer autocomplete. - Quitar toggle manual de completado en Configuración › Obligaciones fiscales › Tareas. - Scripts de soporte para Demo Ventas y utilerías (change-user-email, resend-welcome, import-clave-prod-serv). - Documentación de cambios en docs/CAMBIOS-2026-05-04.md.
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
import type { Pool } from 'pg';
|
||||
import { OBLIGACIONES_CATALOGO, getRecomendaciones, type ObligacionFiscal } from '../constants/obligaciones-fiscales.js';
|
||||
|
||||
function requierePagoPorCatalogo(catalogoId: string | null): boolean {
|
||||
if (!catalogoId) return true;
|
||||
return OBLIGACIONES_CATALOGO.find((o) => o.id === catalogoId)?.requierePago ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyword-based matching: each catalog entry has discriminant keywords
|
||||
* that must ALL appear in the SAT description (normalized, lowercase, no accents).
|
||||
@@ -255,6 +260,7 @@ export async function initRecomendaciones(
|
||||
function inferirFrecuencia(vencimiento: string): string {
|
||||
const lower = vencimiento.toLowerCase();
|
||||
if (lower.includes('mensual') || lower.includes('mes')) return 'mensual';
|
||||
if (lower.includes('cuatrimest')) return 'cuatrimestral';
|
||||
if (lower.includes('bimest')) return 'bimestral';
|
||||
if (lower.includes('trimest')) return 'trimestral';
|
||||
if (lower.includes('anual') || lower.includes('ejercicio') || lower.includes('tres meses siguientes')) return 'anual';
|
||||
@@ -351,13 +357,22 @@ export async function getObligacionesPorPeriodo(
|
||||
|
||||
const [year, month] = periodo.split('-').map(Number);
|
||||
const currentPeriodo = new Date().toISOString().substring(0, 7);
|
||||
const results: Array<ObligacionContribuyente & { periodStatus: string; periodoAplica: string; declaracion: DeclaracionLink | null }> = [];
|
||||
const results: Array<ObligacionContribuyente & {
|
||||
periodStatus: string;
|
||||
periodoAplica: string;
|
||||
declaracion: DeclaracionLink | null;
|
||||
declaracionPresentada: boolean;
|
||||
pagoPresentado: boolean;
|
||||
requierePago: boolean;
|
||||
}> = [];
|
||||
|
||||
// Get all completion records + associated declaration info for this contribuyente
|
||||
const { rows: completions } = await pool.query<{
|
||||
obligacion_id: string;
|
||||
periodo: string;
|
||||
completada: boolean;
|
||||
declaracion_presentada: boolean;
|
||||
pago_presentado: boolean;
|
||||
declaracion_id: number | null;
|
||||
decl_año: number | null;
|
||||
decl_mes: number | null;
|
||||
@@ -365,6 +380,7 @@ export async function getObligacionesPorPeriodo(
|
||||
decl_pdf_filename: string | null;
|
||||
}>(`
|
||||
SELECT op.obligacion_id, op.periodo, op.completada,
|
||||
op.declaracion_presentada, op.pago_presentado,
|
||||
op.declaracion_id,
|
||||
dp.año AS decl_año,
|
||||
dp.mes AS decl_mes,
|
||||
@@ -377,10 +393,14 @@ export async function getObligacionesPorPeriodo(
|
||||
`, [contribuyenteId]);
|
||||
|
||||
const completionMap = new Map<string, boolean>();
|
||||
const declaracionPresentadaMap = new Map<string, boolean>();
|
||||
const pagoPresentadoMap = new Map<string, boolean>();
|
||||
const declaracionMap = new Map<string, DeclaracionLink | null>();
|
||||
for (const c of completions) {
|
||||
const key = `${c.obligacion_id}:${c.periodo}`;
|
||||
completionMap.set(key, c.completada);
|
||||
declaracionPresentadaMap.set(key, c.declaracion_presentada);
|
||||
pagoPresentadoMap.set(key, c.pago_presentado);
|
||||
if (c.declaracion_id && c.decl_año != null && c.decl_mes != null && c.decl_tipo) {
|
||||
declaracionMap.set(key, {
|
||||
id: c.declaracion_id,
|
||||
@@ -407,6 +427,9 @@ export async function getObligacionesPorPeriodo(
|
||||
periodStatus: isCompleted ? 'completada' : 'pendiente',
|
||||
periodoAplica: periodo,
|
||||
declaracion: declaracionMap.get(key) ?? null,
|
||||
declaracionPresentada: declaracionPresentadaMap.get(key) === true,
|
||||
pagoPresentado: pagoPresentadoMap.get(key) === true,
|
||||
requierePago: requierePagoPorCatalogo(ob.catalogoId),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -434,6 +457,9 @@ export async function getObligacionesPorPeriodo(
|
||||
periodStatus: 'atrasada',
|
||||
periodoAplica: pastPeriodo,
|
||||
declaracion: null,
|
||||
declaracionPresentada: declaracionPresentadaMap.get(pastKey) === true,
|
||||
pagoPresentado: pagoPresentadoMap.get(pastKey) === true,
|
||||
requierePago: requierePagoPorCatalogo(ob.catalogoId),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -448,7 +474,14 @@ export async function getObligacionesPorPeriodo(
|
||||
return a.nombre.localeCompare(b.nombre);
|
||||
});
|
||||
|
||||
return results as Array<ObligacionContribuyente & { periodStatus: 'pendiente' | 'completada' | 'atrasada'; periodoAplica: string; declaracion: DeclaracionLink | null }>;
|
||||
return results as Array<ObligacionContribuyente & {
|
||||
periodStatus: 'pendiente' | 'completada' | 'atrasada';
|
||||
periodoAplica: string;
|
||||
declaracion: DeclaracionLink | null;
|
||||
declaracionPresentada: boolean;
|
||||
pagoPresentado: boolean;
|
||||
requierePago: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
function appliesTo(frecuencia: string | null, periodo: string): boolean {
|
||||
@@ -457,6 +490,7 @@ function appliesTo(frecuencia: string | null, periodo: string): boolean {
|
||||
case 'mensual': return true;
|
||||
case 'bimestral': return month % 2 === 1; // Jan, Mar, May...
|
||||
case 'trimestral': return [1, 4, 7, 10].includes(month);
|
||||
case 'cuatrimestral': return [1, 5, 9].includes(month);
|
||||
case 'anual': return month === 3 || month === 4; // March (PM) or April (PF) — show in both
|
||||
case 'eventual': return false; // Don't auto-show
|
||||
default: return true;
|
||||
|
||||
Reference in New Issue
Block a user