Initial commit - Horux Despachos NL

This commit is contained in:
2026-05-03 16:47:53 -06:00
commit b00b677c54
647 changed files with 133843 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
import type { Pool } from 'pg';
export interface MetricaMensual {
anio: number;
mes: number;
regimenFiscal: string | null;
ivaTrasladado16: number;
ivaTrasladado8: number;
ivaTrasladado0: number;
ivaTrasladadoExento: number;
ivaTrasladadoTotal: number;
ivaAcreditable: number;
ivaRetenidoCobrado: number;
ivaRetenidoPagado: number;
ivaResultado: number;
ivaAFavorMes: number;
isrIngresosBrutos: number;
isrDeduccionesAutoriz: number;
isrBase: number;
isrCausado: number;
isrRetenido: number;
isrAPagar: number;
cfdisEmitidosCount: number;
cfdisRecibidosCount: number;
cfdisCanceladosCount: number;
ingresosDevengados: number;
ingresosCobrados: number;
egresosDevengados: number;
egresosPagados: number;
utilidadDevengada: number;
utilidadRealizada: number;
flujoEntradas: number;
flujoSalidas: number;
flujoNeto: number;
cxcSaldoFinal: number;
cxpSaldoFinal: number;
// Surface-only — totales de notas de crédito tipo E PUE en el mes/régimen.
// No participan en cálculos de ingresos/deducciones (ya no se restan); se
// persisten para visibilidad y BI directo sin recomputar.
ncsEmitidas: number;
ncsRecibidas: number;
// Art. 27 fracción III LISR — facturas recibidas pagadas en efectivo > $2,000.
// Surface-only, no afecta deducciones (que ya las EXCLUYE).
gastosNoDeduciblesEfectivo: number;
cerrado: boolean;
computedAt: string;
}
export async function getMetricasMensuales(
pool: Pool,
contribuyenteId: string,
anio: number,
regimenFiscal?: string
): Promise<MetricaMensual[]> {
const currentYear = new Date().getFullYear();
if (anio < currentYear) {
// COLD: read pre-computed
const params: unknown[] = [contribuyenteId, anio];
let query = `
SELECT
anio, mes,
regimen_fiscal AS "regimenFiscal",
iva_trasladado_16 AS "ivaTrasladado16",
iva_trasladado_8 AS "ivaTrasladado8",
iva_trasladado_0 AS "ivaTrasladado0",
iva_trasladado_exento AS "ivaTrasladadoExento",
iva_trasladado_total AS "ivaTrasladadoTotal",
iva_acreditable AS "ivaAcreditable",
iva_retenido_cobrado AS "ivaRetenidoCobrado",
iva_retenido_pagado AS "ivaRetenidoPagado",
iva_resultado AS "ivaResultado",
iva_a_favor_mes AS "ivaAFavorMes",
isr_ingresos_brutos AS "isrIngresosBrutos",
isr_deducciones_autoriz AS "isrDeduccionesAutoriz",
isr_base AS "isrBase",
isr_causado AS "isrCausado",
isr_retenido AS "isrRetenido",
isr_a_pagar AS "isrAPagar",
cfdis_emitidos_count AS "cfdisEmitidosCount",
cfdis_recibidos_count AS "cfdisRecibidosCount",
cfdis_cancelados_count AS "cfdisCanceladosCount",
ingresos_devengados AS "ingresosDevengados",
ingresos_cobrados AS "ingresosCobrados",
egresos_devengados AS "egresosDevengados",
egresos_pagados AS "egresosPagados",
utilidad_devengada AS "utilidadDevengada",
utilidad_realizada AS "utilidadRealizada",
flujo_entradas AS "flujoEntradas",
flujo_salidas AS "flujoSalidas",
flujo_neto AS "flujoNeto",
cxc_saldo_final AS "cxcSaldoFinal",
cxp_saldo_final AS "cxpSaldoFinal",
ncs_emitidas AS "ncsEmitidas",
ncs_recibidas AS "ncsRecibidas",
gastos_no_deducibles_efectivo AS "gastosNoDeduciblesEfectivo",
cerrado,
computed_at AS "computedAt"
FROM metricas_mensuales
WHERE contribuyente_id = $1 AND anio = $2
`;
if (regimenFiscal) {
query += ' AND regimen_fiscal = $3';
params.push(regimenFiscal);
}
query += ' ORDER BY mes';
const { rows } = await pool.query(query, params);
return rows.map(r => ({ ...r, cerrado: r.cerrado ?? false, computedAt: r.computedAt?.toISOString?.() ?? '' }));
}
// HOT: current year — return empty (caller should use dashboard/impuestos services for on-the-fly computation)
return [];
}
export async function markForInvalidation(
pool: Pool,
contribuyenteId: string,
anio: number,
mes: number,
reason: string
): Promise<void> {
await pool.query(`
INSERT INTO metricas_invalidaciones (contribuyente_id, anio, mes, reason)
VALUES ($1, $2, $3, $4)
ON CONFLICT (contribuyente_id, anio, mes) DO UPDATE SET marcado_at = now(), reason = $4
`, [contribuyenteId, anio, mes, reason]);
}
export async function getPendingInvalidations(pool: Pool): Promise<Array<{
contribuyenteId: string;
anio: number;
mes: number;
reason: string;
}>> {
const { rows } = await pool.query(`
SELECT contribuyente_id AS "contribuyenteId", anio, mes, reason
FROM metricas_invalidaciones
ORDER BY anio, mes
`);
return rows;
}
export async function clearInvalidation(pool: Pool, contribuyenteId: string, anio: number, mes: number): Promise<void> {
await pool.query(
'DELETE FROM metricas_invalidaciones WHERE contribuyente_id = $1 AND anio = $2 AND mes = $3',
[contribuyenteId, anio, mes]
);
}
export async function upsertMetricaMensual(
pool: Pool,
contribuyenteId: string,
anio: number,
mes: number,
regimenFiscal: string | null,
data: Partial<MetricaMensual>
): Promise<void> {
await pool.query(`
INSERT INTO metricas_mensuales (
contribuyente_id, anio, mes, regimen_fiscal,
iva_trasladado_total, iva_acreditable, iva_retenido_cobrado, iva_retenido_pagado, iva_resultado,
isr_ingresos_brutos, isr_deducciones_autoriz, isr_base, isr_causado, isr_retenido, isr_a_pagar,
cfdis_emitidos_count, cfdis_recibidos_count, cfdis_cancelados_count,
ingresos_devengados, ingresos_cobrados, egresos_devengados, egresos_pagados,
utilidad_devengada, utilidad_realizada,
flujo_entradas, flujo_salidas, flujo_neto,
cxc_saldo_final, cxp_saldo_final,
ncs_emitidas, ncs_recibidas,
gastos_no_deducibles_efectivo,
computed_at, source_max_cfdi_at
) VALUES (
$1, $2, $3, $4,
$5, $6, $7, $8, $9,
$10, $11, $12, $13, $14, $15,
$16, $17, $18,
$19, $20, $21, $22,
$23, $24,
$25, $26, $27,
$28, $29,
$30, $31,
$32,
now(), now()
)
ON CONFLICT (contribuyente_id, anio, mes, regimen_fiscal)
DO UPDATE SET
iva_trasladado_total = $5, iva_acreditable = $6, iva_retenido_cobrado = $7, iva_retenido_pagado = $8, iva_resultado = $9,
isr_ingresos_brutos = $10, isr_deducciones_autoriz = $11, isr_base = $12,
isr_causado = $13, isr_retenido = $14, isr_a_pagar = $15,
cfdis_emitidos_count = $16, cfdis_recibidos_count = $17, cfdis_cancelados_count = $18,
ingresos_devengados = $19, ingresos_cobrados = $20, egresos_devengados = $21, egresos_pagados = $22,
utilidad_devengada = $23, utilidad_realizada = $24,
flujo_entradas = $25, flujo_salidas = $26, flujo_neto = $27,
cxc_saldo_final = $28, cxp_saldo_final = $29,
ncs_emitidas = $30, ncs_recibidas = $31,
gastos_no_deducibles_efectivo = $32,
computed_at = now(), source_max_cfdi_at = now()
`, [
contribuyenteId, anio, mes, regimenFiscal,
data.ivaTrasladadoTotal ?? 0, data.ivaAcreditable ?? 0, data.ivaRetenidoCobrado ?? 0, data.ivaRetenidoPagado ?? 0, data.ivaResultado ?? 0,
data.isrIngresosBrutos ?? 0, data.isrDeduccionesAutoriz ?? 0, data.isrBase ?? 0,
data.isrCausado ?? 0, data.isrRetenido ?? 0, data.isrAPagar ?? 0,
data.cfdisEmitidosCount ?? 0, data.cfdisRecibidosCount ?? 0, data.cfdisCanceladosCount ?? 0,
data.ingresosDevengados ?? 0, data.ingresosCobrados ?? 0, data.egresosDevengados ?? 0, data.egresosPagados ?? 0,
data.utilidadDevengada ?? 0, data.utilidadRealizada ?? 0,
data.flujoEntradas ?? 0, data.flujoSalidas ?? 0, data.flujoNeto ?? 0,
data.cxcSaldoFinal ?? 0, data.cxpSaldoFinal ?? 0,
data.ncsEmitidas ?? 0, data.ncsRecibidas ?? 0,
data.gastosNoDeduciblesEfectivo ?? 0,
]);
}
export async function closeMonth(pool: Pool, anio: number, mes: number): Promise<void> {
await pool.query(
'UPDATE metricas_mensuales SET cerrado = true WHERE anio = $1 AND mes = $2',
[anio, mes]
);
}
export async function closeYear(pool: Pool, anio: number): Promise<void> {
await pool.query(
'UPDATE metricas_mensuales SET cerrado = true WHERE anio = $1',
[anio]
);
}