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 };
}