feat(pacifictrans): address by current GPS location for trips and freight, reverse geocoding API, and web APK download links

This commit is contained in:
AI Bot 2026-10-18 20:08:10 -05:00
parent ccb4db4e17
commit 9a1190cc0f
6 changed files with 514 additions and 128 deletions

View File

@ -49,7 +49,7 @@
<a href="#reservas" id="nav-link-reservas" class="active">🚗 Viajar</a> <a href="#reservas" id="nav-link-reservas" class="active">🚗 Viajar</a>
<a href="#fletes" id="nav-link-fletes">📦 Envíos</a> <a href="#fletes" id="nav-link-fletes">📦 Envíos</a>
<a href="#estadisticas" id="nav-link-reviews">⭐ Reseñas</a> <a href="#estadisticas" id="nav-link-reviews">⭐ Reseñas</a>
<a href="#descarga-apk" id="nav-link-app">📲 App Móvil</a> <a href="/PacifictransGo.apk" download="PacifictransGo.apk" id="nav-link-app" style="background: rgba(16, 185, 129, 0.15); color: #34d399; border: 1px solid rgba(16, 185, 129, 0.35); border-radius: 999px; padding: 0.35rem 0.85rem; font-weight: 600; text-decoration: none; display: inline-flex; align-items: center; gap: 0.35rem;">📲 Descargar App</a>
<!-- Botón de apertura de Dashboard Administrativo (visible solo para Administrador) --> <!-- Botón de apertura de Dashboard Administrativo (visible solo para Administrador) -->
<button type="button" class="btn-open-dashboard-header" id="nav-btn-open-admin" style="display: none;"> <button type="button" class="btn-open-dashboard-header" id="nav-btn-open-admin" style="display: none;">
@ -521,6 +521,27 @@
<input type="text" class="form-input" id="package-desc" placeholder="Repuestos automotrices / Ropa / Medicamentos"> <input type="text" class="form-input" id="package-desc" placeholder="Repuestos automotrices / Ropa / Medicamentos">
</div> </div>
<!-- DIRECCIONES DE RECOGIDA Y ENTREGA CON UBICACIÓN GPS -->
<div class="form-group">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.35rem;">
<label class="form-label" for="freight-pickup-address" style="margin-bottom: 0;">📍 Dirección de Recogida del Paquete (Origen)</label>
<button type="button" id="btn-freight-gps-pickup" class="btn-gps-chip" title="Detectar mi ubicación actual para la recogida del paquete">
📍 Mi Ubicación Actual
</button>
</div>
<input type="text" class="form-input" id="freight-pickup-address" placeholder="Ej: Calle 5 #66-20, Cali (o pulsa 'Mi Ubicación Actual')">
</div>
<div class="form-group">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.35rem;">
<label class="form-label" for="freight-delivery-address" style="margin-bottom: 0;">🏁 Dirección de Entrega del Paquete (Destino)</label>
<button type="button" id="btn-freight-gps-delivery" class="btn-gps-chip" title="Detectar mi ubicación actual para la entrega del paquete">
📍 Mi Ubicación Actual
</button>
</div>
<input type="text" class="form-input" id="freight-delivery-address" placeholder="Ej: Terminal Buenaventura, Muelle o Dirección exacta">
</div>
<!-- MODALIDAD DE DESPACHO DE ENCOMIENDA: URGENTE INMEDIATO VS PROGRAMADO --> <!-- MODALIDAD DE DESPACHO DE ENCOMIENDA: URGENTE INMEDIATO VS PROGRAMADO -->
<div class="time-modality-container" id="freight-time-modality-container"> <div class="time-modality-container" id="freight-time-modality-container">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">

View File

@ -147,76 +147,125 @@ function initNativeBackButton() {
}); });
} }
// 3. Geolocalización GPS Nativa de Alta Precisión // Variables globales de coordenadas GPS detectadas
function initNativeGPS() { window.passengerGpsCoords = [-76.5320, 3.4215];
const btnGps = document.getElementById('btn-native-gps-cali'); window.freightPickupCoords = [-76.5320, 3.4215];
if (!btnGps) return; window.freightDeliveryCoords = [-77.0250, 3.8820];
btnGps.addEventListener('click', async () => { // Helper Centralizado: Obtiene coordenadas GPS y geocodifica a dirección humana real
async function getCurrentUserLocation(buttonEl = null) {
let originalText = '';
if (buttonEl) {
triggerHaptic('medium'); triggerHaptic('medium');
btnGps.disabled = true; originalText = buttonEl.innerHTML;
const originalText = btnGps.innerHTML; buttonEl.disabled = true;
btnGps.innerHTML = '⏳ Obteniendo GPS...'; buttonEl.innerHTML = '⏳ Obteniendo GPS...';
}
try { try {
let coords = null; let coords = null;
if (window.Capacitor?.isPluginAvailable?.('Geolocation')) { if (window.Capacitor?.isPluginAvailable?.('Geolocation')) {
const { Geolocation } = window.Capacitor.Plugins; const { Geolocation } = window.Capacitor.Plugins;
try {
const check = await Geolocation.checkPermissions(); const check = await Geolocation.checkPermissions();
if (check.location !== 'granted') { if (check.location !== 'granted') {
const req = await Geolocation.requestPermissions(); const req = await Geolocation.requestPermissions();
if (req.location !== 'granted') { if (req.location !== 'granted') {
showNativeToast('⚠️ Permiso de ubicación requerido para GPS'); showNativeToast('⚠️ Permiso de ubicación requerido para GPS');
btnGps.disabled = false; return null;
btnGps.innerHTML = originalText;
return;
} }
} }
const pos = await Geolocation.getCurrentPosition({ } catch (pErr) { }
enableHighAccuracy: true,
timeout: 12000 const pos = await Geolocation.getCurrentPosition({
}); enableHighAccuracy: true,
coords = { lat: pos.coords.latitude, lng: pos.coords.longitude }; timeout: 12000
} else if (navigator.geolocation) { });
coords = await new Promise((resolve, reject) => { coords = { lat: pos.coords.latitude, lng: pos.coords.longitude };
navigator.geolocation.getCurrentPosition( } else if (navigator.geolocation) {
p => resolve({ lat: p.coords.latitude, lng: p.coords.longitude }), coords = await new Promise((resolve, reject) => {
err => reject(err), navigator.geolocation.getCurrentPosition(
{ enableHighAccuracy: true, timeout: 12000 } p => resolve({ lat: p.coords.latitude, lng: p.coords.longitude }),
); err => reject(err),
{ enableHighAccuracy: true, timeout: 12000 }
);
});
}
if (!coords) {
showNativeToast('⚠️ No se pudo obtener la señal GPS');
return null;
}
if (buttonEl) buttonEl.innerHTML = '🔍 Identificando dirección...';
// Geocodificación inversa satelital a través del backend
let address = `Ubicación GPS (${coords.lat.toFixed(5)}, ${coords.lng.toFixed(5)})`;
let city = (coords.lat > 3.6 && coords.lng < -76.8) ? 'Buenaventura' : 'Cali';
try {
const res = await fetch(`/api/geo/reverse?lat=${coords.lat}&lng=${coords.lng}`);
if (res.ok) {
const data = await res.json();
if (data.success && data.address) {
address = data.address;
city = data.city || city;
}
}
} catch (geoErr) {
console.warn('⚠️ [Reverse geocode notice]:', geoErr);
}
triggerHaptic('success');
return {
lat: coords.lat,
lng: coords.lng,
address,
city
};
} catch (err) {
console.warn('[GPS Location Error]:', err);
showNativeToast('⚠️ No se pudo obtener la ubicación actual');
triggerHaptic('warning');
return null;
} finally {
if (buttonEl) {
buttonEl.disabled = false;
buttonEl.innerHTML = originalText;
}
}
}
// 3. Geolocalización GPS Nativa de Alta Precisión para Pasajeros
function initNativeGPS() {
const btnGps = document.getElementById('btn-native-gps-cali');
if (!btnGps) return;
btnGps.addEventListener('click', async () => {
const loc = await getCurrentUserLocation(btnGps);
if (loc) {
window.passengerGpsCoords = [loc.lng, loc.lat];
// Centrar mapa de radar y posicionar marcador del pasajero
if (leafletMap && typeof L !== 'undefined') {
leafletMap.setView([loc.lat, loc.lng], 16);
const userIcon = L.divIcon({
className: 'user-live-gps-pin',
html: '<div class="pulse-user-dot">📍</div>',
iconSize: [36, 36],
iconAnchor: [18, 18]
}); });
L.marker([loc.lat, loc.lng], { icon: userIcon })
.addTo(leafletMap)
.bindPopup(`<b>📍 Tu Ubicación Actual</b><br>${loc.address}`)
.openPopup();
} }
if (coords) { const addrInput = document.getElementById('cali-address');
// Centrar mapa de radar y posicionar marcador del pasajero if (addrInput) {
if (leafletMap && typeof L !== 'undefined') { addrInput.value = loc.address;
leafletMap.setView([coords.lat, coords.lng], 16);
const userIcon = L.divIcon({
className: 'user-live-gps-pin',
html: '<div class="pulse-user-dot">📍</div>',
iconSize: [36, 36],
iconAnchor: [18, 18]
});
L.marker([coords.lat, coords.lng], { icon: userIcon })
.addTo(leafletMap)
.bindPopup('<b>📍 Tu Ubicación Actual</b><br>Coordenadas GPS de alta precisión detectadas.')
.openPopup();
}
const addrInput = document.getElementById('cali-address');
if (addrInput) {
addrInput.value = `📍 GPS (${coords.lat.toFixed(5)}, ${coords.lng.toFixed(5)})`;
}
showNativeToast('✅ Ubicación GPS detectada con éxito');
triggerHaptic('success');
} }
} catch (err) { showNativeToast(`📍 Dirección detectada: ${loc.address}`);
console.warn('[GPS Error]:', err);
showNativeToast('No se pudo obtener el GPS actual');
triggerHaptic('warning');
} finally {
btnGps.disabled = false;
btnGps.innerHTML = originalText;
} }
}); });
} }
@ -2160,8 +2209,22 @@ function initPassengerCalculator() {
function updateOriginDestination() { function updateOriginDestination() {
if (!originSelect) return; if (!originSelect) return;
const isCali = originSelect.value === 'CALI'; const isCali = originSelect.value === 'CALI';
if (addressGroup) addressGroup.style.display = isCali ? 'block' : 'none'; const caliAddressLabel = document.querySelector('label[for="cali-address"]');
if (buenaStopGroup) buenaStopGroup.style.display = isCali ? 'block' : 'none'; const buenaStopLabel = document.querySelector('label[for="buena-stop"]');
if (addressGroup) addressGroup.style.display = 'block';
if (buenaStopGroup) buenaStopGroup.style.display = 'block';
if (caliAddressLabel) {
caliAddressLabel.textContent = isCali
? '📍 Dirección Principal de Recogida en Cali (Domicilio Puerta a Puerta)'
: '🏁 Dirección Principal de Llegada en Cali (Entrega en Domicilio)';
}
if (buenaStopLabel) {
buenaStopLabel.textContent = isCali
? '🏁 Parada de Destino en Buenaventura'
: '📍 Punto Oficial de Salida en Buenaventura';
}
} }
if (originSelect) { if (originSelect) {
@ -2232,14 +2295,18 @@ function initPassengerCalculator() {
const pAddress = (!d.sameAddress && d.address && d.address.trim()) const pAddress = (!d.sameAddress && d.address && d.address.trim())
? d.address.trim() ? d.address.trim()
: (caliAddress || 'Punto Oficial Buenaventura'); : (caliAddress || (isCaliOrigin ? 'Cali, Valle del Cauca' : 'Buenaventura, Valle del Cauca'));
const pCoords = (i === 1 && window.passengerGpsCoords)
? window.passengerGpsCoords
: (isCaliOrigin ? [-76.5320, 3.4215] : [-77.0250, 3.8820]);
validatedPassengers.push({ validatedPassengers.push({
nombre: d.nombre.trim(), nombre: d.nombre.trim(),
documentoIdentidad: d.doc.trim(), documentoIdentidad: d.doc.trim(),
telefono: d.phone.trim(), telefono: d.phone.trim(),
direccionRecogida: pAddress, direccionRecogida: pAddress,
coordenadas: [-76.5320, 3.4215], coordenadas: pCoords,
notas: d.notes ? d.notes.trim() : '', notas: d.notes ? d.notes.trim() : '',
esTitular: (i === 1) esTitular: (i === 1)
}); });
@ -2268,11 +2335,15 @@ function initPassengerCalculator() {
`; `;
try { try {
const userTripCoords = (window.passengerGpsCoords && window.passengerGpsCoords.length === 2)
? window.passengerGpsCoords
: (isCaliOrigin ? [-76.5320, 3.4215] : [-77.0250, 3.8820]);
const payload = { const payload = {
usuarioId: currentUser ? currentUser._id : undefined, usuarioId: currentUser ? currentUser._id : undefined,
sentido: isCaliOrigin ? 'CALI_A_BUENAVENTURA' : 'BUENAVENTURA_A_CALI', sentido: isCaliOrigin ? 'CALI_A_BUENAVENTURA' : 'BUENAVENTURA_A_CALI',
direccionTexto: caliAddress || 'Terminal Buenaventura', direccionTexto: caliAddress || (isCaliOrigin ? 'Cali, Valle del Cauca' : 'Buenaventura, Valle del Cauca'),
coordenadas: [-76.5320, 3.4215], coordenadas: userTripCoords,
puntoFijoBuenaventura: { puntoFijoBuenaventura: {
nombre: buenaStopSelect ? buenaStopSelect.options[buenaStopSelect.selectedIndex].text : 'Terminal de Transporte de Buenaventura', nombre: buenaStopSelect ? buenaStopSelect.options[buenaStopSelect.selectedIndex].text : 'Terminal de Transporte de Buenaventura',
coordenadas: [-77.0250, 3.8820] coordenadas: [-77.0250, 3.8820]
@ -2694,6 +2765,34 @@ function initFreightCalculator() {
}); });
} }
// Botones GPS para Recogida y Entrega de Encomiendas
const btnFreightGpsPickup = document.getElementById('btn-freight-gps-pickup');
const btnFreightGpsDelivery = document.getElementById('btn-freight-gps-delivery');
const freightPickupInput = document.getElementById('freight-pickup-address');
const freightDeliveryInput = document.getElementById('freight-delivery-address');
if (btnFreightGpsPickup) {
btnFreightGpsPickup.addEventListener('click', async () => {
const loc = await getCurrentUserLocation(btnFreightGpsPickup);
if (loc) {
window.freightPickupCoords = [loc.lng, loc.lat];
if (freightPickupInput) freightPickupInput.value = loc.address;
showNativeToast(`📍 Recogida establecida: ${loc.address}`);
}
});
}
if (btnFreightGpsDelivery) {
btnFreightGpsDelivery.addEventListener('click', async () => {
const loc = await getCurrentUserLocation(btnFreightGpsDelivery);
if (loc) {
window.freightDeliveryCoords = [loc.lng, loc.lat];
if (freightDeliveryInput) freightDeliveryInput.value = loc.address;
showNativeToast(`🏁 Entrega establecida: ${loc.address}`);
}
});
}
// Despacho / Creación de Encomienda con Pago Seguro y Cupón // Despacho / Creación de Encomienda con Pago Seguro y Cupón
if (btnBookFreight) { if (btnBookFreight) {
btnBookFreight.addEventListener('click', async (e) => { btnBookFreight.addEventListener('click', async (e) => {
@ -2702,6 +2801,8 @@ function initFreightCalculator() {
const senderPhone = document.getElementById('sender-phone')?.value.trim() || currentUser?.telefono || ''; const senderPhone = document.getElementById('sender-phone')?.value.trim() || currentUser?.telefono || '';
const receiverPhone = document.getElementById('receiver-phone')?.value.trim(); const receiverPhone = document.getElementById('receiver-phone')?.value.trim();
const packageDesc = document.getElementById('package-desc')?.value.trim(); const packageDesc = document.getElementById('package-desc')?.value.trim();
const freightPickupAddr = freightPickupInput?.value.trim() || 'Cali, Valle del Cauca';
const freightDeliveryAddr = freightDeliveryInput?.value.trim() || 'Terminal Buenaventura';
if (!senderPhone || senderPhone.replace(/\D/g, '').length < 7) { if (!senderPhone || senderPhone.replace(/\D/g, '').length < 7) {
alert('⚠️ Por favor ingresa el teléfono/WhatsApp del remitente.'); alert('⚠️ Por favor ingresa el teléfono/WhatsApp del remitente.');
@ -2743,6 +2844,12 @@ function initFreightCalculator() {
remitenteId: currentUser ? (currentUser._id || currentUser.id) : undefined, remitenteId: currentUser ? (currentUser._id || currentUser.id) : undefined,
remitenteNombre: senderName || 'Remitente', remitenteNombre: senderName || 'Remitente',
remitenteTelefono: senderPhone, remitenteTelefono: senderPhone,
direccionRecogida: freightPickupAddr,
coordenadasRecogida: window.freightPickupCoords || [-76.5320, 3.4215],
ciudadOrigen: (window.freightPickupCoords && window.freightPickupCoords[1] > 3.6 && window.freightPickupCoords[0] < -76.8) ? 'BUENAVENTURA' : 'CALI',
direccionEntrega: freightDeliveryAddr,
coordenadasEntrega: window.freightDeliveryCoords || [-77.0250, 3.8820],
ciudadDestino: (window.freightDeliveryCoords && window.freightDeliveryCoords[1] < 3.6 && window.freightDeliveryCoords[0] > -76.8) ? 'CALI' : 'BUENAVENTURA',
tipoPaquete: packageTypeSelect ? packageTypeSelect.options[packageTypeSelect.selectedIndex].text : 'Paquete Pequeño', tipoPaquete: packageTypeSelect ? packageTypeSelect.options[packageTypeSelect.selectedIndex].text : 'Paquete Pequeño',
factor: parseFloat(packageTypeSelect?.value || '1.2'), factor: parseFloat(packageTypeSelect?.value || '1.2'),
valorDeclarado: parseFloat(declaredValueInput?.value || '100000'), valorDeclarado: parseFloat(declaredValueInput?.value || '100000'),

View File

@ -0,0 +1,48 @@
import io
import sys
import urllib.request
import json
import ssl
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
API_URL = "https://pacifictrans.almeira.com.co"
print("1. Probando geocodificacion inversa satelital en Cali...")
req_cali = urllib.request.Request(f"{API_URL}/api/geo/reverse?lat=3.4215&lng=-76.5320")
try:
with urllib.request.urlopen(req_cali, context=ctx, timeout=10) as res:
data = json.loads(res.read().decode("utf-8"))
print(f" [OK] Cali geocodificado: {data.get('address')} | Ciudad: {data.get('city')}")
assert data.get('success') is True
except Exception as e:
print(" Aviso / Error:", e)
print("\n2. Probando geocodificacion inversa satelital en Buenaventura...")
req_bv = urllib.request.Request(f"{API_URL}/api/geo/reverse?lat=3.8820&lng=-77.0250")
try:
with urllib.request.urlopen(req_bv, context=ctx, timeout=10) as res:
data = json.loads(res.read().decode("utf-8"))
print(f" [OK] Buenaventura geocodificado: {data.get('address')} | Ciudad: {data.get('city')}")
assert data.get('success') is True
except Exception as e:
print(" Aviso / Error:", e)
print("\n3. Verificando campos de direccion y botones GPS en index.html...")
req_html = urllib.request.Request(f"{API_URL}/")
with urllib.request.urlopen(req_html, context=ctx, timeout=10) as res:
html = res.read().decode("utf-8")
assert "freight-pickup-address" in html, "Falta freight-pickup-address"
assert "freight-delivery-address" in html, "Falta freight-delivery-address"
assert "btn-freight-gps-pickup" in html, "Falta btn-freight-gps-pickup"
assert "btn-freight-gps-delivery" in html, "Falta btn-freight-gps-delivery"
assert "btn-native-gps-cali" in html, "Falta btn-native-gps-cali"
print(" [OK] Todos los campos de direccion y botones GPS de ubicacion actual estan activos en la interfaz.")
print("\n[OK] Verificacion de direccion por ubicacion actual completada exitosamente.")

View File

@ -49,7 +49,7 @@
<a href="#reservas" id="nav-link-reservas" class="active">🚗 Viajar</a> <a href="#reservas" id="nav-link-reservas" class="active">🚗 Viajar</a>
<a href="#fletes" id="nav-link-fletes">📦 Envíos</a> <a href="#fletes" id="nav-link-fletes">📦 Envíos</a>
<a href="#estadisticas" id="nav-link-reviews">⭐ Reseñas</a> <a href="#estadisticas" id="nav-link-reviews">⭐ Reseñas</a>
<a href="#descarga-apk" id="nav-link-app">📲 App Móvil</a> <a href="/PacifictransGo.apk" download="PacifictransGo.apk" id="nav-link-app" style="background: rgba(16, 185, 129, 0.15); color: #34d399; border: 1px solid rgba(16, 185, 129, 0.35); border-radius: 999px; padding: 0.35rem 0.85rem; font-weight: 600; text-decoration: none; display: inline-flex; align-items: center; gap: 0.35rem;">📲 Descargar App</a>
<!-- Botón de apertura de Dashboard Administrativo (visible solo para Administrador) --> <!-- Botón de apertura de Dashboard Administrativo (visible solo para Administrador) -->
<button type="button" class="btn-open-dashboard-header" id="nav-btn-open-admin" style="display: none;"> <button type="button" class="btn-open-dashboard-header" id="nav-btn-open-admin" style="display: none;">
@ -521,6 +521,27 @@
<input type="text" class="form-input" id="package-desc" placeholder="Repuestos automotrices / Ropa / Medicamentos"> <input type="text" class="form-input" id="package-desc" placeholder="Repuestos automotrices / Ropa / Medicamentos">
</div> </div>
<!-- DIRECCIONES DE RECOGIDA Y ENTREGA CON UBICACIÓN GPS -->
<div class="form-group">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.35rem;">
<label class="form-label" for="freight-pickup-address" style="margin-bottom: 0;">📍 Dirección de Recogida del Paquete (Origen)</label>
<button type="button" id="btn-freight-gps-pickup" class="btn-gps-chip" title="Detectar mi ubicación actual para la recogida del paquete">
📍 Mi Ubicación Actual
</button>
</div>
<input type="text" class="form-input" id="freight-pickup-address" placeholder="Ej: Calle 5 #66-20, Cali (o pulsa 'Mi Ubicación Actual')">
</div>
<div class="form-group">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.35rem;">
<label class="form-label" for="freight-delivery-address" style="margin-bottom: 0;">🏁 Dirección de Entrega del Paquete (Destino)</label>
<button type="button" id="btn-freight-gps-delivery" class="btn-gps-chip" title="Detectar mi ubicación actual para la entrega del paquete">
📍 Mi Ubicación Actual
</button>
</div>
<input type="text" class="form-input" id="freight-delivery-address" placeholder="Ej: Terminal Buenaventura, Muelle o Dirección exacta">
</div>
<!-- MODALIDAD DE DESPACHO DE ENCOMIENDA: URGENTE INMEDIATO VS PROGRAMADO --> <!-- MODALIDAD DE DESPACHO DE ENCOMIENDA: URGENTE INMEDIATO VS PROGRAMADO -->
<div class="time-modality-container" id="freight-time-modality-container"> <div class="time-modality-container" id="freight-time-modality-container">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">

View File

@ -147,76 +147,125 @@ function initNativeBackButton() {
}); });
} }
// 3. Geolocalización GPS Nativa de Alta Precisión // Variables globales de coordenadas GPS detectadas
function initNativeGPS() { window.passengerGpsCoords = [-76.5320, 3.4215];
const btnGps = document.getElementById('btn-native-gps-cali'); window.freightPickupCoords = [-76.5320, 3.4215];
if (!btnGps) return; window.freightDeliveryCoords = [-77.0250, 3.8820];
btnGps.addEventListener('click', async () => { // Helper Centralizado: Obtiene coordenadas GPS y geocodifica a dirección humana real
async function getCurrentUserLocation(buttonEl = null) {
let originalText = '';
if (buttonEl) {
triggerHaptic('medium'); triggerHaptic('medium');
btnGps.disabled = true; originalText = buttonEl.innerHTML;
const originalText = btnGps.innerHTML; buttonEl.disabled = true;
btnGps.innerHTML = '⏳ Obteniendo GPS...'; buttonEl.innerHTML = '⏳ Obteniendo GPS...';
}
try { try {
let coords = null; let coords = null;
if (window.Capacitor?.isPluginAvailable?.('Geolocation')) { if (window.Capacitor?.isPluginAvailable?.('Geolocation')) {
const { Geolocation } = window.Capacitor.Plugins; const { Geolocation } = window.Capacitor.Plugins;
try {
const check = await Geolocation.checkPermissions(); const check = await Geolocation.checkPermissions();
if (check.location !== 'granted') { if (check.location !== 'granted') {
const req = await Geolocation.requestPermissions(); const req = await Geolocation.requestPermissions();
if (req.location !== 'granted') { if (req.location !== 'granted') {
showNativeToast('⚠️ Permiso de ubicación requerido para GPS'); showNativeToast('⚠️ Permiso de ubicación requerido para GPS');
btnGps.disabled = false; return null;
btnGps.innerHTML = originalText;
return;
} }
} }
const pos = await Geolocation.getCurrentPosition({ } catch (pErr) { }
enableHighAccuracy: true,
timeout: 12000 const pos = await Geolocation.getCurrentPosition({
}); enableHighAccuracy: true,
coords = { lat: pos.coords.latitude, lng: pos.coords.longitude }; timeout: 12000
} else if (navigator.geolocation) { });
coords = await new Promise((resolve, reject) => { coords = { lat: pos.coords.latitude, lng: pos.coords.longitude };
navigator.geolocation.getCurrentPosition( } else if (navigator.geolocation) {
p => resolve({ lat: p.coords.latitude, lng: p.coords.longitude }), coords = await new Promise((resolve, reject) => {
err => reject(err), navigator.geolocation.getCurrentPosition(
{ enableHighAccuracy: true, timeout: 12000 } p => resolve({ lat: p.coords.latitude, lng: p.coords.longitude }),
); err => reject(err),
{ enableHighAccuracy: true, timeout: 12000 }
);
});
}
if (!coords) {
showNativeToast('⚠️ No se pudo obtener la señal GPS');
return null;
}
if (buttonEl) buttonEl.innerHTML = '🔍 Identificando dirección...';
// Geocodificación inversa satelital a través del backend
let address = `Ubicación GPS (${coords.lat.toFixed(5)}, ${coords.lng.toFixed(5)})`;
let city = (coords.lat > 3.6 && coords.lng < -76.8) ? 'Buenaventura' : 'Cali';
try {
const res = await fetch(`/api/geo/reverse?lat=${coords.lat}&lng=${coords.lng}`);
if (res.ok) {
const data = await res.json();
if (data.success && data.address) {
address = data.address;
city = data.city || city;
}
}
} catch (geoErr) {
console.warn('⚠️ [Reverse geocode notice]:', geoErr);
}
triggerHaptic('success');
return {
lat: coords.lat,
lng: coords.lng,
address,
city
};
} catch (err) {
console.warn('[GPS Location Error]:', err);
showNativeToast('⚠️ No se pudo obtener la ubicación actual');
triggerHaptic('warning');
return null;
} finally {
if (buttonEl) {
buttonEl.disabled = false;
buttonEl.innerHTML = originalText;
}
}
}
// 3. Geolocalización GPS Nativa de Alta Precisión para Pasajeros
function initNativeGPS() {
const btnGps = document.getElementById('btn-native-gps-cali');
if (!btnGps) return;
btnGps.addEventListener('click', async () => {
const loc = await getCurrentUserLocation(btnGps);
if (loc) {
window.passengerGpsCoords = [loc.lng, loc.lat];
// Centrar mapa de radar y posicionar marcador del pasajero
if (leafletMap && typeof L !== 'undefined') {
leafletMap.setView([loc.lat, loc.lng], 16);
const userIcon = L.divIcon({
className: 'user-live-gps-pin',
html: '<div class="pulse-user-dot">📍</div>',
iconSize: [36, 36],
iconAnchor: [18, 18]
}); });
L.marker([loc.lat, loc.lng], { icon: userIcon })
.addTo(leafletMap)
.bindPopup(`<b>📍 Tu Ubicación Actual</b><br>${loc.address}`)
.openPopup();
} }
if (coords) { const addrInput = document.getElementById('cali-address');
// Centrar mapa de radar y posicionar marcador del pasajero if (addrInput) {
if (leafletMap && typeof L !== 'undefined') { addrInput.value = loc.address;
leafletMap.setView([coords.lat, coords.lng], 16);
const userIcon = L.divIcon({
className: 'user-live-gps-pin',
html: '<div class="pulse-user-dot">📍</div>',
iconSize: [36, 36],
iconAnchor: [18, 18]
});
L.marker([coords.lat, coords.lng], { icon: userIcon })
.addTo(leafletMap)
.bindPopup('<b>📍 Tu Ubicación Actual</b><br>Coordenadas GPS de alta precisión detectadas.')
.openPopup();
}
const addrInput = document.getElementById('cali-address');
if (addrInput) {
addrInput.value = `📍 GPS (${coords.lat.toFixed(5)}, ${coords.lng.toFixed(5)})`;
}
showNativeToast('✅ Ubicación GPS detectada con éxito');
triggerHaptic('success');
} }
} catch (err) { showNativeToast(`📍 Dirección detectada: ${loc.address}`);
console.warn('[GPS Error]:', err);
showNativeToast('No se pudo obtener el GPS actual');
triggerHaptic('warning');
} finally {
btnGps.disabled = false;
btnGps.innerHTML = originalText;
} }
}); });
} }
@ -2160,8 +2209,22 @@ function initPassengerCalculator() {
function updateOriginDestination() { function updateOriginDestination() {
if (!originSelect) return; if (!originSelect) return;
const isCali = originSelect.value === 'CALI'; const isCali = originSelect.value === 'CALI';
if (addressGroup) addressGroup.style.display = isCali ? 'block' : 'none'; const caliAddressLabel = document.querySelector('label[for="cali-address"]');
if (buenaStopGroup) buenaStopGroup.style.display = isCali ? 'block' : 'none'; const buenaStopLabel = document.querySelector('label[for="buena-stop"]');
if (addressGroup) addressGroup.style.display = 'block';
if (buenaStopGroup) buenaStopGroup.style.display = 'block';
if (caliAddressLabel) {
caliAddressLabel.textContent = isCali
? '📍 Dirección Principal de Recogida en Cali (Domicilio Puerta a Puerta)'
: '🏁 Dirección Principal de Llegada en Cali (Entrega en Domicilio)';
}
if (buenaStopLabel) {
buenaStopLabel.textContent = isCali
? '🏁 Parada de Destino en Buenaventura'
: '📍 Punto Oficial de Salida en Buenaventura';
}
} }
if (originSelect) { if (originSelect) {
@ -2232,14 +2295,18 @@ function initPassengerCalculator() {
const pAddress = (!d.sameAddress && d.address && d.address.trim()) const pAddress = (!d.sameAddress && d.address && d.address.trim())
? d.address.trim() ? d.address.trim()
: (caliAddress || 'Punto Oficial Buenaventura'); : (caliAddress || (isCaliOrigin ? 'Cali, Valle del Cauca' : 'Buenaventura, Valle del Cauca'));
const pCoords = (i === 1 && window.passengerGpsCoords)
? window.passengerGpsCoords
: (isCaliOrigin ? [-76.5320, 3.4215] : [-77.0250, 3.8820]);
validatedPassengers.push({ validatedPassengers.push({
nombre: d.nombre.trim(), nombre: d.nombre.trim(),
documentoIdentidad: d.doc.trim(), documentoIdentidad: d.doc.trim(),
telefono: d.phone.trim(), telefono: d.phone.trim(),
direccionRecogida: pAddress, direccionRecogida: pAddress,
coordenadas: [-76.5320, 3.4215], coordenadas: pCoords,
notas: d.notes ? d.notes.trim() : '', notas: d.notes ? d.notes.trim() : '',
esTitular: (i === 1) esTitular: (i === 1)
}); });
@ -2268,11 +2335,15 @@ function initPassengerCalculator() {
`; `;
try { try {
const userTripCoords = (window.passengerGpsCoords && window.passengerGpsCoords.length === 2)
? window.passengerGpsCoords
: (isCaliOrigin ? [-76.5320, 3.4215] : [-77.0250, 3.8820]);
const payload = { const payload = {
usuarioId: currentUser ? currentUser._id : undefined, usuarioId: currentUser ? currentUser._id : undefined,
sentido: isCaliOrigin ? 'CALI_A_BUENAVENTURA' : 'BUENAVENTURA_A_CALI', sentido: isCaliOrigin ? 'CALI_A_BUENAVENTURA' : 'BUENAVENTURA_A_CALI',
direccionTexto: caliAddress || 'Terminal Buenaventura', direccionTexto: caliAddress || (isCaliOrigin ? 'Cali, Valle del Cauca' : 'Buenaventura, Valle del Cauca'),
coordenadas: [-76.5320, 3.4215], coordenadas: userTripCoords,
puntoFijoBuenaventura: { puntoFijoBuenaventura: {
nombre: buenaStopSelect ? buenaStopSelect.options[buenaStopSelect.selectedIndex].text : 'Terminal de Transporte de Buenaventura', nombre: buenaStopSelect ? buenaStopSelect.options[buenaStopSelect.selectedIndex].text : 'Terminal de Transporte de Buenaventura',
coordenadas: [-77.0250, 3.8820] coordenadas: [-77.0250, 3.8820]
@ -2694,6 +2765,34 @@ function initFreightCalculator() {
}); });
} }
// Botones GPS para Recogida y Entrega de Encomiendas
const btnFreightGpsPickup = document.getElementById('btn-freight-gps-pickup');
const btnFreightGpsDelivery = document.getElementById('btn-freight-gps-delivery');
const freightPickupInput = document.getElementById('freight-pickup-address');
const freightDeliveryInput = document.getElementById('freight-delivery-address');
if (btnFreightGpsPickup) {
btnFreightGpsPickup.addEventListener('click', async () => {
const loc = await getCurrentUserLocation(btnFreightGpsPickup);
if (loc) {
window.freightPickupCoords = [loc.lng, loc.lat];
if (freightPickupInput) freightPickupInput.value = loc.address;
showNativeToast(`📍 Recogida establecida: ${loc.address}`);
}
});
}
if (btnFreightGpsDelivery) {
btnFreightGpsDelivery.addEventListener('click', async () => {
const loc = await getCurrentUserLocation(btnFreightGpsDelivery);
if (loc) {
window.freightDeliveryCoords = [loc.lng, loc.lat];
if (freightDeliveryInput) freightDeliveryInput.value = loc.address;
showNativeToast(`🏁 Entrega establecida: ${loc.address}`);
}
});
}
// Despacho / Creación de Encomienda con Pago Seguro y Cupón // Despacho / Creación de Encomienda con Pago Seguro y Cupón
if (btnBookFreight) { if (btnBookFreight) {
btnBookFreight.addEventListener('click', async (e) => { btnBookFreight.addEventListener('click', async (e) => {
@ -2702,6 +2801,8 @@ function initFreightCalculator() {
const senderPhone = document.getElementById('sender-phone')?.value.trim() || currentUser?.telefono || ''; const senderPhone = document.getElementById('sender-phone')?.value.trim() || currentUser?.telefono || '';
const receiverPhone = document.getElementById('receiver-phone')?.value.trim(); const receiverPhone = document.getElementById('receiver-phone')?.value.trim();
const packageDesc = document.getElementById('package-desc')?.value.trim(); const packageDesc = document.getElementById('package-desc')?.value.trim();
const freightPickupAddr = freightPickupInput?.value.trim() || 'Cali, Valle del Cauca';
const freightDeliveryAddr = freightDeliveryInput?.value.trim() || 'Terminal Buenaventura';
if (!senderPhone || senderPhone.replace(/\D/g, '').length < 7) { if (!senderPhone || senderPhone.replace(/\D/g, '').length < 7) {
alert('⚠️ Por favor ingresa el teléfono/WhatsApp del remitente.'); alert('⚠️ Por favor ingresa el teléfono/WhatsApp del remitente.');
@ -2743,6 +2844,12 @@ function initFreightCalculator() {
remitenteId: currentUser ? (currentUser._id || currentUser.id) : undefined, remitenteId: currentUser ? (currentUser._id || currentUser.id) : undefined,
remitenteNombre: senderName || 'Remitente', remitenteNombre: senderName || 'Remitente',
remitenteTelefono: senderPhone, remitenteTelefono: senderPhone,
direccionRecogida: freightPickupAddr,
coordenadasRecogida: window.freightPickupCoords || [-76.5320, 3.4215],
ciudadOrigen: (window.freightPickupCoords && window.freightPickupCoords[1] > 3.6 && window.freightPickupCoords[0] < -76.8) ? 'BUENAVENTURA' : 'CALI',
direccionEntrega: freightDeliveryAddr,
coordenadasEntrega: window.freightDeliveryCoords || [-77.0250, 3.8820],
ciudadDestino: (window.freightDeliveryCoords && window.freightDeliveryCoords[1] < 3.6 && window.freightDeliveryCoords[0] > -76.8) ? 'CALI' : 'BUENAVENTURA',
tipoPaquete: packageTypeSelect ? packageTypeSelect.options[packageTypeSelect.selectedIndex].text : 'Paquete Pequeño', tipoPaquete: packageTypeSelect ? packageTypeSelect.options[packageTypeSelect.selectedIndex].text : 'Paquete Pequeño',
factor: parseFloat(packageTypeSelect?.value || '1.2'), factor: parseFloat(packageTypeSelect?.value || '1.2'),
valorDeclarado: parseFloat(declaredValueInput?.value || '100000'), valorDeclarado: parseFloat(declaredValueInput?.value || '100000'),

View File

@ -123,6 +123,65 @@ const server = http.createServer(async (req, res) => {
return authService.verifyRole(token, allowedRoles); return authService.verifyRole(token, allowedRoles);
}; };
// 1.1. Geocodificación inversa satelital (Dirección por ubicación actual)
if (pathname === '/api/geo/reverse' && req.method === 'GET') {
const lat = parseFloat(url.searchParams.get('lat') || '');
const lng = parseFloat(url.searchParams.get('lng') || url.searchParams.get('lon') || '');
if (isNaN(lat) || isNaN(lng)) {
return sendJson(400, { success: false, error: 'Coordenadas lat y lng inválidas' });
}
try {
const nominatimUrl = `https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${lng}&zoom=18&addressdetails=1`;
const fetchRes = await fetch(nominatimUrl, {
headers: {
'User-Agent': 'PacificTransGO/2.0 (admin@pacifictrans.co)',
'Accept-Language': 'es',
},
});
if (fetchRes.ok) {
const data = await fetchRes.json();
const addr = data.address || {};
// Formatear dirección al estilo colombiano
const parts: string[] = [];
if (addr.road) {
let roadPart = addr.road;
if (addr.house_number) roadPart += ` #${addr.house_number}`;
parts.push(roadPart);
}
if (addr.neighbourhood || addr.suburb) {
parts.push(`Barrio ${addr.neighbourhood || addr.suburb}`);
}
const city = addr.city || addr.town || addr.municipality || (lat > 3.6 ? 'Buenaventura' : 'Cali');
parts.push(city);
if (addr.state) parts.push(addr.state);
const formattedAddress = parts.length > 0 ? parts.join(', ') : data.display_name;
return sendJson(200, {
success: true,
address: formattedAddress,
city,
coordinates: [lng, lat],
raw: addr,
});
}
} catch (err: any) {
console.warn('⚠️ [Nominatim Reverse Geocode Warning]:', err.message);
}
// Fallback inteligente si la red satelital o Nominatim no responde
const inferredCity = (lat > 3.7 && lng < -76.8) ? 'Buenaventura' : 'Cali';
return sendJson(200, {
success: true,
address: `Ubicación GPS (${lat.toFixed(5)}, ${lng.toFixed(5)}), ${inferredCity}`,
city: inferredCity,
coordinates: [lng, lat],
});
}
// 2. Pooling inteligente (Transporte) // 2. Pooling inteligente (Transporte)
if (pathname === '/api/trips/pool' && req.method === 'POST') { if (pathname === '/api/trips/pool' && req.method === 'POST') {
const body = await getJsonBody(); const body = await getJsonBody();
@ -619,15 +678,38 @@ const server = http.createServer(async (req, res) => {
}; };
// Descarga directa del APK Oficial Android // Descarga directa del APK Oficial Android
if (pathname === '/PacifictransGo.apk' || pathname === '/download/apk') { if (
pathname === '/PacifictransGo.apk' ||
pathname === '/download/apk' ||
pathname === '/apk' ||
pathname === '/app' ||
pathname === '/descarga-apk' ||
pathname === '/descargar-apk'
) {
const localApk = path.join(PUBLIC_DIR, 'PacifictransGo.apk'); const localApk = path.join(PUBLIC_DIR, 'PacifictransGo.apk');
const fallbackApk = path.resolve(__dirname, '../../../pacifictrans-app/public/PacifictransGo.apk'); const fallbackApk = path.resolve(__dirname, '../../../pacifictrans-app/public/PacifictransGo.apk');
const vpsApk = '/home/carlos/apps/dripzone-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); const targetApk = fs.existsSync(localApk) ? localApk : (fs.existsSync(fallbackApk) ? fallbackApk : vpsApk);
if (serveStaticFile(targetApk, MIME_TYPES['.apk'])) return; if (fs.existsSync(targetApk)) {
try {
const stat = fs.statSync(targetApk);
res.writeHead(200, {
'Content-Type': 'application/vnd.android.package-archive',
'Content-Length': stat.size,
'Content-Disposition': 'attachment; filename="PacifictransGo.apk"',
'Cache-Control': 'no-cache',
'Access-Control-Allow-Origin': '*',
});
fs.createReadStream(targetApk).pipe(res);
return;
} catch (e) {
console.error('Error serving APK:', e);
}
}
} }
// Archivo estático directo (HTML, CSS, JS, Imágenes) // Archivo estático directo (HTML, CSS, JS, Imágenes)
let relativePath = pathname === '/' ? 'index.html' : pathname.replace(/^\//, ''); let relativePath = pathname === '/' ? 'index.html' : pathname.replace(/^\//, '');
let targetPath = path.join(PUBLIC_DIR, relativePath); let targetPath = path.join(PUBLIC_DIR, relativePath);