/** * ReportsController - Controlador de Reportes Financieros * * Endpoints para estados financieros y exportación. * * @module Finance */ import { Router, Request, Response } from 'express'; import { DataSource } from 'typeorm'; import { FinancialReportsService, ERPIntegrationService } from '../services'; export function createReportsController(dataSource: DataSource): Router { const router = Router(); const reportsService = new FinancialReportsService(dataSource); const integrationService = new ERPIntegrationService(dataSource); // ==================== ESTADOS FINANCIEROS ==================== /** * GET /balance-sheet * Genera balance general */ router.get('/balance-sheet', async (req: Request, res: Response) => { try { const ctx = { tenantId: req.headers['x-tenant-id'] as string, userId: (req as any).user?.id, }; const asOfDate = req.query.asOfDate ? new Date(req.query.asOfDate as string) : new Date(); const options = { projectId: req.query.projectId as string, }; const report = await reportsService.generateBalanceSheet(ctx, asOfDate, options); res.json(report); } catch (error) { res.status(500).json({ error: (error as Error).message }); } }); /** * GET /income-statement * Genera estado de resultados */ router.get('/income-statement', async (req: Request, res: Response): Promise => { try { const ctx = { tenantId: req.headers['x-tenant-id'] as string, userId: (req as any).user?.id, }; const periodStart = new Date(req.query.periodStart as string); const periodEnd = new Date(req.query.periodEnd as string); if (isNaN(periodStart.getTime()) || isNaN(periodEnd.getTime())) { res.status(400).json({ error: 'Fechas inválidas' }); return; } const options = { projectId: req.query.projectId as string, }; const report = await reportsService.generateIncomeStatement( ctx, periodStart, periodEnd, options ); res.json(report); } catch (error) { res.status(500).json({ error: (error as Error).message }); } }); /** * GET /cash-flow-statement * Genera estado de flujo de efectivo */ router.get('/cash-flow-statement', async (req: Request, res: Response): Promise => { try { const ctx = { tenantId: req.headers['x-tenant-id'] as string, userId: (req as any).user?.id, }; const periodStart = new Date(req.query.periodStart as string); const periodEnd = new Date(req.query.periodEnd as string); if (isNaN(periodStart.getTime()) || isNaN(periodEnd.getTime())) { res.status(400).json({ error: 'Fechas inválidas' }); return; } const options = { projectId: req.query.projectId as string, }; const report = await reportsService.generateCashFlowStatement( ctx, periodStart, periodEnd, options ); res.json(report); } catch (error) { res.status(500).json({ error: (error as Error).message }); } }); /** * GET /trial-balance * Genera balanza de comprobación */ router.get('/trial-balance', async (req: Request, res: Response): Promise => { try { const ctx = { tenantId: req.headers['x-tenant-id'] as string, userId: (req as any).user?.id, }; const periodStart = new Date(req.query.periodStart as string); const periodEnd = new Date(req.query.periodEnd as string); if (isNaN(periodStart.getTime()) || isNaN(periodEnd.getTime())) { res.status(400).json({ error: 'Fechas inválidas' }); return; } const options = { projectId: req.query.projectId as string, includeZeroBalances: req.query.includeZeroBalances === 'true', }; const report = await reportsService.generateTrialBalance( ctx, periodStart, periodEnd, options ); res.json(report); } catch (error) { res.status(500).json({ error: (error as Error).message }); } }); /** * GET /account-statement/:accountId * Genera estado de cuenta */ router.get('/account-statement/:accountId', async (req: Request, res: Response): Promise => { try { const ctx = { tenantId: req.headers['x-tenant-id'] as string, userId: (req as any).user?.id, }; const periodStart = new Date(req.query.periodStart as string); const periodEnd = new Date(req.query.periodEnd as string); if (isNaN(periodStart.getTime()) || isNaN(periodEnd.getTime())) { res.status(400).json({ error: 'Fechas inválidas' }); return; } const report = await reportsService.generateAccountStatement( ctx, req.params.accountId, periodStart, periodEnd ); res.json(report); } catch (error) { res.status(500).json({ error: (error as Error).message }); } }); /** * GET /summary * Obtiene resumen financiero */ router.get('/summary', async (req: Request, res: Response) => { try { const ctx = { tenantId: req.headers['x-tenant-id'] as string, userId: (req as any).user?.id, }; const summary = await reportsService.getFinancialSummary(ctx); res.json(summary); } catch (error) { res.status(500).json({ error: (error as Error).message }); } }); // ==================== EXPORTACIÓN ==================== /** * GET /export/sap * Exporta pólizas para SAP */ router.get('/export/sap', async (req: Request, res: Response): Promise => { try { const ctx = { tenantId: req.headers['x-tenant-id'] as string, userId: (req as any).user?.id, }; const periodStart = new Date(req.query.periodStart as string); const periodEnd = new Date(req.query.periodEnd as string); if (isNaN(periodStart.getTime()) || isNaN(periodEnd.getTime())) { res.status(400).json({ error: 'Fechas inválidas' }); return; } const options = { companyCode: req.query.companyCode as string, documentType: req.query.documentType as string, journalNumber: req.query.journalNumber ? parseInt(req.query.journalNumber as string) : undefined, }; const result = await integrationService.exportToSAP(ctx, periodStart, periodEnd, options); res.setHeader('Content-Type', 'text/plain'); res.setHeader('Content-Disposition', `attachment; filename="${result.filename}"`); res.send(result.data); } catch (error) { res.status(500).json({ error: (error as Error).message }); } }); /** * GET /export/contpaqi * Exporta pólizas para CONTPAQi */ router.get('/export/contpaqi', async (req: Request, res: Response): Promise => { try { const ctx = { tenantId: req.headers['x-tenant-id'] as string, userId: (req as any).user?.id, }; const periodStart = new Date(req.query.periodStart as string); const periodEnd = new Date(req.query.periodEnd as string); if (isNaN(periodStart.getTime()) || isNaN(periodEnd.getTime())) { res.status(400).json({ error: 'Fechas inválidas' }); return; } const options = { polizaTipo: req.query.polizaTipo ? parseInt(req.query.polizaTipo as string) : undefined, diario: req.query.diario ? parseInt(req.query.diario as string) : undefined, }; const result = await integrationService.exportToCONTPAQi( ctx, periodStart, periodEnd, options ); res.setHeader('Content-Type', 'text/plain'); res.setHeader('Content-Disposition', `attachment; filename="${result.filename}"`); res.send(result.data); } catch (error) { res.status(500).json({ error: (error as Error).message }); } }); /** * GET /export/cfdi-polizas * Exporta pólizas en formato CFDI */ router.get('/export/cfdi-polizas', async (req: Request, res: Response): Promise => { try { const ctx = { tenantId: req.headers['x-tenant-id'] as string, userId: (req as any).user?.id, }; const periodStart = new Date(req.query.periodStart as string); const periodEnd = new Date(req.query.periodEnd as string); if (isNaN(periodStart.getTime()) || isNaN(periodEnd.getTime())) { res.status(400).json({ error: 'Fechas inválidas' }); return; } const options = { tipoSolicitud: req.query.tipoSolicitud as string, numOrden: req.query.numOrden as string, numTramite: req.query.numTramite as string, }; const result = await integrationService.exportCFDIPolizas( ctx, periodStart, periodEnd, options ); res.setHeader('Content-Type', 'application/xml'); res.setHeader('Content-Disposition', `attachment; filename="${result.filename}"`); res.send(result.data); } catch (error) { res.status(500).json({ error: (error as Error).message }); } }); /** * GET /export/chart-of-accounts * Exporta catálogo de cuentas */ router.get('/export/chart-of-accounts', async (req: Request, res: Response) => { try { const ctx = { tenantId: req.headers['x-tenant-id'] as string, userId: (req as any).user?.id, }; const format = (req.query.format as 'csv' | 'xml' | 'json') || 'csv'; const result = await integrationService.exportChartOfAccounts(ctx, format); const contentType = format === 'xml' ? 'application/xml' : format === 'json' ? 'application/json' : 'text/csv'; res.setHeader('Content-Type', contentType); res.setHeader('Content-Disposition', `attachment; filename="${result.filename}"`); res.send(typeof result.data === 'object' ? JSON.stringify(result.data, null, 2) : result.data); } catch (error) { res.status(500).json({ error: (error as Error).message }); } }); /** * GET /export/trial-balance * Exporta balanza de comprobación */ router.get('/export/trial-balance', async (req: Request, res: Response): Promise => { try { const ctx = { tenantId: req.headers['x-tenant-id'] as string, userId: (req as any).user?.id, }; const periodStart = new Date(req.query.periodStart as string); const periodEnd = new Date(req.query.periodEnd as string); if (isNaN(periodStart.getTime()) || isNaN(periodEnd.getTime())) { res.status(400).json({ error: 'Fechas inválidas' }); return; } const format = (req.query.format as 'csv' | 'xml' | 'json') || 'csv'; const result = await integrationService.exportTrialBalance( ctx, periodStart, periodEnd, format ); const contentType = format === 'xml' ? 'application/xml' : format === 'json' ? 'application/json' : 'text/csv'; res.setHeader('Content-Type', contentType); res.setHeader('Content-Disposition', `attachment; filename="${result.filename}"`); res.send(typeof result.data === 'object' ? JSON.stringify(result.data, null, 2) : result.data); } catch (error) { res.status(500).json({ error: (error as Error).message }); } }); // ==================== IMPORTACIÓN ==================== /** * POST /import/chart-of-accounts * Importa catálogo de cuentas */ router.post('/import/chart-of-accounts', async (req: Request, res: Response): Promise => { try { const ctx = { tenantId: req.headers['x-tenant-id'] as string, userId: (req as any).user?.id, }; const { data, format } = req.body; if (!data || !format) { res.status(400).json({ error: 'Se requieren datos y formato' }); return; } const result = await integrationService.importChartOfAccounts(ctx, data, format); res.json(result); } catch (error) { res.status(400).json({ error: (error as Error).message }); } }); return router; }