perf: complete performance audit optimizations

This commit is contained in:
2026-02-26 22:17:56 -05:00
parent c9a260ab23
commit a8eaad7f35
14 changed files with 439 additions and 33 deletions

View File

@ -5,14 +5,14 @@ import type { BusStop, Route } from '@/types'
export const busStopsService = {
/** Get all bus stops */
async getAllBusStops(): Promise<BusStop[]> {
const { data, error } = await supabase.from('bus_stops').select('*')
const { data, error } = await supabase.from('bus_stops').select('id, name, latitude, longitude, city, address, parent_id, side, stop_type, has_shelter, has_seating, is_accessible, created_at, updated_at')
if (error) throw new Error(error.message)
return data as BusStop[]
},
/** Get a single bus stop by ID */
async getBusStopById(id: string): Promise<BusStop> {
const { data, error } = await supabase.from('bus_stops').select('*').eq('id', id).single()
const { data, error } = await supabase.from('bus_stops').select('id, name, latitude, longitude, city, address, parent_id, side, stop_type, has_shelter, has_seating, is_accessible, created_at, updated_at').eq('id', id).single()
if (error) throw new Error(error.message)
return data as BusStop
},

View File

@ -18,14 +18,14 @@ export const businessService = {
/** Get all businesses */
async getAllBusinesses(): Promise<Business[]> {
const { data, error } = await supabase.from('businesses').select('*')
const { data, error } = await supabase.from('businesses').select('id, name, address, phone, image_url, social_media, category, latitude, longitude, area, updated_at')
if (error) throw new Error(error.message)
return data as Business[]
},
/** Get a single business by ID */
async getBusiness(id: string): Promise<Business> {
const { data, error } = await supabase.from('businesses').select('*').eq('id', id).single()
const { data, error } = await supabase.from('businesses').select('id, name, address, phone, image_url, social_media, category, latitude, longitude, area, updated_at').eq('id', id).single()
if (error) throw new Error(error.message)
return data as Business
},

View File

@ -5,7 +5,7 @@ import type { Route, BusStop } from '@/types'
export const routesService = {
/** Get all routes with optional filtering */
async getAllRoutes(filters?: { originCity?: string, destinationCity?: string }): Promise<Route[]> {
let query = supabase.from('routes').select('*')
let query = supabase.from('routes').select('id, name, description, color, direction, origin_city, destination_city, distance_km, estimated_duration_minutes, average_speed_kmh, status, created_at, updated_at')
if (filters?.originCity) {
query = query.eq('origin_city', filters.originCity)
@ -21,7 +21,7 @@ export const routesService = {
/** Get a single route by ID */
async getRouteById(id: string): Promise<Route> {
const { data, error } = await supabase.from('routes').select('*').eq('id', id).single()
const { data, error } = await supabase.from('routes').select('id, name, description, color, direction, origin_city, destination_city, distance_km, estimated_duration_minutes, average_speed_kmh, status, created_at, updated_at').eq('id', id).single()
if (error) throw new Error(error.message)
return data as Route
},

View File

@ -32,7 +32,16 @@ export const useBusStopStore = defineStore('busStop', () => {
}
}
async function loadBusStopById(id: string) {
async function loadBusStopById(id: string, force = false) {
// Buscar en cache primero
if (!force && busStops.value.length > 0) {
const cachedStop = busStops.value.find(s => s.id === id);
if (cachedStop) {
selectedStop.value = cachedStop;
return;
}
}
isLoading.value = true
error.value = null
try {

View File

@ -12,7 +12,8 @@ export const useRouteStore = defineStore('route', () => {
const isLoadingRoutes = ref(false)
const isLoadingStops = ref(false)
const error = ref<string | null>(null)
const lastFetched = ref<number>(0) // ⚡ CACHÉ ESTÁTICO
const lastFetched = ref<number>(0) // ⚡ CACHÉ ESTÁTICO RUTAS
const stopsCache = ref<Map<string, { fetchedAt: number, stops: BusStop[] }>>(new Map()) // ⚡ CACHÉ ESTÁTICO PARADAS
const hasSelectedRoute = computed(() => selectedRouteId.value !== null && selectedRouteName.value !== null)
@ -38,11 +39,24 @@ export const useRouteStore = defineStore('route', () => {
}
}
async function loadRouteStops(routeId: string) {
async function loadRouteStops(routeId: string, force = false) {
const CACHE_TIME = 1000 * 60 * 15; // 15 minutos
const now = Date.now();
if (!force && stopsCache.value.has(routeId)) {
const cacheEntry = stopsCache.value.get(routeId)!;
if (now - cacheEntry.fetchedAt < CACHE_TIME) {
selectedRouteStops.value = cacheEntry.stops;
return;
}
}
isLoadingStops.value = true
error.value = null
try {
selectedRouteStops.value = await routesService.getRouteStops(routeId)
const stops = await routesService.getRouteStops(routeId)
selectedRouteStops.value = stops
stopsCache.value.set(routeId, { fetchedAt: now, stops })
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to load route stops'
console.error('Error loading route stops:', e)

View File

@ -51,7 +51,8 @@ import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { busStopsService } from '@/services/busStopsService'
import type { BusStop } from '@/types'
import BusStopEditor from '@/components/BusStopEditor.vue'
import { defineAsyncComponent } from 'vue'
const BusStopEditor = defineAsyncComponent(() => import('@/components/BusStopEditor.vue'))
const router = useRouter()
const stops = ref<BusStop[]>([])

View File

@ -1,8 +1,9 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import LoginForm from '@/components/auth/LoginForm.vue'
import RegisterForm from '@/components/auth/RegisterForm.vue'
import { defineAsyncComponent } from 'vue'
const LoginForm = defineAsyncComponent(() => import('@/components/auth/LoginForm.vue'))
const RegisterForm = defineAsyncComponent(() => import('@/components/auth/RegisterForm.vue'))
const isLogin = ref(true)
const toggleAuth = () => { isLogin.value = !isLogin.value }

View File

@ -122,11 +122,12 @@ function getCategoryIcon(category?: string | null) {
<div
v-for="coupon in filteredCoupons"
:key="coupon.id"
v-memo="[coupon.id]"
class="offer-card-new"
@click="openCoupon(coupon)"
>
<div class="offer-image-wrapper">
<img :src="getImageUrl(coupon.image_url)" :alt="coupon.title" class="offer-img">
<img :src="getImageUrl(coupon.image_url)" :alt="coupon.title" loading="lazy" decoding="async" class="offer-img">
<div class="status-badge" :class="{ 'tmr': coupon.title.toLowerCase().includes('mañana') || (coupon.description?.toLowerCase().includes('mañana') ?? false) }">
<span class="material-icons">schedule</span>
{{ coupon.title.toLowerCase().includes('mañana') ? t('coupons.tomorrow') : t('coupons.active') }}

View File

@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n'
import { useTaxiStore } from '@/stores/taxi'
import { useShuttleStore } from '@/stores/shuttle'
import { analyticsService } from '@/services/analyticsService'
import type { Taxi, Shuttle } from '@/types'
import type { Taxi } from '@/types'
import FavoriteButton from '@/components/FavoriteButton.vue'
import { getImageUrl } from '@/utils/imageUrl'