Initial commit - Horux Despachos NL

This commit is contained in:
2026-05-03 16:47:53 -06:00
commit b00b677c54
647 changed files with 133843 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import * as ctrl from '../controllers/admin-addons.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.get('/catalogo', ctrl.listCatalogo);
router.put('/catalogo/:id', ctrl.updateCatalogoItem);
export default router;

View File

@@ -0,0 +1,11 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import * as ctrl from '../controllers/admin-clientes.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.get('/stats', ctrl.getStats);
router.get('/:tenantId/usuarios', ctrl.listUsuarios);
export default router;

View File

@@ -0,0 +1,12 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import * as ctrl from '../controllers/admin-dashboard.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.get('/metrics', ctrl.getMetrics);
router.get('/despachos', ctrl.listDespachos);
router.get('/activity', ctrl.getActivity);
export default router;

View File

@@ -0,0 +1,11 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import * as ctrl from '../controllers/admin-impersonate.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.post('/', ctrl.startImpersonation);
router.post('/stop', ctrl.stopImpersonation);
export default router;

View File

@@ -0,0 +1,34 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as alertasController from '../controllers/alertas.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
router.get('/', alertasController.getAlertas);
router.get('/automaticas', alertasController.getAlertasAutomaticas);
router.get('/manuales', alertasController.getManualesPendientes);
router.patch('/manuales/:id/resolver', alertasController.resolverAlertaManual);
router.get('/drilldown/lista-negra-clientes', alertasController.getListaNegraClientes);
router.get('/drilldown/lista-negra-proveedores', alertasController.getListaNegraProveedores);
router.get('/drilldown/concentracion-clientes', alertasController.getConcentracionClientes);
router.get('/drilldown/concentracion-proveedores', alertasController.getConcentracionProveedores);
router.get('/drilldown/discrepancia-regimen', alertasController.getDiscrepanciaRegimen);
router.get('/drilldown/cancelaciones', alertasController.getCancelados);
router.get('/drilldown/cancelaciones-periodo-anterior', alertasController.getCancelacionesPeriodoAnterior);
router.get('/drilldown/efectivo', alertasController.getEfectivo);
router.get('/drilldown/tipo-relacion-sospechosa', alertasController.getTipoRelacionSospechosa);
router.post('/descartar', alertasController.descartarCfdis);
router.delete('/descartar', alertasController.restaurarDescartados);
router.get('/descartados', alertasController.getDescartados);
router.get('/stats', alertasController.getStats);
router.post('/mark-all-read', alertasController.markAllAsRead);
router.get('/:id', alertasController.getAlerta);
router.post('/', alertasController.createAlerta);
router.patch('/:id', alertasController.updateAlerta);
router.delete('/:id', alertasController.deleteAlerta);
export { router as alertasRoutes };

View File

@@ -0,0 +1,13 @@
import { Router, type IRouter } from 'express';
import { authenticate, authorize } from '../middlewares/auth.middleware.js';
import * as auditLogController from '../controllers/audit-log.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(authorize('owner', 'cfo'));
// Solo admin global (verificado dentro del controller)
router.get('/', auditLogController.listAuditLog);
export { router as auditLogRoutes };

View File

@@ -0,0 +1,69 @@
import { Router, type IRouter } from 'express';
import rateLimit from 'express-rate-limit';
import * as authController from '../controllers/auth.controller.js';
import { authenticate } from '../middlewares/auth.middleware.js';
import { strictLimit } from '../middlewares/rate-limit.middleware.js';
const router: IRouter = Router();
// Rate limiting: 10 login attempts per 15 minutes per IP
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
message: { message: 'Demasiados intentos de login. Intenta de nuevo en 15 minutos.' },
standardHeaders: true,
legacyHeaders: false,
});
// Rate limiting: 3 registrations per hour per IP
const registerLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
max: 3,
message: { message: 'Demasiados registros. Intenta de nuevo en 1 hora.' },
standardHeaders: true,
legacyHeaders: false,
});
// Rate limiting: 20 refresh attempts per 15 minutes per IP
const refreshLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 20,
message: { message: 'Demasiadas solicitudes. Intenta de nuevo más tarde.' },
standardHeaders: true,
legacyHeaders: false,
});
// Rate limiting: 3 password reset requests per hour per IP — evita spam + enumeration
const passwordResetRequestLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
max: 3,
message: { message: 'Demasiadas solicitudes de recuperación. Intenta de nuevo en 1 hora.' },
standardHeaders: true,
legacyHeaders: false,
});
// Rate limiting: 10 confirm attempts per hour per IP — prevenir brute-force del token
const passwordResetConfirmLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
max: 10,
message: { message: 'Demasiados intentos. Intenta de nuevo más tarde.' },
standardHeaders: true,
legacyHeaders: false,
});
router.post('/register', registerLimiter, authController.register);
router.post('/login', loginLimiter, authController.login);
router.post('/refresh', refreshLimiter, authController.refresh);
router.post('/logout', authenticate, authController.logout);
router.get('/me', authenticate, authController.me);
router.post('/password-reset/request', passwordResetRequestLimiter, authController.requestPasswordReset);
router.post('/password-reset/confirm', passwordResetConfirmLimiter, authController.confirmPasswordReset);
// 10/hora — prevenir brute-force del currentPassword
router.post('/password-change', authenticate, strictLimit, authController.changePassword);
router.post('/logout-all', authenticate, authController.logoutAll);
router.post('/switch-tenant', authenticate, authController.switchTenant);
// Auto-dismiss del onboarding (lo llama el frontend cuando el user completa
// los pasos requeridos). Idempotente — múltiples llamadas no rompen.
router.post('/onboarding/dismiss', authenticate, authController.dismissOnboarding);
export { router as authRoutes };

View File

@@ -0,0 +1,16 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as bancosController from '../controllers/bancos.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
router.get('/', bancosController.getBancos);
router.post('/', bancosController.createBanco);
router.put('/:id', bancosController.updateBanco);
router.delete('/:id', bancosController.deleteBanco);
export { router as bancosRoutes };

View File

@@ -0,0 +1,23 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as calendarioController from '../controllers/calendario.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
// GET /api/calendario/generados — eventos fiscales + recordatorios custom
router.get('/generados', calendarioController.getEventosGenerados);
// POST /api/calendario — crear recordatorio custom
router.post('/', calendarioController.createRecordatorio);
// PATCH /api/calendario/:id — editar recordatorio custom
router.patch('/:id', calendarioController.updateRecordatorio);
// DELETE /api/calendario/:id — eliminar recordatorio custom
router.delete('/:id', calendarioController.deleteRecordatorio);
export { router as calendarioRoutes };

View File

@@ -0,0 +1,32 @@
import { Router, type IRouter } from 'express';
import { authenticate, authorize } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as ctrl from '../controllers/cartera.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
// Static routes first
router.get('/supervisores', authorize('owner'), ctrl.getSupervisores);
// Read: owner + supervisor + auxiliar
router.get('/', authorize('owner', 'supervisor', 'auxiliar'), ctrl.list);
router.get('/:id', authorize('owner', 'supervisor', 'auxiliar'), ctrl.getById);
router.get('/:id/subcarteras', authorize('owner', 'supervisor', 'auxiliar'), ctrl.listSubcarteras);
router.get('/:id/entidades', authorize('owner', 'supervisor', 'auxiliar'), ctrl.getEntidades);
router.get('/:id/auxiliares', authorize('owner', 'supervisor', 'auxiliar'), ctrl.getAuxiliares);
router.get('/:supervisorId/auxiliares-disponibles', authorize('owner', 'supervisor'), ctrl.getAuxiliaresDelSupervisor);
// Write: owner + supervisor (with permission checks in controller)
router.post('/', authorize('owner', 'supervisor'), ctrl.create);
router.put('/:id', authorize('owner', 'supervisor'), ctrl.update);
router.delete('/:id', authorize('owner', 'supervisor'), ctrl.remove);
router.post('/:id/subcarteras', authorize('owner', 'supervisor'), ctrl.createSubcartera);
router.post('/:id/entidades', authorize('owner', 'supervisor'), ctrl.addEntidad);
router.delete('/:id/entidades/:entidadId', authorize('owner', 'supervisor'), ctrl.removeEntidad);
router.post('/:id/auxiliares', authorize('owner', 'supervisor'), ctrl.addAuxiliar);
router.delete('/:id/auxiliares/:auxiliarUserId', authorize('owner', 'supervisor'), ctrl.removeAuxiliar);
export default router;

View File

@@ -0,0 +1,21 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { relaxedLimit } from '../middlewares/rate-limit.middleware.js';
import * as catalogosController from '../controllers/catalogos.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(relaxedLimit);
router.get('/forma-pago', catalogosController.getFormasPago);
router.get('/metodo-pago', catalogosController.getMetodosPago);
router.get('/uso-cfdi', catalogosController.getUsosCfdi);
router.get('/moneda', catalogosController.getMonedas);
router.get('/clave-unidad', catalogosController.getClavesUnidad);
router.get('/clave-prod-serv', catalogosController.searchClaveProdServ);
router.get('/objeto-imp', catalogosController.getObjetosImp);
router.get('/tipo-relacion', catalogosController.getTiposRelacion);
router.get('/exportacion', catalogosController.getExportaciones);
export { router as catalogosRoutes };

View File

@@ -0,0 +1,31 @@
import { Router, type IRouter } from 'express';
import express from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import { checkPlanLimits, checkCfdiLimit } from '../middlewares/plan-limits.middleware.js';
import { strictLimit } from '../middlewares/rate-limit.middleware.js';
import * as cfdiController from '../controllers/cfdi.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
router.use(checkPlanLimits);
router.get('/', cfdiController.getCfdis);
router.get('/resumen', cfdiController.getResumen);
router.get('/emisores', cfdiController.getEmisores);
router.get('/receptores', cfdiController.getReceptores);
router.get('/drill-down', cfdiController.drillDown);
// Listado de conceptos cross-CFDI (pestaña Conceptos en /cfdi).
// Debe registrarse antes que /:id para que Express no lo trate como id.
router.get('/conceptos', cfdiController.listConceptos);
router.get('/:id', cfdiController.getCfdiById);
router.get('/:id/conceptos', cfdiController.getConceptos);
router.get('/:id/xml', cfdiController.getXml);
router.post('/', checkCfdiLimit, cfdiController.createCfdi);
// Bulk upload: 10/hora — procesa hasta 50MB, pesado en parseo + inserts
router.post('/bulk', strictLimit, express.json({ limit: '50mb' }), checkCfdiLimit, cfdiController.createManyCfdis);
router.delete('/:id', cfdiController.deleteCfdi);
export { router as cfdiRoutes };

View File

@@ -0,0 +1,17 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import { requireFeature } from '../middlewares/feature-gate.middleware.js';
import * as conciliacionController from '../controllers/conciliacion.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
router.use(requireFeature('conciliacion'));
router.get('/', conciliacionController.getCfdis);
router.post('/', conciliacionController.conciliar);
router.delete('/:id', conciliacionController.desconciliar);
export { router as conciliacionRoutes };

View File

@@ -0,0 +1,15 @@
import { Router, type IRouter } from 'express';
import { authenticate, authorize } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as ctrl from '../controllers/connector.controller.js';
const router: IRouter = Router();
// Public endpoint — called by connector Docker container (no user JWT, uses HORUX_TOKEN)
router.post('/heartbeat', ctrl.heartbeat);
// Authenticated endpoints — for tenant owners managing their connector
router.get('/status', authenticate, tenantMiddleware, ctrl.status);
router.post('/provision', authenticate, tenantMiddleware, authorize('owner', 'cfo'), ctrl.provision);
export default router;

View File

@@ -0,0 +1,47 @@
import { Router, type IRouter } from 'express';
import { authenticate, authorize } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as ctrl from '../controllers/contribuyente.controller.js';
import * as configCtrl from '../controllers/contribuyente-config.controller.js';
import * as obligacionesCtrl from '../controllers/obligaciones.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
// === Static routes FIRST (before /:id to avoid route conflict) ===
router.get('/', ctrl.list);
router.post('/', authorize('owner', 'cfo'), ctrl.create);
router.post('/backfill', authorize('owner'), ctrl.backfill);
router.get('/catalogo-obligaciones', obligacionesCtrl.getCatalogo);
// === Dynamic routes with :id ===
router.get('/:id', ctrl.getById);
router.put('/:id', authorize('owner', 'cfo'), ctrl.update);
router.delete('/:id', authorize('owner'), ctrl.deactivate);
router.post('/:id/cliente-acceso', authorize('owner', 'supervisor'), ctrl.addClienteAcceso);
// FIEL per contribuyente
router.post('/:id/fiel', authorize('owner', 'cfo'), configCtrl.uploadFiel);
router.get('/:id/fiel/status', configCtrl.fielStatus);
router.delete('/:id/fiel', authorize('owner', 'cfo'), configCtrl.deleteFiel);
// Facturapi per contribuyente
router.post('/:id/facturapi/org', authorize('owner', 'cfo'), configCtrl.createOrg);
router.get('/:id/facturapi/status', configCtrl.orgStatus);
router.post('/:id/facturapi/csd', authorize('owner', 'cfo'), configCtrl.uploadCsd);
// Obligaciones fiscales per contribuyente
router.get('/:id/obligaciones/periodo', obligacionesCtrl.getObligacionesPorPeriodo);
router.get('/:id/obligaciones', obligacionesCtrl.getObligaciones);
router.post('/:id/obligaciones/init', authorize('owner', 'cfo'), obligacionesCtrl.initRecomendaciones);
router.post('/:id/obligaciones', authorize('owner', 'cfo'), obligacionesCtrl.addObligacion);
router.delete('/:id/obligaciones/:obligacionId', authorize('owner', 'cfo'), obligacionesCtrl.removeObligacion);
router.post('/:id/obligaciones/:obligacionId/restore', authorize('owner', 'cfo'), obligacionesCtrl.restoreObligacion);
router.post('/:id/obligaciones/:obligacionId/complete', authorize('owner', 'cfo', 'contador', 'auxiliar'), obligacionesCtrl.completeObligacion);
router.post('/:id/obligaciones/:obligacionId/uncomplete', authorize('owner', 'cfo', 'contador', 'auxiliar'), obligacionesCtrl.uncompleteObligacion);
router.post('/:id/obligaciones/:obligacionId/complete-periodo', authorize('owner', 'cfo', 'contador', 'auxiliar'), obligacionesCtrl.completePeriodo);
router.post('/:id/obligaciones/:obligacionId/uncomplete-periodo', authorize('owner', 'cfo', 'contador', 'auxiliar'), obligacionesCtrl.uncompletePeriodo);
export default router;

View File

@@ -0,0 +1,20 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import { checkPlanLimits } from '../middlewares/plan-limits.middleware.js';
import { normalLimit } from '../middlewares/rate-limit.middleware.js';
import * as dashboardController from '../controllers/dashboard.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(normalLimit);
router.use(tenantMiddleware);
router.use(checkPlanLimits);
router.get('/kpis', dashboardController.getKpis);
router.get('/ingresos-egresos', dashboardController.getIngresosEgresos);
router.get('/regimenes-periodo', dashboardController.getRegimenesDelPeriodo);
router.get('/alertas', dashboardController.getAlertas);
export { router as dashboardRoutes };

View File

@@ -0,0 +1,12 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as ctrl from '../controllers/despacho-audit.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
router.get('/', ctrl.getDespachoAuditLog);
export default router;

View File

@@ -0,0 +1,15 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as ctrl from '../controllers/despacho-stats.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
router.get('/contribuyentes-stats', ctrl.getContribuyentesStats);
router.get('/mis-asignados', ctrl.getMisAsignados);
router.get('/equipo-stats', ctrl.getEquipoStats);
export { router as despachoStatsRoutes };

View File

@@ -0,0 +1,17 @@
import { Router, type IRouter } from 'express';
import rateLimit from 'express-rate-limit';
import { signup, getMyPlan } from '../controllers/despacho.controller.js';
import { authenticate } from '../middlewares/auth.middleware.js';
const router: IRouter = Router();
const signupLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
max: 5,
message: { message: 'Demasiados intentos de registro. Intenta en una hora.' },
});
router.post('/signup', signupLimiter, signup);
router.get('/me/plan', authenticate, getMyPlan);
export default router;

View File

@@ -0,0 +1,38 @@
import { Router, type IRouter } from 'express';
import { authenticate, authorize } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import { requireFeature } from '../middlewares/feature-gate.middleware.js';
import { veryStrictLimit } from '../middlewares/rate-limit.middleware.js';
import * as documentosController from '../controllers/documentos.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
router.use(requireFeature('documentos'));
router.get('/opiniones', documentosController.listarOpiniones);
router.get('/opiniones/:id/pdf', documentosController.descargarPdf);
// 2/día — Playwright headless contra portal SAT es caro
router.post('/opiniones/consultar', veryStrictLimit, authorize('owner', 'cfo'), documentosController.consultarManual);
// Constancia de Situación Fiscal
router.get('/constancias', documentosController.listarConstancias);
router.get('/constancias/:id/pdf', documentosController.descargarConstanciaPdf);
router.post('/constancias/consultar', veryStrictLimit, authorize('owner', 'cfo'), documentosController.consultarConstanciaManual);
// Declaraciones provisionales
router.get('/declaraciones', documentosController.listarDeclaraciones);
router.post('/declaraciones', documentosController.crearDeclaracion);
router.post('/declaraciones/:id/comprobante-pago', documentosController.subirComprobantePago);
router.get('/declaraciones/:id/pdf/:variant', documentosController.descargarDeclaracionPdf);
router.delete('/declaraciones/:id', documentosController.eliminarDeclaracion);
// Extras (PDFs libres)
router.get('/extras', documentosController.listarExtras);
router.get('/extras/categorias', documentosController.listarCategoriasExtras);
router.post('/extras', documentosController.crearExtra);
router.get('/extras/:id/pdf', documentosController.descargarExtraPdf);
router.delete('/extras/:id', documentosController.eliminarExtra);
export { router as documentosRoutes };

View File

@@ -0,0 +1,14 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as exportController from '../controllers/export.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
router.get('/cfdis', exportController.exportCfdis);
router.get('/reporte', exportController.exportReporte);
export { router as exportRoutes };

View File

@@ -0,0 +1,64 @@
import { Router, type IRouter } from 'express';
import { authenticate, authorize } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import { strictLimit } from '../middlewares/rate-limit.middleware.js';
import * as facturacionController from '../controllers/facturacion.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
// Organización Facturapi
router.get('/org/status', facturacionController.getOrgStatus);
router.post('/org', authorize('owner', 'cfo'), facturacionController.createOrg);
// CSD
router.post('/csd', authorize('owner', 'cfo'), facturacionController.uploadCsd);
// Estado LCO — banner "CSD en proceso de validación" si hubo rechazo del SAT en últimas 24h
router.get('/lco-status', facturacionController.getLcoStatus);
// Emisión y cancelación (admin + contador). 10/hora — cada emisión quema timbre + side-effect en Facturapi/SAT
router.post('/emitir', strictLimit, facturacionController.emitir);
router.post('/cancelar/:uuid', strictLimit, facturacionController.cancelar);
// Descargas
router.get('/pdf/:id', facturacionController.downloadPdf);
router.get('/xml/:id', facturacionController.downloadXml);
// Timbres
router.get('/timbres', facturacionController.getTimbres);
router.get('/timbres/paquetes-catalogo', facturacionController.getPaquetesCatalogo);
// 10/h — cada compra crea MP Preference (side-effect en tercero)
router.post('/timbres/paquetes/comprar', strictLimit, facturacionController.comprarPaquete);
// Admin global: catálogo completo + editar precios
router.get('/timbres/paquetes-catalogo/admin', facturacionController.getPaquetesCatalogoAdmin);
router.put('/timbres/paquetes-catalogo/:id', facturacionController.updatePaqueteCatalogo);
// Personalización (logo, color)
router.get('/customization', facturacionController.getCustomization);
router.post('/logo', facturacionController.uploadLogo);
router.put('/color', facturacionController.updateColor);
// Datos fiscales del tenant
router.get('/datos-fiscales', facturacionController.getDatosFiscales);
router.put('/datos-fiscales', facturacionController.updateDatosFiscales);
// Preferencias de auto-facturación de pagos de suscripción (Fase 2)
router.get('/preferencias-facturacion', facturacionController.getPreferenciasFacturacion);
router.put('/preferencias-facturacion', facturacionController.updatePreferenciasFacturacion);
// Búsqueda de RFCs para autocompletar
router.get('/rfcs/search', facturacionController.searchRfcs);
// Búsqueda de conceptos previos
router.get('/conceptos/search', facturacionController.searchConceptos);
// CFDIs PPD pendientes de pago por RFC
router.get('/cfdis-ppd', facturacionController.getCfdisPpdPendientes);
// CFDIs emitidos por el contribuyente al receptor (para sección "CFDIs relacionados")
router.get('/cfdis-relacionables', facturacionController.getCfdisRelacionables);
export { router as facturacionRoutes };

View File

@@ -0,0 +1,20 @@
import { Router, type IRouter } from 'express';
import * as fielController from '../controllers/fiel.controller.js';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
// POST /api/fiel/upload - Subir credenciales FIEL
router.post('/upload', fielController.upload);
// GET /api/fiel/status - Obtener estado de la FIEL
router.get('/status', fielController.status);
// DELETE /api/fiel - Eliminar credenciales FIEL
router.delete('/', fielController.remove);
export default router;

View File

@@ -0,0 +1,30 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import { normalLimit } from '../middlewares/rate-limit.middleware.js';
import * as impuestosController from '../controllers/impuestos.controller.js';
import * as activosFijosController from '../controllers/activos-fijos.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(normalLimit);
router.use(tenantMiddleware);
router.get('/iva/mensual', impuestosController.getIvaMensual);
router.get('/iva/resumen', impuestosController.getResumenIva);
router.get('/isr/mensual', impuestosController.getIsrMensual);
router.get('/isr/resumen', impuestosController.getResumenIsr);
router.get('/isr/resumen-desglosado', impuestosController.getResumenIsrDesglosado);
router.get('/isr/coeficiente', impuestosController.getCoeficiente);
router.put('/isr/coeficiente', impuestosController.setCoeficiente);
// Activos fijos: vista informativa de deducción mensual proporcional.
// NO afecta gastos ni ISR — el SAT y el dashboard tratan estos CFDIs
// como gasto del periodo (igual que antes).
router.get('/activos-fijos', activosFijosController.list);
router.put('/activos-fijos/usos-excluidos', activosFijosController.setUsosExcluidos);
router.post('/activos-fijos/:cfdiId/baja', activosFijosController.darDeBaja);
router.delete('/activos-fijos/:cfdiId/baja', activosFijosController.revertirBaja);
export { router as impuestosRoutes };

View File

@@ -0,0 +1,12 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as ctrl from '../controllers/metricas.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
router.get('/mensuales', ctrl.getMensuales);
export default router;

View File

@@ -0,0 +1,14 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as ctrl from '../controllers/notification-preferences.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
router.get('/', ctrl.listPreferences);
router.put('/', ctrl.updatePreferences);
export { router as notificationPreferencesRoutes };

View File

@@ -0,0 +1,18 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as ctrl from '../controllers/papeleria.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
router.get('/', ctrl.list);
router.post('/', ctrl.upload);
router.get('/:id/download', ctrl.download);
router.post('/:id/aprobar', ctrl.aprobar);
router.post('/:id/rechazar', ctrl.rechazar);
router.delete('/:id', ctrl.eliminar);
export { router as papeleriaRoutes };

View File

@@ -0,0 +1,17 @@
import { Router, type IRouter } from 'express';
import * as ctrl from '../controllers/plan-catalogo.controller.js';
import { authenticate } from '../middlewares/auth.middleware.js';
const router: IRouter = Router();
// Public endpoints — no auth needed (pricing page)
router.get('/', ctrl.getPlans);
router.get('/addons', ctrl.getAddons);
// Catálogo despacho — admin only (require auth)
router.get('/despacho', authenticate, ctrl.listDespachoCatalogo);
router.patch('/despacho/:plan', authenticate, ctrl.updateDespachoCatalogo);
router.get('/:codename', ctrl.getPlan);
export default router;

View File

@@ -0,0 +1,16 @@
import { Router, type IRouter } from 'express';
import { authenticate, authorize } from '../middlewares/auth.middleware.js';
import * as platformStaffController from '../controllers/platform-staff.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(authorize('owner', 'cfo'));
// Solo platform_admin (verificado dentro del controller)
router.get('/', platformStaffController.listStaff);
router.get('/search', platformStaffController.searchUsers);
router.post('/grant', platformStaffController.grantRole);
router.post('/revoke', platformStaffController.revokeRole);
export { router as platformStaffRoutes };

View File

@@ -0,0 +1,28 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import { relaxedLimit } from '../middlewares/rate-limit.middleware.js';
import * as regimenController from '../controllers/regimen.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(relaxedLimit);
router.use(tenantMiddleware);
// GET /api/regimenes — catálogo completo
router.get('/', regimenController.getAllRegimenes);
// GET /api/regimenes/activos — regímenes activos del tenant
router.get('/activos', regimenController.getActivos);
// PUT /api/regimenes/activos — configurar regímenes activos
router.put('/activos', regimenController.setActivos);
// GET /api/regimenes/ignorados — regímenes ignorados del tenant actual
router.get('/ignorados', regimenController.getIgnorados);
// PUT /api/regimenes/ignorados — configurar regímenes ignorados
router.put('/ignorados', regimenController.setIgnorados);
export { router as regimenRoutes };

View File

@@ -0,0 +1,24 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import { checkPlanLimits } from '../middlewares/plan-limits.middleware.js';
import { requireFeature } from '../middlewares/feature-gate.middleware.js';
import { normalLimit } from '../middlewares/rate-limit.middleware.js';
import * as reportesController from '../controllers/reportes.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(normalLimit);
router.use(tenantMiddleware);
router.use(checkPlanLimits);
router.use(requireFeature('reportes'));
router.get('/estado-resultados', reportesController.getEstadoResultados);
router.get('/flujo-efectivo', reportesController.getFlujoEfectivo);
router.get('/comparativo', reportesController.getComparativo);
router.get('/concentrado-rfc', reportesController.getConcentradoRfc);
router.get('/cuentas-x-pagar', reportesController.getCuentasXPagar);
router.get('/cuentas-x-cobrar', reportesController.getCuentasXCobrar);
export { router as reportesRoutes };

View File

@@ -0,0 +1,31 @@
import { Router, type IRouter } from 'express';
import * as satController from '../controllers/sat.controller.js';
import { authenticate, authorize } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import { veryStrictLimit } from '../middlewares/rate-limit.middleware.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
// POST /api/sat/sync - Iniciar sincronización manual (2/día por user, admin global exento)
router.post('/sync', veryStrictLimit, satController.start);
// GET /api/sat/sync/status - Estado actual de sincronización
router.get('/sync/status', satController.status);
// GET /api/sat/sync/history - Historial de sincronizaciones
router.get('/sync/history', satController.history);
// GET /api/sat/sync/:id - Detalle de un job
router.get('/sync/:id', satController.jobDetail);
// POST /api/sat/sync/:id/retry - Reintentar job fallido
router.post('/sync/:id/retry', satController.retry);
// Admin-only cron endpoints (global admin verified in controller)
router.get('/cron', authorize('owner', 'cfo'), satController.cronInfo);
router.post('/cron/run', authorize('owner', 'cfo'), satController.runCron);
export default router;

View File

@@ -0,0 +1,37 @@
import { Router, type IRouter } from 'express';
import { authenticate, authorize } from '../middlewares/auth.middleware.js';
import { strictLimit } from '../middlewares/rate-limit.middleware.js';
import * as subscriptionController from '../controllers/subscription.controller.js';
const router: IRouter = Router();
// All endpoints require authentication + admin role
router.use(authenticate);
router.use(authorize('owner', 'cfo'));
// (Endpoints /plans y /plans/:id eliminados con el modelo PlanPrice legacy.
// Para precios despacho usa /api/planes/despacho.)
// Self-serve: el usuario actúa sobre el tenant de su JWT
router.post('/me/trial', subscriptionController.startMyTrial);
// 10/hora — crea preapprovals/preferences en MercadoPago (side-effect en tercero)
router.post('/me/subscribe', strictLimit, subscriptionController.subscribeMe);
router.post('/me/change', strictLimit, subscriptionController.changeMyPlan);
router.post('/me/upgrade', strictLimit, subscriptionController.upgradeMe);
router.post('/me/upgrade/cancel', subscriptionController.cancelMyPendingUpgrade);
router.post('/me/cancel', subscriptionController.cancelMySubscription);
router.post('/me/reactivate', subscriptionController.reactivateMe);
router.get('/me/addons', subscriptionController.getMyAddons);
router.post('/me/addons', strictLimit, subscriptionController.addMyAddon);
router.delete('/me/addons/:addonId', subscriptionController.cancelMyAddon);
// Listar todas las suscripciones (global admin)
router.get('/', subscriptionController.getAllSubscriptions);
// Admin subscription management (global admin verified in controller)
router.get('/:tenantId', subscriptionController.getSubscription);
router.post('/:tenantId/generate-link', subscriptionController.generatePaymentLink);
router.post('/:tenantId/mark-paid', subscriptionController.markAsPaid);
router.get('/:tenantId/payments', subscriptionController.getPayments);
export { router as subscriptionRoutes };

View File

@@ -0,0 +1,20 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as ctrl from '../controllers/tareas.controller.js';
const router: IRouter = Router();
router.use(authenticate);
router.use(tenantMiddleware);
router.get('/', ctrl.listTareas);
router.post('/', ctrl.createTarea);
router.post('/seed', ctrl.seedDefaults);
router.patch('/:id', ctrl.updateTarea);
router.delete('/:id', ctrl.deleteTarea);
router.post('/periodo/:id/completar', ctrl.completarPeriodo);
router.delete('/periodo/:id/completar', ctrl.descompletarPeriodo);
export { router as tareasRoutes };

View File

@@ -0,0 +1,20 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import * as tenantsController from '../controllers/tenants.controller.js';
const router: IRouter = Router();
router.use(authenticate);
// Self-serve multi-tenant (el caller actúa sobre sus propias memberships)
router.get('/mine', tenantsController.getMyTenants);
router.post('/mine', tenantsController.addMyTenant);
// Admin global
router.get('/', tenantsController.getAllTenants);
router.get('/:id', tenantsController.getTenant);
router.post('/', tenantsController.createTenant);
router.put('/:id', tenantsController.updateTenant);
router.delete('/:id', tenantsController.deleteTenant);
export { router as tenantsRoutes };

View File

@@ -0,0 +1,29 @@
import { Router, type IRouter } from 'express';
import { authenticate } from '../middlewares/auth.middleware.js';
import { tenantMiddleware } from '../middlewares/tenant.middleware.js';
import * as usuariosController from '../controllers/usuarios.controller.js';
const router: IRouter = Router();
router.use(authenticate);
// Rutas por tenant
router.get('/', usuariosController.getUsuarios);
router.post('/invite', usuariosController.inviteUsuario);
router.patch('/:id', usuariosController.updateUsuario);
router.delete('/:id', usuariosController.deleteUsuario);
// Cliente accesos (requires tenantPool)
router.get('/:id/accesos', tenantMiddleware, usuariosController.getClienteAccesos);
router.post('/:id/accesos', tenantMiddleware, usuariosController.setClienteAccesos);
// Supervisor de auxiliar (requires tenantPool)
router.get('/:id/supervisor', tenantMiddleware, usuariosController.getSupervisor);
router.put('/:id/supervisor', tenantMiddleware, usuariosController.updateSupervisor);
// Rutas globales (solo admin global)
router.get('/global/all', usuariosController.getAllUsuarios);
router.patch('/global/:id', usuariosController.updateUsuarioGlobal);
router.delete('/global/:id', usuariosController.deleteUsuarioGlobal);
export { router as usuariosRoutes };

View File

@@ -0,0 +1,9 @@
import { Router, type IRouter } from 'express';
import { handleMercadoPagoWebhook } from '../controllers/webhook.controller.js';
const router: IRouter = Router();
// Public endpoint — no auth middleware
router.post('/mercadopago', handleMercadoPagoWebhook);
export { router as webhookRoutes };