diff --git a/apps/api/src/controllers/alertas.controller.ts b/apps/api/src/controllers/alertas.controller.ts index 427cef1..1f418fb 100644 --- a/apps/api/src/controllers/alertas.controller.ts +++ b/apps/api/src/controllers/alertas.controller.ts @@ -1,10 +1,10 @@ -import { Request, Response, NextFunction } from 'express'; +import type { Request, Response, NextFunction } from 'express'; import * as alertasService from '../services/alertas.service.js'; export async function getAlertas(req: Request, res: Response, next: NextFunction) { try { const { leida, resuelta, prioridad } = req.query; - const alertas = await alertasService.getAlertas(req.tenantSchema!, { + const alertas = await alertasService.getAlertas(req.tenantPool!, { leida: leida === 'true' ? true : leida === 'false' ? false : undefined, resuelta: resuelta === 'true' ? true : resuelta === 'false' ? false : undefined, prioridad: prioridad as string, @@ -17,7 +17,7 @@ export async function getAlertas(req: Request, res: Response, next: NextFunction export async function getAlerta(req: Request, res: Response, next: NextFunction) { try { - const alerta = await alertasService.getAlertaById(req.tenantSchema!, parseInt(String(req.params.id))); + const alerta = await alertasService.getAlertaById(req.tenantPool!, parseInt(String(req.params.id))); if (!alerta) { return res.status(404).json({ message: 'Alerta no encontrada' }); } @@ -29,7 +29,7 @@ export async function getAlerta(req: Request, res: Response, next: NextFunction) export async function createAlerta(req: Request, res: Response, next: NextFunction) { try { - const alerta = await alertasService.createAlerta(req.tenantSchema!, req.body); + const alerta = await alertasService.createAlerta(req.tenantPool!, req.body); res.status(201).json(alerta); } catch (error) { next(error); @@ -38,7 +38,7 @@ export async function createAlerta(req: Request, res: Response, next: NextFuncti export async function updateAlerta(req: Request, res: Response, next: NextFunction) { try { - const alerta = await alertasService.updateAlerta(req.tenantSchema!, parseInt(String(req.params.id)), req.body); + const alerta = await alertasService.updateAlerta(req.tenantPool!, parseInt(String(req.params.id)), req.body); res.json(alerta); } catch (error) { next(error); @@ -47,7 +47,7 @@ export async function updateAlerta(req: Request, res: Response, next: NextFuncti export async function deleteAlerta(req: Request, res: Response, next: NextFunction) { try { - await alertasService.deleteAlerta(req.tenantSchema!, parseInt(String(req.params.id))); + await alertasService.deleteAlerta(req.tenantPool!, parseInt(String(req.params.id))); res.status(204).send(); } catch (error) { next(error); @@ -56,7 +56,7 @@ export async function deleteAlerta(req: Request, res: Response, next: NextFuncti export async function getStats(req: Request, res: Response, next: NextFunction) { try { - const stats = await alertasService.getStats(req.tenantSchema!); + const stats = await alertasService.getStats(req.tenantPool!); res.json(stats); } catch (error) { next(error); @@ -65,7 +65,7 @@ export async function getStats(req: Request, res: Response, next: NextFunction) export async function markAllAsRead(req: Request, res: Response, next: NextFunction) { try { - await alertasService.markAllAsRead(req.tenantSchema!); + await alertasService.markAllAsRead(req.tenantPool!); res.json({ success: true }); } catch (error) { next(error); diff --git a/apps/api/src/controllers/calendario.controller.ts b/apps/api/src/controllers/calendario.controller.ts index e13b051..e1ffa3b 100644 --- a/apps/api/src/controllers/calendario.controller.ts +++ b/apps/api/src/controllers/calendario.controller.ts @@ -1,4 +1,4 @@ -import { Request, Response, NextFunction } from 'express'; +import type { Request, Response, NextFunction } from 'express'; import * as calendarioService from '../services/calendario.service.js'; export async function getEventos(req: Request, res: Response, next: NextFunction) { @@ -7,7 +7,7 @@ export async function getEventos(req: Request, res: Response, next: NextFunction const añoNum = parseInt(año as string) || new Date().getFullYear(); const mesNum = mes ? parseInt(mes as string) : undefined; - const eventos = await calendarioService.getEventos(req.tenantSchema!, añoNum, mesNum); + const eventos = await calendarioService.getEventos(req.tenantPool!, añoNum, mesNum); res.json(eventos); } catch (error) { next(error); @@ -17,7 +17,7 @@ export async function getEventos(req: Request, res: Response, next: NextFunction export async function getProximos(req: Request, res: Response, next: NextFunction) { try { const dias = parseInt(req.query.dias as string) || 30; - const eventos = await calendarioService.getProximosEventos(req.tenantSchema!, dias); + const eventos = await calendarioService.getProximosEventos(req.tenantPool!, dias); res.json(eventos); } catch (error) { next(error); @@ -26,7 +26,7 @@ export async function getProximos(req: Request, res: Response, next: NextFunctio export async function createEvento(req: Request, res: Response, next: NextFunction) { try { - const evento = await calendarioService.createEvento(req.tenantSchema!, req.body); + const evento = await calendarioService.createEvento(req.tenantPool!, req.body); res.status(201).json(evento); } catch (error) { next(error); @@ -35,7 +35,7 @@ export async function createEvento(req: Request, res: Response, next: NextFuncti export async function updateEvento(req: Request, res: Response, next: NextFunction) { try { - const evento = await calendarioService.updateEvento(req.tenantSchema!, parseInt(String(req.params.id)), req.body); + const evento = await calendarioService.updateEvento(req.tenantPool!, parseInt(String(req.params.id)), req.body); res.json(evento); } catch (error) { next(error); @@ -44,7 +44,7 @@ export async function updateEvento(req: Request, res: Response, next: NextFuncti export async function deleteEvento(req: Request, res: Response, next: NextFunction) { try { - await calendarioService.deleteEvento(req.tenantSchema!, parseInt(String(req.params.id))); + await calendarioService.deleteEvento(req.tenantPool!, parseInt(String(req.params.id))); res.status(204).send(); } catch (error) { next(error); diff --git a/apps/api/src/controllers/cfdi.controller.ts b/apps/api/src/controllers/cfdi.controller.ts index 53a04e5..1d826f3 100644 --- a/apps/api/src/controllers/cfdi.controller.ts +++ b/apps/api/src/controllers/cfdi.controller.ts @@ -5,8 +5,8 @@ import type { CfdiFilters } from '@horux/shared'; export async function getCfdis(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } const filters: CfdiFilters = { @@ -22,7 +22,7 @@ export async function getCfdis(req: Request, res: Response, next: NextFunction) limit: parseInt(req.query.limit as string) || 20, }; - const result = await cfdiService.getCfdis(req.tenantSchema, filters); + const result = await cfdiService.getCfdis(req.tenantPool, filters); res.json(result); } catch (error) { next(error); @@ -31,11 +31,11 @@ export async function getCfdis(req: Request, res: Response, next: NextFunction) export async function getCfdiById(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } - const cfdi = await cfdiService.getCfdiById(req.tenantSchema, String(req.params.id)); + const cfdi = await cfdiService.getCfdiById(req.tenantPool, String(req.params.id)); if (!cfdi) { return next(new AppError(404, 'CFDI no encontrado')); @@ -49,11 +49,11 @@ export async function getCfdiById(req: Request, res: Response, next: NextFunctio export async function getXml(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } - const xml = await cfdiService.getXmlById(req.tenantSchema, String(req.params.id)); + const xml = await cfdiService.getXmlById(req.tenantPool, String(req.params.id)); if (!xml) { return next(new AppError(404, 'XML no encontrado para este CFDI')); @@ -69,8 +69,8 @@ export async function getXml(req: Request, res: Response, next: NextFunction) { export async function getEmisores(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } const search = (req.query.search as string) || ''; @@ -78,7 +78,7 @@ export async function getEmisores(req: Request, res: Response, next: NextFunctio return res.json([]); } - const emisores = await cfdiService.getEmisores(req.tenantSchema, search); + const emisores = await cfdiService.getEmisores(req.tenantPool, search); res.json(emisores); } catch (error) { next(error); @@ -87,8 +87,8 @@ export async function getEmisores(req: Request, res: Response, next: NextFunctio export async function getReceptores(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } const search = (req.query.search as string) || ''; @@ -96,7 +96,7 @@ export async function getReceptores(req: Request, res: Response, next: NextFunct return res.json([]); } - const receptores = await cfdiService.getReceptores(req.tenantSchema, search); + const receptores = await cfdiService.getReceptores(req.tenantPool, search); res.json(receptores); } catch (error) { next(error); @@ -105,14 +105,14 @@ export async function getReceptores(req: Request, res: Response, next: NextFunct export async function getResumen(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } const año = parseInt(req.query.año as string) || new Date().getFullYear(); const mes = parseInt(req.query.mes as string) || new Date().getMonth() + 1; - const resumen = await cfdiService.getResumenCfdis(req.tenantSchema, año, mes); + const resumen = await cfdiService.getResumenCfdis(req.tenantPool, año, mes); res.json(resumen); } catch (error) { next(error); @@ -121,16 +121,15 @@ export async function getResumen(req: Request, res: Response, next: NextFunction export async function createCfdi(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } - // Only admin and contador can create CFDIs if (!['admin', 'contador'].includes(req.user!.role)) { return next(new AppError(403, 'No tienes permisos para agregar CFDIs')); } - const cfdi = await cfdiService.createCfdi(req.tenantSchema, req.body); + const cfdi = await cfdiService.createCfdi(req.tenantPool, req.body); res.status(201).json(cfdi); } catch (error: any) { if (error.message?.includes('duplicate')) { @@ -142,8 +141,8 @@ export async function createCfdi(req: Request, res: Response, next: NextFunction export async function createManyCfdis(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } if (!['admin', 'contador'].includes(req.user!.role)) { @@ -160,9 +159,9 @@ export async function createManyCfdis(req: Request, res: Response, next: NextFun totalFiles: req.body.totalFiles || req.body.cfdis.length }; - console.log(`[CFDI Bulk] Lote ${batchInfo.batchNumber}/${batchInfo.totalBatches} - ${req.body.cfdis.length} CFDIs para schema ${req.tenantSchema}`); + console.log(`[CFDI Bulk] Lote ${batchInfo.batchNumber}/${batchInfo.totalBatches} - ${req.body.cfdis.length} CFDIs`); - const result = await cfdiService.createManyCfdisBatch(req.tenantSchema, req.body.cfdis); + const result = await cfdiService.createManyCfdisBatch(req.tenantPool, req.body.cfdis); res.status(201).json({ message: `Lote ${batchInfo.batchNumber} procesado`, @@ -171,7 +170,7 @@ export async function createManyCfdis(req: Request, res: Response, next: NextFun inserted: result.inserted, duplicates: result.duplicates, errors: result.errors, - errorMessages: result.errorMessages.slice(0, 5) // Limit error messages + errorMessages: result.errorMessages.slice(0, 5) }); } catch (error: any) { console.error('[CFDI Bulk Error]', error.message, error.stack); @@ -181,15 +180,15 @@ export async function createManyCfdis(req: Request, res: Response, next: NextFun export async function deleteCfdi(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } if (!['admin', 'contador'].includes(req.user!.role)) { return next(new AppError(403, 'No tienes permisos para eliminar CFDIs')); } - await cfdiService.deleteCfdi(req.tenantSchema, String(req.params.id)); + await cfdiService.deleteCfdi(req.tenantPool, String(req.params.id)); res.status(204).send(); } catch (error) { next(error); diff --git a/apps/api/src/controllers/dashboard.controller.ts b/apps/api/src/controllers/dashboard.controller.ts index c1fe61a..8f70f7d 100644 --- a/apps/api/src/controllers/dashboard.controller.ts +++ b/apps/api/src/controllers/dashboard.controller.ts @@ -4,14 +4,14 @@ import { AppError } from '../middlewares/error.middleware.js'; export async function getKpis(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } const año = parseInt(req.query.año as string) || new Date().getFullYear(); const mes = parseInt(req.query.mes as string) || new Date().getMonth() + 1; - const kpis = await dashboardService.getKpis(req.tenantSchema, año, mes); + const kpis = await dashboardService.getKpis(req.tenantPool, año, mes); res.json(kpis); } catch (error) { next(error); @@ -20,13 +20,13 @@ export async function getKpis(req: Request, res: Response, next: NextFunction) { export async function getIngresosEgresos(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } const año = parseInt(req.query.año as string) || new Date().getFullYear(); - const data = await dashboardService.getIngresosEgresos(req.tenantSchema, año); + const data = await dashboardService.getIngresosEgresos(req.tenantPool, año); res.json(data); } catch (error) { next(error); @@ -35,14 +35,14 @@ export async function getIngresosEgresos(req: Request, res: Response, next: Next export async function getResumenFiscal(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } const año = parseInt(req.query.año as string) || new Date().getFullYear(); const mes = parseInt(req.query.mes as string) || new Date().getMonth() + 1; - const resumen = await dashboardService.getResumenFiscal(req.tenantSchema, año, mes); + const resumen = await dashboardService.getResumenFiscal(req.tenantPool, año, mes); res.json(resumen); } catch (error) { next(error); @@ -51,13 +51,13 @@ export async function getResumenFiscal(req: Request, res: Response, next: NextFu export async function getAlertas(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } const limit = parseInt(req.query.limit as string) || 5; - const alertas = await dashboardService.getAlertas(req.tenantSchema, limit); + const alertas = await dashboardService.getAlertas(req.tenantPool, limit); res.json(alertas); } catch (error) { next(error); diff --git a/apps/api/src/controllers/export.controller.ts b/apps/api/src/controllers/export.controller.ts index 2a8e832..d4e7a60 100644 --- a/apps/api/src/controllers/export.controller.ts +++ b/apps/api/src/controllers/export.controller.ts @@ -1,10 +1,10 @@ -import { Request, Response, NextFunction } from 'express'; +import type { Request, Response, NextFunction } from 'express'; import * as exportService from '../services/export.service.js'; export async function exportCfdis(req: Request, res: Response, next: NextFunction) { try { const { tipo, estado, fechaInicio, fechaFin } = req.query; - const buffer = await exportService.exportCfdisToExcel(req.tenantSchema!, { + const buffer = await exportService.exportCfdisToExcel(req.tenantPool!, { tipo: tipo as string, estado: estado as string, fechaInicio: fechaInicio as string, @@ -27,7 +27,7 @@ export async function exportReporte(req: Request, res: Response, next: NextFunct const fin = (fechaFin as string) || now.toISOString().split('T')[0]; const buffer = await exportService.exportReporteToExcel( - req.tenantSchema!, + req.tenantPool!, tipo as 'estado-resultados' | 'flujo-efectivo', inicio, fin diff --git a/apps/api/src/controllers/impuestos.controller.ts b/apps/api/src/controllers/impuestos.controller.ts index 7ebfe7e..3ed005b 100644 --- a/apps/api/src/controllers/impuestos.controller.ts +++ b/apps/api/src/controllers/impuestos.controller.ts @@ -4,12 +4,12 @@ import { AppError } from '../middlewares/error.middleware.js'; export async function getIvaMensual(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } const año = parseInt(req.query.año as string) || new Date().getFullYear(); - const data = await impuestosService.getIvaMensual(req.tenantSchema, año); + const data = await impuestosService.getIvaMensual(req.tenantPool, año); res.json(data); } catch (error) { next(error); @@ -18,14 +18,14 @@ export async function getIvaMensual(req: Request, res: Response, next: NextFunct export async function getResumenIva(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } const año = parseInt(req.query.año as string) || new Date().getFullYear(); const mes = parseInt(req.query.mes as string) || new Date().getMonth() + 1; - const resumen = await impuestosService.getResumenIva(req.tenantSchema, año, mes); + const resumen = await impuestosService.getResumenIva(req.tenantPool, año, mes); res.json(resumen); } catch (error) { next(error); @@ -34,12 +34,12 @@ export async function getResumenIva(req: Request, res: Response, next: NextFunct export async function getIsrMensual(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } const año = parseInt(req.query.año as string) || new Date().getFullYear(); - const data = await impuestosService.getIsrMensual(req.tenantSchema, año); + const data = await impuestosService.getIsrMensual(req.tenantPool, año); res.json(data); } catch (error) { next(error); @@ -48,14 +48,14 @@ export async function getIsrMensual(req: Request, res: Response, next: NextFunct export async function getResumenIsr(req: Request, res: Response, next: NextFunction) { try { - if (!req.tenantSchema) { - return next(new AppError(400, 'Schema no configurado')); + if (!req.tenantPool) { + return next(new AppError(400, 'Tenant no configurado')); } const año = parseInt(req.query.año as string) || new Date().getFullYear(); const mes = parseInt(req.query.mes as string) || new Date().getMonth() + 1; - const resumen = await impuestosService.getResumenIsr(req.tenantSchema, año, mes); + const resumen = await impuestosService.getResumenIsr(req.tenantPool, año, mes); res.json(resumen); } catch (error) { next(error); diff --git a/apps/api/src/controllers/reportes.controller.ts b/apps/api/src/controllers/reportes.controller.ts index 1329f55..cb30406 100644 --- a/apps/api/src/controllers/reportes.controller.ts +++ b/apps/api/src/controllers/reportes.controller.ts @@ -1,4 +1,4 @@ -import { Request, Response, NextFunction } from 'express'; +import type { Request, Response, NextFunction } from 'express'; import * as reportesService from '../services/reportes.service.js'; export async function getEstadoResultados(req: Request, res: Response, next: NextFunction) { @@ -8,8 +8,7 @@ export async function getEstadoResultados(req: Request, res: Response, next: Nex const inicio = (fechaInicio as string) || `${now.getFullYear()}-01-01`; const fin = (fechaFin as string) || now.toISOString().split('T')[0]; - console.log('[reportes] getEstadoResultados - schema:', req.tenantSchema, 'inicio:', inicio, 'fin:', fin); - const data = await reportesService.getEstadoResultados(req.tenantSchema!, inicio, fin); + const data = await reportesService.getEstadoResultados(req.tenantPool!, inicio, fin); res.json(data); } catch (error) { console.error('[reportes] Error en getEstadoResultados:', error); @@ -24,7 +23,7 @@ export async function getFlujoEfectivo(req: Request, res: Response, next: NextFu const inicio = (fechaInicio as string) || `${now.getFullYear()}-01-01`; const fin = (fechaFin as string) || now.toISOString().split('T')[0]; - const data = await reportesService.getFlujoEfectivo(req.tenantSchema!, inicio, fin); + const data = await reportesService.getFlujoEfectivo(req.tenantPool!, inicio, fin); res.json(data); } catch (error) { next(error); @@ -34,7 +33,7 @@ export async function getFlujoEfectivo(req: Request, res: Response, next: NextFu export async function getComparativo(req: Request, res: Response, next: NextFunction) { try { const año = parseInt(req.query.año as string) || new Date().getFullYear(); - const data = await reportesService.getComparativo(req.tenantSchema!, año); + const data = await reportesService.getComparativo(req.tenantPool!, año); res.json(data); } catch (error) { next(error); @@ -49,7 +48,7 @@ export async function getConcentradoRfc(req: Request, res: Response, next: NextF const fin = (fechaFin as string) || now.toISOString().split('T')[0]; const tipoRfc = (tipo as 'cliente' | 'proveedor') || 'cliente'; - const data = await reportesService.getConcentradoRfc(req.tenantSchema!, inicio, fin, tipoRfc); + const data = await reportesService.getConcentradoRfc(req.tenantPool!, inicio, fin, tipoRfc); res.json(data); } catch (error) { next(error); diff --git a/apps/api/src/services/alertas.service.ts b/apps/api/src/services/alertas.service.ts index 911c1c8..ff30f42 100644 --- a/apps/api/src/services/alertas.service.ts +++ b/apps/api/src/services/alertas.service.ts @@ -1,8 +1,8 @@ -import { prisma } from '../config/database.js'; +import type { Pool } from 'pg'; import type { AlertaFull, AlertaCreate, AlertaUpdate, AlertasStats } from '@horux/shared'; export async function getAlertas( - schema: string, + pool: Pool, filters: { leida?: boolean; resuelta?: boolean; prioridad?: string } ): Promise { let whereClause = 'WHERE 1=1'; @@ -22,43 +22,43 @@ export async function getAlertas( params.push(filters.prioridad); } - const alertas = await prisma.$queryRawUnsafe(` + const { rows } = await pool.query(` SELECT id, tipo, titulo, mensaje, prioridad, fecha_vencimiento as "fechaVencimiento", leida, resuelta, created_at as "createdAt" - FROM "${schema}".alertas + FROM alertas ${whereClause} ORDER BY CASE prioridad WHEN 'alta' THEN 1 WHEN 'media' THEN 2 ELSE 3 END, created_at DESC - `, ...params); + `, params); - return alertas; + return rows; } -export async function getAlertaById(schema: string, id: number): Promise { - const [alerta] = await prisma.$queryRawUnsafe(` +export async function getAlertaById(pool: Pool, id: number): Promise { + const { rows } = await pool.query(` SELECT id, tipo, titulo, mensaje, prioridad, fecha_vencimiento as "fechaVencimiento", leida, resuelta, created_at as "createdAt" - FROM "${schema}".alertas + FROM alertas WHERE id = $1 - `, id); - return alerta || null; + `, [id]); + return rows[0] || null; } -export async function createAlerta(schema: string, data: AlertaCreate): Promise { - const [alerta] = await prisma.$queryRawUnsafe(` - INSERT INTO "${schema}".alertas (tipo, titulo, mensaje, prioridad, fecha_vencimiento) +export async function createAlerta(pool: Pool, data: AlertaCreate): Promise { + const { rows } = await pool.query(` + INSERT INTO alertas (tipo, titulo, mensaje, prioridad, fecha_vencimiento) VALUES ($1, $2, $3, $4, $5) RETURNING id, tipo, titulo, mensaje, prioridad, fecha_vencimiento as "fechaVencimiento", leida, resuelta, created_at as "createdAt" - `, data.tipo, data.titulo, data.mensaje, data.prioridad, data.fechaVencimiento || null); - return alerta; + `, [data.tipo, data.titulo, data.mensaje, data.prioridad, data.fechaVencimiento || null]); + return rows[0]; } -export async function updateAlerta(schema: string, id: number, data: AlertaUpdate): Promise { +export async function updateAlerta(pool: Pool, id: number, data: AlertaUpdate): Promise { const sets: string[] = []; const params: any[] = []; let paramIndex = 1; @@ -74,35 +74,35 @@ export async function updateAlerta(schema: string, id: number, data: AlertaUpdat params.push(id); - const [alerta] = await prisma.$queryRawUnsafe(` - UPDATE "${schema}".alertas + const { rows } = await pool.query(` + UPDATE alertas SET ${sets.join(', ')} WHERE id = $${paramIndex} RETURNING id, tipo, titulo, mensaje, prioridad, fecha_vencimiento as "fechaVencimiento", leida, resuelta, created_at as "createdAt" - `, ...params); + `, params); - return alerta; + return rows[0]; } -export async function deleteAlerta(schema: string, id: number): Promise { - await prisma.$queryRawUnsafe(`DELETE FROM "${schema}".alertas WHERE id = $1`, id); +export async function deleteAlerta(pool: Pool, id: number): Promise { + await pool.query(`DELETE FROM alertas WHERE id = $1`, [id]); } -export async function getStats(schema: string): Promise { - const [stats] = await prisma.$queryRawUnsafe(` +export async function getStats(pool: Pool): Promise { + const { rows: [stats] } = await pool.query(` SELECT COUNT(*)::int as total, COUNT(CASE WHEN leida = false THEN 1 END)::int as "noLeidas", COUNT(CASE WHEN prioridad = 'alta' AND resuelta = false THEN 1 END)::int as alta, COUNT(CASE WHEN prioridad = 'media' AND resuelta = false THEN 1 END)::int as media, COUNT(CASE WHEN prioridad = 'baja' AND resuelta = false THEN 1 END)::int as baja - FROM "${schema}".alertas + FROM alertas `); return stats; } -export async function markAllAsRead(schema: string): Promise { - await prisma.$queryRawUnsafe(`UPDATE "${schema}".alertas SET leida = true WHERE leida = false`); +export async function markAllAsRead(pool: Pool): Promise { + await pool.query(`UPDATE alertas SET leida = true WHERE leida = false`); } diff --git a/apps/api/src/services/calendario.service.ts b/apps/api/src/services/calendario.service.ts index 7e112d4..a44affc 100644 --- a/apps/api/src/services/calendario.service.ts +++ b/apps/api/src/services/calendario.service.ts @@ -1,8 +1,8 @@ -import { prisma } from '../config/database.js'; +import type { Pool } from 'pg'; import type { EventoFiscal, EventoCreate, EventoUpdate } from '@horux/shared'; export async function getEventos( - schema: string, + pool: Pool, año: number, mes?: number ): Promise { @@ -14,49 +14,49 @@ export async function getEventos( params.push(mes); } - const eventos = await prisma.$queryRawUnsafe(` + const { rows } = await pool.query(` SELECT id, titulo, descripcion, tipo, fecha_limite as "fechaLimite", recurrencia, completado, notas, created_at as "createdAt" - FROM "${schema}".calendario_fiscal + FROM calendario_fiscal ${whereClause} ORDER BY fecha_limite ASC - `, ...params); + `, params); - return eventos; + return rows; } -export async function getProximosEventos(schema: string, dias = 30): Promise { - const eventos = await prisma.$queryRawUnsafe(` +export async function getProximosEventos(pool: Pool, dias = 30): Promise { + const { rows } = await pool.query(` SELECT id, titulo, descripcion, tipo, fecha_limite as "fechaLimite", recurrencia, completado, notas, created_at as "createdAt" - FROM "${schema}".calendario_fiscal + FROM calendario_fiscal WHERE completado = false - AND fecha_limite BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '${dias} days' + AND fecha_limite BETWEEN CURRENT_DATE AND CURRENT_DATE + $1 * INTERVAL '1 day' ORDER BY fecha_limite ASC - `); + `, [dias]); - return eventos; + return rows; } -export async function createEvento(schema: string, data: EventoCreate): Promise { - const [evento] = await prisma.$queryRawUnsafe(` - INSERT INTO "${schema}".calendario_fiscal +export async function createEvento(pool: Pool, data: EventoCreate): Promise { + const { rows } = await pool.query(` + INSERT INTO calendario_fiscal (titulo, descripcion, tipo, fecha_limite, recurrencia, notas) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, titulo, descripcion, tipo, fecha_limite as "fechaLimite", recurrencia, completado, notas, created_at as "createdAt" - `, data.titulo, data.descripcion, data.tipo, data.fechaLimite, data.recurrencia, data.notas || null); + `, [data.titulo, data.descripcion, data.tipo, data.fechaLimite, data.recurrencia, data.notas || null]); - return evento; + return rows[0]; } -export async function updateEvento(schema: string, id: number, data: EventoUpdate): Promise { +export async function updateEvento(pool: Pool, id: number, data: EventoUpdate): Promise { const sets: string[] = []; const params: any[] = []; let paramIndex = 1; @@ -84,19 +84,19 @@ export async function updateEvento(schema: string, id: number, data: EventoUpdat params.push(id); - const [evento] = await prisma.$queryRawUnsafe(` - UPDATE "${schema}".calendario_fiscal + const { rows } = await pool.query(` + UPDATE calendario_fiscal SET ${sets.join(', ')} WHERE id = $${paramIndex} RETURNING id, titulo, descripcion, tipo, fecha_limite as "fechaLimite", recurrencia, completado, notas, created_at as "createdAt" - `, ...params); + `, params); - return evento; + return rows[0]; } -export async function deleteEvento(schema: string, id: number): Promise { - await prisma.$queryRawUnsafe(`DELETE FROM "${schema}".calendario_fiscal WHERE id = $1`, id); +export async function deleteEvento(pool: Pool, id: number): Promise { + await pool.query(`DELETE FROM calendario_fiscal WHERE id = $1`, [id]); } diff --git a/apps/api/src/services/cfdi.service.ts b/apps/api/src/services/cfdi.service.ts index f7eae11..4709cf6 100644 --- a/apps/api/src/services/cfdi.service.ts +++ b/apps/api/src/services/cfdi.service.ts @@ -1,7 +1,7 @@ -import { prisma } from '../config/database.js'; +import type { Pool } from 'pg'; import type { Cfdi, CfdiFilters, CfdiListResponse } from '@horux/shared'; -export async function getCfdis(schema: string, filters: CfdiFilters): Promise { +export async function getCfdis(pool: Pool, filters: CfdiFilters): Promise { const page = filters.page || 1; const limit = filters.limit || 20; const offset = (page - 1) * limit; @@ -50,9 +50,8 @@ export async function getCfdis(schema: string, filters: CfdiFilters): Promise(` + const { rows: dataWithCount } = await pool.query(` SELECT id, uuid_fiscal as "uuidFiscal", tipo, serie, folio, fecha_emision as "fechaEmision", fecha_timbrado as "fechaTimbrado", @@ -65,14 +64,14 @@ export async function getCfdis(schema: string, filters: CfdiFilters): Promise cfdi) as Cfdi[]; + const data = dataWithCount.map(({ total_count, ...cfdi }: any) => cfdi) as Cfdi[]; return { data, @@ -83,8 +82,8 @@ export async function getCfdis(schema: string, filters: CfdiFilters): Promise { - const result = await prisma.$queryRawUnsafe(` +export async function getCfdiById(pool: Pool, id: string): Promise { + const { rows } = await pool.query(` SELECT id, uuid_fiscal as "uuidFiscal", tipo, serie, folio, fecha_emision as "fechaEmision", fecha_timbrado as "fechaTimbrado", @@ -97,19 +96,19 @@ export async function getCfdiById(schema: string, id: string): Promise { - const result = await prisma.$queryRawUnsafe<[{ xml_original: string | null }]>(` - SELECT xml_original FROM "${schema}".cfdis WHERE id = $1::uuid - `, id); +export async function getXmlById(pool: Pool, id: string): Promise { + const { rows } = await pool.query(` + SELECT xml_original FROM cfdis WHERE id = $1::uuid + `, [id]); - return result[0]?.xml_original || null; + return rows[0]?.xml_original || null; } export interface CreateCfdiData { @@ -139,18 +138,15 @@ export interface CreateCfdiData { pdfUrl?: string; } -export async function createCfdi(schema: string, data: CreateCfdiData): Promise { - // Validate required fields +export async function createCfdi(pool: Pool, data: CreateCfdiData): Promise { if (!data.uuidFiscal) throw new Error('UUID Fiscal es requerido'); if (!data.fechaEmision) throw new Error('Fecha de emisión es requerida'); if (!data.rfcEmisor) throw new Error('RFC Emisor es requerido'); if (!data.rfcReceptor) throw new Error('RFC Receptor es requerido'); - // Parse dates safely - handle YYYY-MM-DD format explicitly let fechaEmision: Date; let fechaTimbrado: Date; - // If date is in YYYY-MM-DD format, add time to avoid timezone issues const dateStr = typeof data.fechaEmision === 'string' && data.fechaEmision.match(/^\d{4}-\d{2}-\d{2}$/) ? `${data.fechaEmision}T12:00:00` : data.fechaEmision; @@ -173,8 +169,8 @@ export async function createCfdi(schema: string, data: CreateCfdiData): Promise< throw new Error(`Fecha de timbrado inválida: ${data.fechaTimbrado}`); } - const result = await prisma.$queryRawUnsafe(` - INSERT INTO "${schema}".cfdis ( + const { rows } = await pool.query(` + INSERT INTO cfdis ( uuid_fiscal, tipo, serie, folio, fecha_emision, fecha_timbrado, rfc_emisor, nombre_emisor, rfc_receptor, nombre_receptor, subtotal, descuento, iva, isr_retenido, iva_retenido, total, @@ -191,7 +187,7 @@ export async function createCfdi(schema: string, data: CreateCfdiData): Promise< forma_pago as "formaPago", uso_cfdi as "usoCfdi", estado, xml_url as "xmlUrl", pdf_url as "pdfUrl", created_at as "createdAt" - `, + `, [ data.uuidFiscal, data.tipo || 'ingreso', data.serie || null, @@ -216,9 +212,9 @@ export async function createCfdi(schema: string, data: CreateCfdiData): Promise< data.estado || 'vigente', data.xmlUrl || null, data.pdfUrl || null - ); + ]); - return result[0]; + return rows[0]; } export interface BatchInsertResult { @@ -228,14 +224,12 @@ export interface BatchInsertResult { errorMessages: string[]; } -// Optimized batch insert using multi-row INSERT -export async function createManyCfdis(schema: string, cfdis: CreateCfdiData[]): Promise { - const result = await createManyCfdisBatch(schema, cfdis); +export async function createManyCfdis(pool: Pool, cfdis: CreateCfdiData[]): Promise { + const result = await createManyCfdisBatch(pool, cfdis); return result.inserted; } -// New optimized batch insert with detailed results -export async function createManyCfdisBatch(schema: string, cfdis: CreateCfdiData[]): Promise { +export async function createManyCfdisBatch(pool: Pool, cfdis: CreateCfdiData[]): Promise { const result: BatchInsertResult = { inserted: 0, duplicates: 0, @@ -245,19 +239,17 @@ export async function createManyCfdisBatch(schema: string, cfdis: CreateCfdiData if (cfdis.length === 0) return result; - // Process in batches of 500 for optimal performance const BATCH_SIZE = 500; for (let batchStart = 0; batchStart < cfdis.length; batchStart += BATCH_SIZE) { const batch = cfdis.slice(batchStart, batchStart + BATCH_SIZE); try { - const batchResult = await insertBatch(schema, batch); + const batchResult = await insertBatch(pool, batch); result.inserted += batchResult.inserted; result.duplicates += batchResult.duplicates; } catch (error: any) { - // If batch fails, try individual inserts for this batch - const individualResult = await insertIndividually(schema, batch); + const individualResult = await insertIndividually(pool, batch); result.inserted += individualResult.inserted; result.duplicates += individualResult.duplicates; result.errors += individualResult.errors; @@ -268,17 +260,14 @@ export async function createManyCfdisBatch(schema: string, cfdis: CreateCfdiData return result; } -// Insert a batch using multi-row INSERT with ON CONFLICT -async function insertBatch(schema: string, cfdis: CreateCfdiData[]): Promise<{ inserted: number; duplicates: number }> { +async function insertBatch(pool: Pool, cfdis: CreateCfdiData[]): Promise<{ inserted: number; duplicates: number }> { if (cfdis.length === 0) return { inserted: 0, duplicates: 0 }; - // Build the VALUES part of the query const values: any[] = []; const valuePlaceholders: string[] = []; let paramIndex = 1; for (const cfdi of cfdis) { - // Parse dates const fechaEmision = parseDate(cfdi.fechaEmision); const fechaTimbrado = cfdi.fechaTimbrado ? parseDate(cfdi.fechaTimbrado) : fechaEmision; @@ -322,9 +311,8 @@ async function insertBatch(schema: string, cfdis: CreateCfdiData[]): Promise<{ i return { inserted: 0, duplicates: 0 }; } - // Use ON CONFLICT to handle duplicates gracefully const query = ` - INSERT INTO "${schema}".cfdis ( + INSERT INTO cfdis ( uuid_fiscal, tipo, serie, folio, fecha_emision, fecha_timbrado, rfc_emisor, nombre_emisor, rfc_receptor, nombre_receptor, subtotal, descuento, iva, isr_retenido, iva_retenido, total, @@ -333,15 +321,12 @@ async function insertBatch(schema: string, cfdis: CreateCfdiData[]): Promise<{ i ON CONFLICT (uuid_fiscal) DO NOTHING `; - await prisma.$executeRawUnsafe(query, ...values); + await pool.query(query, values); - // We can't know exactly how many were inserted vs duplicates with DO NOTHING - // Return optimistic count, duplicates will be 0 (they're silently skipped) return { inserted: valuePlaceholders.length, duplicates: 0 }; } -// Fallback: insert individually when batch fails -async function insertIndividually(schema: string, cfdis: CreateCfdiData[]): Promise { +async function insertIndividually(pool: Pool, cfdis: CreateCfdiData[]): Promise { const result: BatchInsertResult = { inserted: 0, duplicates: 0, @@ -351,7 +336,7 @@ async function insertIndividually(schema: string, cfdis: CreateCfdiData[]): Prom for (const cfdi of cfdis) { try { - await createCfdi(schema, cfdi); + await createCfdi(pool, cfdi); result.inserted++; } catch (error: any) { const errorMsg = error.message || 'Error desconocido'; @@ -369,11 +354,9 @@ async function insertIndividually(schema: string, cfdis: CreateCfdiData[]): Prom return result; } -// Helper to parse dates safely function parseDate(dateStr: string): Date | null { if (!dateStr) return null; - // If date is in YYYY-MM-DD format, add time to avoid timezone issues const normalized = dateStr.match(/^\d{4}-\d{2}-\d{2}$/) ? `${dateStr}T12:00:00` : dateStr; @@ -382,41 +365,34 @@ function parseDate(dateStr: string): Date | null { return isNaN(date.getTime()) ? null : date; } -export async function deleteCfdi(schema: string, id: string): Promise { - await prisma.$queryRawUnsafe(`DELETE FROM "${schema}".cfdis WHERE id = $1`, id); +export async function deleteCfdi(pool: Pool, id: string): Promise { + await pool.query(`DELETE FROM cfdis WHERE id = $1`, [id]); } -export async function getEmisores(schema: string, search: string, limit: number = 10): Promise<{ rfc: string; nombre: string }[]> { - const result = await prisma.$queryRawUnsafe<{ rfc: string; nombre: string }[]>(` +export async function getEmisores(pool: Pool, search: string, limit: number = 10): Promise<{ rfc: string; nombre: string }[]> { + const { rows } = await pool.query(` SELECT DISTINCT rfc_emisor as rfc, nombre_emisor as nombre - FROM "${schema}".cfdis + FROM cfdis WHERE rfc_emisor ILIKE $1 OR nombre_emisor ILIKE $1 ORDER BY nombre_emisor LIMIT $2 - `, `%${search}%`, limit); - return result; + `, [`%${search}%`, limit]); + return rows; } -export async function getReceptores(schema: string, search: string, limit: number = 10): Promise<{ rfc: string; nombre: string }[]> { - const result = await prisma.$queryRawUnsafe<{ rfc: string; nombre: string }[]>(` +export async function getReceptores(pool: Pool, search: string, limit: number = 10): Promise<{ rfc: string; nombre: string }[]> { + const { rows } = await pool.query(` SELECT DISTINCT rfc_receptor as rfc, nombre_receptor as nombre - FROM "${schema}".cfdis + FROM cfdis WHERE rfc_receptor ILIKE $1 OR nombre_receptor ILIKE $1 ORDER BY nombre_receptor LIMIT $2 - `, `%${search}%`, limit); - return result; + `, [`%${search}%`, limit]); + return rows; } -export async function getResumenCfdis(schema: string, año: number, mes: number) { - const result = await prisma.$queryRawUnsafe<[{ - total_ingresos: number; - total_egresos: number; - count_ingresos: number; - count_egresos: number; - iva_trasladado: number; - iva_acreditable: number; - }]>(` +export async function getResumenCfdis(pool: Pool, año: number, mes: number) { + const { rows } = await pool.query(` SELECT COALESCE(SUM(CASE WHEN tipo = 'ingreso' THEN total ELSE 0 END), 0) as total_ingresos, COALESCE(SUM(CASE WHEN tipo = 'egreso' THEN total ELSE 0 END), 0) as total_egresos, @@ -424,13 +400,13 @@ export async function getResumenCfdis(schema: string, año: number, mes: number) COUNT(CASE WHEN tipo = 'egreso' THEN 1 END) as count_egresos, COALESCE(SUM(CASE WHEN tipo = 'ingreso' THEN iva ELSE 0 END), 0) as iva_trasladado, COALESCE(SUM(CASE WHEN tipo = 'egreso' THEN iva ELSE 0 END), 0) as iva_acreditable - FROM "${schema}".cfdis + FROM cfdis WHERE estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 AND EXTRACT(MONTH FROM fecha_emision) = $2 - `, año, mes); + `, [año, mes]); - const r = result[0]; + const r = rows[0]; return { totalIngresos: Number(r?.total_ingresos || 0), totalEgresos: Number(r?.total_egresos || 0), diff --git a/apps/api/src/services/dashboard.service.ts b/apps/api/src/services/dashboard.service.ts index 9a011d0..c99c60e 100644 --- a/apps/api/src/services/dashboard.service.ts +++ b/apps/api/src/services/dashboard.service.ts @@ -1,44 +1,44 @@ -import { prisma } from '../config/database.js'; +import type { Pool } from 'pg'; 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 }]>(` +export async function getKpis(pool: Pool, año: number, mes: number): Promise { + const { rows: [ingresos] } = await pool.query(` SELECT COALESCE(SUM(total), 0) as total - FROM "${schema}".cfdis + FROM cfdis WHERE tipo = 'ingreso' AND estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 AND EXTRACT(MONTH FROM fecha_emision) = $2 - `, año, mes); + `, [año, mes]); - const [egresos] = await prisma.$queryRawUnsafe<[{ total: number }]>(` + const { rows: [egresos] } = await pool.query(` SELECT COALESCE(SUM(total), 0) as total - FROM "${schema}".cfdis + FROM cfdis WHERE tipo = 'egreso' AND estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 AND EXTRACT(MONTH FROM fecha_emision) = $2 - `, año, mes); + `, [año, mes]); - const [ivaData] = await prisma.$queryRawUnsafe<[{ trasladado: number; acreditable: number }]>(` + const { rows: [ivaData] } = await pool.query(` 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 + FROM cfdis WHERE estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 AND EXTRACT(MONTH FROM fecha_emision) = $2 - `, año, mes); + `, [año, mes]); - const [counts] = await prisma.$queryRawUnsafe<[{ emitidos: number; recibidos: number }]>(` + const { rows: [counts] } = await pool.query(` SELECT COUNT(CASE WHEN tipo = 'ingreso' THEN 1 END) as emitidos, COUNT(CASE WHEN tipo = 'egreso' THEN 1 END) as recibidos - FROM "${schema}".cfdis + FROM cfdis WHERE estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 AND EXTRACT(MONTH FROM fecha_emision) = $2 - `, año, mes); + `, [año, mes]); const ingresosVal = Number(ingresos?.total || 0); const egresosVal = Number(egresos?.total || 0); @@ -57,23 +57,23 @@ export async function getKpis(schema: string, año: number, mes: number): Promis }; } -export async function getIngresosEgresos(schema: string, año: number): Promise { - const data = await prisma.$queryRawUnsafe<{ mes: number; ingresos: number; egresos: number }[]>(` +export async function getIngresosEgresos(pool: Pool, año: number): Promise { + const { rows: data } = 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 "${schema}".cfdis + FROM cfdis WHERE estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 GROUP BY EXTRACT(MONTH FROM fecha_emision) ORDER BY mes - `, año); + `, [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); + const found = data.find((d: any) => d.mes === index + 1); return { mes, ingresos: Number(found?.ingresos || 0), @@ -82,16 +82,17 @@ export async function getIngresosEgresos(schema: string, año: number): Promise< }); } -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 +export async function getResumenFiscal(pool: Pool, año: number, mes: number): Promise { + const { rows: ivaRows } = await pool.query(` + SELECT resultado, acumulado FROM iva_mensual WHERE año = $1 AND mes = $2 - `, año, mes) || [{ resultado: 0, acumulado: 0 }]; + `, [año, mes]); + const ivaResult = ivaRows[0] || { resultado: 0, acumulado: 0 }; - const [pendientes] = await prisma.$queryRawUnsafe<[{ count: number }]>(` - SELECT COUNT(*) as count FROM "${schema}".iva_mensual + const { rows: [pendientes] } = await pool.query(` + SELECT COUNT(*) as count FROM iva_mensual WHERE año = $1 AND estado = 'pendiente' - `, año); + `, [año]); const resultado = Number(ivaResult?.resultado || 0); const acumulado = Number(ivaResult?.acumulado || 0); @@ -108,19 +109,19 @@ export async function getResumenFiscal(schema: string, año: number, mes: number }; } -export async function getAlertas(schema: string, limit = 5): Promise { - const alertas = await prisma.$queryRawUnsafe(` +export async function getAlertas(pool: Pool, limit = 5): Promise { + const { rows } = await pool.query(` SELECT id, tipo, titulo, mensaje, prioridad, fecha_vencimiento as "fechaVencimiento", leida, resuelta, created_at as "createdAt" - FROM "${schema}".alertas + FROM 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); + `, [limit]); - return alertas; + return rows; } diff --git a/apps/api/src/services/export.service.ts b/apps/api/src/services/export.service.ts index 9a84ab4..0635516 100644 --- a/apps/api/src/services/export.service.ts +++ b/apps/api/src/services/export.service.ts @@ -1,8 +1,8 @@ import ExcelJS from 'exceljs'; -import { prisma } from '../config/database.js'; +import type { Pool } from 'pg'; export async function exportCfdisToExcel( - schema: string, + pool: Pool, filters: { tipo?: string; estado?: string; fechaInicio?: string; fechaFin?: string } ): Promise { let whereClause = 'WHERE 1=1'; @@ -26,15 +26,15 @@ export async function exportCfdisToExcel( params.push(filters.fechaFin); } - const cfdis = await prisma.$queryRawUnsafe(` + const { rows: cfdis } = await pool.query(` SELECT uuid_fiscal, tipo, serie, folio, fecha_emision, fecha_timbrado, rfc_emisor, nombre_emisor, rfc_receptor, nombre_receptor, subtotal, descuento, iva, isr_retenido, iva_retenido, total, moneda, metodo_pago, forma_pago, uso_cfdi, estado - FROM "${schema}".cfdis + FROM cfdis ${whereClause} ORDER BY fecha_emision DESC - `, ...params); + `, params); const workbook = new ExcelJS.Workbook(); const sheet = workbook.addWorksheet('CFDIs'); @@ -63,7 +63,7 @@ export async function exportCfdisToExcel( }; sheet.getRow(1).font = { bold: true, color: { argb: 'FFFFFFFF' } }; - cfdis.forEach(cfdi => { + cfdis.forEach((cfdi: any) => { sheet.addRow({ ...cfdi, fecha_emision: new Date(cfdi.fecha_emision).toLocaleDateString('es-MX'), @@ -78,7 +78,7 @@ export async function exportCfdisToExcel( } export async function exportReporteToExcel( - schema: string, + pool: Pool, tipo: 'estado-resultados' | 'flujo-efectivo', fechaInicio: string, fechaFin: string @@ -87,13 +87,13 @@ export async function exportReporteToExcel( const sheet = workbook.addWorksheet(tipo === 'estado-resultados' ? 'Estado de Resultados' : 'Flujo de Efectivo'); if (tipo === 'estado-resultados') { - const [totales] = await prisma.$queryRawUnsafe<[{ ingresos: number; egresos: number }]>(` + const { rows: [totales] } = 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 - FROM "${schema}".cfdis + FROM cfdis WHERE estado = 'vigente' AND fecha_emision BETWEEN $1 AND $2 - `, fechaInicio, fechaFin); + `, [fechaInicio, fechaFin]); sheet.columns = [ { header: 'Concepto', key: 'concepto', width: 40 }, diff --git a/apps/api/src/services/impuestos.service.ts b/apps/api/src/services/impuestos.service.ts index 32b6654..32ae2e0 100644 --- a/apps/api/src/services/impuestos.service.ts +++ b/apps/api/src/services/impuestos.service.ts @@ -1,8 +1,8 @@ -import { prisma } from '../config/database.js'; +import type { Pool } from 'pg'; import type { IvaMensual, IsrMensual, ResumenIva, ResumenIsr } from '@horux/shared'; -export async function getIvaMensual(schema: string, año: number): Promise { - const data = await prisma.$queryRawUnsafe(` +export async function getIvaMensual(pool: Pool, año: number): Promise { + const { rows: data } = await pool.query(` SELECT id, año, mes, iva_trasladado as "ivaTrasladado", @@ -10,12 +10,12 @@ export async function getIvaMensual(schema: string, año: number): Promise ({ + return data.map((row: any) => ({ ...row, ivaTrasladado: Number(row.ivaTrasladado), ivaAcreditable: Number(row.ivaAcreditable), @@ -25,19 +25,18 @@ export async function getIvaMensual(schema: string, año: number): Promise { - // Get from iva_mensual if exists - const existing = await prisma.$queryRawUnsafe(` - SELECT * FROM "${schema}".iva_mensual WHERE año = $1 AND mes = $2 - `, año, mes); +export async function getResumenIva(pool: Pool, año: number, mes: number): Promise { + const { rows: existing } = await pool.query(` + SELECT * FROM iva_mensual WHERE año = $1 AND mes = $2 + `, [año, mes]); if (existing && existing.length > 0) { const record = existing[0]; - const [acumuladoResult] = await prisma.$queryRawUnsafe<[{ total: number }]>(` + const { rows: [acumuladoResult] } = await pool.query(` SELECT COALESCE(SUM(resultado), 0) as total - FROM "${schema}".iva_mensual + FROM iva_mensual WHERE año = $1 AND mes <= $2 - `, año, mes); + `, [año, mes]); return { trasladado: Number(record.iva_trasladado || 0), @@ -48,21 +47,16 @@ export async function getResumenIva(schema: string, año: number, mes: number): }; } - // Calculate from CFDIs if no iva_mensual record - const [calcResult] = await prisma.$queryRawUnsafe<[{ - trasladado: number; - acreditable: number; - retenido: number; - }]>(` + const { rows: [calcResult] } = await pool.query(` 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, COALESCE(SUM(iva_retenido), 0) as retenido - FROM "${schema}".cfdis + FROM cfdis WHERE estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 AND EXTRACT(MONTH FROM fecha_emision) = $2 - `, año, mes); + `, [año, mes]); const trasladado = Number(calcResult?.trasladado || 0); const acreditable = Number(calcResult?.acreditable || 0); @@ -78,10 +72,9 @@ export async function getResumenIva(schema: string, año: number, mes: number): }; } -export async function getIsrMensual(schema: string, año: number): Promise { - // Check if isr_mensual table exists +export async function getIsrMensual(pool: Pool, año: number): Promise { try { - const data = await prisma.$queryRawUnsafe(` + const { rows: data } = await pool.query(` SELECT id, año, mes, ingresos_acumulados as "ingresosAcumulados", @@ -92,12 +85,12 @@ export async function getIsrMensual(schema: string, año: number): Promise ({ + return data.map((row: any) => ({ ...row, ingresosAcumulados: Number(row.ingresosAcumulados), deducciones: Number(row.deducciones), @@ -107,43 +100,40 @@ export async function getIsrMensual(schema: string, año: number): Promise { - // Calculate from CFDIs - const [ingresos] = await prisma.$queryRawUnsafe<[{ total: number }]>(` +export async function getResumenIsr(pool: Pool, año: number, mes: number): Promise { + const { rows: [ingresos] } = await pool.query(` SELECT COALESCE(SUM(total), 0) as total - FROM "${schema}".cfdis + FROM cfdis WHERE tipo = 'ingreso' AND estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 AND EXTRACT(MONTH FROM fecha_emision) <= $2 - `, año, mes); + `, [año, mes]); - const [egresos] = await prisma.$queryRawUnsafe<[{ total: number }]>(` + const { rows: [egresos] } = await pool.query(` SELECT COALESCE(SUM(total), 0) as total - FROM "${schema}".cfdis + FROM cfdis WHERE tipo = 'egreso' AND estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 AND EXTRACT(MONTH FROM fecha_emision) <= $2 - `, año, mes); + `, [año, mes]); - const [retenido] = await prisma.$queryRawUnsafe<[{ total: number }]>(` + const { rows: [retenido] } = await pool.query(` SELECT COALESCE(SUM(isr_retenido), 0) as total - FROM "${schema}".cfdis + FROM cfdis WHERE estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 AND EXTRACT(MONTH FROM fecha_emision) <= $2 - `, año, mes); + `, [año, mes]); const ingresosAcumulados = Number(ingresos?.total || 0); const deducciones = Number(egresos?.total || 0); const baseGravable = Math.max(0, ingresosAcumulados - deducciones); - // Simplified ISR calculation (actual calculation would use SAT tables) - const isrCausado = baseGravable * 0.30; // 30% simplified rate + const isrCausado = baseGravable * 0.30; const isrRetenido = Number(retenido?.total || 0); const isrAPagar = Math.max(0, isrCausado - isrRetenido); diff --git a/apps/api/src/services/reportes.service.ts b/apps/api/src/services/reportes.service.ts index 6292207..67430d9 100644 --- a/apps/api/src/services/reportes.service.ts +++ b/apps/api/src/services/reportes.service.ts @@ -1,7 +1,6 @@ -import { prisma } from '../config/database.js'; +import type { Pool } from 'pg'; 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; @@ -14,37 +13,37 @@ function toNumber(value: unknown): number { } export async function getEstadoResultados( - schema: string, + pool: Pool, fechaInicio: string, fechaFin: string ): Promise { - const ingresos = await prisma.$queryRawUnsafe<{ rfc: string; nombre: string; total: unknown }[]>(` + const { rows: ingresos } = await pool.query(` SELECT rfc_receptor as rfc, nombre_receptor as nombre, SUM(subtotal) as total - FROM "${schema}".cfdis + 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); + `, [fechaInicio, fechaFin]); - const egresos = await prisma.$queryRawUnsafe<{ rfc: string; nombre: string; total: unknown }[]>(` + const { rows: egresos } = await pool.query(` SELECT rfc_emisor as rfc, nombre_emisor as nombre, SUM(subtotal) as total - FROM "${schema}".cfdis + 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); + `, [fechaInicio, fechaFin]); - const totalesResult = await prisma.$queryRawUnsafe<{ ingresos: unknown; egresos: unknown; iva: unknown }[]>(` + 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 "${schema}".cfdis + FROM cfdis WHERE estado = 'vigente' AND fecha_emision BETWEEN $1::date AND $2::date - `, fechaInicio, fechaFin); + `, [fechaInicio, fechaFin]); const totales = totalesResult[0]; const totalIngresos = toNumber(totales?.ingresos); @@ -54,8 +53,8 @@ export async function getEstadoResultados( 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) })), + 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, @@ -65,36 +64,36 @@ export async function getEstadoResultados( } export async function getFlujoEfectivo( - schema: string, + pool: Pool, fechaInicio: string, fechaFin: string ): Promise { - const entradas = await prisma.$queryRawUnsafe<{ mes: string; total: unknown }[]>(` + const { rows: entradas } = await pool.query(` SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, SUM(total) as total - FROM "${schema}".cfdis + 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); + `, [fechaInicio, fechaFin]); - const salidas = await prisma.$queryRawUnsafe<{ mes: string; total: unknown }[]>(` + const { rows: salidas } = await pool.query(` SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, SUM(total) as total - FROM "${schema}".cfdis + 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); + `, [fechaInicio, fechaFin]); - const totalEntradas = entradas.reduce((sum, e) => sum + toNumber(e.total), 0); - const totalSalidas = salidas.reduce((sum, s) => sum + toNumber(s.total), 0); + 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 => ({ concepto: e.mes, monto: toNumber(e.total) })), - salidas: salidas.map(s => ({ concepto: s.mes, monto: toNumber(s.total) })), + 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, @@ -103,36 +102,36 @@ export async function getFlujoEfectivo( } export async function getComparativo( - schema: string, + pool: Pool, año: number ): Promise { - const actual = await prisma.$queryRawUnsafe<{ mes: number; ingresos: unknown; egresos: unknown }[]>(` + 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 "${schema}".cfdis + FROM cfdis WHERE estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 GROUP BY mes ORDER BY mes - `, año); + `, [año]); - const anterior = await prisma.$queryRawUnsafe<{ mes: number; ingresos: unknown; egresos: unknown }[]>(` + 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 "${schema}".cfdis + FROM cfdis WHERE estado = 'vigente' AND EXTRACT(YEAR FROM fecha_emision) = $1 GROUP BY mes ORDER BY mes - `, año - 1); + `, [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 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, b) => a + toNumber(b.ingresos), 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, b) => a + toNumber(b.egresos), 0); + const totalAnteriorEgr = anterior.reduce((a: number, b: any) => a + toNumber(b.egresos), 0); return { periodos: meses, @@ -146,25 +145,25 @@ export async function getComparativo( } export async function getConcentradoRfc( - schema: string, + pool: Pool, 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 }[]>(` + 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 "${schema}".cfdis + 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 => ({ + `, [fechaInicio, fechaFin]); + return data.map((d: any) => ({ rfc: d.rfc, nombre: d.nombre, tipo: 'cliente' as const, @@ -173,19 +172,19 @@ export async function getConcentradoRfc( cantidadCfdis: d.cantidadCfdis })); } else { - const data = await prisma.$queryRawUnsafe<{ rfc: string; nombre: string; tipo: string; totalFacturado: unknown; totalIva: unknown; cantidadCfdis: number }[]>(` + 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 "${schema}".cfdis + 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 => ({ + `, [fechaInicio, fechaFin]); + return data.map((d: any) => ({ rfc: d.rfc, nombre: d.nombre, tipo: 'proveedor' as const, diff --git a/apps/api/src/services/sat/sat.service.ts b/apps/api/src/services/sat/sat.service.ts index c5869c9..ac699ce 100644 --- a/apps/api/src/services/sat/sat.service.ts +++ b/apps/api/src/services/sat/sat.service.ts @@ -1,4 +1,4 @@ -import { prisma } from '../../config/database.js'; +import { prisma, tenantDb } from '../../config/database.js'; import { getDecryptedFiel } from '../fiel.service.js'; import { createSatService, @@ -10,6 +10,7 @@ import { import { processPackage, type CfdiParsed } from './sat-parser.service.js'; import type { SatSyncJob, CfdiSyncType, SatSyncType } from '@horux/shared'; import type { Service } from '@nodecfdi/sat-ws-descarga-masiva'; +import type { Pool } from 'pg'; const POLL_INTERVAL_MS = 30000; // 30 segundos const MAX_POLL_ATTEMPTS = 60; // 30 minutos máximo @@ -20,7 +21,7 @@ interface SyncContext { service: Service; rfc: string; tenantId: string; - databaseName: string; + pool: Pool; } /** @@ -54,7 +55,7 @@ async function updateJobProgress( * Guarda los CFDIs en la base de datos del tenant */ async function saveCfdis( - databaseName: string, + pool: Pool, cfdis: CfdiParsed[], jobId: string ): Promise<{ inserted: number; updated: number }> { @@ -63,16 +64,14 @@ async function saveCfdis( for (const cfdi of cfdis) { try { - // Usar raw query para el esquema del tenant - const existing = await prisma.$queryRawUnsafe<{ id: string }[]>( - `SELECT id FROM "${databaseName}".cfdis WHERE uuid_fiscal = $1`, - cfdi.uuidFiscal + const { rows: existing } = await pool.query( + `SELECT id FROM cfdis WHERE uuid_fiscal = $1`, + [cfdi.uuidFiscal] ); if (existing.length > 0) { - // Actualizar CFDI existente - await prisma.$executeRawUnsafe( - `UPDATE "${databaseName}".cfdis SET + await pool.query( + `UPDATE cfdis SET tipo = $2, serie = $3, folio = $4, @@ -99,36 +98,37 @@ async function saveCfdis( sat_sync_job_id = $24::uuid, updated_at = NOW() WHERE uuid_fiscal = $1`, - cfdi.uuidFiscal, - cfdi.tipo, - cfdi.serie, - cfdi.folio, - cfdi.fechaEmision, - cfdi.fechaTimbrado, - cfdi.rfcEmisor, - cfdi.nombreEmisor, - cfdi.rfcReceptor, - cfdi.nombreReceptor, - cfdi.subtotal, - cfdi.descuento, - cfdi.iva, - cfdi.isrRetenido, - cfdi.ivaRetenido, - cfdi.total, - cfdi.moneda, - cfdi.tipoCambio, - cfdi.metodoPago, - cfdi.formaPago, - cfdi.usoCfdi, - cfdi.estado, - cfdi.xmlOriginal, - jobId + [ + cfdi.uuidFiscal, + cfdi.tipo, + cfdi.serie, + cfdi.folio, + cfdi.fechaEmision, + cfdi.fechaTimbrado, + cfdi.rfcEmisor, + cfdi.nombreEmisor, + cfdi.rfcReceptor, + cfdi.nombreReceptor, + cfdi.subtotal, + cfdi.descuento, + cfdi.iva, + cfdi.isrRetenido, + cfdi.ivaRetenido, + cfdi.total, + cfdi.moneda, + cfdi.tipoCambio, + cfdi.metodoPago, + cfdi.formaPago, + cfdi.usoCfdi, + cfdi.estado, + cfdi.xmlOriginal, + jobId + ] ); updated++; } else { - // Insertar nuevo CFDI - await prisma.$executeRawUnsafe( - `INSERT INTO "${databaseName}".cfdis ( + await pool.query( + `INSERT INTO cfdis ( id, uuid_fiscal, tipo, serie, folio, fecha_emision, fecha_timbrado, rfc_emisor, nombre_emisor, rfc_receptor, nombre_receptor, subtotal, descuento, iva, isr_retenido, iva_retenido, total, @@ -139,30 +139,32 @@ async function saveCfdis( $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, 'sat', $24::uuid, NOW(), NOW() )`, - cfdi.uuidFiscal, - cfdi.tipo, - cfdi.serie, - cfdi.folio, - cfdi.fechaEmision, - cfdi.fechaTimbrado, - cfdi.rfcEmisor, - cfdi.nombreEmisor, - cfdi.rfcReceptor, - cfdi.nombreReceptor, - cfdi.subtotal, - cfdi.descuento, - cfdi.iva, - cfdi.isrRetenido, - cfdi.ivaRetenido, - cfdi.total, - cfdi.moneda, - cfdi.tipoCambio, - cfdi.metodoPago, - cfdi.formaPago, - cfdi.usoCfdi, - cfdi.estado, - cfdi.xmlOriginal, - jobId + [ + cfdi.uuidFiscal, + cfdi.tipo, + cfdi.serie, + cfdi.folio, + cfdi.fechaEmision, + cfdi.fechaTimbrado, + cfdi.rfcEmisor, + cfdi.nombreEmisor, + cfdi.rfcReceptor, + cfdi.nombreReceptor, + cfdi.subtotal, + cfdi.descuento, + cfdi.iva, + cfdi.isrRetenido, + cfdi.ivaRetenido, + cfdi.total, + cfdi.moneda, + cfdi.tipoCambio, + cfdi.metodoPago, + cfdi.formaPago, + cfdi.usoCfdi, + cfdi.estado, + cfdi.xmlOriginal, + jobId + ] ); inserted++; } @@ -186,11 +188,9 @@ async function processDateRange( ): Promise<{ found: number; downloaded: number; inserted: number; updated: number }> { console.log(`[SAT] Procesando ${tipoCfdi} desde ${fechaInicio.toISOString()} hasta ${fechaFin.toISOString()}`); - // 1. Solicitar descarga const queryResult = await querySat(ctx.service, fechaInicio, fechaFin, tipoCfdi); if (!queryResult.success) { - // Código 5004 = No hay CFDIs en el rango if (queryResult.statusCode === '5004') { console.log('[SAT] No se encontraron CFDIs en el rango'); return { found: 0, downloaded: 0, inserted: 0, updated: 0 }; @@ -203,7 +203,6 @@ async function processDateRange( await updateJobProgress(jobId, { satRequestId: requestId }); - // 2. Esperar y verificar solicitud let verifyResult; let attempts = 0; @@ -227,7 +226,6 @@ async function processDateRange( throw new Error('Timeout esperando respuesta del SAT'); } - // 3. Descargar paquetes const packageIds = verifyResult.packageIds; await updateJobProgress(jobId, { satPackageIds: packageIds, @@ -249,17 +247,15 @@ async function processDateRange( continue; } - // 4. Procesar paquete (el contenido viene en base64) const cfdis = processPackage(downloadResult.packageContent); totalDownloaded += cfdis.length; console.log(`[SAT] Procesando ${cfdis.length} CFDIs del paquete`); - const { inserted, updated } = await saveCfdis(ctx.databaseName, cfdis, jobId); + const { inserted, updated } = await saveCfdis(ctx.pool, cfdis, jobId); totalInserted += inserted; totalUpdated += updated; - // Actualizar progreso const progress = Math.round(((i + 1) / packageIds.length) * 100); await updateJobProgress(jobId, { cfdisDownloaded: totalDownloaded, @@ -287,7 +283,6 @@ async function processInitialSync( customDateTo?: Date ): Promise { const ahora = new Date(); - // Usar fechas personalizadas si se proporcionan, sino calcular desde YEARS_TO_SYNC const inicioHistorico = customDateFrom || new Date(ahora.getFullYear() - YEARS_TO_SYNC, ahora.getMonth(), 1); const fechaFin = customDateTo || ahora; @@ -296,14 +291,12 @@ async function processInitialSync( let totalInserted = 0; let totalUpdated = 0; - // Procesar por meses para evitar límites del SAT let currentDate = new Date(inicioHistorico); while (currentDate < fechaFin) { const monthEnd = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0, 23, 59, 59); const rangeEnd = monthEnd > fechaFin ? fechaFin : monthEnd; - // Procesar emitidos try { const emitidos = await processDateRange(ctx, jobId, currentDate, rangeEnd, 'emitidos'); totalFound += emitidos.found; @@ -314,7 +307,6 @@ async function processInitialSync( console.error(`[SAT] Error procesando emitidos ${currentDate.toISOString()}:`, error.message); } - // Procesar recibidos try { const recibidos = await processDateRange(ctx, jobId, currentDate, rangeEnd, 'recibidos'); totalFound += recibidos.found; @@ -325,10 +317,8 @@ async function processInitialSync( console.error(`[SAT] Error procesando recibidos ${currentDate.toISOString()}:`, error.message); } - // Siguiente mes currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1); - // Pequeña pausa entre meses para no saturar el SAT await new Promise(resolve => setTimeout(resolve, 5000)); } @@ -352,7 +342,6 @@ async function processDailySync(ctx: SyncContext, jobId: string): Promise let totalInserted = 0; let totalUpdated = 0; - // Procesar emitidos del mes try { const emitidos = await processDateRange(ctx, jobId, inicioMes, ahora, 'emitidos'); totalFound += emitidos.found; @@ -363,7 +352,6 @@ async function processDailySync(ctx: SyncContext, jobId: string): Promise console.error('[SAT] Error procesando emitidos:', error.message); } - // Procesar recibidos del mes try { const recibidos = await processDateRange(ctx, jobId, inicioMes, ahora, 'recibidos'); totalFound += recibidos.found; @@ -391,7 +379,6 @@ export async function startSync( dateFrom?: Date, dateTo?: Date ): Promise { - // Obtener credenciales FIEL const decryptedFiel = await getDecryptedFiel(tenantId); if (!decryptedFiel) { throw new Error('No hay FIEL configurada o está vencida'); @@ -403,10 +390,8 @@ export async function startSync( password: decryptedFiel.password, }; - // Crear servicio SAT const service = createSatService(fielData); - // Obtener datos del tenant const tenant = await prisma.tenant.findUnique({ where: { id: tenantId }, select: { databaseName: true }, @@ -416,7 +401,6 @@ export async function startSync( throw new Error('Tenant no encontrado'); } - // Verificar que no haya sync activo const activeSync = await prisma.satSyncJob.findFirst({ where: { tenantId, @@ -428,7 +412,6 @@ export async function startSync( throw new Error('Ya hay una sincronización en curso'); } - // Crear job const now = new Date(); const job = await prisma.satSyncJob.create({ data: { @@ -446,7 +429,7 @@ export async function startSync( service, rfc: decryptedFiel.rfc, tenantId, - databaseName: tenant.databaseName, + pool: tenantDb.getPool(tenantId, tenant.databaseName), }; // Ejecutar sincronización en background