Files
SIB/frontend/src/composables/useETA.ts

193 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ref } from 'vue';
import { supabase } from '@/supabase';
import type { BusStop } from '@/types';
export interface BusETA {
horario_id: string;
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ó)
estado: 'próximo' | 'en_camino' | 'pasó';
}
export function useETA() {
const busesActivos = ref<BusETA[]>([]);
const cargando = ref<boolean>(false);
// Configuración para el cálculo del ETA mejorado
const VELOCIDAD_PROMEDIO_KMH = 35; // km/h (promedio ciudad/carretera)
const TIEMPO_PARADA_SEGUNDOS = 45; // segundos detenido por parada
// Fórmula Haversine para distancia en línea recta (km)
function getDistanceKm(lat1: number, lon1: number, lat2: number, lon2: number): number {
const R = 6371;
const dLat = (lat2 - lat1) * (Math.PI / 180);
const dLon = (lon2 - lon1) * (Math.PI / 180);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
const calcularETA = async (ruta_id: string, parada_cercana: BusStop) => {
cargando.value = true;
busesActivos.value = [];
try {
// PASO 1: Obtener detalles de la ruta y todas sus paradas para calcular distancia real
const [routeRes, stopsRes] = await Promise.all([
supabase
.from('routes')
.select('distance_km, average_speed_kmh')
.eq('id', ruta_id)
.single(),
supabase
.from('route_stops')
.select('stop_id, stop_order, bus_stops(latitude, longitude)')
.eq('route_id', ruta_id)
.order('stop_order', { ascending: true })
]);
if (routeRes.error || !stopsRes.data) throw new Error('Error al cargar datos de ruta');
const routeData = routeRes.data;
const routeStops = stopsRes.data;
// Encontrar el orden de la parada donde está el usuario
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
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
);
}
}
// 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
const ahora = new Date();
const minutosAhora = ahora.getHours() * 60 + ahora.getMinutes();
const resultados: BusETA[] = [];
for (const h of horariosHoy) {
const salida = h.departure_time;
if (!salida) continue;
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';
} else if (etaMinutos >= -2) {
estado = 'en_camino';
} else {
estado = 'pasó';
}
if (etaMinutos < -60) continue;
resultados.push({
horario_id: h.id,
hora_salida: horaSalidaFormato,
horaLlegadaParada: horaLlegada,
etaMinutos: Math.round(etaMinutos),
estado
});
}
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];
}
return a.etaMinutos - b.etaMinutos;
});
busesActivos.value = resultados.slice(0, 3);
} catch (e) {
console.error('SIBU | Error calculando ETA:', e);
} finally {
cargando.value = false;
}
};
// 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')}`;
}
return { calcularETA, busesActivos, cargando };
}