From 7c800a0551e219aaf340af5b273ac5b80882d770 Mon Sep 17 00:00:00 2001 From: Hanzo_dev <2002samudiojohan@gmail.com> Date: Fri, 27 Feb 2026 13:55:44 -0500 Subject: [PATCH] feat(eta): rewrite useETA to use database times and update ETACard --- frontend/src/components/ETACard.vue | 15 ++- frontend/src/composables/useETA.ts | 175 ++++++++++++++++------------ 2 files changed, 112 insertions(+), 78 deletions(-) diff --git a/frontend/src/components/ETACard.vue b/frontend/src/components/ETACard.vue index 7fb2ec3..5fdead0 100644 --- a/frontend/src/components/ETACard.vue +++ b/frontend/src/components/ETACard.vue @@ -85,10 +85,17 @@ {{ index === 0 ? 'Bus más cercano' : 'Siguiente bus' }} - - schedule - Salió de terminal a las {{ bus.hora_salida }} - +
+ + schedule + {{ bus.estado === 'pasó' ? 'Salió a las' : 'Sale a las' }} {{ bus.hora_salida }} + + + Pasó por tu parada a las + Llega a tu parada ~ + {{ bus.horaLlegadaParada }} + +
diff --git a/frontend/src/composables/useETA.ts b/frontend/src/composables/useETA.ts index ebb14ae..2aaa7d1 100644 --- a/frontend/src/composables/useETA.ts +++ b/frontend/src/composables/useETA.ts @@ -4,8 +4,9 @@ import type { BusStop } from '@/types'; export interface BusETA { horario_id: string; - hora_salida: string; - etaMinutos: number; + 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ó'; } @@ -18,110 +19,136 @@ export function useETA() { 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('*') + // 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('is_active', true); + .eq('stop_id', parada_cercana.id) + .single(); - if (error) throw error; + if (rsError || !routeStopData) { + console.warn('SIBU | No se encontró travel_time para la parada:', + parada_cercana.name); + return; + } - const horariosHoy = (horarios || []).filter(h => { + // 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']; + 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; + + // Filtrar horarios que operan hoy + const horariosHoy = (horarios ?? []).filter(h => { if (h.dias_operacion) { - return h.dias_operacion.includes('todos') || h.dias_operacion.includes(diaString); + return h.dias_operacion.includes('todos') || + h.dias_operacion.includes(diaString); } - return h.schedule_type === tipoDia || h.schedule_type === 'todos' || !h.schedule_type; + 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 - + // PASO 3: Calcular ETA para cada horario const ahora = new Date(); - const horasAhora = ahora.getHours(); - const minsAhora = ahora.getMinutes(); - const tiempoActualMinutos = horasAhora * 60 + minsAhora; + const minutosAhora = ahora.getHours() * 60 + ahora.getMinutes(); const resultados: BusETA[] = []; for (const h of horariosHoy) { - const horaSalida = h.departure_time || h.hora_salida; - if (!horaSalida) continue; + const salida = h.departure_time; // "HH:mm:ss" + if (!salida) continue; - const [hSalidaStr, mSalidaStr] = horaSalida.split(':'); - const tiempoSalidaMins = parseInt(hSalidaStr, 10) * 60 + parseInt(mSalidaStr, 10); + // Parsear hora de salida a minutos desde medianoche + const [hStr, mStr] = salida.split(':'); + const minutosSalida = parseInt(hStr) * 60 + parseInt(mStr); - const tiempoTranscurridoMins = tiempoActualMinutos - tiempoSalidaMins; + // ── FÓRMULA PRINCIPAL ──────────────────────────── + // Hora de llegada del bus a la parada del usuario: + const minutosLlegadaParada = minutosSalida + minutosHastaParada; - // ¿Qué tan lejos está la parada del usuario desde el inicio? - const distanciaParadaUsuario = ordenParada * DISTANCIA_PROMEDIO_PARADA_KM; + // ETA = cuántos minutos faltan desde ahora: + const etaMinutos = minutosLlegadaParada - minutosAhora; + // ───────────────────────────────────────────────── - // 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; + // Formatear hora de llegada para mostrar en UI + const horaLlegada = minutosAHora(minutosLlegadaParada); + const horaSalidaFormato = `${hStr.padStart(2, '0')}:${mStr.padStart(2, '0')}`; - resultados.push({ - horario_id: h.id, - hora_salida: horaSalida.slice(0, 5), // Solo HH:mm - etaMinutos: Math.round(etaReal), - estado: 'próximo' - }); + // 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'; } 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' - }); - } + estado = 'pasó'; } + + // 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 + }); } - // Ordenar por prioridad (en_camino < próximo < pasó) y tiempo + // PASO 4: Ordenar resultados + // Prioridad: en_camino → próximo → pasó + // Dentro de cada grupo: menor ETA primero resultados.sort((a, b) => { - if (a.estado === 'pasó' && b.estado !== 'pasó') return 1; - if (b.estado === 'pasó' && a.estado !== 'pasó') return -1; + 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); // Max 3 buses + // Máximo 3 buses + busesActivos.value = resultados.slice(0, 3); } catch (e) { - console.error("Error calculando ETA", 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 }; }