refactor(map): fix race conditions and async issues with atomic GPS/Data fetch
This commit is contained in:
@ -27,7 +27,7 @@ export const useFlujoPrincipal = () => {
|
|||||||
|
|
||||||
const procesarSeleccionDeRuta = async (
|
const procesarSeleccionDeRuta = async (
|
||||||
_ruta: { id: string },
|
_ruta: { id: string },
|
||||||
paradas: BusStop[],
|
paradasExistentes: BusStop[],
|
||||||
map: google.maps.Map | undefined,
|
map: google.maps.Map | undefined,
|
||||||
addCleanMarker: Function,
|
addCleanMarker: Function,
|
||||||
skipGuidedZoom = false,
|
skipGuidedZoom = false,
|
||||||
@ -36,16 +36,32 @@ export const useFlujoPrincipal = () => {
|
|||||||
if (!map) return
|
if (!map) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// ── PASO 1: Limpiar todo lo que había antes ──────────
|
// ── PASO 1: Inicio Atómico ──
|
||||||
limpiarMapa()
|
|
||||||
cargando.value = true
|
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;
|
let ubicacion: { lat: number, lng: number } | null = null;
|
||||||
try {
|
if (ubicacionRes.status === 'fulfilled') {
|
||||||
ubicacion = await obtenerUbicacion();
|
ubicacion = ubicacionRes.value;
|
||||||
} catch (err) {
|
} else {
|
||||||
console.warn('SIBU | No se pudo obtener ubicación', err);
|
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) => ({
|
const paradasFormateadas = paradas.map((p, i) => ({
|
||||||
@ -56,17 +72,24 @@ export const useFlujoPrincipal = () => {
|
|||||||
orden: i
|
orden: i
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (paradasFormateadas.length < 2) return;
|
if (paradasFormateadas.length < 2) {
|
||||||
|
console.warn('SIBU | No hay suficientes paradas para trazar ruta');
|
||||||
if (!ubicacion) {
|
|
||||||
await trazarRuta(paradasFormateadas, map, false);
|
|
||||||
return;
|
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);
|
await trazarRuta(paradasFormateadas, map, true);
|
||||||
|
|
||||||
// ── PASO 4: Calcular parada más cercana (Omitir si skipGuidedZoom) ───────────────
|
|
||||||
if (skipGuidedZoom) {
|
if (skipGuidedZoom) {
|
||||||
const bounds = new google.maps.LatLngBounds()
|
const bounds = new google.maps.LatLngBounds()
|
||||||
paradasFormateadas.forEach(p => bounds.extend(new google.maps.LatLng(p.latitud, p.longitud)))
|
paradasFormateadas.forEach(p => bounds.extend(new google.maps.LatLng(p.latitud, p.longitud)))
|
||||||
@ -78,10 +101,7 @@ export const useFlujoPrincipal = () => {
|
|||||||
const paradaCercanaFound = paradaCercana.value
|
const paradaCercanaFound = paradaCercana.value
|
||||||
if (!paradaCercanaFound) return;
|
if (!paradaCercanaFound) return;
|
||||||
|
|
||||||
const routeStore = useRouteStore()
|
// Dibujar tramo relevante
|
||||||
if (routeStore.selectedRouteId !== _ruta.id) return;
|
|
||||||
|
|
||||||
// ── PASO 5: Dibujar tramo relevante (Amarillo Vivo) ───────
|
|
||||||
const idx = paradasFormateadas.findIndex(p => {
|
const idx = paradasFormateadas.findIndex(p => {
|
||||||
const samePos = Math.abs(p.longitud - paradaCercanaFound.longitude) < 0.0001 &&
|
const samePos = Math.abs(p.longitud - paradaCercanaFound.longitude) < 0.0001 &&
|
||||||
Math.abs(p.latitud - paradaCercanaFound.latitude) < 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) => {
|
paradasFormateadas.forEach((p, i) => {
|
||||||
const esCercana = i === idx;
|
const esCercana = i === idx;
|
||||||
const esPasada = idx !== -1 && 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)
|
hacerZoomAlTramoRelevante(ubicacion, paradaCercanaFound, map)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('SIBU | Error procesando ruta:', error)
|
console.error('SIBU | Error procesando ruta:', error)
|
||||||
errorMsg.value = 'No se pudo cargar la ruta'
|
errorMsg.value = 'No se pudo cargar la ruta'
|
||||||
} finally {
|
} finally {
|
||||||
|
// Apagar estado de carga
|
||||||
cargando.value = false
|
cargando.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,29 +43,31 @@ export const useRouteStore = defineStore('route', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadRouteStops(routeId: string, force = false) {
|
async function loadRouteStops(routeId: string, force = false): Promise<BusStop[]> {
|
||||||
const CACHE_TIME = 1000 * 60 * 15; // 15 minutos
|
const CACHE_TIME = 1000 * 60 * 15; // 15 minutos
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
if (isLoadingStops.value) return;
|
if (stopsCache.value.has(routeId) && !force) {
|
||||||
if (!force && stopsCache.value.has(routeId)) {
|
|
||||||
const cacheEntry = stopsCache.value.get(routeId)!;
|
const cacheEntry = stopsCache.value.get(routeId)!;
|
||||||
if (now - cacheEntry.fetchedAt < CACHE_TIME) {
|
if (now - cacheEntry.fetchedAt < CACHE_TIME) {
|
||||||
selectedRouteStops.value = cacheEntry.stops;
|
selectedRouteStops.value = cacheEntry.stops;
|
||||||
return;
|
return cacheEntry.stops;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isLoadingStops.value) return [];
|
||||||
isLoadingStops.value = true
|
isLoadingStops.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
const stops = await routesService.getRouteStops(routeId)
|
const stops = await routesService.getRouteStops(routeId)
|
||||||
selectedRouteStops.value = stops
|
selectedRouteStops.value = stops
|
||||||
stopsCache.value.set(routeId, { fetchedAt: now, stops })
|
stopsCache.value.set(routeId, { fetchedAt: now, stops })
|
||||||
|
return stops
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = e instanceof Error ? e.message : 'Failed to load route stops'
|
error.value = e instanceof Error ? e.message : 'Failed to load route stops'
|
||||||
console.error('Error loading route stops:', e)
|
console.error('Error loading route stops:', e)
|
||||||
selectedRouteStops.value = []
|
selectedRouteStops.value = []
|
||||||
|
return []
|
||||||
} finally {
|
} finally {
|
||||||
isLoadingStops.value = false
|
isLoadingStops.value = false
|
||||||
}
|
}
|
||||||
@ -75,8 +77,7 @@ export const useRouteStore = defineStore('route', () => {
|
|||||||
if (selectedRouteId.value === routeId) return
|
if (selectedRouteId.value === routeId) return
|
||||||
selectedRouteId.value = routeId
|
selectedRouteId.value = routeId
|
||||||
selectedRouteName.value = routeName
|
selectedRouteName.value = routeName
|
||||||
selectedRouteStops.value = [] // Clear old stops immediately
|
selectedRouteStops.value = [] // Limpia para forzar recarga atómica en la vista
|
||||||
await loadRouteStops(routeId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSelection() {
|
function clearSelection() {
|
||||||
|
|||||||
@ -37,7 +37,7 @@ const { estasCargando: estasCargandoRuta, errorRuta } = useDirectionsRoute();
|
|||||||
const { encontrarParadaCercana, paradaCercana, distanciaMetros, duracionCaminata, limpiarCaminata } = useParadaCercana();
|
const { encontrarParadaCercana, paradaCercana, distanciaMetros, duracionCaminata, limpiarCaminata } = useParadaCercana();
|
||||||
const { calcularETA, busesActivos, cargando: etaCargando } = useETA();
|
const { calcularETA, busesActivos, cargando: etaCargando } = useETA();
|
||||||
|
|
||||||
const { procesarSeleccionDeRuta } = useFlujoPrincipal();
|
const { procesarSeleccionDeRuta, cargando: flujoCargando } = useFlujoPrincipal();
|
||||||
const { limpiarMapa: limpiarTodoCentralizado } = useMapState();
|
const { limpiarMapa: limpiarTodoCentralizado } = useMapState();
|
||||||
|
|
||||||
const showETACard = ref(false);
|
const showETACard = ref(false);
|
||||||
@ -237,20 +237,22 @@ function reDrawUserMarker() {
|
|||||||
async function updateMapMarkers(skipZoom = false) {
|
async function updateMapMarkers(skipZoom = false) {
|
||||||
if (!isLoaded.value || !map.value || isUpdatingMarkers.value) return;
|
if (!isLoaded.value || !map.value || isUpdatingMarkers.value) return;
|
||||||
|
|
||||||
isUpdatingMarkers.value = true;
|
|
||||||
const currentRequestRouteId = routeStore.selectedRouteId;
|
const currentRequestRouteId = routeStore.selectedRouteId;
|
||||||
const stops = [...routeStore.selectedRouteStops];
|
if (!currentRequestRouteId) {
|
||||||
|
|
||||||
try {
|
|
||||||
if (!currentRequestRouteId || stops.length === 0) {
|
|
||||||
clearMapMarkers();
|
clearMapMarkers();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUpdatingMarkers.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
const selectedRouteObj = routeStore.allRoutes.find(r => r.id === currentRequestRouteId) || { id: currentRequestRouteId, short_name: currentRequestRouteId };
|
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(
|
await procesarSeleccionDeRuta(
|
||||||
selectedRouteObj,
|
selectedRouteObj,
|
||||||
stops as BusStop[],
|
routeStore.selectedRouteStops, // El composable decidirá si necesita recargar en paralelo
|
||||||
map.value,
|
map.value,
|
||||||
addCleanMarker,
|
addCleanMarker,
|
||||||
skipZoom,
|
skipZoom,
|
||||||
@ -326,6 +328,7 @@ function selectRouteAndClose(route: any) {
|
|||||||
highlightOptimalStopForRoute();
|
highlightOptimalStopForRoute();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
showUberSearch.value = false;
|
showUberSearch.value = false;
|
||||||
routeStore.wasSelectedFromMap = true;
|
routeStore.wasSelectedFromMap = true;
|
||||||
routeStore.selectRoute(route.id, route.name);
|
routeStore.selectRoute(route.id, route.name);
|
||||||
@ -476,8 +479,8 @@ watch([() => authStore.userProfile?.auto_location, isLoaded], ([canLocate, loade
|
|||||||
<div class="split-view">
|
<div class="split-view">
|
||||||
<div class="map-side">
|
<div class="map-side">
|
||||||
<div class="map-view">
|
<div class="map-view">
|
||||||
<div v-if="estasCargandoRuta || errorRuta" class="status-indicator">
|
<div v-if="flujoCargando || estasCargandoRuta || errorRuta" class="status-indicator">
|
||||||
<div v-if="estasCargandoRuta" class="loading-pill">{{ t('map.calculatingRoute') }}</div>
|
<div v-if="flujoCargando || estasCargandoRuta" class="loading-pill">{{ t('map.calculatingRoute') }}</div>
|
||||||
<div v-if="errorRuta" class="error-pill">{{ errorRuta }}</div>
|
<div v-if="errorRuta" class="error-pill">{{ errorRuta }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user