fix: corrección de errores en mapa y filtros de horarios

This commit is contained in:
2026-02-27 21:24:26 -05:00
parent d33c4c4ab1
commit 8084032f25
4 changed files with 108 additions and 153 deletions

View File

@ -323,67 +323,44 @@ export function useGoogleMaps() {
} }
function addHtmlMarker( function addHtmlMarker(
position: { lat: number; lng: number }, position: { lat: number, lng: number },
htmlContent: string, html: string,
offset: { x: number; y: number } = { x: 0, y: 0 } offset: { x: number, y: number } = { x: 0, y: 0 }
) { ) {
if (!map.value) return null; if (!map.value) return null;
class CustomOverlay extends google.maps.OverlayView { const overlay = new google.maps.OverlayView();
private div: HTMLElement | null = null; let container: HTMLDivElement | null = null;
private pos: google.maps.LatLng;
constructor(pos: google.maps.LatLng) { overlay.onAdd = function () {
super(); container = document.createElement('div');
this.pos = pos; container.style.position = 'absolute';
container.innerHTML = html;
const panes = this.getPanes();
if (panes) panes.overlayMouseTarget.appendChild(container);
};
overlay.draw = function () {
const projection = this.getProjection();
if (!projection || !container) return;
const pos = projection.fromLatLngToDivPixel(new google.maps.LatLng(position.lat, position.lng));
if (pos) {
container.style.left = (pos.x + offset.x) + 'px';
container.style.top = (pos.y + offset.y) + 'px';
} }
};
onAdd() { overlay.onRemove = function () {
const div = document.createElement('div'); if (container && container.parentNode) {
div.style.position = 'absolute'; container.parentNode.removeChild(container);
div.style.cursor = 'pointer';
div.innerHTML = htmlContent;
this.div = div;
const panes = this.getPanes();
panes?.overlayMouseTarget.appendChild(div);
} }
container = null;
};
draw() {
const overlayProjection = this.getProjection();
const point = overlayProjection.fromLatLngToDivPixel(this.pos);
if (point && this.div) {
this.div.style.left = (point.x + offset.x) + 'px';
this.div.style.top = (point.y + offset.y) + 'px';
}
}
onRemove() {
if (this.div) {
try {
// Safer element removal
if (this.div.parentNode) {
this.div.parentNode.removeChild(this.div);
} else {
this.div.remove();
}
} catch (e) {
console.warn('CustomOverlay: element already removed or parent mismatch', e);
}
this.div = null;
}
}
setPosition(newPos: { lat: number; lng: number }) {
this.pos = new google.maps.LatLng(newPos.lat, newPos.lng);
this.draw();
}
}
const overlay = new CustomOverlay(new google.maps.LatLng(position.lat, position.lng));
overlay.setMap(map.value); overlay.setMap(map.value);
registrarMarker(overlay); registrarMarker(overlay as any);
// Track for cleanup // Track in global overlay tracker
if (!globalOverlays.has(map.value)) { if (!globalOverlays.has(map.value)) {
globalOverlays.set(map.value, new Set()); globalOverlays.set(map.value, new Set());
} }

View File

@ -48,13 +48,18 @@ export const useMapState = () => {
// ⚠️ FUNCIÓN CRÍTICA: limpiar ABSOLUTAMENTE TODO del mapa // ⚠️ FUNCIÓN CRÍTICA: limpiar ABSOLUTAMENTE TODO del mapa
const limpiarMapa = () => { const limpiarMapa = () => {
// Eliminar markers console.log(`SIBU | Iniciando limpieza de ${markers.value.length} markers, ${renderers.value.length} renderers, ${polylines.value.length} polylines...`)
// Eliminar markers y overlays HTML
markers.value.forEach(m => { markers.value.forEach(m => {
try { try {
if (m && typeof m.setMap === 'function') m.setMap(null) if (m) {
if (m && typeof (m as any).remove === 'function') (m as any).remove() // para HTML markers custom if (typeof m.setMap === 'function') m.setMap(null);
if (m && google?.maps?.event?.clearInstanceListeners) { if (typeof (m as any).remove === 'function') (m as any).remove();
google.maps.event.clearInstanceListeners(m) if (typeof (m as any).onRemove === 'function') (m as any).onRemove();
if (google?.maps?.event?.clearInstanceListeners) {
google.maps.event.clearInstanceListeners(m);
}
} }
} catch (e) { } catch (e) {
console.warn('Error limpiando marker', e) console.warn('Error limpiando marker', e)
@ -65,8 +70,10 @@ export const useMapState = () => {
// Eliminar renderers de Directions // Eliminar renderers de Directions
renderers.value.forEach(r => { renderers.value.forEach(r => {
try { try {
if (r && typeof r.setMap === 'function') r.setMap(null) if (r) {
if (r && typeof r.setDirections === 'function') r.setDirections({ routes: [] } as any) if (typeof r.setMap === 'function') r.setMap(null);
if (typeof r.setDirections === 'function') r.setDirections({ routes: [] } as any);
}
} catch (e) { } catch (e) {
console.warn('Error limpiando renderer', e) console.warn('Error limpiando renderer', e)
} }
@ -93,7 +100,7 @@ export const useMapState = () => {
}) })
infoWindows.value = [] infoWindows.value = []
// Eliminar circles (pulso de ubicación) // Added custom HTML markers too since they often act as circles // Eliminar circles
circles.value.forEach(c => { circles.value.forEach(c => {
try { try {
if (c && typeof c.setMap === 'function') c.setMap(null) if (c && typeof c.setMap === 'function') c.setMap(null)
@ -106,7 +113,9 @@ export const useMapState = () => {
// Ejecutar callback de limpieza externa si existe // Ejecutar callback de limpieza externa si existe
if (onLimpiarCallback.value) { if (onLimpiarCallback.value) {
try { onLimpiarCallback.value() } catch (e) { try {
onLimpiarCallback.value()
} catch (e) {
console.warn('Error en callback de limpieza externa', e) console.warn('Error en callback de limpieza externa', e)
} }
} }

View File

@ -27,9 +27,9 @@ const mapStore = useMapStore();
const busStopStore = useBusStopStore(); const busStopStore = useBusStopStore();
const couponStore = useCouponStore(); const couponStore = useCouponStore();
const { map, isLoaded, error: mapsError, initMap, addCleanMarker, addHtmlMarker, setCenter, setZoom, addMarker, clearAllOverlays } = useGoogleMaps(); const { map, isLoaded, error: mapsError, initMap, addCleanMarker, addHtmlMarker, setCenter, setZoom, addMarker } = useGoogleMaps();
const { estasCargando: estasCargandoRuta, errorRuta } = useDirectionsRoute(); const { estasCargando: estasCargandoRuta, errorRuta } = useDirectionsRoute();
const { encontrarParadaCercana, limpiarCaminata, paradaCercana, distanciaMetros, duracionCaminata } = useParadaCercana(); const { encontrarParadaCercana, paradaCercana, distanciaMetros, duracionCaminata } = useParadaCercana();
const { calcularETA, busesActivos, cargando: etaCargando } = useETA(); const { calcularETA, busesActivos, cargando: etaCargando } = useETA();
const { procesarSeleccionDeRuta } = useFlujoPrincipal(); const { procesarSeleccionDeRuta } = useFlujoPrincipal();
@ -107,7 +107,7 @@ function closeUberSearch() {
} }
async function clearAllMapData() { async function clearAllMapData() {
console.log('🤖 JARVIS: Iniciando PURGA nuclear...'); console.log('🤖 JARVIS: Iniciando PURGA nuclear centralizada...');
// 1. UI inmediata // 1. UI inmediata
showUberSearch.value = false; showUberSearch.value = false;
@ -124,85 +124,23 @@ async function clearAllMapData() {
routeStore.clearSelection(); routeStore.clearSelection();
lastProcessedRouteId.value = null; lastProcessedRouteId.value = null;
// 4. Limpiar markers locales // 4. LIMPIEZA CENTRALIZADA (useMapState)
const sweep = (arrayRef: any) => { // Esto limpia markers, renderers, polylines, overlays HTML, etc.
if (!arrayRef.value) return;
arrayRef.value.forEach((m: any) => {
try { if (m && m.setMap) m.setMap(null); } catch (e) {}
});
arrayRef.value = [];
};
sweep(markers);
sweep(promoMarkers);
// Limpiar unidades de transporte
if (unitMarkers.value) {
unitMarkers.value.forEach((m: any) => {
try { if (m && m.setMap) m.setMap(null); } catch (e) {}
});
unitMarkers.value.clear();
}
// 5. Limpiar polilíneas (CORREGIDO: agregar walkingPolylineBorder)
if (polyline.value) {
polyline.value.setMap(null);
polyline.value = null;
}
if (walkingPolyline.value) {
walkingPolyline.value.setMap(null);
walkingPolyline.value = null;
}
// ✅ NUEVO: limpiar el borde blanco de la ruta caminando
if (walkingPolylineBorder.value) {
walkingPolylineBorder.value.setMap(null);
walkingPolylineBorder.value = null;
}
// 6. Limpiar pulso de parada óptima (CORREGIDO)
if (optimalStopPulse.value) {
try {
// Intentar setMap primero
if (typeof optimalStopPulse.value.setMap === 'function') {
optimalStopPulse.value.setMap(null);
}
// Si es un overlay HTML, también intentar remove()
if (typeof optimalStopPulse.value.remove === 'function') {
optimalStopPulse.value.remove();
}
// Si tiene onRemove (OverlayView pattern)
if (typeof optimalStopPulse.value.onRemove === 'function') {
optimalStopPulse.value.onRemove();
}
} catch(e) {
console.warn('SIBU | No se pudo limpiar optimalStopPulse:', e);
}
optimalStopPulse.value = null;
}
// 7. Limpiar composables
limpiarCaminata();
// 8. Barrido profundo de Google Maps overlays
if (typeof clearAllOverlays === 'function') {
try { clearAllOverlays(); } catch (e) {}
}
// 9. Purgación centralizada (useMapState)
limpiarTodoCentralizado(); limpiarTodoCentralizado();
// 10. Restaurar SOLO el marcador del usuario // 5. Limpiar referencias locales del componente (aunque no tengan mapa asignado ya)
markers.value = [];
promoMarkers.value = [];
unitMarkers.value.clear();
polyline.value = null;
walkingPolyline.value = null;
walkingPolylineBorder.value = null;
optimalStopPulse.value = null;
// 6. Restaurar SOLO el marcador del usuario si tenemos ubicación
await nextTick(); await nextTick();
if (userCoords.value) { if (userCoords.value) {
const { lat, lng } = userCoords.value; const { lat, lng } = userCoords.value;
// Limpiar marcador anterior del usuario
if (userMarker.value) {
try {
if (userMarker.value.setMap) userMarker.value.setMap(null);
if (userMarker.value.remove) userMarker.value.remove();
} catch(e) {}
}
// Redibujar solo el sonar del usuario
userMarker.value = addHtmlMarker( userMarker.value = addHtmlMarker(
{ lat, lng }, { lat, lng },
sonarHtml, sonarHtml,
@ -210,7 +148,7 @@ async function clearAllMapData() {
); );
} }
console.log('🤖 JARVIS: Purga completada. Solo queda el usuario ✓'); console.log('🤖 JARVIS: Purga completada ✓');
} catch (err) { } catch (err) {
console.error('❌ JARVIS: Error en purga:', err); console.error('❌ JARVIS: Error en purga:', err);
} }
@ -758,6 +696,8 @@ function calculateWalkingPath(origin: { lat: number, lng: number }, targetStop:
if (walkingPolyline.value) walkingPolyline.value.setMap(null); if (walkingPolyline.value) walkingPolyline.value.setMap(null);
if (walkingPolylineBorder.value) walkingPolylineBorder.value.setMap(null); if (walkingPolylineBorder.value) walkingPolylineBorder.value.setMap(null);
const { registrarPolyline: regPoly } = useMapState();
// CAPA 1: Borde blanco (Para dar contraste estilo Google Maps) // CAPA 1: Borde blanco (Para dar contraste estilo Google Maps)
walkingPolylineBorder.value = new google.maps.Polyline({ walkingPolylineBorder.value = new google.maps.Polyline({
path: route.overview_path, path: route.overview_path,
@ -768,6 +708,7 @@ function calculateWalkingPath(origin: { lat: number, lng: number }, targetStop:
map: map.value, map: map.value,
zIndex: 5 zIndex: 5
}); });
regPoly(walkingPolylineBorder.value);
// CAPA 2: Línea Indigo Central (La ruta principal) // CAPA 2: Línea Indigo Central (La ruta principal)
walkingPolyline.value = new google.maps.Polyline({ walkingPolyline.value = new google.maps.Polyline({
@ -779,6 +720,7 @@ function calculateWalkingPath(origin: { lat: number, lng: number }, targetStop:
map: map.value, map: map.value,
zIndex: 10 zIndex: 10
}); });
regPoly(walkingPolyline.value);
// Ajustar zoom para mostrar toda la ruta de caminata // Ajustar zoom para mostrar toda la ruta de caminata
if (map.value) { if (map.value) {

View File

@ -39,42 +39,69 @@ function getBusStatus(timeStr: string): 'departing' | 'ontime' | 'upcoming' | 'p
return 'upcoming' return 'upcoming'
} }
// ── Calcular si el horario es "hoy" o "mañana" según tipo de día
// ── Calcular si el horario es "hoy" o "mañana" según tipo de día // ── Calcular si el horario es "hoy" o "mañana" según tipo de día
function getScheduleDay(schedule: any): 'today' | 'tomorrow' | 'other' { function getScheduleDay(schedule: any): 'today' | 'tomorrow' | 'other' {
const now = new Date() const now = new Date()
const dow = now.getDay() // 0=Dom, 6=Sab const tomorrow = new Date(now)
const isWeekend = dow === 0 || dow === 6 tomorrow.setDate(now.getDate() + 1)
const isTomorrow = (!isWeekend && schedule.schedule_type === 'weekend') ||
(isWeekend && schedule.schedule_type === 'weekday') const getDayType = (date: Date) => {
const dow = date.getDay() // 0=Dom, 6=Sab
return (dow === 0 || dow === 6) ? 'weekend' : 'weekday'
}
const todayType = getDayType(now)
const tomorrowType = getDayType(tomorrow)
// Comparar con el tipo del horario
// Nota: Si el horario es 'todos', cuenta para hoy y mañana (pero priorizamos hoy si pides hoy)
const type = schedule.schedule_type || 'todos'
const isToday = type === todayType || type === 'todos'
const isTomorrow = type === tomorrowType || type === 'todos'
if (isToday) return 'today'
if (isTomorrow) return 'tomorrow' if (isTomorrow) return 'tomorrow'
if (schedule.schedule_type === 'holiday') return 'other' if (type === 'holiday') return 'other'
return 'today'
return 'other'
} }
function getDayLabel(schedule: any): string { function getDayLabel(schedule: any): string {
const d = getScheduleDay(schedule) const type = schedule.schedule_type || 'todos'
if (d === 'today') return 'Hoy' const now = new Date()
if (d === 'tomorrow') return 'Mañana' const todayType = (now.getDay() === 0 || now.getDay() === 6) ? 'weekend' : 'weekday'
return 'Próximo'
if (type === 'todos') return 'Diario'
if (type === todayType) return 'Hoy'
return 'Mañana'
} }
// ── Filtrado de horarios // ── Filtrado de horarios
const filteredSchedules = computed(() => { const filteredSchedules = computed(() => {
const now = new Date()
const hhmmAhora = now.getHours() * 100 + now.getMinutes()
return scheduleStore.schedules.filter(s => { return scheduleStore.schedules.filter(s => {
const d = getScheduleDay(s) const d = getScheduleDay(s)
const status = getBusStatus(s.departure_time) const [hStr, mStr] = (s.departure_time || '00:00').split(':')
const h = parseInt(hStr || '0')
const m = parseInt(mStr || '0')
const hhmmSched = h * 100 + m
const isPassed = hhmmSched < hhmmAhora - 2 // margen de 2 min
// Filtro Hoy: Solo buses de hoy que NO han pasado // Filtro Hoy: Es hoy Y no ha pasado (o es de los que dice salir en este rango)
if (dayFilter.value === 'today') { if (dayFilter.value === 'today') {
return d === 'today' && status !== 'passed' return d === 'today' && !isPassed
} }
// Filtro Mañana: Solo buses de mañana // Filtro Mañana: Es mañana
if (dayFilter.value === 'tomorrow') { if (dayFilter.value === 'tomorrow') {
return d === 'tomorrow' return d === 'tomorrow'
} }
// Filtro Todos: Mostrar todo // Filtro Todos: Mostrar todo sin importar si pasó o es otro día
return true return true
}) })
}) })