feat(UI): actualización de colores de ruta a amarillo y fix navegación transporte

This commit is contained in:
2026-02-27 10:57:42 -05:00
parent a8eaad7f35
commit b90eb83acb
12 changed files with 1624 additions and 332 deletions

View File

@ -0,0 +1,203 @@
import { ref } from 'vue'
import { useMapState } from './useMapState'
import { useDirectionsRoute } from './useDirectionsRoute'
import { useParadaCercana } from './useParadaCercana'
import type { BusStop } from '@/types'
export const useFlujoPrincipal = () => {
const { limpiarMapa, registrarMarker } = useMapState()
const { encontrarParadaCercana } = useParadaCercana()
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 },
paradas: BusStop[],
map: google.maps.Map | undefined
) => {
if (!map) return
try {
// ── PASO 1: Limpiar todo lo que había antes ──────────
limpiarMapa()
cargando.value = true
// ── PASO 2: Obtener ubicación ──
// Paradas ya vienen precargadas desde store para evitar doble fetch
let ubicacion: { lat: number, lng: number } | null = null;
try {
ubicacion = await obtenerUbicacion();
} catch (err) {
console.warn('SIBU | No se pudo obtener ubicación', err);
}
// Format Paradas for trazarRuta
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
}));
// Si no hay paradas o muy pocas, abortar
if (paradasFormateadas.length < 2) return;
if (!ubicacion) {
// Fallback: solo dibujar la ruta sin parada cercana (o podríamos no hacer zoom guiado)
await trazarRuta(paradasFormateadas, map, false);
return;
}
// ── PASO 3: Dibujar ruta completa (fondo, gris tenue) ─
await trazarRuta(paradasFormateadas, map, true);
// ── PASO 4: Calcular parada más cercana ───────────────
await encontrarParadaCercana(ubicacion, paradas, map)
// Buscamos manualmente porque encontrarParadaCercana guarda en su propio ref interno que no retornamos fácil
// o podemos usar useParadaCercana().paradaCercana.value
// Actually, refactor from useParadaCercana: finding the closest
const { paradaCercana } = useParadaCercana()
await encontrarParadaCercana(ubicacion, paradas, undefined) // sin mapa para no dibujar polilínea vieja por encima
const paradaCercanaFound = paradaCercana.value
if (!paradaCercanaFound) {
return;
}
// ── PASO 5: Dibujar tramo relevante (azul vivo) ───────
const idx = paradasFormateadas.findIndex(p => p.longitud === paradaCercanaFound.longitude && p.latitud === paradaCercanaFound.latitude)
if (idx !== -1) {
const tramoRelevante = paradasFormateadas.slice(idx)
if (tramoRelevante.length > 1) {
await trazarRuta(tramoRelevante, map, false)
}
}
// ── PASO 6: Marcador de parada cercana ────────────────
registrarMarker(
new google.maps.Marker({
position: { lat: paradaCercanaFound.latitude, lng: paradaCercanaFound.longitude },
map,
icon: {
path: google.maps.SymbolPath.CIRCLE,
fillColor: '#F59E0B',
fillOpacity: 1,
strokeColor: '#FFFFFF',
strokeWeight: 3,
scale: 12
},
title: paradaCercanaFound.name,
zIndex: 10
})
)
// ── PASO 7: Pulso animado en ubicación del usuario ────
dibujarPulsoUsuario(ubicacion, map)
// ── PASO 8: 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 {
cargando.value = false
}
}
// PULSO ANIMADO EN LA UBICACIÓN DEL USUARIO:
const dibujarPulsoUsuario = (
ubicacion: { lat: number, lng: number },
map: google.maps.Map
) => {
const { registrarCircle, registrarMarker } = useMapState()
// Círculo exterior pulsante (efecto ripple)
registrarCircle(
new google.maps.Circle({
map,
center: ubicacion,
radius: 80, // metros
fillColor: '#3B82F6',
fillOpacity: 0.15,
strokeColor: '#3B82F6',
strokeOpacity: 0.4,
strokeWeight: 1,
zIndex: 1
})
)
// Punto central sólido del usuario
registrarMarker(
new google.maps.Marker({
position: ubicacion,
map,
icon: {
path: google.maps.SymbolPath.CIRCLE,
fillColor: '#3B82F6', // azul
fillOpacity: 1,
strokeColor: '#FFFFFF',
strokeWeight: 3,
scale: 9
},
title: 'Tu ubicación',
zIndex: 20 // encima de todo
})
)
}
// 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 }
}