fix(map): two-layer cancel token system to fully prevent orphan markers/polylines on banner close

This commit is contained in:
2026-03-04 12:04:02 -05:00
parent 6c197ba1f8
commit c5e5946738
2 changed files with 53 additions and 19 deletions

View File

@ -49,6 +49,8 @@ const userMarker = shallowRef<any>(null);
const isUpdatingMarkers = ref(false);
// Cancellation token: increment to invalidate any in-flight marker draw
const markerGenerationId = ref(0);
// Object-based cancel token passed into procesarSeleccionDeRuta so it can abort from within
let currentCancelToken: { cancelled: boolean } = { cancelled: false };
const unitMarkers = shallowRef<Map<string, any>>(new Map());
const unitFetchInterval = ref<any>(null);
const userCoords = ref<{ lat: number; lng: number } | null>(null);
@ -101,13 +103,17 @@ function closeUberSearch() {
async function animateAndReload() {
isBannerClosing.value = true;
// 🔥 CRÍTICO: Invalidar cualquier dibujado de markers en vuelo
// 🔥 CANCELACIÓN TOTAL: invalidar la operación en vuelo desde adentro Y desde afuera
markerGenerationId.value++;
isUpdatingMarkers.value = false; // liberar el lock para que no quede bloqueado
currentCancelToken.cancelled = true; // aborta procesarSeleccionDeRuta desde adentro
currentCancelToken = { cancelled: false }; // prepara un token limpio para la próxima operación
isUpdatingMarkers.value = false; // liberar el lock
routeStore.setWasSelectedFromMap(false);
clearMapMarkers();
// Limpiar mapa INMEDIATAMENTE - dos capas de limpieza
clearMapMarkers(); // limpia markers[] y globalOverlays
limpiarTodoCentralizado(); // limpia polylines[], infoWindows[], circles[]
limpiarCaminata();
routeStore.clearSelection();
@ -116,10 +122,10 @@ async function animateAndReload() {
showETACard.value = false;
routePhase.value = 'idle';
// Limpieza extra garantizada después de un tick, por si algún await en vuelo
// terminó justo antes e intentó redibujar markers
// Segunda pasada tras el tick: por si algún await en vuelo terminó justo antes
await nextTick();
clearMapMarkers();
limpiarTodoCentralizado();
if (map.value) {
setCenter(mapStore.center.lat, mapStore.center.lng);
@ -303,14 +309,20 @@ async function updateMapMarkers(skipZoom = false) {
// Capturar el token de generación ANTES de cualquier await
const myGeneration = markerGenerationId.value;
// Crear un token de cancelación para esta operación específica
const myToken = { cancelled: false };
currentCancelToken = myToken;
isUpdatingMarkers.value = true;
try {
const selectedRouteObj = routeStore.allRoutes.find(r => r.id === currentRequestRouteId) || { id: currentRequestRouteId, short_name: currentRequestRouteId };
// Guard de generación: si se canceló mientras esperábamos, abortar
if (markerGenerationId.value !== myGeneration) return;
// Guard de generación: si se canceló mientras esperabamos, abortar
if (myToken.cancelled || markerGenerationId.value !== myGeneration) {
limpiarTodoCentralizado();
return;
}
await procesarSeleccionDeRuta(
selectedRouteObj,
@ -320,16 +332,18 @@ async function updateMapMarkers(skipZoom = false) {
skipZoom,
(stop: BusStop) => {
// Solo actualizar si aún somos la generación vigente
if (markerGenerationId.value === myGeneration) {
if (!myToken.cancelled && markerGenerationId.value === myGeneration) {
paradaCercana.value = stop;
showETACard.value = true;
}
}
},
myToken // <-- pasar el cancel token al composable
);
// Guard final: verificar que no se canceló durante el await largo
if (markerGenerationId.value !== myGeneration || routeStore.selectedRouteId !== currentRequestRouteId) {
if (myToken.cancelled || markerGenerationId.value !== myGeneration || routeStore.selectedRouteId !== currentRequestRouteId) {
clearMapMarkers();
limpiarTodoCentralizado();
return;
}