347 lines
15 KiB
JavaScript
347 lines
15 KiB
JavaScript
// 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
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initTabs();
|
|
initPassengerCalculator();
|
|
initFreightCalculator();
|
|
initRadarSimulation();
|
|
initTariffsManager();
|
|
fetchTariffs();
|
|
});
|
|
|
|
// TABS NAVIGATION
|
|
function initTabs() {
|
|
const tabBtns = document.querySelectorAll('.tab-btn');
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
|
|
tabBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const targetId = btn.getAttribute('data-tab');
|
|
|
|
tabBtns.forEach(b => b.classList.remove('active'));
|
|
tabContents.forEach(c => c.classList.remove('active'));
|
|
|
|
btn.classList.add('active');
|
|
const targetEl = document.getElementById(targetId);
|
|
if (targetEl) targetEl.classList.add('active');
|
|
});
|
|
});
|
|
}
|
|
|
|
// CARGA DINÁMICA DE TARIFAS DESDE EL BACKEND / MONGODB
|
|
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();
|
|
}
|
|
}
|
|
|
|
// ACTUALIZAR TODOS LOS VALORES DE PRECIO EN LA INTERFAZ
|
|
function syncTariffsToUI() {
|
|
// 1. Campos del formulario administrativo de tarifas
|
|
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');
|
|
|
|
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;
|
|
|
|
// 2. 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 = `
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
|
|
</svg>
|
|
Confirmar y Pagar Cupo (${formattedPasajero})
|
|
`;
|
|
}
|
|
|
|
// 3. Recalcular módulo de mensajería y fletes
|
|
if (window.recalculateFreight) {
|
|
window.recalculateFreight();
|
|
}
|
|
}
|
|
|
|
// GESTOR DE CONFIGURACIÓN DE TARIFAS (ADMIN)
|
|
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')
|
|
};
|
|
|
|
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 = `✅ <strong>¡Tarifas guardadas y sincronizadas en MongoDB!</strong> Pasajero: $${payload.tarifaPasajeroFija.toLocaleString('es-CO')} COP | Base Flete: $${payload.precioBasePaquete.toLocaleString('es-CO')} COP.`;
|
|
statusEl.style.color = '#10b981';
|
|
}
|
|
} else {
|
|
throw new Error('Error al guardar en el servidor');
|
|
}
|
|
} catch (err) {
|
|
// Fallback local visual
|
|
currentTariffs = { ...currentTariffs, ...payload };
|
|
syncTariffsToUI();
|
|
if (statusEl) {
|
|
statusEl.innerHTML = `✅ <strong>Tarifas actualizadas en la plataforma:</strong> Pasajero: $${payload.tarifaPasajeroFija.toLocaleString('es-CO')} COP.`;
|
|
statusEl.style.color = '#10b981';
|
|
}
|
|
} finally {
|
|
if (btn) {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '💾 Guardar y Aplicar Nuevas Tarifas';
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// PASSENGER POOLING & BOOKING LOGIC
|
|
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');
|
|
const poolBadge = document.getElementById('pool-status-badge');
|
|
|
|
let currentSeatsFilled = 3; // Starts at 3/4 for instant visual demonstration
|
|
|
|
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();
|
|
bookBtn.disabled = true;
|
|
bookBtn.innerHTML = `
|
|
<svg class="animate-spin" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<circle cx="12" cy="12" r="10" stroke-opacity="0.25"></circle>
|
|
<path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor"></path>
|
|
</svg> Procesando Agrupación Geoespacial...
|
|
`;
|
|
|
|
setTimeout(() => {
|
|
currentSeatsFilled = 4;
|
|
updateSeatVisuals(currentSeatsFilled);
|
|
|
|
if (poolBadge) {
|
|
poolBadge.innerHTML = `🚨 <strong>¡CUPO COMPLETO (4/4)!</strong> Viaje en estado <code>POOL_COMPLETED</code>. Notificando a la Administración para asignar conductor óptimo según lugar de residencia en Cali.`;
|
|
poolBadge.style.background = 'rgba(245, 158, 11, 0.15)';
|
|
poolBadge.style.borderColor = 'rgba(245, 158, 11, 0.4)';
|
|
poolBadge.style.color = '#fbbf24';
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// FREIGHT & PARCEL CALCULATOR CON TARIFAS CONFIGURABLES
|
|
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');
|
|
// Determinar factor según tarifas actuales
|
|
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();
|
|
}
|
|
|
|
// RADAR FLEET SIMULATION & LIVE DATA
|
|
function initRadarSimulation() {
|
|
const radarContainer = document.getElementById('radar-canvas-container');
|
|
if (!radarContainer) return;
|
|
|
|
const activeDrivers = [
|
|
{ id: '1', nombre: 'Carlos Mario G.', vehiculo: 'Renault Duster (WLZ-890)', barrio: 'Granada (Comuna 2)', distCali: '1.2 km', lat: 3.455, lng: -76.535, 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.448, lng: -76.541, estado: 'ASIGNADO' },
|
|
{ id: '3', nombre: 'Mauricio V.', vehiculo: 'Chevrolet Tracker (HJK-456)', barrio: 'El Ingenio (Comuna 17)', distCali: '6.8 km', lat: 3.385, lng: -76.529, estado: 'DISPONIBLE' },
|
|
{ id: '4', nombre: 'Javier Restrepo', vehiculo: 'Nissan Kicks (RTY-789)', barrio: 'Limonar (Comuna 19)', distCali: '5.1 km', lat: 3.402, lng: -76.538, estado: 'EN_RUTA' }
|
|
];
|
|
|
|
const driverListEl = document.getElementById('suggested-drivers-list');
|
|
if (driverListEl) {
|
|
driverListEl.innerHTML = activeDrivers.map((d, index) => `
|
|
<div class="driver-card">
|
|
<div>
|
|
<strong style="color: #fff; font-size: 1.05rem;">#${index + 1} ${d.nombre}</strong>
|
|
<div style="font-size: 0.85rem; color: #94a3b8;">${d.vehiculo}</div>
|
|
<div style="font-size: 0.82rem; color: #06b6d4; margin-top: 3px;">
|
|
🏠 Residencia en Cali: <strong>${d.barrio}</strong>
|
|
</div>
|
|
<span class="driver-tag">Distancia al 1er Pasajero: ${d.distCali}</span>
|
|
</div>
|
|
<div style="text-align: right;">
|
|
<span style="display: inline-block; padding: 4px 10px; border-radius: 6px; font-size: 0.75rem; font-weight: 700; background: ${d.estado === 'DISPONIBLE' ? 'rgba(16, 185, 129, 0.2)' : 'rgba(245, 158, 11, 0.2)'}; color: ${d.estado === 'DISPONIBLE' ? '#10b981' : '#f59e0b'};">
|
|
${d.estado}
|
|
</span>
|
|
<button onclick="asignarConductorDemo('${d.nombre}', '${d.barrio}')" style="margin-top: 8px; display: block; background: #0f172a; border: 1px solid rgba(6, 182, 212, 0.4); color: #06b6d4; font-size: 0.8rem; padding: 5px 12px; border-radius: 6px; cursor: pointer;">
|
|
Asignar y Programar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
}
|
|
|
|
// Ventana de asignación y disparo de notificación WhatsApp
|
|
window.asignarConductorDemo = function(conductor, barrio) {
|
|
const resultDiv = document.getElementById('schedule-dispatch-result');
|
|
if (resultDiv) {
|
|
resultDiv.style.display = 'block';
|
|
resultDiv.innerHTML = `
|
|
<div style="background: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.4); border-radius: 12px; padding: 1.25rem; color: #10b981;">
|
|
<h4 style="margin-bottom: 0.5rem; font-size: 1.1rem; color: #fff;">✅ Conductor Asignado por Residencia: <strong>${conductor}</strong> (${barrio})</h4>
|
|
<p style="color: #cbd5e1; font-size: 0.92rem; margin-bottom: 0.75rem;">
|
|
🚀 <strong>Disparo Inmediato de Notificación WhatsApp Ejecutado:</strong>
|
|
Los 4 pasajeros han recibido su confirmación oficial con el conductor asignado, vehículo, hora estimada escalonada de recogida y enlace de Waze / mapa en vivo.
|
|
</p>
|
|
<div style="background: rgba(0,0,0,0.4); padding: 0.75rem; border-radius: 8px; font-family: monospace; font-size: 0.85rem; color: #6ee7b7;">
|
|
📲 [Evolution API WhatsApp Status: 201 Created] Enviado a los 4 Pasajeros + Conductor.
|
|
</div>
|
|
</div>
|
|
`;
|
|
resultDiv.scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
};
|