feat: real-time bus ETA engine via distance approximation and routing

This commit is contained in:
2026-02-26 13:13:56 -05:00
parent fc489c4f46
commit 30c3f092d8
4 changed files with 457 additions and 31 deletions

View File

@ -0,0 +1,127 @@
import { ref } from 'vue';
import { supabase } from '@/supabase';
import type { BusStop } from '@/types';
export interface BusETA {
horario_id: string;
hora_salida: string;
etaMinutos: number;
estado: 'próximo' | 'en_camino' | 'pasó';
}
export function useETA() {
const busesActivos = ref<BusETA[]>([]);
const cargando = ref<boolean>(false);
const calcularETA = async (ruta_id: string, parada_cercana: BusStop) => {
cargando.value = true;
busesActivos.value = [];
try {
// 1. Obtener horarios activos de la ruta para el día de hoy
const diaActual = new Date().getDay(); // 0 = Domingo, 1 = Lunes...
const dias = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado'];
const diaString = dias[diaActual];
const tipoDia = (diaActual === 0 || diaActual === 6) ? 'weekend' : 'weekday';
// Consulta flexible a supabase
const { data: horarios, error } = await supabase
.from('bus_schedules')
.select('*')
.eq('route_id', ruta_id)
.eq('is_active', true);
if (error) throw error;
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;
});
// 2. Parámetros físicos e información horaria
const VELOCIDAD_PROMEDIO_KMH = 30; // velocidad promedio 30 km/h
// Distancia promedio en km entre la parada de origen (índice 0) hasta llegar a esta.
// Si la base de datos no tiene un "orden" numérico, usamos índice o un estimado global de 0.5km por orden de parada.
const ordenParada = typeof parada_cercana.stop_order === 'number' ? parada_cercana.stop_order : 1;
const DISTANCIA_PROMEDIO_PARADA_KM = 0.5; // asumiendo que cada parada dista 500m
const DISTANCIA_TOTAL_RUTA_KM = 15; // Estimado ruta ciudad
const ahora = new Date();
const horasAhora = ahora.getHours();
const minsAhora = ahora.getMinutes();
const tiempoActualMinutos = horasAhora * 60 + minsAhora;
const resultados: BusETA[] = [];
for (const h of horariosHoy) {
const horaSalida = h.departure_time || h.hora_salida;
if (!horaSalida) continue;
const [hSalidaStr, mSalidaStr] = horaSalida.split(':');
const tiempoSalidaMins = parseInt(hSalidaStr, 10) * 60 + parseInt(mSalidaStr, 10);
const tiempoTranscurridoMins = tiempoActualMinutos - tiempoSalidaMins;
// ¿Qué tan lejos está la parada del usuario desde el inicio?
const distanciaParadaUsuario = ordenParada * DISTANCIA_PROMEDIO_PARADA_KM;
// Si el bus sale en el futuro
if (tiempoTranscurridoMins < 0) {
const tiempoA_ParadaMins = (distanciaParadaUsuario / VELOCIDAD_PROMEDIO_KMH) * 60;
const etaReal = Math.abs(tiempoTranscurridoMins) + tiempoA_ParadaMins;
resultados.push({
horario_id: h.id,
hora_salida: horaSalida.slice(0, 5), // Solo HH:mm
etaMinutos: Math.round(etaReal),
estado: 'próximo'
});
} else {
// El bus ya salió. ¿Dónde está?
const kmsRecorridos = (tiempoTranscurridoMins / 60) * VELOCIDAD_PROMEDIO_KMH;
// Ya pasó la parada
if (kmsRecorridos > distanciaParadaUsuario) {
// Si no ha terminado la ruta general
if (kmsRecorridos <= DISTANCIA_TOTAL_RUTA_KM) {
resultados.push({
horario_id: h.id,
hora_salida: horaSalida.slice(0, 5),
etaMinutos: 0,
estado: 'pasó'
});
}
} else {
// En camino
const kmsRestantes = distanciaParadaUsuario - kmsRecorridos;
const etaMinutos = (kmsRestantes / VELOCIDAD_PROMEDIO_KMH) * 60;
resultados.push({
horario_id: h.id,
hora_salida: horaSalida.slice(0, 5),
etaMinutos: Math.round(etaMinutos),
estado: 'en_camino'
});
}
}
}
// Ordenar por prioridad (en_camino < próximo < pasó) y tiempo
resultados.sort((a, b) => {
if (a.estado === 'pasó' && b.estado !== 'pasó') return 1;
if (b.estado === 'pasó' && a.estado !== 'pasó') return -1;
return a.etaMinutos - b.etaMinutos;
});
busesActivos.value = resultados.slice(0, 3); // Max 3 buses
} catch (e) {
console.error("Error calculando ETA", e);
} finally {
cargando.value = false;
}
};
return { calcularETA, busesActivos, cargando };
}