erp-construccion-backend-v2/src/modules/finance/controllers/reports.controller.ts
Adrian Flores Cortes 369f461695 [TS-FIX] fix: Fix TS7030 errors in finance controllers
- Add Promise<void> return type to async handlers with conditional returns
- Change 'return res.status()' to 'res.status(); return;' pattern
- Fixed controllers: ap, ar, bank-reconciliation, cash-flow, reports, accounting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 14:21:33 -06:00

420 lines
12 KiB
TypeScript

/**
* 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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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;
}