import { prisma } from '../config/database.js'; import type { EstadoResultados, FlujoEfectivo, ComparativoPeriodos, ConcentradoRfc } from '@horux/shared'; // Helper to convert Prisma Decimal/BigInt to number function toNumber(value: unknown): number { if (value === null || value === undefined) return 0; if (typeof value === 'number') return value; if (typeof value === 'bigint') return Number(value); if (typeof value === 'string') return parseFloat(value) || 0; if (typeof value === 'object' && value !== null && 'toNumber' in value) { return (value as { toNumber: () => number }).toNumber(); } return Number(value) || 0; } export async function getEstadoResultados( schema: string, fechaInicio: string, fechaFin: string ): Promise { const ingresos = await prisma.$queryRawUnsafe<{ rfc: string; nombre: string; total: unknown }[]>(` SELECT rfc_receptor as rfc, nombre_receptor as nombre, SUM(subtotal) as total FROM "${schema}".cfdis WHERE tipo = 'ingreso' AND estado = 'vigente' AND fecha_emision BETWEEN $1::date AND $2::date GROUP BY rfc_receptor, nombre_receptor ORDER BY total DESC LIMIT 10 `, fechaInicio, fechaFin); const egresos = await prisma.$queryRawUnsafe<{ rfc: string; nombre: string; total: unknown }[]>(` SELECT rfc_emisor as rfc, nombre_emisor as nombre, SUM(subtotal) as total FROM "${schema}".cfdis WHERE tipo = 'egreso' AND estado = 'vigente' AND fecha_emision BETWEEN $1::date AND $2::date GROUP BY rfc_emisor, nombre_emisor ORDER BY total DESC LIMIT 10 `, fechaInicio, fechaFin); const totalesResult = await prisma.$queryRawUnsafe<{ ingresos: unknown; egresos: unknown; iva: unknown }[]>(` SELECT COALESCE(SUM(CASE WHEN tipo = 'ingreso' THEN subtotal ELSE 0 END), 0) as ingresos, COALESCE(SUM(CASE WHEN tipo = 'egreso' THEN subtotal ELSE 0 END), 0) as egresos, COALESCE(SUM(CASE WHEN tipo = 'ingreso' THEN iva ELSE 0 END), 0) - COALESCE(SUM(CASE WHEN tipo = 'egreso' THEN iva ELSE 0 END), 0) as iva FROM "${schema}".cfdis WHERE estado = 'vigente' AND fecha_emision BETWEEN $1::date AND $2::date `, fechaInicio, fechaFin); const totales = totalesResult[0]; const totalIngresos = toNumber(totales?.ingresos); const totalEgresos = toNumber(totales?.egresos); const utilidadBruta = totalIngresos - totalEgresos; const impuestos = toNumber(totales?.iva); return { periodo: { inicio: fechaInicio, fin: fechaFin }, ingresos: ingresos.map(i => ({ concepto: i.nombre, monto: toNumber(i.total) })), egresos: egresos.map(e => ({ concepto: e.nombre, monto: toNumber(e.total) })), totalIngresos, totalEgresos, utilidadBruta, impuestos, utilidadNeta: utilidadBruta - (impuestos > 0 ? impuestos : 0), }; } export async function getFlujoEfectivo( schema: string, fechaInicio: string, fechaFin: string ): Promise { const entradas = await prisma.$queryRawUnsafe<{ mes: string; total: unknown }[]>(` SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, SUM(total) as total FROM "${schema}".cfdis WHERE tipo = 'ingreso' AND estado = 'vigente' AND fecha_emision BETWEEN $1::date AND $2::date GROUP BY TO_CHAR(fecha_emision, 'YYYY-MM') ORDER BY mes `, fechaInicio, fechaFin); const salidas = await prisma.$queryRawUnsafe<{ mes: string; total: unknown }[]>(` SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, SUM(total) as total FROM "${schema}".cfdis WHERE tipo = 'egreso' AND estado = 'vigente' AND fecha_emision BETWEEN $1::date AND $2::date GROUP BY TO_CHAR(fecha_emision, 'YYYY-MM') ORDER BY mes `, fechaInicio, fechaFin); const totalEntradas = entradas.reduce((sum, e) => sum + toNumber(e.total), 0); const totalSalidas = salidas.reduce((sum, s) => sum + toNumber(s.total), 0); return { periodo: { inicio: fechaInicio, fin: fechaFin }, saldoInicial: 0, entradas: entradas.map(e => ({ concepto: e.mes, monto: toNumber(e.total) })), salidas: salidas.map(s => ({ concepto: s.mes, monto: toNumber(s.total) })), totalEntradas, totalSalidas, flujoNeto: totalEntradas - totalSalidas, saldoFinal: totalEntradas - totalSalidas, }; } export async function getComparativo( schema: string, año: number ): Promise { const actual = await prisma.$queryRawUnsafe<{ mes: number; ingresos: unknown; egresos: unknown }[]>(` 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 mes ORDER BY mes `, año); const anterior = await prisma.$queryRawUnsafe<{ mes: number; ingresos: unknown; egresos: unknown }[]>(` 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 mes ORDER BY mes `, año - 1); const meses = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic']; const ingresos = meses.map((_, i) => toNumber(actual.find(a => a.mes === i + 1)?.ingresos)); const egresos = meses.map((_, i) => toNumber(actual.find(a => a.mes === i + 1)?.egresos)); const utilidad = ingresos.map((ing, i) => ing - egresos[i]); const totalActualIng = ingresos.reduce((a, b) => a + b, 0); const totalAnteriorIng = anterior.reduce((a, b) => a + toNumber(b.ingresos), 0); const totalActualEgr = egresos.reduce((a, b) => a + b, 0); const totalAnteriorEgr = anterior.reduce((a, b) => a + toNumber(b.egresos), 0); return { periodos: meses, ingresos, egresos, utilidad, variacionIngresos: totalAnteriorIng > 0 ? ((totalActualIng - totalAnteriorIng) / totalAnteriorIng) * 100 : 0, variacionEgresos: totalAnteriorEgr > 0 ? ((totalActualEgr - totalAnteriorEgr) / totalAnteriorEgr) * 100 : 0, variacionUtilidad: 0, }; } export async function getConcentradoRfc( schema: string, fechaInicio: string, fechaFin: string, tipo: 'cliente' | 'proveedor' ): Promise { if (tipo === 'cliente') { const data = await prisma.$queryRawUnsafe<{ rfc: string; nombre: string; tipo: string; totalFacturado: unknown; totalIva: unknown; cantidadCfdis: number }[]>(` SELECT rfc_receptor as rfc, nombre_receptor as nombre, 'cliente' as tipo, SUM(total) as "totalFacturado", SUM(iva) as "totalIva", COUNT(*)::int as "cantidadCfdis" FROM "${schema}".cfdis WHERE tipo = 'ingreso' AND estado = 'vigente' AND fecha_emision BETWEEN $1::date AND $2::date GROUP BY rfc_receptor, nombre_receptor ORDER BY "totalFacturado" DESC `, fechaInicio, fechaFin); return data.map(d => ({ rfc: d.rfc, nombre: d.nombre, tipo: 'cliente' as const, totalFacturado: toNumber(d.totalFacturado), totalIva: toNumber(d.totalIva), cantidadCfdis: d.cantidadCfdis })); } else { const data = await prisma.$queryRawUnsafe<{ rfc: string; nombre: string; tipo: string; totalFacturado: unknown; totalIva: unknown; cantidadCfdis: number }[]>(` SELECT rfc_emisor as rfc, nombre_emisor as nombre, 'proveedor' as tipo, SUM(total) as "totalFacturado", SUM(iva) as "totalIva", COUNT(*)::int as "cantidadCfdis" FROM "${schema}".cfdis WHERE tipo = 'egreso' AND estado = 'vigente' AND fecha_emision BETWEEN $1::date AND $2::date GROUP BY rfc_emisor, nombre_emisor ORDER BY "totalFacturado" DESC `, fechaInicio, fechaFin); return data.map(d => ({ rfc: d.rfc, nombre: d.nombre, tipo: 'proveedor' as const, totalFacturado: toNumber(d.totalFacturado), totalIva: toNumber(d.totalIva), cantidadCfdis: d.cantidadCfdis })); } }