import { ref } from 'vue' import { useMapState } from './useMapState' import { useDirectionsRoute } from './useDirectionsRoute' import { useParadaCercana } from './useParadaCercana' import type { BusStop } from '@/types' import { useRouteStore } from '@/stores/route' export const useFlujoPrincipal = () => { const { limpiarMapa } = useMapState() const paradaCercanaInst = useParadaCercana() const { encontrarParadaCercana, paradaCercana } = paradaCercanaInst const { trazarRuta } = useDirectionsRoute() const cargando = ref(false) const errorMsg = ref('') // Simulated obtenerUbicacion (since it was just navigator.geolocation) const obtenerUbicacion = (): Promise<{ lat: number, lng: number }> => { return new Promise((resolve, reject) => { if (!navigator.geolocation) return reject(new Error('No geolocation')) navigator.geolocation.getCurrentPosition( (pos) => resolve({ lat: pos.coords.latitude, lng: pos.coords.longitude }), (err) => reject(err), { enableHighAccuracy: true, timeout: 8000, maximumAge: 30000 } ) }) } const procesarSeleccionDeRuta = async ( _ruta: { id: string }, paradasExistentes: BusStop[], map: google.maps.Map | undefined, addCleanMarker: Function, skipGuidedZoom = false, onStopClick?: (stop: BusStop) => void, cancelToken?: { cancelled: boolean } // token de cancelación pasado desde el llamador ) => { if (!map) return try { // ── PASO 1: Inicio Atómico ── cargando.value = true limpiarMapa() const routeStore = useRouteStore() // ── PASO 2: Sincronización (Promise.all) ── // Disparamos Supabase (paradas) y GPS al mismo tiempo console.log('SIB | Iniciando carga síncrona de Datos + GPS...'); const [ubicacionRes, paradasRes] = await Promise.allSettled([ obtenerUbicacion(), paradasExistentes.length > 0 ? Promise.resolve(paradasExistentes) : routeStore.loadRouteStops(_ruta.id) ]); // Guard: abandono si el token fue cancelado o la ruta ya no está activa if (cancelToken?.cancelled || routeStore.selectedRouteId !== _ruta.id) { console.log('SIB | procesarSeleccionDeRuta abortado tras Promise.allSettled (ruta ya no activa o cancelada)'); limpiarMapa(); // limpiar cualquier polyline ya dibujada return; } // ── PASO 3: Asignación Segura ── let ubicacion: { lat: number, lng: number } | null = null; if (ubicacionRes.status === 'fulfilled') { ubicacion = ubicacionRes.value; } else { console.warn('SIB | 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) => ({ id: typeof p.id === 'string' ? parseInt(p.id) || i : p.id || i, nombre: p.name, latitud: p.latitude, longitud: p.longitude, orden: i })); if (paradasFormateadas.length < 2) { console.warn('SIB | No hay suficientes paradas para trazar ruta'); return; } // Si no detectamos GPS, trazamos la ruta completa sin zoom guiado if (!ubicacion) { await trazarRuta(paradasFormateadas, map, false); if (cancelToken?.cancelled) { limpiarMapa(); return; } 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 ── if (cancelToken?.cancelled || routeStore.selectedRouteId !== _ruta.id) { limpiarMapa(); return; } // Dibujar ruta completa (fondo) await trazarRuta(paradasFormateadas, map, true); if (cancelToken?.cancelled || routeStore.selectedRouteId !== _ruta.id) { limpiarMapa(); // limpiar polylines ya dibujadas return; } if (skipGuidedZoom) { 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; } await encontrarParadaCercana(ubicacion, paradas, map) const paradaCercanaFound = paradaCercana.value if (cancelToken?.cancelled || routeStore.selectedRouteId !== _ruta.id) { limpiarMapa(); return; } if (!paradaCercanaFound) return; // 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; return samePos || p.nombre === paradaCercanaFound.name; }); if (idx !== -1) { const tramoRelevante = paradasFormateadas.slice(idx) if (tramoRelevante.length > 1) { await trazarRuta(tramoRelevante, map, false) } } // Guard final antes de pintar markers if (cancelToken?.cancelled || routeStore.selectedRouteId !== _ruta.id) { limpiarMapa(); return; } // Renderizado Condicional de Marcadores paradasFormateadas.forEach((p, i) => { const esCercana = i === idx; const esPasada = idx !== -1 && i < idx; addCleanMarker( { lat: p.latitud, lng: p.longitud }, p.nombre, esCercana ? 'cercana' : (esPasada ? 'pasada' : 'normal'), () => { if (onStopClick && paradas[i]) { onStopClick(paradas[i]); } } ); }); hacerZoomAlTramoRelevante(ubicacion, paradaCercanaFound, map) } catch (error) { console.error('SIB | Error procesando ruta:', error) errorMsg.value = 'No se pudo cargar la ruta' } finally { // Apagar estado de carga cargando.value = false } } // ZOOM QUE MUESTRA USUARIO Y PARADA CERCANA: const hacerZoomAlTramoRelevante = ( ubicacion: { lat: number, lng: number }, paradaCercana: BusStop, map: google.maps.Map ) => { const bounds = new google.maps.LatLngBounds() // Incluir solo estos dos puntos clave bounds.extend(new google.maps.LatLng(ubicacion.lat, ubicacion.lng)) bounds.extend( new google.maps.LatLng(paradaCercana.latitude, paradaCercana.longitude) ) // Ajustar zoom con padding generoso para que se vean bien map.fitBounds(bounds, { top: 100, // espacio para la navbar bottom: 200, // espacio para el bottom sheet de ETA left: 60, right: 60 }) // Asegurar zoom mínimo para que se vea el contexto de la calle google.maps.event.addListenerOnce( map, 'bounds_changed', () => { if ((map.getZoom() ?? 0) > 17) { map.setZoom(17) // no acercar demasiado } if ((map.getZoom() ?? 0) < 14) { map.setZoom(14) // no alejar demasiado } } ) } return { procesarSeleccionDeRuta, cargando, errorMsg } }