feat: optimización de ETA, limpieza automática de rutas y smart location
This commit is contained in:
@ -31,12 +31,46 @@ export function useETA() {
|
||||
return R * c;
|
||||
}
|
||||
|
||||
const calcularETA = async (ruta_id: string, parada_cercana: BusStop) => {
|
||||
const calcularETA = async (ruta_id: string, parada_cercana: BusStop | null) => {
|
||||
cargando.value = true;
|
||||
busesActivos.value = [];
|
||||
|
||||
try {
|
||||
// PASO 1: Obtener detalles de la ruta y todas sus paradas para calcular distancia real
|
||||
// OPTIMIZACIÓN: PASO 1 - Verificar horarios primero (más rápido que cálculos geo)
|
||||
// Esto permite dar feedback instantáneo de "No hay buses" sin esperar a la ubicación
|
||||
const diaActual = new Date().getDay();
|
||||
const dias = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado'];
|
||||
const diaString = dias[diaActual];
|
||||
const tipoDia = (diaActual === 0 || diaActual === 6) ? 'weekend' : 'weekday';
|
||||
|
||||
const { data: horarios, error: hError } = await supabase
|
||||
.from('bus_schedules')
|
||||
.select('id, departure_time, dias_operacion, schedule_type')
|
||||
.eq('route_id', ruta_id)
|
||||
.eq('is_active', true)
|
||||
.eq('is_published', true);
|
||||
|
||||
if (hError) throw hError;
|
||||
|
||||
const horariosHoy = (horarios ?? []).filter(h => {
|
||||
if (h.dias_operacion) {
|
||||
return h.dias_operacion.includes('todos') || h.dias_operacion.includes(diaString);
|
||||
}
|
||||
return h.schedule_type === tipoDia || h.schedule_type === 'todos' || !h.schedule_type;
|
||||
});
|
||||
|
||||
if (horariosHoy.length === 0) {
|
||||
// No hay horarios hoy, no perdemos tiempo en geo
|
||||
busesActivos.value = [];
|
||||
cargando.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Si no tenemos parada aún, no podemos calcular ETA real, pero sabemos que SÍ hay buses.
|
||||
// Mantenemos el estado 'cargando' hasta que llegue la parada.
|
||||
if (!parada_cercana) return;
|
||||
|
||||
// PASO 2: Obtener detalles de la ruta y todas sus paradas para calcular distancia
|
||||
const [routeRes, stopsRes] = await Promise.all([
|
||||
supabase
|
||||
.from('routes')
|
||||
@ -59,69 +93,27 @@ export function useETA() {
|
||||
const targetStopIndex = routeStops.findIndex(s => s.stop_id === parada_cercana.id);
|
||||
if (targetStopIndex === -1) return;
|
||||
|
||||
// CALCULAR DISTANCIA ACUMULADA (Terminal hasta Parada Destino)
|
||||
// Usamos Haversine entre cada parada consecutiva para mayor precisión que una línea recta total
|
||||
// CALCULAR DISTANCIA ACUMULADA
|
||||
let distanciaAcumuladaKm = 0;
|
||||
for (let i = 0; i < targetStopIndex; i++) {
|
||||
const stopA = routeStops[i];
|
||||
const stopB = routeStops[i + 1];
|
||||
|
||||
const start = stopA ? (stopA.bus_stops as any) : null;
|
||||
const end = stopB ? (stopB.bus_stops as any) : null;
|
||||
|
||||
if (start?.latitude != null && start?.longitude != null &&
|
||||
end?.latitude != null && end?.longitude != null) {
|
||||
distanciaAcumuladaKm += getDistanceKm(
|
||||
start.latitude, start.longitude,
|
||||
end.latitude, end.longitude
|
||||
);
|
||||
distanciaAcumuladaKm += getDistanceKm(start.latitude, start.longitude, end.latitude, end.longitude);
|
||||
}
|
||||
}
|
||||
|
||||
// Aplicar factor de corrección de ruta (las calles no son rectas, aprox +20%)
|
||||
distanciaAcumuladaKm *= 1.2;
|
||||
|
||||
// PASO 2: Aplicar Fórmula Mejorada (Requerida por el usuario)
|
||||
// ETA = (Distancia / Velocidad) + (Nº Paradas * Tiempo Parada)
|
||||
|
||||
const velocidad = routeData.average_speed_kmh || VELOCIDAD_PROMEDIO_KMH;
|
||||
|
||||
// Tiempo de viaje (en minutos)
|
||||
const tiempoViajeMinutos = (distanciaAcumuladaKm / velocidad) * 60;
|
||||
|
||||
// Tiempo total en paradas (N paradas previas × tiempo promedio)
|
||||
// numeroParadas = stops antes de llegar a la parada destino
|
||||
const numeroParadas = targetStopIndex;
|
||||
const tiempoParadasMinutos = (numeroParadas * TIEMPO_PARADA_SEGUNDOS) / 60;
|
||||
|
||||
// Tiempo total hasta la parada del usuario
|
||||
const minutosHastaParada = tiempoViajeMinutos + tiempoParadasMinutos;
|
||||
|
||||
console.log(`SIBU ETA | Ruta: ${ruta_id} | Dist: ${distanciaAcumuladaKm.toFixed(2)}km | Paradas: ${numeroParadas} | Total: ${minutosHastaParada.toFixed(1)}min`);
|
||||
|
||||
// PASO 3: Obtener horarios activos para hoy
|
||||
const diaActual = new Date().getDay();
|
||||
const dias = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado'];
|
||||
const diaString = dias[diaActual];
|
||||
const tipoDia = (diaActual === 0 || diaActual === 6) ? 'weekend' : 'weekday';
|
||||
|
||||
const { data: horarios, error: hError } = await supabase
|
||||
.from('bus_schedules')
|
||||
.select('id, departure_time, dias_operacion, schedule_type')
|
||||
.eq('route_id', ruta_id)
|
||||
.eq('is_active', true)
|
||||
.eq('is_published', true);
|
||||
|
||||
if (hError) throw hError;
|
||||
|
||||
const horariosHoy = (horarios ?? []).filter(h => {
|
||||
if (h.dias_operacion) {
|
||||
return h.dias_operacion.includes('todos') || h.dias_operacion.includes(diaString);
|
||||
}
|
||||
return h.schedule_type === tipoDia || h.schedule_type === 'todos' || !h.schedule_type;
|
||||
});
|
||||
|
||||
// PASO 4: Calcular ETA para cada salida desde la terminal
|
||||
// PASO 3: Calcular ETA para cada salida
|
||||
const ahora = new Date();
|
||||
const minutosAhora = ahora.getHours() * 60 + ahora.getMinutes();
|
||||
const resultados: BusETA[] = [];
|
||||
@ -132,20 +124,14 @@ export function useETA() {
|
||||
|
||||
const [hStr, mStr] = salida.split(':');
|
||||
const minutosSalida = parseInt(hStr) * 60 + parseInt(mStr);
|
||||
|
||||
// Hora estimada de llegada a la parada del usuario:
|
||||
const minutosLlegadaParada = minutosSalida + minutosHastaParada;
|
||||
|
||||
// ETA = cuántos minutos faltan desde ahora:
|
||||
const etaMinutos = minutosLlegadaParada - minutosAhora;
|
||||
|
||||
const horaLlegada = minutosAHora(minutosLlegadaParada);
|
||||
const horaSalidaFormato = `${hStr.padStart(2, '0')}:${mStr.padStart(2, '0')}`;
|
||||
|
||||
let estado: BusETA['estado'];
|
||||
if (etaMinutos > 5) {
|
||||
const yaPartio = minutosAhora >= minutosSalida;
|
||||
estado = yaPartio ? 'en_camino' : 'próximo';
|
||||
estado = (minutosAhora >= minutosSalida) ? 'en_camino' : 'próximo';
|
||||
} else if (etaMinutos >= -2) {
|
||||
estado = 'en_camino';
|
||||
} else {
|
||||
@ -165,14 +151,11 @@ export function useETA() {
|
||||
|
||||
resultados.sort((a, b) => {
|
||||
const prioridad = { 'en_camino': 0, 'próximo': 1, 'pasó': 2 };
|
||||
if (prioridad[a.estado] !== prioridad[b.estado]) {
|
||||
return prioridad[a.estado] - prioridad[b.estado];
|
||||
}
|
||||
if (prioridad[a.estado] !== prioridad[b.estado]) return prioridad[a.estado] - prioridad[b.estado];
|
||||
return a.etaMinutos - b.etaMinutos;
|
||||
});
|
||||
|
||||
busesActivos.value = resultados.slice(0, 3);
|
||||
|
||||
} catch (e) {
|
||||
console.error('SIBU | Error calculando ETA:', e);
|
||||
} finally {
|
||||
|
||||
@ -59,24 +59,14 @@ export function useParadaCercana() {
|
||||
const { RoutesService } = await google.maps.importLibrary("routes") as any;
|
||||
const routeService = new RoutesService();
|
||||
|
||||
for (const stop of top5) {
|
||||
const routePromises = top5.map(async (stop) => {
|
||||
try {
|
||||
const response = await routeService.computeRoutes({
|
||||
origin: {
|
||||
location: {
|
||||
latLng: {
|
||||
latitude: ubicacionUsuario.lat,
|
||||
longitude: ubicacionUsuario.lng
|
||||
}
|
||||
}
|
||||
location: { latLng: { latitude: ubicacionUsuario.lat, longitude: ubicacionUsuario.lng } }
|
||||
},
|
||||
destination: {
|
||||
location: {
|
||||
latLng: {
|
||||
latitude: stop.latitude,
|
||||
longitude: stop.longitude
|
||||
}
|
||||
}
|
||||
location: { latLng: { latitude: stop.latitude, longitude: stop.longitude } }
|
||||
},
|
||||
travelMode: 'DRIVE',
|
||||
routingPreference: 'TRAFFIC_UNAWARE',
|
||||
@ -86,31 +76,28 @@ export function useParadaCercana() {
|
||||
|
||||
if (response.routes && response.routes.length > 0) {
|
||||
const route = response.routes[0];
|
||||
let distTotal = 0;
|
||||
let durTotal = 0;
|
||||
|
||||
if (route.distanceMeters) {
|
||||
distTotal = route.distanceMeters;
|
||||
}
|
||||
|
||||
if (route.duration) {
|
||||
// La duración viene como string "123s"
|
||||
durTotal = parseInt(route.duration);
|
||||
}
|
||||
|
||||
if (distTotal < minimaDistanciaCalles) {
|
||||
minimaDistanciaCalles = distTotal;
|
||||
mejorDuracion = durTotal;
|
||||
mejorParada = stop;
|
||||
|
||||
if (route.polyline && route.polyline.encodedPolyline) {
|
||||
mejorRutaPuntos = google.maps.geometry.encoding.decodePath(route.polyline.encodedPolyline);
|
||||
}
|
||||
}
|
||||
return {
|
||||
stop,
|
||||
distance: route.distanceMeters || Infinity,
|
||||
duration: parseInt(route.duration || "0"),
|
||||
points: route.polyline?.encodedPolyline ? google.maps.geometry.encoding.decodePath(route.polyline.encodedPolyline) : []
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error calculando ruta a parada', stop.name, e);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const results = await Promise.all(routePromises);
|
||||
|
||||
for (const res of results) {
|
||||
if (res && res.distance < minimaDistanciaCalles) {
|
||||
minimaDistanciaCalles = res.distance;
|
||||
mejorDuracion = res.duration;
|
||||
mejorParada = res.stop;
|
||||
mejorRutaPuntos = res.points;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error cargando Routes API en useParadaCercana', e);
|
||||
|
||||
Reference in New Issue
Block a user