From 767667b1b6773b0c852511b61e34e93d983f2b03 Mon Sep 17 00:00:00 2001 From: Hanzo_dev <2002samudiojohan@gmail.com> Date: Mon, 2 Mar 2026 19:22:58 -0500 Subject: [PATCH] refactor(map): fix race conditions and async issues with atomic GPS/Data fetch --- frontend/src/composables/useFlujoPrincipal.ts | 60 ++++++++++++------- frontend/src/stores/route.ts | 13 ++-- frontend/src/views/MapView.vue | 25 ++++---- 3 files changed, 61 insertions(+), 37 deletions(-) diff --git a/frontend/src/composables/useFlujoPrincipal.ts b/frontend/src/composables/useFlujoPrincipal.ts index e01b829..b18750b 100644 --- a/frontend/src/composables/useFlujoPrincipal.ts +++ b/frontend/src/composables/useFlujoPrincipal.ts @@ -27,7 +27,7 @@ export const useFlujoPrincipal = () => { const procesarSeleccionDeRuta = async ( _ruta: { id: string }, - paradas: BusStop[], + paradasExistentes: BusStop[], map: google.maps.Map | undefined, addCleanMarker: Function, skipGuidedZoom = false, @@ -36,16 +36,32 @@ export const useFlujoPrincipal = () => { if (!map) return try { - // ── PASO 1: Limpiar todo lo que había antes ────────── - limpiarMapa() + // ── PASO 1: Inicio Atómico ── cargando.value = true + limpiarMapa() + const routeStore = useRouteStore() - // ── PASO 2: Obtener ubicación ── + // ── PASO 2: Sincronización (Promise.all) ── + // Disparamos Supabase (paradas) y GPS al mismo tiempo + console.log('SIBU | Iniciando carga síncrona de Datos + GPS...'); + + const [ubicacionRes, paradasRes] = await Promise.allSettled([ + obtenerUbicacion(), + paradasExistentes.length > 0 ? Promise.resolve(paradasExistentes) : routeStore.loadRouteStops(_ruta.id) + ]); + + // ── PASO 3: Asignación Segura ── let ubicacion: { lat: number, lng: number } | null = null; - try { - ubicacion = await obtenerUbicacion(); - } catch (err) { - console.warn('SIBU | No se pudo obtener ubicación', err); + if (ubicacionRes.status === 'fulfilled') { + ubicacion = ubicacionRes.value; + } else { + console.warn('SIBU | GPS falló o fue denegado'); + } + + let paradas: BusStop[] = []; + if (paradasRes.status === 'fulfilled') { + // Si loadRouteStops no devolviera los datos directamente, los tomamos del store + paradas = paradasRes.value || routeStore.selectedRouteStops; } const paradasFormateadas = paradas.map((p, i) => ({ @@ -56,17 +72,24 @@ export const useFlujoPrincipal = () => { orden: i })); - if (paradasFormateadas.length < 2) return; - - if (!ubicacion) { - await trazarRuta(paradasFormateadas, map, false); + if (paradasFormateadas.length < 2) { + console.warn('SIBU | No hay suficientes paradas para trazar ruta'); return; } - // ── PASO 3: Dibujar ruta completa (fondo, gris tenue) ─ + // Si no detectamos GPS, trazamos la ruta completa sin zoom guiado + if (!ubicacion) { + await trazarRuta(paradasFormateadas, map, false); + const bounds = new google.maps.LatLngBounds() + paradasFormateadas.forEach(p => bounds.extend(new google.maps.LatLng(p.latitud, p.longitud))) + map.fitBounds(bounds, { top: 100, bottom: 100, left: 60, right: 60 }) + return; + } + + // ── PASO 4: Dibujar y Renderizar ── + // Dibujar ruta completa (fondo) await trazarRuta(paradasFormateadas, map, true); - // ── PASO 4: Calcular parada más cercana (Omitir si skipGuidedZoom) ─────────────── if (skipGuidedZoom) { const bounds = new google.maps.LatLngBounds() paradasFormateadas.forEach(p => bounds.extend(new google.maps.LatLng(p.latitud, p.longitud))) @@ -78,10 +101,7 @@ export const useFlujoPrincipal = () => { const paradaCercanaFound = paradaCercana.value if (!paradaCercanaFound) return; - const routeStore = useRouteStore() - if (routeStore.selectedRouteId !== _ruta.id) return; - - // ── PASO 5: Dibujar tramo relevante (Amarillo Vivo) ─────── + // Dibujar tramo relevante const idx = paradasFormateadas.findIndex(p => { const samePos = Math.abs(p.longitud - paradaCercanaFound.longitude) < 0.0001 && Math.abs(p.latitud - paradaCercanaFound.latitude) < 0.0001; @@ -95,7 +115,7 @@ export const useFlujoPrincipal = () => { } } - // ── PASO 6: Dibujar marcadores de todas las paradas ────────── + // Renderizado Condicional de Marcadores paradasFormateadas.forEach((p, i) => { const esCercana = i === idx; const esPasada = idx !== -1 && i < idx; @@ -112,13 +132,13 @@ export const useFlujoPrincipal = () => { ); }); - // ── PASO 7: Zoom centrado en usuario + parada cercana ─ hacerZoomAlTramoRelevante(ubicacion, paradaCercanaFound, map) } catch (error) { console.error('SIBU | Error procesando ruta:', error) errorMsg.value = 'No se pudo cargar la ruta' } finally { + // Apagar estado de carga cargando.value = false } } diff --git a/frontend/src/stores/route.ts b/frontend/src/stores/route.ts index 9a840bf..0dd3a89 100644 --- a/frontend/src/stores/route.ts +++ b/frontend/src/stores/route.ts @@ -43,29 +43,31 @@ export const useRouteStore = defineStore('route', () => { } } - async function loadRouteStops(routeId: string, force = false) { + async function loadRouteStops(routeId: string, force = false): Promise { const CACHE_TIME = 1000 * 60 * 15; // 15 minutos const now = Date.now(); - if (isLoadingStops.value) return; - if (!force && stopsCache.value.has(routeId)) { + if (stopsCache.value.has(routeId) && !force) { const cacheEntry = stopsCache.value.get(routeId)!; if (now - cacheEntry.fetchedAt < CACHE_TIME) { selectedRouteStops.value = cacheEntry.stops; - return; + return cacheEntry.stops; } } + if (isLoadingStops.value) return []; isLoadingStops.value = true error.value = null try { const stops = await routesService.getRouteStops(routeId) selectedRouteStops.value = stops stopsCache.value.set(routeId, { fetchedAt: now, stops }) + return stops } catch (e) { error.value = e instanceof Error ? e.message : 'Failed to load route stops' console.error('Error loading route stops:', e) selectedRouteStops.value = [] + return [] } finally { isLoadingStops.value = false } @@ -75,8 +77,7 @@ export const useRouteStore = defineStore('route', () => { if (selectedRouteId.value === routeId) return selectedRouteId.value = routeId selectedRouteName.value = routeName - selectedRouteStops.value = [] // Clear old stops immediately - await loadRouteStops(routeId) + selectedRouteStops.value = [] // Limpia para forzar recarga atómica en la vista } function clearSelection() { diff --git a/frontend/src/views/MapView.vue b/frontend/src/views/MapView.vue index 6764dba..a2d8967 100644 --- a/frontend/src/views/MapView.vue +++ b/frontend/src/views/MapView.vue @@ -37,7 +37,7 @@ const { estasCargando: estasCargandoRuta, errorRuta } = useDirectionsRoute(); const { encontrarParadaCercana, paradaCercana, distanciaMetros, duracionCaminata, limpiarCaminata } = useParadaCercana(); const { calcularETA, busesActivos, cargando: etaCargando } = useETA(); -const { procesarSeleccionDeRuta } = useFlujoPrincipal(); +const { procesarSeleccionDeRuta, cargando: flujoCargando } = useFlujoPrincipal(); const { limpiarMapa: limpiarTodoCentralizado } = useMapState(); const showETACard = ref(false); @@ -237,20 +237,22 @@ function reDrawUserMarker() { async function updateMapMarkers(skipZoom = false) { if (!isLoaded.value || !map.value || isUpdatingMarkers.value) return; - isUpdatingMarkers.value = true; const currentRequestRouteId = routeStore.selectedRouteId; - const stops = [...routeStore.selectedRouteStops]; + if (!currentRequestRouteId) { + clearMapMarkers(); + return; + } + + isUpdatingMarkers.value = true; try { - if (!currentRequestRouteId || stops.length === 0) { - clearMapMarkers(); - return; - } - const selectedRouteObj = routeStore.allRoutes.find(r => r.id === currentRequestRouteId) || { id: currentRequestRouteId, short_name: currentRequestRouteId }; + + // ── PASO ATÓMICO: Procesar con carga paralela de GPS y Paradas ── + // Pasamos [] a paradasExistentes para forzar que procesarSeleccionDeRuta maneje el Promise.all await procesarSeleccionDeRuta( selectedRouteObj, - stops as BusStop[], + routeStore.selectedRouteStops, // El composable decidirá si necesita recargar en paralelo map.value, addCleanMarker, skipZoom, @@ -326,6 +328,7 @@ function selectRouteAndClose(route: any) { highlightOptimalStopForRoute(); return; } + showUberSearch.value = false; routeStore.wasSelectedFromMap = true; routeStore.selectRoute(route.id, route.name); @@ -476,8 +479,8 @@ watch([() => authStore.userProfile?.auto_location, isLoaded], ([canLocate, loade
-
-
{{ t('map.calculatingRoute') }}
+
+
{{ t('map.calculatingRoute') }}
{{ errorRuta }}