// Pacifictrans Go - Interactive Web Platform Logic let currentTariffs = { tarifaPasajeroFija: 35000, precioBasePaquete: 25000, precioKmPaquete: 1200, factorPaquetes: { sobre: 1.0, mediano: 1.6, grande: 2.2 }, seguroBasePaquete: 5000, porcentajeComisionAdmin: 15, costoPenalidadCancelacion: 15000, radioMaxPoolingMetros: 4000, numeroAtencionWhatsApp: '573167455877', nombreSoporte: 'Atención Pacifictrans Go' }; let currentUser = null; let leafletMap = null; let deferredPrompt = null; // Drivers Flota con coordenadas reales en el corredor Cali - Buenaventura const activeDrivers = [ { id: '1', nombre: 'Mario Fernando G.', vehiculo: 'Renault Duster (WLZ-890)', barrio: 'Granada (Comuna 2)', distCali: '1.2 km', lat: 3.4552, lng: -76.5350, estado: 'DISPONIBLE' }, { id: '2', nombre: 'Hernán Darío P.', vehiculo: 'Toyota Fortuner (VBF-312)', barrio: 'San Antonio (Comuna 3)', distCali: '2.4 km', lat: 3.4485, lng: -76.5415, estado: 'ASIGNADO' }, { id: '3', nombre: 'Mauricio V.', vehiculo: 'Chevrolet Tracker (HJK-456)', barrio: 'El Ingenio (Comuna 17)', distCali: '6.8 km', lat: 3.3850, lng: -76.5290, estado: 'DISPONIBLE' }, { id: '4', nombre: 'Javier Restrepo', vehiculo: 'Nissan Kicks (RTY-789)', barrio: 'Limonar (Comuna 19)', distCali: '5.1 km', lat: 3.6580, lng: -76.6900, estado: 'EN_RUTA' } ]; document.addEventListener('DOMContentLoaded', () => { initPWA(); initTabs(); initAuthSystem(); initPassengerCalculator(); initFreightCalculator(); initLeafletRadarMap(); initReviewsAndStats(); initTariffsManager(); fetchTariffs(); fetchStatsAndReviews(); }); /* ========================================================================== 1. PWA & IPHONE INSTALLATION SYSTEM ========================================================================== */ function initPWA() { if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js') .then(reg => console.log('✅ [PWA] Service Worker registrado exitosamente:', reg.scope)) .catch(err => console.warn('⚠️ [PWA Service Worker Warning]:', err)); }); } // Guardar evento de instalación de Android window.addEventListener('beforeinstallprompt', (e) => { e.preventDefault(); deferredPrompt = e; }); const btnInstall = document.getElementById('btn-install-pwa'); const modalInstall = document.getElementById('modal-pwa-install'); const btnClose = document.getElementById('btn-close-pwa-modal'); const btnTriggerAndroid = document.getElementById('btn-trigger-android-pwa'); if (btnInstall && modalInstall) { btnInstall.addEventListener('click', () => { modalInstall.classList.add('active'); }); } if (btnClose && modalInstall) { btnClose.addEventListener('click', () => { modalInstall.classList.remove('active'); }); modalInstall.addEventListener('click', (e) => { if (e.target === modalInstall) modalInstall.classList.remove('active'); }); } if (btnTriggerAndroid) { btnTriggerAndroid.addEventListener('click', async () => { if (deferredPrompt) { deferredPrompt.prompt(); const { outcome } = await deferredPrompt.userChoice; console.log('PWA Outcome:', outcome); deferredPrompt = null; if (modalInstall) modalInstall.classList.remove('active'); } else { alert('Para instalar en iPhone, sigue los 4 pasos mostrados arriba en Safari. En Android Chrome, toca los 3 puntos (⋮) y selecciona "Instalar aplicación".'); } }); } } /* ========================================================================== 2. TABS & ADMIN SUBTABS NAVIGATION ========================================================================== */ function initTabs() { const tabBtns = document.querySelectorAll('.tab-btn'); const tabContents = document.querySelectorAll('.tab-content'); window.activateTab = function(targetId) { tabBtns.forEach(b => { if (b.getAttribute('data-tab') === targetId) { b.classList.add('active'); } else { b.classList.remove('active'); } }); tabContents.forEach(c => { if (c.id === targetId) { c.classList.add('active'); } else { c.classList.remove('active'); } }); // Si se activa el radar, refrescar el tamaño del mapa Leaflet if (targetId === 'tab-admin' && leafletMap) { setTimeout(() => { leafletMap.invalidateSize(); }, 250); } }; tabBtns.forEach(btn => { btn.addEventListener('click', () => { const targetId = btn.getAttribute('data-tab'); window.activateTab(targetId); }); }); // Enlaces de la barra de navegación const navBtnAdmin = document.getElementById('nav-btn-admin'); if (navBtnAdmin) { navBtnAdmin.addEventListener('click', (e) => { e.preventDefault(); window.activateTab('tab-admin'); document.getElementById('admin')?.scrollIntoView({ behavior: 'smooth' }); }); } const navBtnStats = document.getElementById('nav-btn-stats'); if (navBtnStats) { navBtnStats.addEventListener('click', (e) => { e.preventDefault(); window.activateTab('tab-estadisticas'); document.getElementById('tab-estadisticas')?.scrollIntoView({ behavior: 'smooth' }); }); } // Sub-tabs de la sección administrativa const subRadarBtn = document.getElementById('btn-admin-subtab-radar'); const subTarifasBtn = document.getElementById('btn-admin-subtab-tarifas'); const viewRadar = document.getElementById('admin-view-radar'); const viewTarifas = document.getElementById('admin-view-tarifas'); if (subRadarBtn && subTarifasBtn && viewRadar && viewTarifas) { subRadarBtn.addEventListener('click', () => { subRadarBtn.classList.add('active'); subTarifasBtn.classList.remove('active'); viewRadar.style.display = 'block'; viewTarifas.style.display = 'none'; if (leafletMap) { setTimeout(() => leafletMap.invalidateSize(), 200); } }); subTarifasBtn.addEventListener('click', () => { subTarifasBtn.classList.add('active'); subRadarBtn.classList.remove('active'); viewRadar.style.display = 'none'; viewTarifas.style.display = 'block'; }); } } /* ========================================================================== 3. AUTHENTICATION (DRIPZONE STYLE CON OTP WHATSAPP + EMAIL) ========================================================================== */ function initAuthSystem() { const modal = document.getElementById('modal-auth-dripzone'); const btnOpen = document.getElementById('btn-open-auth-modal'); const btnClose = document.getElementById('btn-close-auth-modal'); const btnLogout = document.getElementById('btn-logout'); const modeLoginBtn = document.getElementById('btn-auth-mode-login'); const modeRegisterBtn = document.getElementById('btn-auth-mode-register'); const groupName = document.getElementById('group-auth-name'); const authModalTitle = document.getElementById('auth-modal-title'); const step1 = document.getElementById('auth-step-1'); const step2 = document.getElementById('auth-step-2'); const formStep1 = document.getElementById('form-auth-request-otp'); const btnVerifyOtp = document.getElementById('btn-submit-verify-otp'); const btnResend = document.getElementById('btn-auth-resend-otp'); const reqError = document.getElementById('auth-request-error'); const verError = document.getElementById('auth-verify-error'); const targetLabel = document.getElementById('auth-sent-target'); let authMode = 'login'; // 'login' | 'register' let currentIdentifier = ''; let currentEmail = ''; // Abrir y cerrar modal if (btnOpen && modal) { btnOpen.addEventListener('click', () => { reqError.style.display = 'none'; verError.style.display = 'none'; step1.style.display = 'block'; step2.style.display = 'none'; modal.classList.add('active'); }); } if (btnClose && modal) { btnClose.addEventListener('click', () => modal.classList.remove('active')); modal.addEventListener('click', (e) => { if (e.target === modal) modal.classList.remove('active'); }); } // Alternar entre Login y Registro if (modeLoginBtn && modeRegisterBtn) { modeLoginBtn.addEventListener('click', () => { authMode = 'login'; modeLoginBtn.classList.add('active'); modeRegisterBtn.classList.remove('active'); if (groupName) groupName.style.display = 'none'; if (authModalTitle) authModalTitle.textContent = 'Acceso Seguro con Verificación OTP'; }); modeRegisterBtn.addEventListener('click', () => { authMode = 'register'; modeRegisterBtn.classList.add('active'); modeLoginBtn.classList.remove('active'); if (groupName) groupName.style.display = 'block'; if (authModalTitle) authModalTitle.textContent = 'Registro de Usuario & Validación 2FA'; }); } // Paso 1: Enviar OTP por WhatsApp if (formStep1) { formStep1.addEventListener('submit', async (e) => { e.preventDefault(); const phoneInput = document.getElementById('auth-input-phone'); const emailInput = document.getElementById('auth-input-email'); const nameInput = document.getElementById('auth-input-name'); const btnSubmit = document.getElementById('btn-submit-send-otp'); const phone = phoneInput?.value.trim() || ''; const email = emailInput?.value.trim() || ''; const name = nameInput?.value.trim() || ''; if (!phone) { showError(reqError, 'Ingresa tu número de WhatsApp.'); return; } currentIdentifier = phone; currentEmail = email; btnSubmit.disabled = true; btnSubmit.innerHTML = '⏳ Enviando código de seguridad...'; reqError.style.display = 'none'; try { const res = await fetch('/api/auth/send-otp', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identifier: phone, email }) }); const data = await res.json(); if (res.ok && data.success) { step1.style.display = 'none'; step2.style.display = 'block'; if (targetLabel) targetLabel.textContent = data.maskedPhone || `+${phone}`; // Si el servidor devolvió preview (entorno local sin SIM de Evolution), mostrar aviso if (data.codePreview) { console.log(`[Demo Code OTP]: ${data.codePreview}`); verError.style.display = 'block'; verError.style.color = '#38bdf8'; verError.innerHTML = `ℹ️ Código de prueba generado: ${data.codePreview}`; } focusFirstOtp(); } else { showError(reqError, data.error || 'No se pudo enviar el código OTP.'); } } catch (err) { showError(reqError, 'Error de conexión. Inténtalo de nuevo.'); } finally { btnSubmit.disabled = false; btnSubmit.innerHTML = '📲 Enviar Código de Verificación OTP'; } }); } // Cajas OTP con avance automático y soporte de pegado const otpInputs = [ document.getElementById('otp-1'), document.getElementById('otp-2'), document.getElementById('otp-3'), document.getElementById('otp-4'), document.getElementById('otp-5'), document.getElementById('otp-6') ]; otpInputs.forEach((input, index) => { if (!input) return; input.addEventListener('input', (e) => { const val = e.target.value; if (val.length === 1 && index < 5) { otpInputs[index + 1].focus(); } checkAutoSubmitOtp(); }); input.addEventListener('keydown', (e) => { if (e.key === 'Backspace' && !input.value && index > 0) { otpInputs[index - 1].focus(); } }); input.addEventListener('paste', (e) => { e.preventDefault(); const pasteData = e.clipboardData.getData('text').trim(); if (/^\d{6}$/.test(pasteData)) { pasteData.split('').forEach((digit, i) => { if (otpInputs[i]) otpInputs[i].value = digit; }); checkAutoSubmitOtp(); } }); }); function focusFirstOtp() { otpInputs.forEach(i => { if (i) i.value = ''; }); if (otpInputs[0]) otpInputs[0].focus(); } function getEnteredOtp() { return otpInputs.map(i => i?.value || '').join(''); } function checkAutoSubmitOtp() { const code = getEnteredOtp(); if (code.length === 6) { submitVerifyOtp(code); } } // Paso 2: Verificar OTP if (btnVerifyOtp) { btnVerifyOtp.addEventListener('click', () => { const code = getEnteredOtp(); if (code.length < 6) { showError(verError, 'Ingresa los 6 dígitos del código.'); return; } submitVerifyOtp(code); }); } async function submitVerifyOtp(code) { if (!btnVerifyOtp) return; btnVerifyOtp.disabled = true; btnVerifyOtp.innerHTML = '⏳ Verificando código...'; verError.style.display = 'none'; const nameInput = document.getElementById('auth-input-name'); const name = nameInput?.value.trim() || ''; try { const res = await fetch('/api/auth/verify-otp', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identifier: currentIdentifier, code, nombre: name, email: currentEmail }) }); const data = await res.json(); if (res.ok && data.success) { localStorage.setItem('pt_token', data.token); currentUser = data.user; updateUserUI(currentUser); modal.classList.remove('active'); alert(`¡Bienvenido a Pacifictrans Go, ${currentUser.nombre}! Tu identidad ha sido verificada con éxito.`); } else { showError(verError, data.error || 'Código incorrecto. Verifica los 6 dígitos.'); } } catch (err) { showError(verError, 'Error al validar el código OTP.'); } finally { btnVerifyOtp.disabled = false; btnVerifyOtp.innerHTML = '✅ Validar y Acceder'; } } // Reenviar OTP if (btnResend) { btnResend.addEventListener('click', () => { formStep1.dispatchEvent(new Event('submit')); }); } // Cerrar sesión if (btnLogout) { btnLogout.addEventListener('click', () => { localStorage.removeItem('pt_token'); currentUser = null; updateUserUI(null); }); } // Validar sesión persistida checkPersistedSession(); } async function checkPersistedSession() { const token = localStorage.getItem('pt_token'); if (!token) return; try { const res = await fetch(`/api/auth/me?token=${token}`); if (res.ok) { const data = await res.json(); if (data.user) { currentUser = data.user; updateUserUI(currentUser); } } else { localStorage.removeItem('pt_token'); } } catch (err) { console.warn('⚠️ [Session Verify Error]:', err); } } function updateUserUI(user) { const btnAuthOpen = document.getElementById('btn-open-auth-modal'); const userBadge = document.getElementById('user-profile-badge'); const userNameEl = document.getElementById('user-display-name'); const inputUserName = document.getElementById('user-name'); const inputUserPhone = document.getElementById('user-phone'); const inputSenderPhone = document.getElementById('sender-phone'); if (user) { if (btnAuthOpen) btnAuthOpen.style.display = 'none'; if (userBadge) userBadge.style.display = 'inline-flex'; if (userNameEl) userNameEl.textContent = user.nombre.split(' ')[0]; // Autofill en formularios de la plataforma if (inputUserName && !inputUserName.value) inputUserName.value = user.nombre; if (inputUserPhone && !inputUserPhone.value) inputUserPhone.value = user.telefono; if (inputSenderPhone && !inputSenderPhone.value) inputSenderPhone.value = user.telefono; } else { if (btnAuthOpen) btnAuthOpen.style.display = 'inline-flex'; if (userBadge) userBadge.style.display = 'none'; } } function showError(el, msg) { if (!el) return; el.textContent = msg; el.style.display = 'block'; } /* ========================================================================== 4. LEAFLET RADAR FLEET MAP (MAPA ESTÁNDAR CON TOOLTIPS) ========================================================================== */ function initLeafletRadarMap() { const mapContainer = document.getElementById('fleet-live-map'); if (!mapContainer || typeof L === 'undefined') return; // Centro entre Cali (3.45) y Buenaventura (3.88) const corridorCenter = [3.65, -76.78]; leafletMap = L.map('fleet-live-map', { center: corridorCenter, zoom: 10, zoomControl: true, scrollWheelZoom: false }); // Capa de Mapa Estándar OpenStreetMap L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, attribution: '© OpenStreetMap contributors • Pacifictrans Go GPS' }).addTo(leafletMap); // Línea poligonal oficial de la Carretera Vía al Mar (Ruta 40) const corridorWaypoints = [ [3.4516, -76.5320], // Cali [3.4750, -76.5650], // Portada al Mar [3.5800, -76.6300], // Km 18 / Saladito [3.6580, -76.6900], // Dagua [3.7650, -76.7350], // Loboguerrero [3.7920, -76.8150], // Cisneros [3.8450, -76.9200], // Córdoba [3.8814, -77.0315] // Buenaventura ]; L.polyline(corridorWaypoints, { color: '#06b6d4', weight: 5, opacity: 0.85, dashArray: '8, 6' }).addTo(leafletMap); // Marcadores de Hitos Urbanos addCityMarker(leafletMap, [3.4516, -76.5320], '🟢 Cali (Km 0)'); addCityMarker(leafletMap, [3.6580, -76.6900], '🔵 Dagua (Km 45)'); addCityMarker(leafletMap, [3.7650, -76.7350], '🔵 Loboguerrero (Km 60)'); addCityMarker(leafletMap, [3.8814, -77.0315], '🟡 Buenaventura (Km 115)'); // Vehículos de la Flota en Tiempo Real activeDrivers.forEach((driver, index) => { const customIcon = L.divIcon({ className: 'vehicle-marker-icon', html: `
🚗
`, iconSize: [44, 44], iconAnchor: [22, 22] }); const marker = L.marker([driver.lat, driver.lng], { icon: customIcon }).addTo(leafletMap); // Tooltip enriquecido al pasar el cursor o pulsar const tooltipContent = `
#${index + 1} ${driver.nombre} ● ${driver.estado}
${driver.vehiculo}
🏠 Domicilio: ${driver.barrio}
📍 Cercanía a 1er Pasajero: ${driver.distCali}
`; marker.bindTooltip(tooltipContent, { direction: 'top', className: 'custom-vehicle-tooltip', offset: [0, -20] }); marker.on('click', () => { window.asignarConductorDemo(driver.nombre, driver.barrio); }); }); renderSuggestedDriversList(); } function addCityMarker(map, coords, label) { const icon = L.divIcon({ className: 'city-marker-icon', html: `
${label}
`, iconSize: [110, 26], iconAnchor: [55, 13] }); L.marker(coords, { icon }).addTo(map); } function renderSuggestedDriversList() { const driverListEl = document.getElementById('suggested-drivers-list'); if (!driverListEl) return; driverListEl.innerHTML = activeDrivers.map((d, index) => `
#${index + 1} ${d.nombre}
${d.vehiculo}
🏠 Residencia en Cali: ${d.barrio}
Distancia al 1er Pasajero: ${d.distCali}
${d.estado}
`).join(''); } window.asignarConductorDemo = function(conductor, barrio) { const resultDiv = document.getElementById('schedule-dispatch-result'); if (resultDiv) { resultDiv.style.display = 'block'; resultDiv.innerHTML = `

✅ Conductor Asignado por Residencia: ${conductor} (${barrio})

🚀 Disparo Inmediato de Notificación WhatsApp Ejecutado: Los 4 pasajeros han recibido su confirmación oficial con el conductor asignado, vehículo, hora estimada escalonada de recogida y enlace de Waze en vivo.

📲 [Evolution API WhatsApp Status: 201 Created] Enviado a los 4 Pasajeros + Conductor.
`; resultDiv.scrollIntoView({ behavior: 'smooth' }); } }; /* ========================================================================== 5. TRIP STATISTICS & COMMUNITY REVIEWS ========================================================================== */ function initReviewsAndStats() { const btnOpenModal = document.getElementById('btn-open-review-modal'); const modalReview = document.getElementById('modal-add-review'); const btnCloseModal = document.getElementById('btn-close-review-modal'); const formReview = document.getElementById('form-submit-review'); const starPicker = document.getElementById('star-picker'); const hiddenScore = document.getElementById('review-input-score'); if (btnOpenModal && modalReview) { btnOpenModal.addEventListener('click', () => { if (currentUser && document.getElementById('review-input-name')) { document.getElementById('review-input-name').value = currentUser.nombre; } modalReview.classList.add('active'); }); } if (btnCloseModal && modalReview) { btnCloseModal.addEventListener('click', () => modalReview.classList.remove('active')); modalReview.addEventListener('click', (e) => { if (e.target === modalReview) modalReview.classList.remove('active'); }); } // Selector interactivo de estrellas (1-5) if (starPicker) { const stars = starPicker.querySelectorAll('span'); stars.forEach(star => { star.addEventListener('click', () => { const val = parseInt(star.getAttribute('data-star') || '5', 10); if (hiddenScore) hiddenScore.value = val; stars.forEach((s, idx) => { if (idx < val) s.classList.add('selected'); else s.classList.remove('selected'); }); }); }); } // Envío de reseña if (formReview) { formReview.addEventListener('submit', async (e) => { e.preventDefault(); const btnSubmit = document.getElementById('btn-submit-review'); const score = parseFloat(hiddenScore?.value || '5'); const name = document.getElementById('review-input-name')?.value.trim(); const role = document.getElementById('review-input-role')?.value; const target = document.getElementById('review-input-target')?.value.trim(); const comment = document.getElementById('review-input-comment')?.value.trim(); if (!name || !comment) return; btnSubmit.disabled = true; btnSubmit.innerHTML = '⏳ Publicando reseña...'; try { const res = await fetch('/api/stats-reviews', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ autorId: currentUser?.id || `anon-${Date.now()}`, autorNombre: name, autorRol: role, destinatarioNombre: target, puntuacion: score, comentario: comment, etiquetas: ['Puntualidad ⏱️', 'Servicio Seguro 🛡️', 'Verificado ✅'] }) }); if (res.ok) { formReview.reset(); if (modalReview) modalReview.classList.remove('active'); fetchStatsAndReviews(); alert('¡Gracias por tu calificación! Ha sido agregada al muro de la comunidad.'); } } catch (err) { console.warn('Error al enviar reseña:', err); } finally { btnSubmit.disabled = false; btnSubmit.innerHTML = '⭐ Publicar Mi Calificación'; } }); } } async function fetchStatsAndReviews() { try { const res = await fetch('/api/stats-reviews'); if (res.ok) { const data = await res.json(); if (data.stats?.global) { const g = data.stats.global; const totalViajesEl = document.getElementById('stat-total-viajes'); const ratingAvgEl = document.getElementById('stat-rating-avg'); const puntualidadEl = document.getElementById('stat-puntualidad'); const conductoresActivosEl = document.getElementById('stat-conductores-activos'); if (totalViajesEl) totalViajesEl.textContent = `${g.totalViajes}+`; if (ratingAvgEl) ratingAvgEl.textContent = `${g.calificacionPromedio} / 5.0`; if (puntualidadEl) puntualidadEl.textContent = g.indicePuntualidad; if (conductoresActivosEl) conductoresActivosEl.textContent = g.conductoresCertificados; } if (data.recentReviews && Array.isArray(data.recentReviews)) { renderReviewsGrid(data.recentReviews); } } } catch (err) { console.warn('⚠️ [Stats/Reviews Error]:', err); } } function renderReviewsGrid(reviews) { const container = document.getElementById('reviews-list-grid'); if (!container) return; container.innerHTML = reviews.map(r => { const starsCount = Math.round(r.puntuacion || 5); const starsString = '★'.repeat(starsCount) + '☆'.repeat(5 - starsCount); const tagsHtml = (r.etiquetas || []).map(t => `${t}`).join(''); const isPasajero = r.autorRol === 'PASAJERO'; return `
${r.autorNombre}
${isPasajero ? '👤 Pasajero Verificado' : '🚗 Conductor Certificado'}
${starsString}
Ruta: ${r.ruta || 'Cali ➔ Buenaventura'} • Calificó a: ${r.destinatarioNombre}
"${r.comentario}"
${tagsHtml}
`; }).join(''); } /* ========================================================================== 6. CONFIGURACIÓN DE TARIFAS Y CANAL DE ATENCIÓN WHATSAPP ========================================================================== */ async function fetchTariffs() { try { const res = await fetch('/api/tariffs'); if (res.ok) { const data = await res.json(); if (data.config) { currentTariffs = { ...currentTariffs, ...data.config }; syncTariffsToUI(); } } } catch (err) { console.warn('⚠️ [Tarifas API]: Modo local/offline activo, utilizando tarifas por defecto.', err); syncTariffsToUI(); } } function syncTariffsToUI() { // 1. Formulario administrativo de tarifas y soporte const elPasajero = document.getElementById('cfg-tarifa-pasajero'); const elBasePaquete = document.getElementById('cfg-base-paquete'); const elKmPaquete = document.getElementById('cfg-km-paquete'); const elSobre = document.getElementById('cfg-factor-sobre'); const elMediano = document.getElementById('cfg-factor-mediano'); const elGrande = document.getElementById('cfg-factor-grande'); const elSeguro = document.getElementById('cfg-seguro-base'); const elComision = document.getElementById('cfg-comision'); const elPenalidad = document.getElementById('cfg-penalidad'); const elRadio = document.getElementById('cfg-radio-pooling'); const elPhone = document.getElementById('cfg-admin-phone'); const elName = document.getElementById('cfg-admin-name'); if (elPasajero) elPasajero.value = currentTariffs.tarifaPasajeroFija; if (elBasePaquete) elBasePaquete.value = currentTariffs.precioBasePaquete; if (elKmPaquete) elKmPaquete.value = currentTariffs.precioKmPaquete; if (elSobre && currentTariffs.factorPaquetes) elSobre.value = currentTariffs.factorPaquetes.sobre || 1.0; if (elMediano && currentTariffs.factorPaquetes) elMediano.value = currentTariffs.factorPaquetes.mediano || 1.6; if (elGrande && currentTariffs.factorPaquetes) elGrande.value = currentTariffs.factorPaquetes.grande || 2.2; if (elSeguro) elSeguro.value = currentTariffs.seguroBasePaquete; if (elComision) elComision.value = currentTariffs.porcentajeComisionAdmin; if (elPenalidad) elPenalidad.value = currentTariffs.costoPenalidadCancelacion; if (elRadio) elRadio.value = currentTariffs.radioMaxPoolingMetros; if (elPhone && currentTariffs.numeroAtencionWhatsApp) elPhone.value = currentTariffs.numeroAtencionWhatsApp; if (elName && currentTariffs.nombreSoporte) elName.value = currentTariffs.nombreSoporte; // 2. Sincronización del enlace dinámico de atención WhatsApp en el footer y navbar const phoneFormatted = formatPhoneNumber(currentTariffs.numeroAtencionWhatsApp || '573167455877'); const cleanPhone = (currentTariffs.numeroAtencionWhatsApp || '573167455877').replace(/\D/g, ''); const footerPhoneEl = document.getElementById('footer-support-phone'); const footerLinkEl = document.getElementById('footer-support-link'); if (footerPhoneEl) footerPhoneEl.textContent = phoneFormatted; if (footerLinkEl) footerLinkEl.href = `https://wa.me/${cleanPhone}?text=Hola%20Pacifictrans%20Go,%20deseo%20informaci%C3%B3n%20sobre%20un%20servicio`; // 3. Precios en módulo de reservas de pasajeros const formattedPasajero = `$${currentTariffs.tarifaPasajeroFija.toLocaleString('es-CO')} COP`; const totalPriceEl = document.querySelector('.total-price'); if (totalPriceEl) totalPriceEl.textContent = formattedPasajero; const btnBook = document.getElementById('btn-book-pool'); if (btnBook && !btnBook.disabled) { btnBook.innerHTML = ` Confirmar y Pagar Cupo (${formattedPasajero}) `; } // 4. Recalcular módulo de mensajería y fletes if (window.recalculateFreight) { window.recalculateFreight(); } } function formatPhoneNumber(num) { const clean = num.replace(/\D/g, ''); if (clean.startsWith('57') && clean.length === 12) { return `+57 ${clean.slice(2, 5)} ${clean.slice(5, 8)} ${clean.slice(8)}`; } return `+${clean}`; } function initTariffsManager() { const form = document.getElementById('form-tariffs-config'); const statusEl = document.getElementById('tariffs-save-status'); if (!form) return; form.addEventListener('submit', async (e) => { e.preventDefault(); const btn = document.getElementById('btn-save-tariffs'); if (btn) { btn.disabled = true; btn.innerHTML = '⏳ Guardando en MongoDB...'; } const payload = { tarifaPasajeroFija: parseFloat(document.getElementById('cfg-tarifa-pasajero')?.value || '35000'), precioBasePaquete: parseFloat(document.getElementById('cfg-base-paquete')?.value || '25000'), precioKmPaquete: parseFloat(document.getElementById('cfg-km-paquete')?.value || '1200'), factorPaquetes: { sobre: parseFloat(document.getElementById('cfg-factor-sobre')?.value || '1.0'), mediano: parseFloat(document.getElementById('cfg-factor-mediano')?.value || '1.6'), grande: parseFloat(document.getElementById('cfg-factor-grande')?.value || '2.2') }, seguroBasePaquete: parseFloat(document.getElementById('cfg-seguro-base')?.value || '5000'), porcentajeComisionAdmin: parseFloat(document.getElementById('cfg-comision')?.value || '15'), costoPenalidadCancelacion: parseFloat(document.getElementById('cfg-penalidad')?.value || '15000'), radioMaxPoolingMetros: parseFloat(document.getElementById('cfg-radio-pooling')?.value || '4000'), numeroAtencionWhatsApp: document.getElementById('cfg-admin-phone')?.value.trim() || '573167455877', nombreSoporte: document.getElementById('cfg-admin-name')?.value.trim() || 'Atención Pacifictrans Go' }; try { const res = await fetch('/api/tariffs', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (res.ok) { const data = await res.json(); currentTariffs = { ...currentTariffs, ...data.config }; syncTariffsToUI(); if (statusEl) { statusEl.innerHTML = `✅ ¡Configuración guardada y sincronizada! Pasajero: $${payload.tarifaPasajeroFija.toLocaleString('es-CO')} COP | WhatsApp Atención: +${payload.numeroAtencionWhatsApp}`; statusEl.style.color = '#10b981'; } } else { throw new Error('Error al guardar en el servidor'); } } catch (err) { currentTariffs = { ...currentTariffs, ...payload }; syncTariffsToUI(); if (statusEl) { statusEl.innerHTML = `✅ Configuración actualizada localmente.`; statusEl.style.color = '#10b981'; } } finally { if (btn) { btn.disabled = false; btn.innerHTML = '💾 Guardar y Aplicar Configuración'; } } }); } /* ========================================================================== 7. RESERVA DE PASAJEROS & POOLING ========================================================================== */ function initPassengerCalculator() { const originSelect = document.getElementById('pool-origin'); const addressGroup = document.getElementById('cali-address-group'); const buenaStopGroup = document.getElementById('buena-stop-group'); const bookBtn = document.getElementById('btn-book-pool'); const poolDots = document.querySelectorAll('.seat-dot'); let currentSeatsFilled = 3; function updateOriginDestination() { if (!originSelect) return; const isCali = originSelect.value === 'CALI'; if (addressGroup) addressGroup.style.display = isCali ? 'block' : 'none'; if (buenaStopGroup) buenaStopGroup.style.display = isCali ? 'block' : 'none'; } if (originSelect) { originSelect.addEventListener('change', updateOriginDestination); updateOriginDestination(); } if (bookBtn) { bookBtn.addEventListener('click', async (e) => { e.preventDefault(); // Verificación de seguridad: si no ha validado identidad con OTP, pedir autenticación if (!currentUser) { alert('🛡️ Por seguridad de los conductores y de los demás pasajeros, debes verificar tu identidad con OTP antes de confirmar tu reserva.'); document.getElementById('modal-auth-dripzone')?.classList.add('active'); return; } bookBtn.disabled = true; bookBtn.innerHTML = ` Procesando Reserva... `; setTimeout(() => { currentSeatsFilled = 4; updateSeatVisuals(currentSeatsFilled); const alertBox = document.getElementById('pool-dispatch-alert'); if (alertBox) { alertBox.style.display = 'block'; alertBox.scrollIntoView({ behavior: 'smooth' }); } bookBtn.disabled = false; const formatted = `$${currentTariffs.tarifaPasajeroFija.toLocaleString('es-CO')} COP`; bookBtn.innerHTML = `✅ Cupo Reservado Exitosamente (${formatted})`; }, 1200); }); } function updateSeatVisuals(count) { poolDots.forEach((dot, index) => { dot.classList.remove('active', 'pending'); if (index < count) { dot.classList.add('active'); } else if (index === count) { dot.classList.add('pending'); } }); const seatCounter = document.getElementById('seat-counter-text'); if (seatCounter) seatCounter.textContent = `${count}/4 Pasajeros`; } updateSeatVisuals(currentSeatsFilled); } /* ========================================================================== 8. CALCULADORA DE MENSAJERÍA & FLETES ========================================================================== */ function initFreightCalculator() { const packageTypeSelect = document.getElementById('package-type'); const declaredValueInput = document.getElementById('declared-value'); const totalPriceEl = document.getElementById('freight-total-price'); const baseCostEl = document.getElementById('freight-base-cost'); const distCostEl = document.getElementById('freight-dist-cost'); const insuranceCostEl = document.getElementById('freight-insurance-cost'); const KM_DIST = 115; // Corredor Cali - Buenaventura window.recalculateFreight = function() { const rawFactor = parseFloat(packageTypeSelect?.value || '1.0'); let factor = rawFactor; if (rawFactor === 1.0 && currentTariffs.factorPaquetes?.sobre) { factor = currentTariffs.factorPaquetes.sobre; } else if (rawFactor === 1.6 && currentTariffs.factorPaquetes?.mediano) { factor = currentTariffs.factorPaquetes.mediano; } else if (rawFactor === 2.2 && currentTariffs.factorPaquetes?.grande) { factor = currentTariffs.factorPaquetes.grande; } const declaredValue = parseFloat(declaredValueInput?.value || '0'); const basePrice = currentTariffs.precioBasePaquete || 25000; const costPerKm = currentTariffs.precioKmPaquete || 1200; const distancePart = KM_DIST * costPerKm; const subtotal = (basePrice + distancePart) * factor; const seguro = Math.round(declaredValue * 0.01) + (currentTariffs.seguroBasePaquete || 0); const total = Math.round(subtotal + seguro); if (baseCostEl) baseCostEl.textContent = `$${basePrice.toLocaleString('es-CO')} COP`; if (distCostEl) distCostEl.textContent = `$${Math.round(distancePart * factor).toLocaleString('es-CO')} COP`; if (insuranceCostEl) insuranceCostEl.textContent = `$${seguro.toLocaleString('es-CO')} COP`; if (totalPriceEl) totalPriceEl.textContent = `$${total.toLocaleString('es-CO')} COP`; }; if (packageTypeSelect) packageTypeSelect.addEventListener('change', window.recalculateFreight); if (declaredValueInput) declaredValueInput.addEventListener('input', window.recalculateFreight); window.recalculateFreight(); }