226 lines
8.1 KiB
TypeScript
226 lines
8.1 KiB
TypeScript
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]
|
|
);
|
|
}
|