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 { Trip } from './models/Trip'; const PORT = process.env.PORT || 4000; const PUBLIC_DIR = path.resolve(__dirname, '../public'); const MIME_TYPES: Record = { '.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 => { 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: 'Pacifictrans Go API - Transporte & Mensajería Cali - Buenaventura' }); } // 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 if (pathname === '/api/trips/pending' && req.method === 'GET') { const pending = await adminApprovalService.getTripsPendientesAprobacion(); return sendJson(200, { success: true, count: pending.length, trips: pending }); } // 5. Panel Admin: Aprobar y Activar Viaje (Asignando conductor y disparando Push) if (pathname === '/api/trips/approve' && req.method === 'POST') { 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 if (pathname === '/api/trips/ready-to-schedule' && req.method === 'GET') { const trips = await tripSchedulingService.getViajesListosParaProgramar(); return sendJson(200, { success: true, count: trips.length, trips }); } // 5.2. Panel Admin: Programar formalmente horario de salida, escalonar paradas y notificar if (pathname === '/api/trips/schedule' && req.method === 'POST') { 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 (Estilo DripZone) 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 }); } // 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 }); } // 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() { await connectDatabase(); server.listen(PORT, () => { console.log(`🚀 [Servidor Transporte Cali - Buenaventura] Ejecutándose en puerto ${PORT}`); }); } bootstrap().catch(console.error);