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([]); const cargando = ref(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 | null) => { cargando.value = true; busesActivos.value = []; try { // 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') .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 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 *= 1.2; const velocidad = routeData.average_speed_kmh || VELOCIDAD_PROMEDIO_KMH; const tiempoViajeMinutos = (distanciaAcumuladaKm / velocidad) * 60; const numeroParadas = targetStopIndex; const tiempoParadasMinutos = (numeroParadas * TIEMPO_PARADA_SEGUNDOS) / 60; const minutosHastaParada = tiempoViajeMinutos + tiempoParadasMinutos; // PASO 3: Calcular ETA para cada salida 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); const minutosLlegadaParada = minutosSalida + minutosHastaParada; 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) { estado = (minutosAhora >= minutosSalida) ? '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 totalMinutes = Math.round(minutos); const m = ((totalMinutes % 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 }; }