import type { Pool } from 'pg'; import type { EstadoResultados, FlujoEfectivo, ComparativoPeriodos, ConcentradoRfc } from '@horux/shared'; 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( pool: Pool, fechaInicio: string, fechaFin: string ): Promise { const { rows: ingresos } = await pool.query(` SELECT rfc_receptor as rfc, nombre_receptor as nombre, SUM(subtotal) as total FROM 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 { rows: egresos } = await pool.query(` SELECT rfc_emisor as rfc, nombre_emisor as nombre, SUM(subtotal) as total FROM 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 { rows: totalesResult } = await pool.query(` 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 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: any) => ({ concepto: i.nombre, monto: toNumber(i.total) })), egresos: egresos.map((e: any) => ({ concepto: e.nombre, monto: toNumber(e.total) })), totalIngresos, totalEgresos, utilidadBruta, impuestos, utilidadNeta: utilidadBruta - (impuestos > 0 ? impuestos : 0), }; } export async function getFlujoEfectivo( pool: Pool, fechaInicio: string, fechaFin: string ): Promise { const { rows: entradas } = await pool.query(` SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, SUM(total) as total FROM 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 { rows: salidas } = await pool.query(` SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, SUM(total) as total FROM 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: number, e: any) => sum + toNumber(e.total), 0); const totalSalidas = salidas.reduce((sum: number, s: any) => sum + toNumber(s.total), 0); return { periodo: { inicio: fechaInicio, fin: fechaFin }, saldoInicial: 0, entradas: entradas.map((e: any) => ({ concepto: e.mes, monto: toNumber(e.total) })), salidas: salidas.map((s: any) => ({ concepto: s.mes, monto: toNumber(s.total) })), totalEntradas, totalSalidas, flujoNeto: totalEntradas - totalSalidas, saldoFinal: totalEntradas - totalSalidas, }; } export async function getComparativo( pool: Pool, año: number ): Promise { const { rows: actual } = await pool.query(` 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 cfdis WHERE estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 GROUP BY mes ORDER BY mes `, [año]); const { rows: anterior } = await pool.query(` 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 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: any) => a.mes === i + 1)?.ingresos)); const egresos = meses.map((_, i) => toNumber(actual.find((a: any) => 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: number, b: any) => a + toNumber(b.ingresos), 0); const totalActualEgr = egresos.reduce((a, b) => a + b, 0); const totalAnteriorEgr = anterior.reduce((a: number, b: any) => 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( pool: Pool, fechaInicio: string, fechaFin: string, tipo: 'cliente' | 'proveedor' ): Promise { if (tipo === 'cliente') { const { rows: data } = await pool.query(` 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 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: any) => ({ rfc: d.rfc, nombre: d.nombre, tipo: 'cliente' as const, totalFacturado: toNumber(d.totalFacturado), totalIva: toNumber(d.totalIva), cantidadCfdis: d.cantidadCfdis })); } else { const { rows: data } = await pool.query(` 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 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: any) => ({ rfc: d.rfc, nombre: d.nombre, tipo: 'proveedor' as const, totalFacturado: toNumber(d.totalFacturado), totalIva: toNumber(d.totalIva), cantidadCfdis: d.cantidadCfdis })); } }