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 { 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 { 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 { 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 { const alertas = await prisma.$queryRawUnsafe(` 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; }