Files
Horux360/apps/api/src/app.ts
Consultoria AS 706d9694f1 feat: SAT sync improvements, XML export, and operational fixes
SAT sync enhancements:
- Filter active (vigente) CFDIs only via DocumentStatus to avoid SAT
  rejecting recibidos with "No se permite descarga de XML cancelados"
- Reclassify CFDIs at save time: tipo='ingreso' received by tenant
  becomes 'egreso' based on RFC (emisor vs receptor)
- Fix pool cleanup bug during long syncs: refresh getPool() on each
  saveCfdis call instead of holding stale reference for 45+ minutes
- Add X-View-Tenant support to SAT controller via viewingTenantId
- Add tenantMiddleware to SAT routes for global admin impersonation

Cron jobs:
- Add separate every-6-hours schedule for specific RFCs
- ROEM691011EZ4 configured for frequent sync (00, 06, 12, 18 MX time)

XML filesystem export:
- Write .xml files to /var/horux/xml/<RFC>/YYYY/MM/UUID.xml
- Activated per-RFC via XML_EXPORT_RFCS allowlist
- Organized by year/month for browsability

Auth improvements:
- Send welcome + admin-notification emails on /auth/register
  (previously only /tenants createTenant flow sent emails)
- Set role='contador' for self-registered users (not admin) to prevent
  new tenants from accessing cross-tenant data

Infrastructure:
- Set express trust proxy=1 to accept X-Forwarded-For from Nginx
  (fixes ERR_ERL_UNEXPECTED_X_FORWARDED_FOR from rate limiter)

Operational scripts:
- setup-horux360-tenant.ts: Provision Horux 360 tenant manually
- send-welcome-aaron.ts: Resend welcome email for Aaron (registered
  before welcome-on-register was added)
- export-xmls-roem.ts: Backfill filesystem XMLs from DB for ROEM

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 00:35:14 +00:00

62 lines
2.2 KiB
TypeScript

import express, { type Express } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { env, getCorsOrigins } from './config/env.js';
import { errorMiddleware } from './middlewares/error.middleware.js';
import { authRoutes } from './routes/auth.routes.js';
import { dashboardRoutes } from './routes/dashboard.routes.js';
import { cfdiRoutes } from './routes/cfdi.routes.js';
import { impuestosRoutes } from './routes/impuestos.routes.js';
import { exportRoutes } from './routes/export.routes.js';
import { alertasRoutes } from './routes/alertas.routes.js';
import { calendarioRoutes } from './routes/calendario.routes.js';
import { reportesRoutes } from './routes/reportes.routes.js';
import { usuariosRoutes } from './routes/usuarios.routes.js';
import { tenantsRoutes } from './routes/tenants.routes.js';
import fielRoutes from './routes/fiel.routes.js';
import satRoutes from './routes/sat.routes.js';
import { webhookRoutes } from './routes/webhook.routes.js';
import { subscriptionRoutes } from './routes/subscription.routes.js';
const app: Express = express();
// Trust Nginx reverse proxy (for correct IP in rate limiting)
app.set('trust proxy', 1);
// Security
app.use(helmet());
app.use(cors({
origin: getCorsOrigins(),
credentials: true,
}));
// Body parsing - 10MB default, bulk CFDI route has its own higher limit
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// API Routes
app.use('/api/auth', authRoutes);
app.use('/api/dashboard', dashboardRoutes);
app.use('/api/cfdi', cfdiRoutes);
app.use('/api/impuestos', impuestosRoutes);
app.use('/api/export', exportRoutes);
app.use('/api/alertas', alertasRoutes);
app.use('/api/calendario', calendarioRoutes);
app.use('/api/reportes', reportesRoutes);
app.use('/api/usuarios', usuariosRoutes);
app.use('/api/tenants', tenantsRoutes);
app.use('/api/fiel', fielRoutes);
app.use('/api/sat', satRoutes);
app.use('/api/webhooks', webhookRoutes);
app.use('/api/subscriptions', subscriptionRoutes);
// Error handling
app.use(errorMiddleware);
export { app };