feat: real-time bus ETA engine via distance approximation and routing

This commit is contained in:
2026-02-26 13:13:56 -05:00
parent fc489c4f46
commit 30c3f092d8
4 changed files with 457 additions and 31 deletions

View File

@ -11,7 +11,10 @@ import { analyticsService } from "@/services/analyticsService";
import { getImageUrl } from "@/utils/imageUrl";
import { useDirectionsRoute } from "@/composables/useDirectionsRoute";
import { useParadaCercana } from "@/composables/useParadaCercana";
import { useETA } from "@/composables/useETA";
const BusStopInfoModal = defineAsyncComponent(() => import("@/components/BusStopInfoModal.vue"));
const ETACard = defineAsyncComponent(() => import("@/components/ETACard.vue"));
import type { BusStop } from '@/types'
const router = useRouter();
@ -24,6 +27,10 @@ const couponStore = useCouponStore();
const { map, isLoaded, error: mapsError, initMap, addNumberedMarker, addHtmlMarker, fitBounds, setCenter, setZoom, addMarker, clearAllOverlays } = useGoogleMaps();
const { trazarRuta, limpiarRuta, estasCargando: estasCargandoRuta, errorRuta } = useDirectionsRoute();
const { encontrarParadaCercana, limpiarCaminata, paradaCercana, distanciaMetros, duracionCaminata } = useParadaCercana();
const { calcularETA, busesActivos, cargando: etaCargando } = useETA();
const showETACard = ref(false);
const markers = ref<any[]>([]);
const promoMarkers = ref<any[]>([]);
@ -145,6 +152,8 @@ async function clearAllMapData() {
optimalStopPulse.value = null;
}
limpiarRuta();
limpiarCaminata();
showETACard.value = false;
// 7. Restaurar Solo Usuario tras un breve respiro
await nextTick();
@ -449,6 +458,8 @@ function clearMapMarkers() {
// Clear directions route
limpiarRuta();
limpiarCaminata();
showETACard.value = false;
}
async function updateMapMarkers() {
@ -718,45 +729,26 @@ function locateUser() {
* Encuentra la parada más cercana dentro de la ruta seleccionada
* y la resalta para el usuario.
*/
function highlightOptimalStopForRoute() {
async function highlightOptimalStopForRoute() {
if (!userCoords.value || routeStore.selectedRouteStops.length === 0) {
console.warn('🤖 JARVIS: Sin ubicación o paradas para calcular parada óptima.');
return;
}
console.log('🤖 JARVIS: Calculando punto de abordaje óptimo sobre la ruta...');
console.log('🤖 JARVIS: Calculando punto de abordaje óptimo sobre la ruta mediante calles...');
let nearestStop = null;
let minDistance = Infinity;
// Encontrar parada real y añadir ruta peatonal azul punteada
await encontrarParadaCercana(userCoords.value, routeStore.selectedRouteStops as BusStop[], map.value || undefined);
const getDistance = (l1: any, l2: any) => {
const R = 6371; // Radio de la Tierra en km
const dLat = (l2.lat - l1.lat) * Math.PI / 180;
const dLon = (l2.lng - l1.lng) * Math.PI / 180;
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(l1.lat * Math.PI / 180) * Math.cos(l2.lat * 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;
};
routeStore.selectedRouteStops.forEach(stop => {
const dist = getDistance(userCoords.value, { lat: stop.latitude, lng: stop.longitude });
if (dist < minDistance) {
minDistance = dist;
nearestStop = stop;
}
});
if (nearestStop) {
const stopObj = nearestStop as BusStop;
console.log(`🤖 JARVIS: Parada óptima detectada: ${stopObj.name} (${minDistance.toFixed(2)} km)`);
if (paradaCercana.value) {
const stopObj = paradaCercana.value as BusStop;
console.log(`🤖 JARVIS: Parada óptima detectada: ${stopObj.name}`);
// Centrar mapa en la parada para guiar al usuario
// Centrar mapa
setCenter(stopObj.latitude, stopObj.longitude);
setZoom(17);
// Añadir el PULSO NARANJA de "Aborda aquí"
// Añadir el PULSO NARANJA
if (optimalStopPulse.value && typeof optimalStopPulse.value.setMap === 'function') {
optimalStopPulse.value.setMap(null);
}
@ -767,12 +759,16 @@ function highlightOptimalStopForRoute() {
{ x: -30, y: -30 }
);
// Mini-notificación informativa
// Mini-notificación (Opcional, se cubre ahora también con ETA card)
navigationInfo.value = {
distance: minDistance < 1 ? `${(minDistance * 1000).toFixed(0)} m` : `${minDistance.toFixed(1)} km`,
duration: "Más cercana",
distance: distanciaMetros.value < 1000 ? `${distanciaMetros.value.toFixed(0)} m` : `${(distanciaMetros.value/1000).toFixed(1)} km`,
duration: "Calculada",
targetName: stopObj.name
};
// Calcular ETAs
await calcularETA(routeStore.selectedRouteId!, stopObj);
showETACard.value = true;
}
}
@ -1218,6 +1214,17 @@ function clearNavigation() {
</div>
</div>
</Transition>
<ETACard
:is-open="showETACard"
:stop-name="paradaCercana?.name || ''"
:walk-distance="distanciaMetros"
:walk-duration="duracionCaminata"
:buses="busesActivos"
:is-loading="etaCargando"
@close="showETACard = false"
@refresh="paradaCercana && routeStore.selectedRouteId ? calcularETA(routeStore.selectedRouteId, paradaCercana) : null"
/>
</div>
</template>