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

278 lines
11 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 { 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: '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);
}
// 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);