const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const compression = require('compression'); const rateLimit = require('express-rate-limit'); require('dotenv').config(); // Configuración de PostgreSQL const { Pool } = require('pg'); const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 20, idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, }); // Establecer search_path para incluir el esquema dtic pool.on('connect', (client) => { client.query('SET search_path TO dtic, public'); }); // Importar rutas const authRoutes = require('./routes/auth'); const tecnicosRoutes = require('./routes/tecnicos'); const tareasRoutes = require('./routes/tareas'); const recursosRoutes = require('./routes/recursos'); const usuariosRelacionadosRoutes = require('./routes/usuarios_relacionados'); const tareaRecursosRoutes = require('./routes/tarea-recursos'); const systemRoutes = require('./routes/system'); // Configuración de la aplicación const app = express(); const PORT = process.env.PORT || 3001; // Middleware de seguridad app.use(helmet({ crossOriginResourcePolicy: { policy: "cross-origin" } })); // Rate limiting - más permisivo para desarrollo const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutos max: 1000, // límite de 1000 requests por windowMs (aumentado para desarrollo) message: 'Demasiadas solicitudes desde esta IP, por favor intenta más tarde.' }); app.use('/api/', limiter); // Rate limiting más estricto para autenticación const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutos max: 5, // límite de 5 requests por windowMs message: 'Demasiados intentos de autenticación, por favor intenta más tarde.' }); app.use('/api/auth/login', authLimiter); // CORS - Allow all origins for development and remote access app.use(cors({ origin: true, // Allow all origins credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'] })); // Compresión app.use(compression()); // Body parsing with logging app.use(express.json({ limit: '10mb', verify: (req, res, buf) => { console.log(`[PAYLOAD SIZE] ${req.method} ${req.path}: ${buf.length} bytes`); } })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Logging básico app.use((req, res, next) => { console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`); next(); }); // Health check app.get('/health', (req, res) => { res.json({ status: 'OK', timestamp: new Date().toISOString() }); }); // Rutas de la API app.use('/api/auth', authRoutes); app.use('/api/tecnicos', tecnicosRoutes); app.use('/api/tareas', tareasRoutes); app.use('/api/recursos', recursosRoutes); app.use('/api/tarea-recursos', tareaRecursosRoutes); // Rutas para asignaciones tarea-recurso app.use('/api/system', systemRoutes); // Ruta para servir el archivo de configuración YAML app.get('/api/config/entities.yml', (req, res) => { const path = require('path'); const fs = require('fs'); // El archivo está montado en /host/_app-npm/frontend/src/config/entities.yml const configPath = path.join('/host', '_app-npm', 'frontend', 'src', 'config', 'entities.yml'); console.log(`[CONFIG] Intentando leer archivo desde: ${configPath}`); fs.readFile(configPath, 'utf8', (err, data) => { if (err) { console.error('Error leyendo archivo de configuración:', err); console.error('Path attempted:', configPath); return res.status(500).json({ success: false, message: 'Error al leer el archivo de configuración', details: process.env.NODE_ENV === 'development' ? err.message : undefined }); } console.log(`[CONFIG] Archivo de configuración leído exitosamente (${data.length} bytes)`); res.setHeader('Content-Type', 'text/yaml'); res.send(data); }); }); app.use('/api/usuarios_relacionados', usuariosRelacionadosRoutes); // Manejo de errores 404 app.use('/api/*', (req, res) => { res.status(404).json({ success: false, message: 'Endpoint no encontrado' }); }); // Middleware de manejo de errores app.use((err, req, res, next) => { console.error('Error:', err); // Error de validación if (err.name === 'ValidationError') { return res.status(400).json({ success: false, message: 'Datos de entrada inválidos', errors: err.errors }); } // Error de base de datos if (err.code && err.code.startsWith('23')) { return res.status(400).json({ success: false, message: 'Error de integridad de datos' }); } // Error interno del servidor res.status(500).json({ success: false, message: process.env.NODE_ENV === 'production' ? 'Error interno del servidor' : err.message }); }); // Función para verificar conexión a la base de datos const testDatabaseConnection = async () => { try { const client = await pool.connect(); console.log('✅ Conexión a PostgreSQL exitosa'); client.release(); } catch (err) { console.error('❌ Error conectando a PostgreSQL:', err); process.exit(1); } }; // Iniciar servidor const startServer = async () => { await testDatabaseConnection(); app.listen(PORT, () => { console.log(`🚀 Servidor ejecutándose en puerto ${PORT}`); console.log(`📊 Ambiente: ${process.env.NODE_ENV || 'development'}`); }); }; // Manejo de señales para cerrar gracefully process.on('SIGTERM', () => { console.log('SIGTERM recibido, cerrando servidor...'); pool.end(() => { console.log('Pool de conexiones cerrado'); process.exit(0); }); }); process.on('SIGINT', () => { console.log('SIGINT recibido, cerrando servidor...'); pool.end(() => { console.log('Pool de conexiones cerrado'); process.exit(0); }); }); // Exportar app para uso en otros módulos si es necesario module.exports = { app, pool }; startServer();