Implement Smart Location: auto-detect user location if preference is enabled, hide location button, and handle permission denial by resetting preference

This commit is contained in:
2026-03-01 12:15:08 -05:00
parent d0d75b8c98
commit 4d7b472c6c
20 changed files with 852 additions and 344 deletions

View File

@ -6,6 +6,7 @@ import { useRouteStore } from "@/stores/route";
import { useMapStore } from "@/stores/map";
import { useBusStopStore } from "@/stores/busStop";
import { useCouponStore } from "@/stores/coupon";
import { useAuthStore } from "@/stores/auth";
import { useGoogleMaps } from "@/composables/useGoogleMaps";
import { analyticsService } from "@/services/analyticsService";
import { getImageUrl } from "@/utils/imageUrl";
@ -25,6 +26,7 @@ const routeStore = useRouteStore();
const mapStore = useMapStore();
const busStopStore = useBusStopStore();
const couponStore = useCouponStore();
const authStore = useAuthStore();
const { map, isLoaded, error: mapsError, initMap, addHtmlMarker, setCenter, setZoom, addMarker } = useGoogleMaps();
const { estasCargando: estasCargandoRuta, errorRuta } = useDirectionsRoute();
@ -248,6 +250,12 @@ async function initializeMap() {
// Show promotions on the map
updatePromoMarkers();
// Smart Location: Detect automatically if enabled in profile
if (authStore.userProfile?.auto_location) {
console.log('🤖 JARVIS: Smart Location detectado — localizando automaticamente...');
locateUser();
}
// Apply initial styles based on current zoom
updateMarkersStyles();
}
@ -486,6 +494,14 @@ function locateUser(): Promise<void> {
},
(error) => {
console.warn("SIBU | Geolocalización denegada:", error.message);
// Si el usuario tenía auto_location pero denegó el permiso del navegador,
// lo desmarcamos para que no lo vuelva a intentar infinitamente.
if (authStore.userProfile?.auto_location) {
console.log('🤖 JARVIS: Permiso denegado — desactivando Smart Location.');
authStore.updateProfile({ auto_location: false });
}
resolve();
},
{
@ -563,7 +579,7 @@ async function highlightOptimalStopForRoute() {
<!-- Status overlay para SIBU Directions API -->
<div v-if="estasCargandoRuta || errorRuta" class="status-indicator">
<div v-if="estasCargandoRuta" class="loading-pill">
Calculando ruta real...
{{ t('map.calculatingRoute') }}
</div>
<div v-if="errorRuta" class="error-pill">
{{ errorRuta }}
@ -577,7 +593,7 @@ async function highlightOptimalStopForRoute() {
<!-- Floating Offers Button at exact location -->
<div v-if="mapsError" class="error">
<div style="text-align: center; padding: 20px; max-width: 600px; margin: 0 auto;">
<h3 style="color: var(--text-primary); margin-bottom: 15px;"> Error al cargar mapa</h3>
<h3 style="color: var(--text-primary); margin-bottom: 15px;"> {{ t('map.mapLoadingError') }}</h3>
<div style="color: var(--text-primary); margin-bottom: 15px; white-space: pre-line; text-align: left; background: var(--bg-secondary); padding: 15px; border-radius: 8px;">
{{ mapsError }}
</div>
@ -603,9 +619,9 @@ async function highlightOptimalStopForRoute() {
</span>
</button>
<!-- Location Button (Animated Pin) -->
<!-- Location Button (Animated Pin) - Hidden if Smart Location is active -->
<button
v-if="isLoaded"
v-if="isLoaded && !authStore.userProfile?.auto_location"
class="location-loader-btn"
@click="locateUser"
:title="t('map.showMyLocation')"
@ -625,7 +641,7 @@ async function highlightOptimalStopForRoute() {
v-if="routeStore.selectedRouteId && wasSelectedFromMap"
class="uber-search-trigger circular"
@click="openUberSearch"
title="Buscar"
:title="t('map.search')"
>
<span class="material-icons">search</span>
</div>
@ -637,7 +653,7 @@ async function highlightOptimalStopForRoute() {
@click="openUberSearch"
>
<span class="material-icons search-icon">directions_bus</span>
<span class="trigger-label">ver rutas</span>
<span class="trigger-label">{{ t('map.viewRoutes') }}</span>
</div>
<!-- Nuevo Banner de Parada Cercana Alineado (Redimensionado y con ETA) -->
@ -651,7 +667,7 @@ async function highlightOptimalStopForRoute() {
</div>
<div class="flex flex-col flex-1 truncate ml-2" style="min-width: 0;">
<span class="text-[9px] uppercase font-bold text-gray-500 dark:text-gray-400 leading-none">Tiempo de llegada</span>
<span class="text-[9px] uppercase font-bold text-gray-500 dark:text-gray-400 leading-none">{{ t('map.arrivalTime') }}</span>
<span class="trigger-text-compact truncate leading-tight">{{ paradaCercana?.name }}</span>
</div>
@ -683,7 +699,7 @@ async function highlightOptimalStopForRoute() {
<button class="back-btn" @click="closeUberSearch">
<span class="material-icons">arrow_back</span>
</button>
<div class="search-title">Rutas Disponibles</div>
<div class="search-title">{{ t('map.availableRoutes') }}</div>
</div>
<!-- Inputs and Toggle removed per request -->
@ -706,7 +722,7 @@ async function highlightOptimalStopForRoute() {
</div>
<div class="result-content">
<div class="result-name">{{ route.name }}</div>
<div class="result-address">Ruta de Autobús</div>
<div class="result-address">{{ t('map.busRoute') }}</div>
</div>
<span class="material-icons check-icon">
{{ route.id === routeStore.selectedRouteId ? 'check_circle' : 'chevron_right' }}
@ -725,7 +741,7 @@ async function highlightOptimalStopForRoute() {
<!-- Header -->
<div class="sheet-header">
<div class="sheet-title-group">
<span class="sheet-title">Ofertas</span>
<span class="sheet-title">{{ t('coupons.title') }}</span>
</div>
<button class="sheet-close" @click="showPromos = false">
<span class="material-icons">close</span>
@ -753,7 +769,7 @@ async function highlightOptimalStopForRoute() {
<span class="sheet-biz-name">{{ currentPromo.business?.name || 'Local' }}</span>
<h3 class="sheet-promo-title">{{ currentPromo.title }}</h3>
<div class="sheet-actions">
<button class="sheet-cta" @click="handlePromoClick(currentPromo)">Ver detalles</button>
<button class="sheet-cta" @click="handlePromoClick(currentPromo)">{{ t('coupons.viewDetails') }}</button>
<span v-if="currentPromo.discount_percentage" class="sheet-discount-tag">-{{ currentPromo.discount_percentage }}%</span>
</div>
</div>
@ -791,7 +807,7 @@ async function highlightOptimalStopForRoute() {
class="promo-img-modal"
@error="handleImageError"
/>
<div class="promo-badge-modal">PROMO</div>
<div class="promo-badge-modal">{{ t('map.promo') }}</div>
</div>
<div class="promo-body-modal">
<h2 class="promo-title-modal">{{ selectedPromo.title }}</h2>
@ -799,7 +815,7 @@ async function highlightOptimalStopForRoute() {
<p>{{ selectedPromo.description }}</p>
</div>
<div class="promo-actions-modal">
<button class="business-detail-btn-modal" style="flex: 1; width: 100%;" @click="router.push('/business/' + selectedPromo.business_id)">Ver Negocio</button>
<button class="business-detail-btn-modal" style="flex: 1; width: 100%;" @click="router.push('/business/' + selectedPromo.business_id)">{{ t('business.viewBusiness') }}</button>
</div>
</div>
</div>