Implement Session Persistence: 'Keep me logged in' now works by toggling between localStorage and sessionStorage based on user choice
This commit is contained in:
@ -22,7 +22,7 @@ const handleLogin = async () => {
|
|||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await authStore.login(email.value.trim().toLowerCase(), password.value)
|
await authStore.login(email.value.trim().toLowerCase(), password.value, keepSession.value)
|
||||||
// El rol ya está disponible en el store (del JWT), navegar directo
|
// El rol ya está disponible en el store (del JWT), navegar directo
|
||||||
navigateByUserRole(authStore.role || 'PASSENGER')
|
navigateByUserRole(authStore.role || 'PASSENGER')
|
||||||
|
|
||||||
|
|||||||
@ -14,38 +14,96 @@ export function useETA() {
|
|||||||
const busesActivos = ref<BusETA[]>([]);
|
const busesActivos = ref<BusETA[]>([]);
|
||||||
const cargando = ref<boolean>(false);
|
const cargando = ref<boolean>(false);
|
||||||
|
|
||||||
|
// Configuración para el cálculo del ETA mejorado
|
||||||
|
const VELOCIDAD_PROMEDIO_KMH = 35; // km/h (promedio ciudad/carretera)
|
||||||
|
const TIEMPO_PARADA_SEGUNDOS = 45; // segundos detenido por parada
|
||||||
|
|
||||||
|
// Fórmula Haversine para distancia en línea recta (km)
|
||||||
|
function getDistanceKm(lat1: number, lon1: number, lat2: number, lon2: number): number {
|
||||||
|
const R = 6371;
|
||||||
|
const dLat = (lat2 - lat1) * (Math.PI / 180);
|
||||||
|
const dLon = (lon2 - lon1) * (Math.PI / 180);
|
||||||
|
const a =
|
||||||
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||||
|
Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) *
|
||||||
|
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||||
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
return R * c;
|
||||||
|
}
|
||||||
|
|
||||||
const calcularETA = async (ruta_id: string, parada_cercana: BusStop) => {
|
const calcularETA = async (ruta_id: string, parada_cercana: BusStop) => {
|
||||||
cargando.value = true;
|
cargando.value = true;
|
||||||
busesActivos.value = [];
|
busesActivos.value = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// PASO 1: Obtener travel_time_minutes de la parada del usuario
|
// PASO 1: Obtener detalles de la ruta y todas sus paradas para calcular distancia real
|
||||||
// desde route_stops usando el stop_id de la parada cercana
|
const [routeRes, stopsRes] = await Promise.all([
|
||||||
const { data: routeStopData, error: rsError } = await supabase
|
supabase
|
||||||
|
.from('routes')
|
||||||
|
.select('distance_km, average_speed_kmh')
|
||||||
|
.eq('id', ruta_id)
|
||||||
|
.single(),
|
||||||
|
supabase
|
||||||
.from('route_stops')
|
.from('route_stops')
|
||||||
.select('travel_time_minutes, stop_order, stop_delay_minutes')
|
.select('stop_id, stop_order, bus_stops(latitude, longitude)')
|
||||||
.eq('route_id', ruta_id)
|
.eq('route_id', ruta_id)
|
||||||
.eq('stop_id', parada_cercana.id)
|
.order('stop_order', { ascending: true })
|
||||||
.single();
|
]);
|
||||||
|
|
||||||
if (rsError || !routeStopData) {
|
if (routeRes.error || !stopsRes.data) throw new Error('Error al cargar datos de ruta');
|
||||||
console.warn('SIBU | No se encontró travel_time para la parada:',
|
|
||||||
parada_cercana.name);
|
const routeData = routeRes.data;
|
||||||
return;
|
const routeStops = stopsRes.data;
|
||||||
|
|
||||||
|
// Encontrar el orden de la parada donde está el usuario
|
||||||
|
const targetStopIndex = routeStops.findIndex(s => s.stop_id === parada_cercana.id);
|
||||||
|
if (targetStopIndex === -1) return;
|
||||||
|
|
||||||
|
// CALCULAR DISTANCIA ACUMULADA (Terminal hasta Parada Destino)
|
||||||
|
// Usamos Haversine entre cada parada consecutiva para mayor precisión que una línea recta total
|
||||||
|
let distanciaAcumuladaKm = 0;
|
||||||
|
for (let i = 0; i < targetStopIndex; i++) {
|
||||||
|
const stopA = routeStops[i];
|
||||||
|
const stopB = routeStops[i + 1];
|
||||||
|
|
||||||
|
const start = stopA ? (stopA.bus_stops as any) : null;
|
||||||
|
const end = stopB ? (stopB.bus_stops as any) : null;
|
||||||
|
|
||||||
|
if (start?.latitude != null && start?.longitude != null &&
|
||||||
|
end?.latitude != null && end?.longitude != null) {
|
||||||
|
distanciaAcumuladaKm += getDistanceKm(
|
||||||
|
start.latitude, start.longitude,
|
||||||
|
end.latitude, end.longitude
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tiempo total hasta la parada del usuario en minutos
|
// Aplicar factor de corrección de ruta (las calles no son rectas, aprox +20%)
|
||||||
const minutosHastaParada =
|
distanciaAcumuladaKm *= 1.2;
|
||||||
(routeStopData.travel_time_minutes ?? 0) +
|
|
||||||
(routeStopData.stop_delay_minutes ?? 0);
|
|
||||||
|
|
||||||
// PASO 2: Obtener horarios activos para hoy
|
// PASO 2: Aplicar Fórmula Mejorada (Requerida por el usuario)
|
||||||
|
// ETA = (Distancia / Velocidad) + (Nº Paradas * Tiempo Parada)
|
||||||
|
|
||||||
|
const velocidad = routeData.average_speed_kmh || VELOCIDAD_PROMEDIO_KMH;
|
||||||
|
|
||||||
|
// Tiempo de viaje (en minutos)
|
||||||
|
const tiempoViajeMinutos = (distanciaAcumuladaKm / velocidad) * 60;
|
||||||
|
|
||||||
|
// Tiempo total en paradas (N paradas previas × tiempo promedio)
|
||||||
|
// numeroParadas = stops antes de llegar a la parada destino
|
||||||
|
const numeroParadas = targetStopIndex;
|
||||||
|
const tiempoParadasMinutos = (numeroParadas * TIEMPO_PARADA_SEGUNDOS) / 60;
|
||||||
|
|
||||||
|
// Tiempo total hasta la parada del usuario
|
||||||
|
const minutosHastaParada = tiempoViajeMinutos + tiempoParadasMinutos;
|
||||||
|
|
||||||
|
console.log(`SIBU ETA | Ruta: ${ruta_id} | Dist: ${distanciaAcumuladaKm.toFixed(2)}km | Paradas: ${numeroParadas} | Total: ${minutosHastaParada.toFixed(1)}min`);
|
||||||
|
|
||||||
|
// PASO 3: Obtener horarios activos para hoy
|
||||||
const diaActual = new Date().getDay();
|
const diaActual = new Date().getDay();
|
||||||
const dias = ['domingo', 'lunes', 'martes', 'miercoles',
|
const dias = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado'];
|
||||||
'jueves', 'viernes', 'sabado'];
|
|
||||||
const diaString = dias[diaActual];
|
const diaString = dias[diaActual];
|
||||||
const tipoDia = (diaActual === 0 || diaActual === 6)
|
const tipoDia = (diaActual === 0 || diaActual === 6) ? 'weekend' : 'weekday';
|
||||||
? 'weekend' : 'weekday';
|
|
||||||
|
|
||||||
const { data: horarios, error: hError } = await supabase
|
const { data: horarios, error: hError } = await supabase
|
||||||
.from('bus_schedules')
|
.from('bus_schedules')
|
||||||
@ -56,60 +114,44 @@ export function useETA() {
|
|||||||
|
|
||||||
if (hError) throw hError;
|
if (hError) throw hError;
|
||||||
|
|
||||||
// Filtrar horarios que operan hoy
|
|
||||||
const horariosHoy = (horarios ?? []).filter(h => {
|
const horariosHoy = (horarios ?? []).filter(h => {
|
||||||
if (h.dias_operacion) {
|
if (h.dias_operacion) {
|
||||||
return h.dias_operacion.includes('todos') ||
|
return h.dias_operacion.includes('todos') || h.dias_operacion.includes(diaString);
|
||||||
h.dias_operacion.includes(diaString);
|
|
||||||
}
|
}
|
||||||
return h.schedule_type === tipoDia ||
|
return h.schedule_type === tipoDia || h.schedule_type === 'todos' || !h.schedule_type;
|
||||||
h.schedule_type === 'todos' ||
|
|
||||||
!h.schedule_type;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// PASO 3: Calcular ETA para cada horario
|
// PASO 4: Calcular ETA para cada salida desde la terminal
|
||||||
const ahora = new Date();
|
const ahora = new Date();
|
||||||
const minutosAhora = ahora.getHours() * 60 + ahora.getMinutes();
|
const minutosAhora = ahora.getHours() * 60 + ahora.getMinutes();
|
||||||
|
|
||||||
const resultados: BusETA[] = [];
|
const resultados: BusETA[] = [];
|
||||||
|
|
||||||
for (const h of horariosHoy) {
|
for (const h of horariosHoy) {
|
||||||
const salida = h.departure_time; // "HH:mm:ss"
|
const salida = h.departure_time;
|
||||||
if (!salida) continue;
|
if (!salida) continue;
|
||||||
|
|
||||||
// Parsear hora de salida a minutos desde medianoche
|
|
||||||
const [hStr, mStr] = salida.split(':');
|
const [hStr, mStr] = salida.split(':');
|
||||||
const minutosSalida = parseInt(hStr) * 60 + parseInt(mStr);
|
const minutosSalida = parseInt(hStr) * 60 + parseInt(mStr);
|
||||||
|
|
||||||
// ── FÓRMULA PRINCIPAL ────────────────────────────
|
// Hora estimada de llegada a la parada del usuario:
|
||||||
// Hora de llegada del bus a la parada del usuario:
|
|
||||||
const minutosLlegadaParada = minutosSalida + minutosHastaParada;
|
const minutosLlegadaParada = minutosSalida + minutosHastaParada;
|
||||||
|
|
||||||
// ETA = cuántos minutos faltan desde ahora:
|
// ETA = cuántos minutos faltan desde ahora:
|
||||||
const etaMinutos = minutosLlegadaParada - minutosAhora;
|
const etaMinutos = minutosLlegadaParada - minutosAhora;
|
||||||
// ─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// Formatear hora de llegada para mostrar en UI
|
|
||||||
const horaLlegada = minutosAHora(minutosLlegadaParada);
|
const horaLlegada = minutosAHora(minutosLlegadaParada);
|
||||||
const horaSalidaFormato = `${hStr.padStart(2, '0')}:${mStr.padStart(2, '0')}`;
|
const horaSalidaFormato = `${hStr.padStart(2, '0')}:${mStr.padStart(2, '0')}`;
|
||||||
|
|
||||||
// Determinar estado
|
|
||||||
let estado: BusETA['estado'];
|
let estado: BusETA['estado'];
|
||||||
|
|
||||||
if (etaMinutos > 5) {
|
if (etaMinutos > 5) {
|
||||||
// Bus aún no llega a la parada
|
|
||||||
// Verificar si ya salió del terminal o no
|
|
||||||
const yaPartio = minutosAhora >= minutosSalida;
|
const yaPartio = minutosAhora >= minutosSalida;
|
||||||
estado = yaPartio ? 'en_camino' : 'próximo';
|
estado = yaPartio ? 'en_camino' : 'próximo';
|
||||||
} else if (etaMinutos >= -2) {
|
} else if (etaMinutos >= -2) {
|
||||||
// Llegando ahora (ventana de ±2 minutos)
|
|
||||||
estado = 'en_camino';
|
estado = 'en_camino';
|
||||||
} else {
|
} else {
|
||||||
// Ya pasó la parada
|
|
||||||
estado = 'pasó';
|
estado = 'pasó';
|
||||||
}
|
}
|
||||||
|
|
||||||
// No incluir buses que pasaron hace más de 60 minutos
|
|
||||||
if (etaMinutos < -60) continue;
|
if (etaMinutos < -60) continue;
|
||||||
|
|
||||||
resultados.push({
|
resultados.push({
|
||||||
@ -121,9 +163,6 @@ export function useETA() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// PASO 4: Ordenar resultados
|
|
||||||
// Prioridad: en_camino → próximo → pasó
|
|
||||||
// Dentro de cada grupo: menor ETA primero
|
|
||||||
resultados.sort((a, b) => {
|
resultados.sort((a, b) => {
|
||||||
const prioridad = { 'en_camino': 0, 'próximo': 1, 'pasó': 2 };
|
const prioridad = { 'en_camino': 0, 'próximo': 1, 'pasó': 2 };
|
||||||
if (prioridad[a.estado] !== prioridad[b.estado]) {
|
if (prioridad[a.estado] !== prioridad[b.estado]) {
|
||||||
@ -132,7 +171,6 @@ export function useETA() {
|
|||||||
return a.etaMinutos - b.etaMinutos;
|
return a.etaMinutos - b.etaMinutos;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Máximo 3 buses
|
|
||||||
busesActivos.value = resultados.slice(0, 3);
|
busesActivos.value = resultados.slice(0, 3);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -35,12 +35,22 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function login(email: string, pass: string) {
|
async function login(email: string, pass: string, keepSession: boolean = false) {
|
||||||
const { data, error } = await supabase.auth.signInWithPassword({ email, password: pass })
|
const { data, error } = await supabase.auth.signInWithPassword({ email, password: pass })
|
||||||
if (error) throw new Error(error.message)
|
if (error) throw new Error(error.message)
|
||||||
|
|
||||||
if (data.user) {
|
if (data.user) {
|
||||||
// Rol disponible al instante desde el JWT — sin consultas BD bloqueantes
|
// Manejo de persistencia: Si el usuario NO quiere mantener sesión iniciada,
|
||||||
|
// movemos el token de localStorage a sessionStorage.
|
||||||
|
if (!keepSession) {
|
||||||
|
const storageKey = `sb-bjgixlugjzsccazdfmph-auth-token`
|
||||||
|
const sessionData = localStorage.getItem(storageKey)
|
||||||
|
if (sessionData) {
|
||||||
|
sessionStorage.setItem(storageKey, sessionData)
|
||||||
|
localStorage.removeItem(storageKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
userSession.value = data.session
|
userSession.value = data.session
|
||||||
userProfile.value = {
|
userProfile.value = {
|
||||||
id: data.user.id,
|
id: data.user.id,
|
||||||
|
|||||||
@ -3,4 +3,28 @@ import { createClient } from '@supabase/supabase-js'
|
|||||||
export const SUPABASE_URL = 'https://bjgixlugjzsccazdfmph.supabase.co'
|
export const SUPABASE_URL = 'https://bjgixlugjzsccazdfmph.supabase.co'
|
||||||
export const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJqZ2l4bHVnanpzY2NhemRmbXBoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzIwNjQyMTAsImV4cCI6MjA4NzY0MDIxMH0.untLQoPi4yUr3cPnxo23wYSlg6xnNK0daKu9UHmFTp8'
|
export const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJqZ2l4bHVnanpzY2NhemRmbXBoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzIwNjQyMTAsImV4cCI6MjA4NzY0MDIxMH0.untLQoPi4yUr3cPnxo23wYSlg6xnNK0daKu9UHmFTp8'
|
||||||
|
|
||||||
export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
|
// SIBU | Hybrid Storage: Maneja persistencia según la voluntad del usuario
|
||||||
|
const authStorage = {
|
||||||
|
getItem: (key: string) => localStorage.getItem(key) || sessionStorage.getItem(key),
|
||||||
|
setItem: (key: string, val: string) => {
|
||||||
|
// Si ya existe en sessionStorage, seguimos guardando allí (no persistente)
|
||||||
|
if (sessionStorage.getItem(key)) {
|
||||||
|
sessionStorage.setItem(key, val)
|
||||||
|
} else {
|
||||||
|
// Por defecto guardamos en localStorage (persistente)
|
||||||
|
localStorage.setItem(key, val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeItem: (key: string) => {
|
||||||
|
localStorage.removeItem(key)
|
||||||
|
sessionStorage.removeItem(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
||||||
|
auth: {
|
||||||
|
storage: authStorage as any,
|
||||||
|
autoRefreshToken: true,
|
||||||
|
persistSession: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user