refactor: migrate fully to Supabase, remove Firebase/Render/Python backend

- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)

- MIGRATED services to Supabase native:
  schedulesService, favoritesService, usersService,
  telemetryService (stub), reportsService, analyticsService (stub)

- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs

- FIXED router/index.ts: guard now uses supabase.auth.getSession()
  instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
  that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
This commit is contained in:
2026-02-25 21:49:26 -05:00
parent 7cd97365f2
commit 84055a25de
267 changed files with 377 additions and 37834 deletions

View File

@ -1,171 +1,188 @@
/** Vue Router configuration */
import { createRouter, createWebHistory } from 'vue-router'
import { supabase } from '@/supabase'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
// ─── Vistas Públicas Core (cargadas en el chunk principal) ───────────
// ─── Vistas Públicas Core ───────────────────────────────────────────
{
path: '/',
name: 'splash',
component: () => import(/* webpackChunkName: "splash" */ '@/views/SplashScreen.vue'),
component: () => import('@/views/SplashScreen.vue'),
},
{
path: '/map',
name: 'map',
component: () => import(/* webpackChunkName: "map" */ '@/views/MapView.vue'),
component: () => import('@/views/MapView.vue'),
},
{
path: '/login',
name: 'auth',
component: () => import(/* webpackChunkName: "auth" */ '@/views/AuthView.vue'),
component: () => import('@/views/AuthView.vue'),
},
// ─── Vistas de Transporte (chunk: transport) ─────────────────────────
// ─── Vistas de Transporte ────────────────────────────────────────────
{
path: '/routes',
name: 'routes',
component: () => import(/* webpackChunkName: "transport" */ '@/views/RoutesView.vue'),
component: () => import('@/views/RoutesView.vue'),
},
{
path: '/schedules',
name: 'schedules',
component: () => import(/* webpackChunkName: "transport" */ '@/views/SchedulesView.vue'),
component: () => import('@/views/SchedulesView.vue'),
},
{
path: '/bus-stop/:id',
name: 'bus-stop-details',
component: () => import(/* webpackChunkName: "transport" */ '@/views/BusStopDetailsView.vue'),
component: () => import('@/views/BusStopDetailsView.vue'),
},
{
path: '/taxi',
name: 'taxi',
component: () => import(/* webpackChunkName: "transport" */ '@/views/TaxiView.vue'),
component: () => import('@/views/TaxiView.vue'),
},
// ─── Vistas de Descubrir (chunk: discover) ───────────────────────────
// ─── Vistas de Descubrir ─────────────────────────────────────────────
{
path: '/discover',
name: 'discover',
component: () => import(/* webpackChunkName: "discover" */ '@/views/DiscoverView.vue'),
component: () => import('@/views/DiscoverView.vue'),
},
{
path: '/business/:id',
name: 'business-details',
component: () => import(/* webpackChunkName: "discover" */ '@/views/BusinessDetailsView.vue'),
component: () => import('@/views/BusinessDetailsView.vue'),
},
{
path: '/coupons',
name: 'coupons',
component: () => import(/* webpackChunkName: "discover" */ '@/views/CouponsView.vue'),
component: () => import('@/views/CouponsView.vue'),
},
// ─── Vistas de Usuario (chunk: user) ─────────────────────────────────
// ─── Vistas de Usuario ───────────────────────────────────────────────
{
path: '/favorites',
name: 'favorites',
component: () => import(/* webpackChunkName: "user" */ '@/views/FavoritesView.vue'),
component: () => import('@/views/FavoritesView.vue'),
},
{
path: '/profile',
name: 'profile',
component: () => import(/* webpackChunkName: "user" */ '@/views/ProfileView.vue'),
component: () => import('@/views/ProfileView.vue'),
meta: { requiresAuth: true }
},
// ─── Vistas de Admin (chunk: admin — solo carga para admins) ─────────
// ─── Vistas de Admin ─────────────────────────────────────────────────
{
path: '/admin',
name: 'admin-panel',
component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminPanel.vue'),
meta: { requiresAuth: true, role: 'admin' }
component: () => import('@/views/AdminPanel.vue'),
meta: { requiresAuth: true, role: 'ADMIN' }
},
{
path: '/admin/bus-stops',
name: 'admin-bus-stops',
component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminBusStops.vue'),
meta: { requiresAuth: true, role: 'admin' }
component: () => import('@/views/AdminBusStops.vue'),
meta: { requiresAuth: true, role: 'ADMIN' }
},
{
path: '/admin/routes',
name: 'admin-routes',
component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminRoutes.vue'),
meta: { requiresAuth: true, role: 'admin' }
component: () => import('@/views/AdminRoutes.vue'),
meta: { requiresAuth: true, role: 'ADMIN' }
},
{
path: '/admin/reports',
name: 'admin-reports',
component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminReports.vue'),
meta: { requiresAuth: true, role: 'admin' }
component: () => import('@/views/AdminReports.vue'),
meta: { requiresAuth: true, role: 'ADMIN' }
},
{
path: '/admin/schedules',
name: 'admin-schedules',
component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminSchedules.vue'),
meta: { requiresAuth: true, role: 'admin' }
component: () => import('@/views/AdminSchedules.vue'),
meta: { requiresAuth: true, role: 'ADMIN' }
},
{
path: '/admin/drivers',
name: 'admin-drivers',
component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminDrivers.vue'),
meta: { requiresAuth: true, role: 'admin' }
component: () => import('@/views/AdminDrivers.vue'),
meta: { requiresAuth: true, role: 'ADMIN' }
},
{
path: '/admin/analytics',
name: 'admin-analytics',
component: () => import(/* webpackChunkName: "admin" */ '@/views/StrategicAnalytics.vue'),
meta: { requiresAuth: true, role: 'admin' }
component: () => import('@/views/StrategicAnalytics.vue'),
meta: { requiresAuth: true, role: 'ADMIN' }
},
{
path: '/admin/taxis',
name: 'admin-taxis',
component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminTaxis.vue'),
meta: { requiresAuth: true, role: 'admin' }
component: () => import('@/views/AdminTaxis.vue'),
meta: { requiresAuth: true, role: 'ADMIN' }
},
{
path: '/admin/shuttles',
name: 'admin-shuttles',
component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminShuttles.vue'),
meta: { requiresAuth: true, role: 'admin' }
component: () => import('@/views/AdminShuttles.vue'),
meta: { requiresAuth: true, role: 'ADMIN' }
},
// ─── Vistas de Roles Especiales (chunk: roles) ───────────────────────
// ─── Vistas de Roles Especiales ──────────────────────────────────────
{
path: '/promoter',
name: 'promoter-dashboard',
component: () => import(/* webpackChunkName: "roles" */ '@/views/PromoterDashboard.vue'),
component: () => import('@/views/PromoterDashboard.vue'),
meta: { requiresAuth: true, role: ['PROMOTER', 'ADMIN'] }
},
{
path: '/driver',
name: 'driver-dashboard',
component: () => import(/* webpackChunkName: "roles" */ '@/views/DriverDashboard.vue'),
component: () => import('@/views/DriverDashboard.vue'),
meta: { requiresAuth: true, role: ['DRIVER', 'ADMIN'] }
},
],
})
router.beforeEach((to, _from, next) => {
const token = localStorage.getItem('auth_token')
const role = localStorage.getItem('user_role')?.toUpperCase()
if (to.meta.requiresAuth && !token) {
next('/login')
} else if (to.meta.role) {
const allowedRoles = Array.isArray(to.meta.role) ? to.meta.role : [to.meta.role]
const hasAccess = allowedRoles.some(r => r.toUpperCase() === role)
if (!hasAccess) {
if (role === 'ADMIN') next('/admin')
else if (role === 'DRIVER') next('/driver')
else if (role === 'PROMOTER') next('/promoter')
else next('/map')
} else {
next()
}
} else {
next()
router.beforeEach(async (to, _from, next) => {
// Rutas públicas: siempre pasan sin verificación
if (!to.meta.requiresAuth && !to.meta.role) {
return next()
}
// Leer sesión activa de Supabase (sustituye el viejo localStorage)
const { data: { session } } = await supabase.auth.getSession()
// Ruta protegida sin sesión → redirige a login
if (to.meta.requiresAuth && !session) {
return next('/login')
}
// Ruta con restricción de rol → verificar en la BD
if (to.meta.role && session) {
const { data: profile } = await supabase
.from('users')
.select('role')
.eq('id', session.user.id)
.single()
const userRole = profile?.role?.toUpperCase() || 'PASSENGER'
const allowedRoles = Array.isArray(to.meta.role)
? (to.meta.role as string[]).map(r => r.toUpperCase())
: [(to.meta.role as string).toUpperCase()]
if (!allowedRoles.includes(userRole)) {
// Redirigir a la vista correcta según su rol real
if (userRole === 'ADMIN') return next('/admin')
else if (userRole === 'DRIVER') return next('/driver')
else if (userRole === 'PROMOTER') return next('/promoter')
else return next('/map')
}
}
next()
})
export default router