Files
Horux360/apps/api/src/services/dashboard.service.ts

127 lines
4.6 KiB
TypeScript

import { prisma } from '../config/database.js';
import type { KpiData, IngresosEgresosData, ResumenFiscal, Alerta } from '@horux/shared';
export async function getKpis(schema: string, año: number, mes: number): Promise<KpiData> {
const [ingresos] = await prisma.$queryRawUnsafe<[{ total: number }]>(`
SELECT COALESCE(SUM(total), 0) as total
FROM "${schema}".cfdis
WHERE tipo = 'ingreso'
AND estado = 'vigente'
AND EXTRACT(YEAR FROM fecha_emision) = $1
AND EXTRACT(MONTH FROM fecha_emision) = $2
`, año, mes);
const [egresos] = await prisma.$queryRawUnsafe<[{ total: number }]>(`
SELECT COALESCE(SUM(total), 0) as total
FROM "${schema}".cfdis
WHERE tipo = 'egreso'
AND estado = 'vigente'
AND EXTRACT(YEAR FROM fecha_emision) = $1
AND EXTRACT(MONTH FROM fecha_emision) = $2
`, año, mes);
const [ivaData] = await prisma.$queryRawUnsafe<[{ trasladado: number; acreditable: number }]>(`
SELECT
COALESCE(SUM(CASE WHEN tipo = 'ingreso' THEN iva ELSE 0 END), 0) as trasladado,
COALESCE(SUM(CASE WHEN tipo = 'egreso' THEN iva ELSE 0 END), 0) as acreditable
FROM "${schema}".cfdis
WHERE estado = 'vigente'
AND EXTRACT(YEAR FROM fecha_emision) = $1
AND EXTRACT(MONTH FROM fecha_emision) = $2
`, año, mes);
const [counts] = await prisma.$queryRawUnsafe<[{ emitidos: number; recibidos: number }]>(`
SELECT
COUNT(CASE WHEN tipo = 'ingreso' THEN 1 END) as emitidos,
COUNT(CASE WHEN tipo = 'egreso' THEN 1 END) as recibidos
FROM "${schema}".cfdis
WHERE estado = 'vigente'
AND EXTRACT(YEAR FROM fecha_emision) = $1
AND EXTRACT(MONTH FROM fecha_emision) = $2
`, año, mes);
const ingresosVal = Number(ingresos?.total || 0);
const egresosVal = Number(egresos?.total || 0);
const utilidad = ingresosVal - egresosVal;
const margen = ingresosVal > 0 ? (utilidad / ingresosVal) * 100 : 0;
const ivaBalance = Number(ivaData?.trasladado || 0) - Number(ivaData?.acreditable || 0);
return {
ingresos: ingresosVal,
egresos: egresosVal,
utilidad,
margen: Math.round(margen * 100) / 100,
ivaBalance,
cfdisEmitidos: Number(counts?.emitidos || 0),
cfdisRecibidos: Number(counts?.recibidos || 0),
};
}
export async function getIngresosEgresos(schema: string, año: number): Promise<IngresosEgresosData[]> {
const data = await prisma.$queryRawUnsafe<{ mes: number; ingresos: number; egresos: number }[]>(`
SELECT
EXTRACT(MONTH FROM fecha_emision)::int as mes,
COALESCE(SUM(CASE WHEN tipo = 'ingreso' THEN total ELSE 0 END), 0) as ingresos,
COALESCE(SUM(CASE WHEN tipo = 'egreso' THEN total ELSE 0 END), 0) as egresos
FROM "${schema}".cfdis
WHERE estado = 'vigente'
AND EXTRACT(YEAR FROM fecha_emision) = $1
GROUP BY EXTRACT(MONTH FROM fecha_emision)
ORDER BY mes
`, año);
const meses = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'];
return meses.map((mes, index) => {
const found = data.find(d => d.mes === index + 1);
return {
mes,
ingresos: Number(found?.ingresos || 0),
egresos: Number(found?.egresos || 0),
};
});
}
export async function getResumenFiscal(schema: string, año: number, mes: number): Promise<ResumenFiscal> {
const [ivaResult] = await prisma.$queryRawUnsafe<[{ resultado: number; acumulado: number }]>(`
SELECT resultado, acumulado FROM "${schema}".iva_mensual
WHERE año = $1 AND mes = $2
`, año, mes) || [{ resultado: 0, acumulado: 0 }];
const [pendientes] = await prisma.$queryRawUnsafe<[{ count: number }]>(`
SELECT COUNT(*) as count FROM "${schema}".iva_mensual
WHERE año = $1 AND estado = 'pendiente'
`, año);
const resultado = Number(ivaResult?.resultado || 0);
const acumulado = Number(ivaResult?.acumulado || 0);
return {
ivaPorPagar: resultado > 0 ? resultado : 0,
ivaAFavor: acumulado < 0 ? Math.abs(acumulado) : 0,
isrPorPagar: 0,
declaracionesPendientes: Number(pendientes?.count || 0),
proximaObligacion: {
titulo: 'Declaracion mensual IVA/ISR',
fecha: new Date(año, mes, 17).toISOString(),
},
};
}
export async function getAlertas(schema: string, limit = 5): Promise<Alerta[]> {
const alertas = await prisma.$queryRawUnsafe<Alerta[]>(`
SELECT id, tipo, titulo, mensaje, prioridad,
fecha_vencimiento as "fechaVencimiento",
leida, resuelta,
created_at as "createdAt"
FROM "${schema}".alertas
WHERE resuelta = false
ORDER BY
CASE prioridad WHEN 'alta' THEN 1 WHEN 'media' THEN 2 ELSE 3 END,
created_at DESC
LIMIT $1
`, limit);
return alertas;
}