2026-02-26 13:13:56 -05:00
|
|
|
import { ref } from 'vue';
|
|
|
|
|
import { supabase } from '@/supabase';
|
|
|
|
|
import type { BusStop } from '@/types';
|
|
|
|
|
|
|
|
|
|
export interface BusETA {
|
|
|
|
|
horario_id: string;
|
2026-02-27 13:55:44 -05:00
|
|
|
hora_salida: string; // "HH:mm" para mostrar en UI
|
|
|
|
|
horaLlegadaParada: string; // "HH:mm" hora estimada de llegada a la parada
|
|
|
|
|
etaMinutos: number; // minutos hasta que llega (negativo = ya pasó)
|
2026-02-26 13:13:56 -05:00
|
|
|
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 {
|
2026-02-27 13:55:44 -05:00
|
|
|
// PASO 1: Obtener travel_time_minutes de la parada del usuario
|
|
|
|
|
// desde route_stops usando el stop_id de la parada cercana
|
|
|
|
|
const { data: routeStopData, error: rsError } = await supabase
|
|
|
|
|
.from('route_stops')
|
|
|
|
|
.select('travel_time_minutes, stop_order, stop_delay_minutes')
|
|
|
|
|
.eq('route_id', ruta_id)
|
|
|
|
|
.eq('stop_id', parada_cercana.id)
|
|
|
|
|
.single();
|
|
|
|
|
|
|
|
|
|
if (rsError || !routeStopData) {
|
|
|
|
|
console.warn('SIBU | No se encontró travel_time para la parada:',
|
|
|
|
|
parada_cercana.name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tiempo total hasta la parada del usuario en minutos
|
|
|
|
|
const minutosHastaParada =
|
|
|
|
|
(routeStopData.travel_time_minutes ?? 0) +
|
|
|
|
|
(routeStopData.stop_delay_minutes ?? 0);
|
|
|
|
|
|
|
|
|
|
// PASO 2: Obtener horarios activos para hoy
|
|
|
|
|
const diaActual = new Date().getDay();
|
|
|
|
|
const dias = ['domingo', 'lunes', 'martes', 'miercoles',
|
|
|
|
|
'jueves', 'viernes', 'sabado'];
|
2026-02-26 13:13:56 -05:00
|
|
|
const diaString = dias[diaActual];
|
2026-02-27 13:55:44 -05:00
|
|
|
const tipoDia = (diaActual === 0 || diaActual === 6)
|
|
|
|
|
? 'weekend' : 'weekday';
|
2026-02-26 13:13:56 -05:00
|
|
|
|
2026-02-27 13:55:44 -05:00
|
|
|
const { data: horarios, error: hError } = await supabase
|
2026-02-26 13:13:56 -05:00
|
|
|
.from('bus_schedules')
|
2026-02-27 13:55:44 -05:00
|
|
|
.select('id, departure_time, dias_operacion, schedule_type')
|
2026-02-26 13:13:56 -05:00
|
|
|
.eq('route_id', ruta_id)
|
2026-02-27 13:55:44 -05:00
|
|
|
.eq('is_active', true)
|
|
|
|
|
.eq('is_published', true);
|
2026-02-26 13:13:56 -05:00
|
|
|
|
2026-02-27 13:55:44 -05:00
|
|
|
if (hError) throw hError;
|
2026-02-26 13:13:56 -05:00
|
|
|
|
2026-02-27 13:55:44 -05:00
|
|
|
// Filtrar horarios que operan hoy
|
|
|
|
|
const horariosHoy = (horarios ?? []).filter(h => {
|
2026-02-26 13:13:56 -05:00
|
|
|
if (h.dias_operacion) {
|
2026-02-27 13:55:44 -05:00
|
|
|
return h.dias_operacion.includes('todos') ||
|
|
|
|
|
h.dias_operacion.includes(diaString);
|
2026-02-26 13:13:56 -05:00
|
|
|
}
|
2026-02-27 13:55:44 -05:00
|
|
|
return h.schedule_type === tipoDia ||
|
|
|
|
|
h.schedule_type === 'todos' ||
|
|
|
|
|
!h.schedule_type;
|
2026-02-26 13:13:56 -05:00
|
|
|
});
|
|
|
|
|
|
2026-02-27 13:55:44 -05:00
|
|
|
// PASO 3: Calcular ETA para cada horario
|
2026-02-26 13:13:56 -05:00
|
|
|
const ahora = new Date();
|
2026-02-27 13:55:44 -05:00
|
|
|
const minutosAhora = ahora.getHours() * 60 + ahora.getMinutes();
|
2026-02-26 13:13:56 -05:00
|
|
|
|
|
|
|
|
const resultados: BusETA[] = [];
|
|
|
|
|
|
|
|
|
|
for (const h of horariosHoy) {
|
2026-02-27 13:55:44 -05:00
|
|
|
const salida = h.departure_time; // "HH:mm:ss"
|
|
|
|
|
if (!salida) continue;
|
|
|
|
|
|
|
|
|
|
// Parsear hora de salida a minutos desde medianoche
|
|
|
|
|
const [hStr, mStr] = salida.split(':');
|
|
|
|
|
const minutosSalida = parseInt(hStr) * 60 + parseInt(mStr);
|
|
|
|
|
|
|
|
|
|
// ── FÓRMULA PRINCIPAL ────────────────────────────
|
|
|
|
|
// Hora de llegada del bus a la parada del usuario:
|
|
|
|
|
const minutosLlegadaParada = minutosSalida + minutosHastaParada;
|
|
|
|
|
|
|
|
|
|
// ETA = cuántos minutos faltan desde ahora:
|
|
|
|
|
const etaMinutos = minutosLlegadaParada - minutosAhora;
|
|
|
|
|
// ─────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
// Formatear hora de llegada para mostrar en UI
|
|
|
|
|
const horaLlegada = minutosAHora(minutosLlegadaParada);
|
|
|
|
|
const horaSalidaFormato = `${hStr.padStart(2, '0')}:${mStr.padStart(2, '0')}`;
|
|
|
|
|
|
|
|
|
|
// Determinar estado
|
|
|
|
|
let estado: BusETA['estado'];
|
|
|
|
|
|
|
|
|
|
if (etaMinutos > 5) {
|
|
|
|
|
// Bus aún no llega a la parada
|
|
|
|
|
// Verificar si ya salió del terminal o no
|
|
|
|
|
const yaPartio = minutosAhora >= minutosSalida;
|
|
|
|
|
estado = yaPartio ? 'en_camino' : 'próximo';
|
|
|
|
|
} else if (etaMinutos >= -2) {
|
|
|
|
|
// Llegando ahora (ventana de ±2 minutos)
|
|
|
|
|
estado = 'en_camino';
|
2026-02-26 13:13:56 -05:00
|
|
|
} else {
|
|
|
|
|
// Ya pasó la parada
|
2026-02-27 13:55:44 -05:00
|
|
|
estado = 'pasó';
|
2026-02-26 13:13:56 -05:00
|
|
|
}
|
2026-02-27 13:55:44 -05:00
|
|
|
|
|
|
|
|
// No incluir buses que pasaron hace más de 60 minutos
|
|
|
|
|
if (etaMinutos < -60) continue;
|
|
|
|
|
|
|
|
|
|
resultados.push({
|
|
|
|
|
horario_id: h.id,
|
|
|
|
|
hora_salida: horaSalidaFormato,
|
|
|
|
|
horaLlegadaParada: horaLlegada,
|
|
|
|
|
etaMinutos: Math.round(etaMinutos),
|
|
|
|
|
estado
|
|
|
|
|
});
|
2026-02-26 13:13:56 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-27 13:55:44 -05:00
|
|
|
// PASO 4: Ordenar resultados
|
|
|
|
|
// Prioridad: en_camino → próximo → pasó
|
|
|
|
|
// Dentro de cada grupo: menor ETA primero
|
2026-02-26 13:13:56 -05:00
|
|
|
resultados.sort((a, b) => {
|
2026-02-27 13:55:44 -05:00
|
|
|
const prioridad = { 'en_camino': 0, 'próximo': 1, 'pasó': 2 };
|
|
|
|
|
if (prioridad[a.estado] !== prioridad[b.estado]) {
|
|
|
|
|
return prioridad[a.estado] - prioridad[b.estado];
|
|
|
|
|
}
|
2026-02-26 13:13:56 -05:00
|
|
|
return a.etaMinutos - b.etaMinutos;
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-27 13:55:44 -05:00
|
|
|
// Máximo 3 buses
|
|
|
|
|
busesActivos.value = resultados.slice(0, 3);
|
2026-02-26 13:13:56 -05:00
|
|
|
|
|
|
|
|
} catch (e) {
|
2026-02-27 13:55:44 -05:00
|
|
|
console.error('SIBU | Error calculando ETA:', e);
|
2026-02-26 13:13:56 -05:00
|
|
|
} finally {
|
|
|
|
|
cargando.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-27 13:55:44 -05:00
|
|
|
// Helper: convierte minutos desde medianoche a "HH:mm"
|
|
|
|
|
function minutosAHora(minutos: number): string {
|
|
|
|
|
const m = ((minutos % 1440) + 1440) % 1440; // normalizar 0-1439
|
|
|
|
|
const h = Math.floor(m / 60);
|
|
|
|
|
const min = m % 60;
|
|
|
|
|
return `${String(h).padStart(2, '0')}:${String(min).padStart(2, '0')}`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 13:13:56 -05:00
|
|
|
return { calcularETA, busesActivos, cargando };
|
|
|
|
|
}
|