almeira-web/transporte-cali-buenaventura/backend/src/index.ts

616 lines
28 KiB
TypeScript

import http from 'http';
import fs from 'fs';
import path from 'path';
import { connectDatabase } from './config/database';
import { poolingService } from './services/pooling.service';
import { messagingFleteService } from './services/messaging-flete.service';
import { adminApprovalService } from './services/admin-approval.service';
import { cancellationService } from './services/cancellation.service';
import { userAdminService } from './services/user-admin.service';
import { boldPaymentService } from './services/bold.service';
import { adminDashboardService } from './services/admin-dashboard.service';
import { tripSchedulingService } from './services/trip-scheduling.service';
import { tariffsService } from './services/tariffs.service';
import { authService } from './services/auth.service';
import { statsReviewsService } from './services/stats-reviews.service';
import { settlementService } from './services/settlement.service';
import { whatsAppNotificationService } from './services/whatsapp-notification.service';
import { couponService } from './services/coupon.service';
import { Trip } from './models/Trip';
const PORT = process.env.PORT || 4000;
const PUBLIC_DIR = path.resolve(__dirname, '../public');
const MIME_TYPES: Record<string, string> = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.apk': 'application/vnd.android.package-archive',
'.woff2': 'font/woff2'
};
// Servidor HTTP nativo Node.js sin dependencias pesadas extras
const server = http.createServer(async (req, res) => {
// CORS Headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, x-api-key');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
const url = new URL(req.url || '/', `http://${req.headers.host}`);
const pathname = url.pathname;
// Helper para leer body JSON
const getJsonBody = (): Promise<any> => {
return new Promise((resolve) => {
let data = '';
req.on('data', (chunk) => (data += chunk));
req.on('end', () => {
try {
resolve(data ? JSON.parse(data) : {});
} catch {
resolve({});
}
});
});
};
const sendJson = (status: number, data: any) => {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
};
try {
// 1. Healthcheck
if (pathname === '/api/health' && req.method === 'GET') {
return sendJson(200, { status: 'ONLINE', service: 'Pacific Trans GO API - Transporte & Mensajería Cali - Buenaventura' });
}
// 1.1. Autenticación: Enviar OTP de seguridad por WhatsApp & Email
if (pathname === '/api/auth/send-otp' && req.method === 'POST') {
const body = await getJsonBody();
if (!body.identifier) {
return sendJson(400, { success: false, error: 'Se requiere número de WhatsApp o identificador' });
}
const result = await authService.sendOtp(body.identifier, body.email);
return sendJson(200, result);
}
// 1.2. Autenticación: Validar OTP de 6 dígitos e iniciar sesión / registro
if (pathname === '/api/auth/verify-otp' && req.method === 'POST') {
const body = await getJsonBody();
if (!body.identifier || !body.code) {
return sendJson(400, { success: false, error: 'Identificador y código OTP requeridos' });
}
const result = await authService.verifyOtp(body);
return sendJson(result.success ? 200 : 400, result);
}
// 1.3. Autenticación: Obtener perfil del usuario actual (sesión activa)
if (pathname === '/api/auth/me' && req.method === 'GET') {
const authHeader = req.headers['authorization'] || '';
const token = authHeader.replace(/^Bearer\s+/i, '') || url.searchParams.get('token') || '';
const user = await authService.getUserFromToken(token);
if (!user) {
return sendJson(401, { success: false, error: 'Sesión no válida o expirada (lapso de 24 horas superado)' });
}
return sendJson(200, { success: true, user });
}
// Helper de seguridad por roles y permisos en rutas API
const getToken = (): string => {
const authHeader = (req.headers['authorization'] as string) || '';
return authHeader.replace(/^Bearer\s+/i, '').trim() || url.searchParams.get('token') || (req.headers['x-session-token'] as string) || '';
};
const requireRole = async (allowedRoles: ('ADMIN' | 'CONDUCTOR' | 'PASAJERO' | 'OPERADOR')[]) => {
const token = getToken();
if (!token) {
return { allowed: false, error: 'Autenticación requerida: Inicia sesión con código OTP de seguridad.' };
}
return authService.verifyRole(token, allowedRoles);
};
// 2. Pooling inteligente (Transporte)
if (pathname === '/api/trips/pool' && req.method === 'POST') {
const body = await getJsonBody();
const result = await poolingService.processBookingAndPool(body);
return sendJson(result.success ? 200 : 400, result);
}
// 3. Cotizar / Despachar Encomienda (Mensajería 100% Prepagada con Bold)
if (pathname === '/api/trips/package' && req.method === 'POST') {
const body = await getJsonBody();
const result = await messagingFleteService.registrarEnvioPaquete(body);
return sendJson(200, result);
}
// 4. Panel Admin: Listar viajes pendientes de aprobación (Protegido por Rol)
if (pathname === '/api/trips/pending' && req.method === 'GET') {
const auth = await requireRole(['ADMIN', 'OPERADOR']);
if (!auth.allowed) return sendJson(403, { success: false, error: auth.error });
const pending = await adminApprovalService.getTripsPendientesAprobacion();
return sendJson(200, { success: true, count: pending.length, trips: pending });
}
// 5. Panel Admin: Aprobar y Activar Viaje (Protegido por Rol)
if (pathname === '/api/trips/approve' && req.method === 'POST') {
const auth = await requireRole(['ADMIN', 'OPERADOR']);
if (!auth.allowed) return sendJson(403, { success: false, error: auth.error });
const body = await getJsonBody();
const approved = await adminApprovalService.aprobarYActivarViaje(body);
return sendJson(200, { success: true, trip: approved });
}
// 5.1. Panel Admin: Listar viajes con cupo lleno (4/4) listos para programación (Protegido)
if (pathname === '/api/trips/ready-to-schedule' && req.method === 'GET') {
const auth = await requireRole(['ADMIN', 'OPERADOR']);
if (!auth.allowed) return sendJson(403, { success: false, error: auth.error });
const trips = await tripSchedulingService.getViajesListosParaProgramar();
return sendJson(200, { success: true, count: trips.length, trips });
}
// 5.2. Panel Admin: Programar formalmente horario de salida (Protegido)
if (pathname === '/api/trips/schedule' && req.method === 'POST') {
const auth = await requireRole(['ADMIN', 'OPERADOR']);
if (!auth.allowed) return sendJson(403, { success: false, error: auth.error });
const body = await getJsonBody();
const scheduled = await tripSchedulingService.programarViaje(body);
return sendJson(200, { success: true, trip: scheduled });
}
// 5.3. Panel Admin: Sugerir conductores ordenados por cercanía de residencia a las recogidas en Cali
if (pathname.startsWith('/api/trips/') && pathname.endsWith('/suggested-drivers') && req.method === 'GET') {
const parts = pathname.split('/');
const tripId = parts[3];
const suggestions = await tripSchedulingService.sugerirConductoresPorCercaniaResidencia(tripId);
return sendJson(200, { success: true, count: suggestions.length, suggestions });
}
// 6. Cancelación de Viajes con Evaluación de Penalidad
if (pathname === '/api/trips/cancel' && req.method === 'POST') {
const body = await getJsonBody();
const result = await cancellationService.procesarCancelacion(body.tripId, body.usuarioId, body.motivo);
return sendJson(200, result);
}
// 7. Liquidación de Penalidades
if (pathname === '/api/penalties/settle' && req.method === 'POST') {
const body = await getJsonBody();
const result = await cancellationService.liquidarPenalidad(body.usuarioId, body.monto, body.referencia);
return sendJson(200, result);
}
// 8. Administración de Usuarios y Perfil Personal
// 8.0. Actualización de Información Personal (No permite cambiar número de celular registrado)
if ((pathname === '/api/users/profile' || pathname === '/api/auth/profile') && req.method === 'GET') {
const auth = await requireRole(['PASAJERO', 'CONDUCTOR', 'ADMIN', 'OPERADOR']);
if (!auth.allowed) return sendJson(401, { success: false, error: auth.error });
return sendJson(200, { success: true, user: auth.user });
}
if ((pathname === '/api/users/profile' || pathname === '/api/auth/profile') && (req.method === 'PATCH' || req.method === 'PUT' || req.method === 'POST')) {
const auth = await requireRole(['PASAJERO', 'CONDUCTOR', 'ADMIN', 'OPERADOR']);
if (!auth.allowed) return sendJson(401, { success: false, error: auth.error });
const body = await getJsonBody();
try {
const updated = await userAdminService.updateUserProfile(auth.user.id || auth.user._id, body);
return sendJson(200, { success: true, message: 'Información personal actualizada correctamente', user: updated });
} catch (err: any) {
return sendJson(400, { success: false, error: err.message });
}
}
// 8.1. Gestión Centralizada de Administradores por el Superadministrador
if (pathname === '/api/admin/administrators' && req.method === 'GET') {
const auth = await requireRole(['ADMIN']);
if (!auth.allowed) return sendJson(403, { success: false, error: auth.error });
const administrators = await userAdminService.getAdministrators();
return sendJson(200, { success: true, count: administrators.length, administrators });
}
if (pathname === '/api/admin/administrators' && req.method === 'POST') {
const auth = await requireRole(['ADMIN']);
if (!auth.allowed) return sendJson(403, { success: false, error: auth.error });
const body = await getJsonBody();
try {
const administrator = await userAdminService.createAdministrator(body);
return sendJson(201, { success: true, administrator });
} catch (err: any) {
return sendJson(400, { success: false, error: err.message });
}
}
if ((pathname === '/api/admin/administrators/update' || (pathname.startsWith('/api/admin/administrators/') && !pathname.endsWith('/toggle-active'))) && (req.method === 'PUT' || req.method === 'POST')) {
const auth = await requireRole(['ADMIN']);
if (!auth.allowed) return sendJson(403, { success: false, error: auth.error });
const body = await getJsonBody();
const adminId = pathname.split('/')[4] || body.id || body.adminId;
try {
const administrator = await userAdminService.updateAdministrator(adminId, body, auth.user.id);
return sendJson(200, { success: true, administrator });
} catch (err: any) {
return sendJson(400, { success: false, error: err.message });
}
}
if ((pathname === '/api/admin/administrators/toggle-active' || (pathname.startsWith('/api/admin/administrators/') && pathname.endsWith('/toggle-active'))) && (req.method === 'POST' || req.method === 'PATCH')) {
const auth = await requireRole(['ADMIN']);
if (!auth.allowed) return sendJson(403, { success: false, error: auth.error });
const body = await getJsonBody();
const adminId = body.id || body.adminId || pathname.split('/')[4];
try {
const administrator = await userAdminService.toggleAdministratorActive(adminId, body.isActive);
return sendJson(200, { success: true, administrator });
} catch (err: any) {
return sendJson(400, { success: false, error: err.message });
}
}
if ((pathname === '/api/admin/administrators/delete' && req.method === 'POST') || (pathname.startsWith('/api/admin/administrators/') && req.method === 'DELETE')) {
const auth = await requireRole(['ADMIN']);
if (!auth.allowed) return sendJson(403, { success: false, error: auth.error });
const body = await getJsonBody();
const adminId = pathname.split('/')[4] || body.id || body.adminId;
try {
await userAdminService.deleteAdministrator(adminId, auth.user.id);
return sendJson(200, { success: true, message: 'Administrador eliminado exitosamente' });
} catch (err: any) {
return sendJson(400, { success: false, error: err.message });
}
}
if (pathname === '/api/users' && req.method === 'GET') {
const users = await userAdminService.getAllUsers();
return sendJson(200, { success: true, count: users.length, users });
}
if (pathname === '/api/users/status' && req.method === 'GET') {
const query = url.searchParams.get('query') || '';
const status = await userAdminService.getUserByPhoneOrId(query);
return sendJson(200, { success: true, ...status });
}
if (pathname === '/api/users/toggle-active' && req.method === 'POST') {
const body = await getJsonBody();
const updated = await userAdminService.toggleUserActiveStatus(body.userId, body.isActive);
return sendJson(200, { success: true, user: updated });
}
if (pathname === '/api/users/change-role' && req.method === 'POST') {
const body = await getJsonBody();
const updated = await userAdminService.updateUserRole(body.userId, body.newRole);
return sendJson(200, { success: true, user: updated });
}
// 9. Webhook oficial de Bold para confirmación de transacciones
if (pathname === '/api/payments/bold/webhook' && req.method === 'POST') {
const body = await getJsonBody();
console.log('🔔 [Bold Webhook Received]:', JSON.stringify(body));
const eventData = body.data || body;
const ref = eventData.reference || '';
const status = eventData.status;
if (status === 'PAID') {
if (ref.startsWith('PENALTY_SETTLEMENT-')) {
const parts = ref.split('-');
const userId = parts[1];
const amount = eventData.amount?.total_amount || 0;
await cancellationService.liquidarPenalidad(userId, amount, eventData.id || ref);
} else if (ref.startsWith('TRIP_PASSENGER-') || ref.startsWith('MESSAGING_PACKAGE-')) {
const tripId = ref.split('-')[1];
await Trip.findByIdAndUpdate(tripId, {
'boldPayment.transactionStatus': 'PAID',
'boldPayment.paidAt': new Date(),
});
}
}
return sendJson(200, { received: true });
}
// 10. Telemetría GPS en tiempo real de conductores para el Panel Admin
if (pathname === '/api/admin/drivers-realtime' && req.method === 'GET') {
const drivers = await adminDashboardService.getUbicacionesConductoresEnVivo();
return sendJson(200, { success: true, count: drivers.length, drivers });
}
// 11. Analítica de Demanda por Zonas y Déficit de Vehículos
if (pathname === '/api/admin/demand-analytics' && req.method === 'GET') {
const demand = await adminDashboardService.getAnalisisDemandaYDeficitVehicular();
return sendJson(200, { success: true, ...demand });
}
// 12. KPIs Operativos y Financieros del Administrador
if (pathname === '/api/admin/kpis' && req.method === 'GET') {
const kpis = await adminDashboardService.getDashboardKPIs();
return sendJson(200, { success: true, kpis });
}
// 12.1. Administrador: Consultar grupos de WhatsApp para despacho oficial de Pacific Trans GO
if (pathname === '/api/admin/whatsapp/groups' && req.method === 'GET') {
const pacificJid = await whatsAppNotificationService.getPacificTransNotificationTarget();
return sendJson(200, {
success: true,
pacificTrans: {
groupName: 'Pacific Trans Notificaciones',
resolvedJid: pacificJid,
isGroup: pacificJid.endsWith('@g.us'),
},
});
}
// 13. Actualización de posición GPS desde la App del Conductor
if (pathname === '/api/drivers/location-update' && req.method === 'POST') {
const body = await getJsonBody();
const updateResult = await adminDashboardService.actualizarUbicacionConductor(
body.userId,
body.lat,
body.lng
);
return sendJson(200, updateResult);
}
// 13.1. Obtener Configuración de Tarifas
if (pathname === '/api/tariffs' && req.method === 'GET') {
const config = await tariffsService.getTarifasActivas();
return sendJson(200, { success: true, config });
}
// 13.2. Actualizar Tarifas Configurables desde la Plataforma
if (pathname === '/api/tariffs' && (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH')) {
const body = await getJsonBody();
const updated = await tariffsService.updateTarifas(body);
return sendJson(200, { success: true, message: 'Tarifas actualizadas correctamente en MongoDB', config: updated });
}
// 13.2.1. Gestión de Bonos y Cupones de Descuento (Viajes y Encomiendas)
if (pathname === '/api/coupons' && req.method === 'GET') {
const coupons = await couponService.getAllCoupons();
return sendJson(200, { success: true, count: coupons.length, coupons });
}
if (pathname === '/api/coupons' && req.method === 'POST') {
const body = await getJsonBody();
try {
const coupon = await couponService.createCoupon(body);
return sendJson(201, { success: true, coupon });
} catch (err: any) {
return sendJson(400, { success: false, error: err.message || 'Error al crear cupón' });
}
}
if ((pathname === '/api/coupons/toggle' || (pathname.startsWith('/api/coupons/') && pathname.endsWith('/toggle'))) && (req.method === 'POST' || req.method === 'PATCH')) {
const body = await getJsonBody();
const id = body.id || body.couponId || pathname.split('/')[3];
if (!id) return sendJson(400, { success: false, error: 'ID de cupón requerido' });
const coupon = await couponService.toggleCouponStatus(id, body.estaActivo);
return sendJson(200, { success: true, coupon });
}
if ((pathname === '/api/coupons/delete' && req.method === 'POST') || (pathname.startsWith('/api/coupons/') && req.method === 'DELETE')) {
const body = await getJsonBody();
const id = pathname.split('/')[3] || body.id || body.couponId;
if (!id) return sendJson(400, { success: false, error: 'ID de cupón requerido' });
const success = await couponService.deleteCoupon(id);
return sendJson(200, { success });
}
if (pathname === '/api/coupons/validate' && req.method === 'POST') {
const body = await getJsonBody();
const validation = await couponService.validateCoupon(
body.codigo || body.code,
body.serviceType || body.tipoServicio || 'VIAJES',
parseFloat(body.subtotal || 0)
);
return sendJson(200, validation);
}
// 13.3. Estadísticas de Viajes y Sistema de Calificaciones/Comentarios
if (pathname === '/api/stats-reviews' && req.method === 'GET') {
const data = await statsReviewsService.getStatsAndReviews();
return sendJson(200, { success: true, ...data });
}
if (pathname === '/api/stats-reviews' && req.method === 'POST') {
const body = await getJsonBody();
if (!body.autorNombre || !body.puntuacion || !body.comentario) {
return sendJson(400, { success: false, error: 'Nombre, puntuación y comentario requeridos' });
}
const review = await statsReviewsService.addReview(body);
return sendJson(200, { success: true, review });
}
// 13.4. Control de Pagos, Comisiones, Liquidación y Transferencias Bre-B
// 13.4.1. Panorama general de liquidaciones
if (pathname === '/api/settlements/overview' && req.method === 'GET') {
const overview = await settlementService.getOverviewLiquidaciones();
return sendJson(200, { success: true, ...overview });
}
// 13.4.2. Desglose financiero y estado de pago de pasajeros de un viaje
if (pathname.startsWith('/api/settlements/trip/') && req.method === 'GET') {
const parts = pathname.split('/');
const tripId = parts[4];
if (!tripId) return sendJson(400, { success: false, error: 'tripId requerido' });
const summary = await settlementService.getTripFinancialSummary(tripId);
return sendJson(200, { success: true, summary });
}
// 13.4.3. Marcar estado de pago de un pasajero (ej. pago recibido en efectivo o revertir)
if (pathname === '/api/settlements/toggle-passenger-payment' && req.method === 'POST') {
const body = await getJsonBody();
if (!body.tripId || !body.pasajeroId || !body.nuevoEstado) {
return sendJson(400, { success: false, error: 'tripId, pasajeroId y nuevoEstado requeridos' });
}
const summary = await settlementService.marcarPagoPasajero(body.tripId, body.pasajeroId, body.nuevoEstado);
return sendJson(200, { success: true, summary });
}
// 13.4.4. Finalizar viaje formalmente (COMPLETED) y liquidar saldo al conductor
if (pathname === '/api/settlements/complete-and-settle' && req.method === 'POST') {
const body = await getJsonBody();
if (!body.tripId) {
return sendJson(400, { success: false, error: 'tripId requerido' });
}
const result = await settlementService.finalizarRecorridoYLiquidar(body.tripId, body.adminId);
return sendJson(200, { success: true, ...result });
}
// 13.4.5. Obtener billetera y estado financiero de un conductor
if (pathname.startsWith('/api/settlements/driver/') && pathname.endsWith('/wallet') && req.method === 'GET') {
const parts = pathname.split('/');
const driverId = parts[4];
if (!driverId) return sendJson(400, { success: false, error: 'driverId requerido' });
const wallet = await settlementService.getBilleteraConductor(driverId);
return sendJson(200, { success: true, wallet });
}
// 13.4.6. Configurar o actualizar Llave Bre-B del conductor
if (pathname === '/api/settlements/driver/bre-b-key' && req.method === 'POST') {
const body = await getJsonBody();
if (!body.conductorId || !body.valor || !body.bancoDestino) {
return sendJson(400, { success: false, error: 'conductorId, valor y bancoDestino requeridos' });
}
const llave = await settlementService.actualizarLlaveBreB(body.conductorId, body);
return sendJson(200, { success: true, llave });
}
// 13.4.7. Realizar transferencia inmediata por Bre-B (Banco de la República)
if (pathname === '/api/settlements/driver/payout-bre-b' && req.method === 'POST') {
const body = await getJsonBody();
if (!body.conductorId || !body.monto) {
return sendJson(400, { success: false, error: 'conductorId y monto requeridos' });
}
const receipt = await settlementService.realizarTransferenciaBreB(body);
return sendJson(200, receipt);
}
// 13.4.8. Conductor crea una Solicitud de Transferencia Bre-B
if (pathname === '/api/settlements/payout-requests/create' && req.method === 'POST') {
const body = await getJsonBody();
if (!body.conductorId || !body.monto) {
return sendJson(400, { success: false, error: 'conductorId y monto requeridos' });
}
const solicitud = await settlementService.crearSolicitudTransferencia(body);
return sendJson(200, { success: true, solicitud });
}
// 13.4.9. Consultar solicitudes de transferencia (Bandeja Admin y Conductor)
if (pathname === '/api/settlements/payout-requests' && req.method === 'GET') {
const estado = url.searchParams.get('estado') || undefined;
const conductorId = url.searchParams.get('conductorId') || undefined;
const solicitudes = await settlementService.obtenerSolicitudesTransferencia({ estado, conductorId });
return sendJson(200, { success: true, count: solicitudes.length, solicitudes });
}
// 13.4.10. Administrador aprueba y ejecuta la transferencia por Bre-B
if (pathname === '/api/settlements/payout-requests/approve-and-transfer' && req.method === 'POST') {
const body = await getJsonBody();
if (!body.solicitudId) {
return sendJson(400, { success: false, error: 'solicitudId requerido' });
}
const result = await settlementService.aprobarYTransferirBreB(body.solicitudId, body.adminId, body.notas);
return sendJson(200, { success: true, ...result });
}
// 13.4.11. Administrador rechaza solicitud de transferencia
if (pathname === '/api/settlements/payout-requests/reject' && req.method === 'POST') {
const body = await getJsonBody();
if (!body.solicitudId) {
return sendJson(400, { success: false, error: 'solicitudId requerido' });
}
const solicitud = await settlementService.rechazarSolicitudTransferencia(body.solicitudId, body.adminId, body.motivo);
return sendJson(200, { success: true, solicitud });
}
// 14. Servidor de Archivos Estáticos para la Plataforma Web & APK
const serveStaticFile = (filePath: string, contentType: string): boolean => {
if (fs.existsSync(filePath)) {
const stat = fs.statSync(filePath);
if (stat.isFile()) {
res.writeHead(200, {
'Content-Type': contentType,
'Content-Length': stat.size,
'Cache-Control': contentType.includes('apk') ? 'no-cache' : 'public, max-age=3600'
});
fs.createReadStream(filePath).pipe(res);
return true;
}
}
return false;
};
// Descarga directa del APK Oficial Android
if (pathname === '/PacifictransGo.apk' || pathname === '/download/apk') {
const localApk = path.join(PUBLIC_DIR, 'PacifictransGo.apk');
const fallbackApk = path.resolve(__dirname, '../../../pacifictrans-app/public/PacifictransGo.apk');
const vpsApk = '/home/carlos/apps/dripzone-app/public/PacifictransGo.apk';
const targetApk = fs.existsSync(localApk) ? localApk : (fs.existsSync(fallbackApk) ? fallbackApk : vpsApk);
if (serveStaticFile(targetApk, MIME_TYPES['.apk'])) return;
}
// Archivo estático directo (HTML, CSS, JS, Imágenes)
let relativePath = pathname === '/' ? 'index.html' : pathname.replace(/^\//, '');
let targetPath = path.join(PUBLIC_DIR, relativePath);
const ext = path.extname(targetPath).toLowerCase();
const mime = MIME_TYPES[ext] || 'application/octet-stream';
if (serveStaticFile(targetPath, mime)) {
return;
}
// Fallback SPA a index.html para rutas sin extensión
if (!ext) {
const indexPath = path.join(PUBLIC_DIR, 'index.html');
if (serveStaticFile(indexPath, MIME_TYPES['.html'])) {
return;
}
}
// Ruta no encontrada
sendJson(404, { error: 'Recurso o Endpoint no encontrado' });
} catch (error: any) {
console.error('❌ [API Error]:', error);
sendJson(500, { error: error.message || 'Error interno del servidor' });
}
});
async function bootstrap() {
// 1. Iniciar servidor HTTP de inmediato para responder 200 OK a Traefik y visitantes
server.listen(PORT, () => {
console.log(`🚀 [Pacific Trans GO] Plataforma Web y API escuchando en puerto ${PORT}`);
});
// 2. Conectar a MongoDB de forma no bloqueante con reintento automático
const tryConnect = async () => {
try {
await connectDatabase();
console.log('✅ [MongoDB] Conectado exitosamente en segundo plano.');
await couponService.ensureSeedCoupons();
await userAdminService.ensureSuperAdminExists();
} catch (err: any) {
console.warn(`⚠️ [MongoDB Warning] Conexión no disponible (${err.message || 'ENOTFOUND'}). Reintentando en 10s...`);
setTimeout(tryConnect, 10000);
}
};
tryConnect();
}
bootstrap().catch(console.error);