feat(eta): rewrite useETA to use database times and update ETACard
This commit is contained in:
@ -85,10 +85,17 @@
|
|||||||
<span class="text-sm font-bold text-gray-900 dark:text-white line-clamp-1">
|
<span class="text-sm font-bold text-gray-900 dark:text-white line-clamp-1">
|
||||||
{{ index === 0 ? 'Bus más cercano' : 'Siguiente bus' }}
|
{{ index === 0 ? 'Bus más cercano' : 'Siguiente bus' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-xs font-semibold text-gray-500 dark:text-gray-400 flex items-center gap-1 mt-0.5">
|
<div class="flex flex-col mt-0.5 gap-0.5">
|
||||||
|
<span class="text-xs font-semibold text-gray-500 dark:text-gray-400 flex items-center gap-1">
|
||||||
<span class="material-icons" style="font-size: 14px">schedule</span>
|
<span class="material-icons" style="font-size: 14px">schedule</span>
|
||||||
Salió de terminal a las {{ bus.hora_salida }}
|
{{ bus.estado === 'pasó' ? 'Salió a las' : 'Sale a las' }} {{ bus.hora_salida }}
|
||||||
</span>
|
</span>
|
||||||
|
<span class="text-[11px] font-medium text-gray-500 dark:text-gray-400 pl-4">
|
||||||
|
<span v-if="bus.estado === 'pasó'">Pasó por tu parada a las</span>
|
||||||
|
<span v-else>Llega a tu parada ~</span>
|
||||||
|
{{ bus.horaLlegadaParada }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,9 @@ import type { BusStop } from '@/types';
|
|||||||
|
|
||||||
export interface BusETA {
|
export interface BusETA {
|
||||||
horario_id: string;
|
horario_id: string;
|
||||||
hora_salida: string;
|
hora_salida: string; // "HH:mm" para mostrar en UI
|
||||||
etaMinutos: number;
|
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ó';
|
estado: 'próximo' | 'en_camino' | 'pasó';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,110 +19,136 @@ export function useETA() {
|
|||||||
busesActivos.value = [];
|
busesActivos.value = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Obtener horarios activos de la ruta para el día de hoy
|
// PASO 1: Obtener travel_time_minutes de la parada del usuario
|
||||||
const diaActual = new Date().getDay(); // 0 = Domingo, 1 = Lunes...
|
// desde route_stops usando el stop_id de la parada cercana
|
||||||
const dias = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado'];
|
const { data: routeStopData, error: rsError } = await supabase
|
||||||
const diaString = dias[diaActual];
|
.from('route_stops')
|
||||||
const tipoDia = (diaActual === 0 || diaActual === 6) ? 'weekend' : 'weekday';
|
.select('travel_time_minutes, stop_order, stop_delay_minutes')
|
||||||
|
|
||||||
// Consulta flexible a supabase
|
|
||||||
const { data: horarios, error } = await supabase
|
|
||||||
.from('bus_schedules')
|
|
||||||
.select('*')
|
|
||||||
.eq('route_id', ruta_id)
|
.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:',
|
||||||
const horariosHoy = (horarios || []).filter(h => {
|
parada_cercana.name);
|
||||||
if (h.dias_operacion) {
|
return;
|
||||||
return h.dias_operacion.includes('todos') || h.dias_operacion.includes(diaString);
|
|
||||||
}
|
}
|
||||||
return h.schedule_type === tipoDia || h.schedule_type === 'todos' || !h.schedule_type;
|
|
||||||
|
// 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.schedule_type === tipoDia ||
|
||||||
|
h.schedule_type === 'todos' ||
|
||||||
|
!h.schedule_type;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Parámetros físicos e información horaria
|
// PASO 3: Calcular ETA para cada horario
|
||||||
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
|
|
||||||
|
|
||||||
const ahora = new Date();
|
const ahora = new Date();
|
||||||
const horasAhora = ahora.getHours();
|
const minutosAhora = ahora.getHours() * 60 + ahora.getMinutes();
|
||||||
const minsAhora = ahora.getMinutes();
|
|
||||||
const tiempoActualMinutos = horasAhora * 60 + minsAhora;
|
|
||||||
|
|
||||||
const resultados: BusETA[] = [];
|
const resultados: BusETA[] = [];
|
||||||
|
|
||||||
for (const h of horariosHoy) {
|
for (const h of horariosHoy) {
|
||||||
const horaSalida = h.departure_time || h.hora_salida;
|
const salida = h.departure_time; // "HH:mm:ss"
|
||||||
if (!horaSalida) continue;
|
if (!salida) continue;
|
||||||
|
|
||||||
const [hSalidaStr, mSalidaStr] = horaSalida.split(':');
|
// Parsear hora de salida a minutos desde medianoche
|
||||||
const tiempoSalidaMins = parseInt(hSalidaStr, 10) * 60 + parseInt(mSalidaStr, 10);
|
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?
|
// ETA = cuántos minutos faltan desde ahora:
|
||||||
const distanciaParadaUsuario = ordenParada * DISTANCIA_PROMEDIO_PARADA_KM;
|
const etaMinutos = minutosLlegadaParada - minutosAhora;
|
||||||
|
// ─────────────────────────────────────────────────
|
||||||
|
|
||||||
// Si el bus sale en el futuro
|
// Formatear hora de llegada para mostrar en UI
|
||||||
if (tiempoTranscurridoMins < 0) {
|
const horaLlegada = minutosAHora(minutosLlegadaParada);
|
||||||
const tiempoA_ParadaMins = (distanciaParadaUsuario / VELOCIDAD_PROMEDIO_KMH) * 60;
|
const horaSalidaFormato = `${hStr.padStart(2, '0')}:${mStr.padStart(2, '0')}`;
|
||||||
const etaReal = Math.abs(tiempoTranscurridoMins) + tiempoA_ParadaMins;
|
|
||||||
|
|
||||||
resultados.push({
|
// Determinar estado
|
||||||
horario_id: h.id,
|
let estado: BusETA['estado'];
|
||||||
hora_salida: horaSalida.slice(0, 5), // Solo HH:mm
|
|
||||||
etaMinutos: Math.round(etaReal),
|
if (etaMinutos > 5) {
|
||||||
estado: 'próximo'
|
// 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 {
|
} else {
|
||||||
// El bus ya salió. ¿Dónde está?
|
|
||||||
const kmsRecorridos = (tiempoTranscurridoMins / 60) * VELOCIDAD_PROMEDIO_KMH;
|
|
||||||
|
|
||||||
// Ya pasó la parada
|
// Ya pasó la parada
|
||||||
if (kmsRecorridos > distanciaParadaUsuario) {
|
estado = 'pasó';
|
||||||
// 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'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ordenar por prioridad (en_camino < próximo < pasó) y tiempo
|
// 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// PASO 4: Ordenar resultados
|
||||||
|
// Prioridad: en_camino → próximo → pasó
|
||||||
|
// Dentro de cada grupo: menor ETA primero
|
||||||
resultados.sort((a, b) => {
|
resultados.sort((a, b) => {
|
||||||
if (a.estado === 'pasó' && b.estado !== 'pasó') return 1;
|
const prioridad = { 'en_camino': 0, 'próximo': 1, 'pasó': 2 };
|
||||||
if (b.estado === 'pasó' && a.estado !== 'pasó') return -1;
|
if (prioridad[a.estado] !== prioridad[b.estado]) {
|
||||||
|
return prioridad[a.estado] - prioridad[b.estado];
|
||||||
|
}
|
||||||
return a.etaMinutos - b.etaMinutos;
|
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) {
|
} catch (e) {
|
||||||
console.error("Error calculando ETA", e);
|
console.error('SIBU | Error calculando ETA:', e);
|
||||||
} finally {
|
} finally {
|
||||||
cargando.value = false;
|
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 };
|
return { calcularETA, busesActivos, cargando };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user