import type { Request, Response, NextFunction } from 'express'; import * as cfdiService from '../services/cfdi.service.js'; import { AppError } from '../middlewares/error.middleware.js'; import type { CfdiFilters } from '@horux/shared'; export async function getCfdis(req: Request, res: Response, next: NextFunction) { try { if (!req.tenantPool) { return next(new AppError(400, 'Tenant no configurado')); } const filters: CfdiFilters = { tipo: req.query.tipo as any, estado: req.query.estado as any, fechaInicio: req.query.fechaInicio as string, fechaFin: req.query.fechaFin as string, rfc: req.query.rfc as string, emisor: req.query.emisor as string, receptor: req.query.receptor as string, search: req.query.search as string, page: parseInt(req.query.page as string) || 1, limit: parseInt(req.query.limit as string) || 20, }; const result = await cfdiService.getCfdis(req.tenantPool, filters); res.json(result); } catch (error) { next(error); } } export async function getCfdiById(req: Request, res: Response, next: NextFunction) { try { if (!req.tenantPool) { return next(new AppError(400, 'Tenant no configurado')); } const cfdi = await cfdiService.getCfdiById(req.tenantPool, String(req.params.id)); if (!cfdi) { return next(new AppError(404, 'CFDI no encontrado')); } res.json(cfdi); } catch (error) { next(error); } } export async function getXml(req: Request, res: Response, next: NextFunction) { try { if (!req.tenantPool) { return next(new AppError(400, 'Tenant no configurado')); } const xml = await cfdiService.getXmlById(req.tenantPool, String(req.params.id)); if (!xml) { return next(new AppError(404, 'XML no encontrado para este CFDI')); } res.set('Content-Type', 'application/xml'); res.set('Content-Disposition', `attachment; filename="cfdi-${req.params.id}.xml"`); res.send(xml); } catch (error) { next(error); } } export async function getEmisores(req: Request, res: Response, next: NextFunction) { try { if (!req.tenantPool) { return next(new AppError(400, 'Tenant no configurado')); } const search = (req.query.search as string) || ''; if (search.length < 2) { return res.json([]); } const emisores = await cfdiService.getEmisores(req.tenantPool, search); res.json(emisores); } catch (error) { next(error); } } export async function getReceptores(req: Request, res: Response, next: NextFunction) { try { if (!req.tenantPool) { return next(new AppError(400, 'Tenant no configurado')); } const search = (req.query.search as string) || ''; if (search.length < 2) { return res.json([]); } const receptores = await cfdiService.getReceptores(req.tenantPool, search); res.json(receptores); } catch (error) { next(error); } } export async function getResumen(req: Request, res: Response, next: NextFunction) { try { 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.tenantPool, año, mes); res.json(resumen); } catch (error) { next(error); } } export async function createCfdi(req: Request, res: Response, next: NextFunction) { try { 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 agregar CFDIs')); } const cfdi = await cfdiService.createCfdi(req.tenantPool, req.body); res.status(201).json(cfdi); } catch (error: any) { if (error.message?.includes('duplicate')) { return next(new AppError(409, 'Este CFDI ya existe (UUID duplicado)')); } next(error); } } export async function createManyCfdis(req: Request, res: Response, next: NextFunction) { try { 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 agregar CFDIs')); } if (!Array.isArray(req.body.cfdis)) { return next(new AppError(400, 'Se requiere un array de CFDIs')); } const batchInfo = { batchNumber: req.body.batchNumber || 1, totalBatches: req.body.totalBatches || 1, totalFiles: req.body.totalFiles || req.body.cfdis.length }; console.log(`[CFDI Bulk] Lote ${batchInfo.batchNumber}/${batchInfo.totalBatches} - ${req.body.cfdis.length} CFDIs`); const result = await cfdiService.createManyCfdisBatch(req.tenantPool, req.body.cfdis); res.status(201).json({ message: `Lote ${batchInfo.batchNumber} procesado`, batchNumber: batchInfo.batchNumber, totalBatches: batchInfo.totalBatches, inserted: result.inserted, duplicates: result.duplicates, errors: result.errors, errorMessages: result.errorMessages.slice(0, 5) }); } catch (error: any) { console.error('[CFDI Bulk Error]', error.message, error.stack); next(new AppError(400, error.message || 'Error al procesar CFDIs')); } } export async function deleteCfdi(req: Request, res: Response, next: NextFunction) { try { 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.tenantPool, String(req.params.id)); res.status(204).send(); } catch (error) { next(error); } }