Initial commit: Vuelato - buscador de vuelos
Some checks failed
ci / ci (22, ubuntu-latest) (push) Has been cancelled
Some checks failed
ci / ci (22, ubuntu-latest) (push) Has been cancelled
Nuxt 4 + Supabase + Flightics API. Incluye búsqueda de vuelos, inspiraciones, watchlist, tracking de precios y mapa interactivo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
130
plans/search-queue-tracking.md
Normal file
130
plans/search-queue-tracking.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Plan: Sistema de cola de busquedas y seguimiento de precios
|
||||
|
||||
## Contexto
|
||||
|
||||
Vuelato tiene watchlist manual (check precio uno a uno) y busquedas recientes, pero no hay:
|
||||
- Busquedas automaticas en segundo plano
|
||||
- Historico de precios con graficos
|
||||
- Cache de resultados entre usuarios
|
||||
|
||||
El usuario quiere un sistema completo: definir busquedas recurrentes desde la UI, ver fluctuaciones de precio en tablas/graficos, y ademas una capa de cache que evite llamadas duplicadas a Flightics (TTL 1h) reutilizable tanto por el worker como por busquedas manuales.
|
||||
|
||||
---
|
||||
|
||||
## 1. Esquema de base de datos
|
||||
|
||||
**Archivo: `supabase/migrations/00002_search_queue.sql`**
|
||||
|
||||
### Tabla `search_cache` — Cache de resultados (publica, sin RLS)
|
||||
- `id` SERIAL PRIMARY KEY
|
||||
- `params_hash` TEXT UNIQUE — SHA-256 del JSON de SearchParams normalizado
|
||||
- `search_params` JSONB — el payload completo
|
||||
- `trips` JSONB — array completo de trips devueltos
|
||||
- `cheapest_price` NUMERIC(10,2)
|
||||
- `total_results` INTEGER
|
||||
- `fetched_at` TIMESTAMPTZ DEFAULT now()
|
||||
|
||||
### Tabla `tracked_searches` — Busquedas recurrentes (RLS por user_id)
|
||||
- `id` UUID PK DEFAULT gen_random_uuid()
|
||||
- `user_id` UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE
|
||||
- `name` TEXT NOT NULL
|
||||
- `search_params` JSONB NOT NULL
|
||||
- `route_summary` TEXT NOT NULL
|
||||
- `interval_hours` INTEGER NOT NULL DEFAULT 24
|
||||
- `is_active` BOOLEAN DEFAULT true
|
||||
- `next_run_at` TIMESTAMPTZ DEFAULT now()
|
||||
- `last_run_at` TIMESTAMPTZ
|
||||
- `run_count` INTEGER DEFAULT 0
|
||||
- `last_error` TEXT
|
||||
- `expires_at` TIMESTAMPTZ
|
||||
- `created_at` TIMESTAMPTZ DEFAULT now()
|
||||
|
||||
### Tabla `search_runs` — Log de ejecuciones (RLS via tracked_search_id)
|
||||
- `id` UUID PK
|
||||
- `tracked_search_id` UUID NOT NULL REFERENCES tracked_searches(id) ON DELETE CASCADE
|
||||
- `status` TEXT DEFAULT 'pending'
|
||||
- `cheapest_price` NUMERIC(10,2)
|
||||
- `total_trips_found` INTEGER DEFAULT 0
|
||||
- `top_trips` JSONB
|
||||
- `from_cache` BOOLEAN DEFAULT false
|
||||
- `error_message` TEXT
|
||||
- `started_at` TIMESTAMPTZ
|
||||
- `completed_at` TIMESTAMPTZ
|
||||
- `created_at` TIMESTAMPTZ DEFAULT now()
|
||||
|
||||
### Tabla `price_snapshots` — Datos para graficos (RLS via tracked_search_id)
|
||||
- `id` UUID PK
|
||||
- `tracked_search_id` UUID NOT NULL REFERENCES tracked_searches(id) ON DELETE CASCADE
|
||||
- `search_run_id` UUID NOT NULL REFERENCES search_runs(id) ON DELETE CASCADE
|
||||
- `cheapest_price` NUMERIC(10,2) NOT NULL
|
||||
- `avg_price` NUMERIC(10,2)
|
||||
- `median_price` NUMERIC(10,2)
|
||||
- `total_results` INTEGER DEFAULT 0
|
||||
- `recorded_at` TIMESTAMPTZ DEFAULT now()
|
||||
|
||||
---
|
||||
|
||||
## 2. Cache de busquedas — Flujo integrado
|
||||
|
||||
### Hash de parametros (`server/utils/search-hash.ts`)
|
||||
- Normaliza SearchParams (ordena keys, elimina `_poll`), genera SHA-256
|
||||
- Compartido entre `/api/search` y el worker
|
||||
|
||||
### Endpoint `/api/search` modificado (cache-aware)
|
||||
1. Calcular params_hash
|
||||
2. Buscar en search_cache WHERE params_hash = hash AND fetched_at > now() - 1 hour
|
||||
3. Si hay cache fresco → devolver trips del cache
|
||||
4. Si no → llamar a Flightics, guardar en search_cache, devolver
|
||||
|
||||
### Worker usa el mismo cache
|
||||
- Lee cache antes de llamar a Flightics
|
||||
- Escribe al cache tras cada busqueda nueva
|
||||
|
||||
---
|
||||
|
||||
## 3. Worker (`server/plugins/search-worker.ts`)
|
||||
|
||||
- Nitro plugin con `setInterval(processQueue, 60_000)`
|
||||
- Mutex para evitar solapamiento
|
||||
- Procesa hasta 5 jobs por ciclo con 10s entre cada uno
|
||||
- Usa `server/utils/supabase-admin.ts` (cliente Supabase con service_role sin H3 event)
|
||||
|
||||
---
|
||||
|
||||
## 4. API Endpoints (`server/api/tracking/`)
|
||||
|
||||
| Archivo | Metodo | Funcion |
|
||||
|---------|--------|---------|
|
||||
| `index.get.ts` | GET | Listar tracked_searches con ultimo snapshot |
|
||||
| `index.post.ts` | POST | Crear tracked_search |
|
||||
| `[id].patch.ts` | PATCH | Editar configuracion |
|
||||
| `[id].delete.ts` | DELETE | Eliminar + cascade |
|
||||
| `[id]/history.get.ts` | GET | Price snapshots para graficos |
|
||||
| `[id]/runs.get.ts` | GET | Log de ejecuciones |
|
||||
|
||||
---
|
||||
|
||||
## 5. Composable (`app/composables/useTrackedSearches.ts`)
|
||||
|
||||
Patron identico a useWatchlist.ts con watch(user) para auto-load/cleanup.
|
||||
|
||||
---
|
||||
|
||||
## 6. UI: vue-chartjs + chart.js
|
||||
|
||||
## 7. Componentes: tracking/PriceChart, TrackedSearchCard, CreateTrackingForm, RunHistory, TrackingConfig
|
||||
|
||||
## 8. Paginas: /tracking (dashboard) + /tracking/[id] (detalle con grafico)
|
||||
|
||||
## 9. Integracion: boton en /results, icono en recientes, link en nav
|
||||
|
||||
---
|
||||
|
||||
## Orden de implementacion
|
||||
|
||||
### Fase 1: DB + Cache
|
||||
### Fase 2: Worker
|
||||
### Fase 3: API tracking
|
||||
### Fase 4: Composable + UI basica
|
||||
### Fase 5: Graficos y detalle
|
||||
### Fase 6: Integracion
|
||||
393
plans/vuelato-plan.md
Normal file
393
plans/vuelato-plan.md
Normal file
@@ -0,0 +1,393 @@
|
||||
# Vuelato - Frontend + Backend Supabase
|
||||
|
||||
## Contexto
|
||||
Tenemos un buscador de vuelos con un cliente API completo de Flightics (9 endpoints REST funcionando). El frontend actual tiene 3 paginas basicas. Queremos:
|
||||
1. Un backend Supabase/PostgreSQL que wrapee Flightics y persista datos
|
||||
2. Un frontend ambicioso que explote toda la API
|
||||
|
||||
## Por que Supabase
|
||||
- **Persistencia**: watchlists, preferencias, busquedas recientes -> entre dispositivos
|
||||
- **Cache inteligente**: airports, countries, inspirations en Postgres (no llamar a Flightics cada vez)
|
||||
- **Auth**: usuarios con sus datos
|
||||
- **Cron/Edge Functions**: verificar precios de watchlist automaticamente
|
||||
- **Realtime**: notificar bajadas de precio
|
||||
- **Row Level Security**: cada usuario ve solo sus datos
|
||||
|
||||
---
|
||||
|
||||
## Stack
|
||||
- **Nuxt 4** + **@nuxt/ui v4** + **Tailwind CSS v4** (ya instalado)
|
||||
- **@nuxtjs/supabase v2** - modulo oficial, composables auto-importados
|
||||
- **Supabase PostgreSQL** - tablas, RLS, funciones
|
||||
- **Leaflet** - mapa explorador
|
||||
- Server routes Nitro wrapeando Flightics -> Supabase cache
|
||||
|
||||
---
|
||||
|
||||
## Esquema de base de datos (Supabase/PostgreSQL)
|
||||
|
||||
### Tablas de cache (datos de Flightics, publicas, sin RLS)
|
||||
|
||||
```sql
|
||||
-- Aeropuertos, ciudades, paises, regiones (de getLocations + getCountries)
|
||||
-- Se sincroniza via cron o al primer request, TTL 24h
|
||||
CREATE TABLE airports (
|
||||
iata TEXT PRIMARY KEY,
|
||||
icao TEXT,
|
||||
name TEXT NOT NULL,
|
||||
lat DOUBLE PRECISION,
|
||||
lon DOUBLE PRECISION,
|
||||
city_id TEXT,
|
||||
city_name TEXT,
|
||||
country_code TEXT,
|
||||
country_name TEXT,
|
||||
region_slug TEXT,
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE countries (
|
||||
iso_code2 TEXT PRIMARY KEY,
|
||||
iso_code3 TEXT,
|
||||
name_eng TEXT,
|
||||
name_native TEXT,
|
||||
phone_prefix TEXT,
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE regions (
|
||||
slug TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
name_localized TEXT,
|
||||
airport_codes TEXT[], -- array de IATA codes
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Cache de inspiraciones (por aeropuerto origen, TTL 1h)
|
||||
CREATE TABLE inspirations_cache (
|
||||
id SERIAL PRIMARY KEY,
|
||||
from_airport TEXT NOT NULL,
|
||||
to_airports TEXT[] NOT NULL,
|
||||
min_price NUMERIC(10,2),
|
||||
min_stops INTEGER,
|
||||
fetched_at TIMESTAMPTZ DEFAULT now(),
|
||||
UNIQUE(from_airport, to_airports)
|
||||
);
|
||||
|
||||
-- Cache de multi-city inspirations (TTL 1h)
|
||||
CREATE TABLE multi_city_cache (
|
||||
id SERIAL PRIMARY KEY,
|
||||
origin_codes TEXT[] NOT NULL,
|
||||
from_airport TEXT,
|
||||
stops TEXT[] NOT NULL,
|
||||
min_price NUMERIC(10,2),
|
||||
fetched_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
### Tablas de usuario (con RLS)
|
||||
|
||||
```sql
|
||||
-- Perfiles de usuario (extends auth.users)
|
||||
CREATE TABLE profiles (
|
||||
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
home_airports TEXT[] DEFAULT '{}',
|
||||
default_adults INTEGER DEFAULT 1,
|
||||
default_children INTEGER DEFAULT 0,
|
||||
default_infants INTEGER DEFAULT 0,
|
||||
locale TEXT DEFAULT 'es',
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Watchlist: vuelos guardados para monitorizar precio
|
||||
CREATE TABLE watchlist (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
booking_token TEXT NOT NULL,
|
||||
route_summary TEXT NOT NULL, -- "MAD > NTE > MAD"
|
||||
departure_code TEXT,
|
||||
arrival_code TEXT,
|
||||
departure_date TEXT,
|
||||
original_price NUMERIC(10,2) NOT NULL,
|
||||
current_price NUMERIC(10,2),
|
||||
price_status TEXT DEFAULT 'saved', -- saved, available, price_up, price_down, unavailable
|
||||
passengers_adult INTEGER DEFAULT 1,
|
||||
passengers_child INTEGER DEFAULT 0,
|
||||
passengers_infant INTEGER DEFAULT 0,
|
||||
last_checked_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Busquedas recientes
|
||||
CREATE TABLE recent_searches (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
search_params JSONB NOT NULL, -- el payload completo de busqueda
|
||||
route_summary TEXT, -- "AGP,GRX > NTE"
|
||||
search_mode TEXT DEFAULT 'roundtrip',
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Alertas de precio (futuro: cron verifica y notifica)
|
||||
CREATE TABLE price_alerts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
watchlist_id UUID REFERENCES watchlist(id) ON DELETE CASCADE,
|
||||
target_price NUMERIC(10,2), -- notificar si baja de este precio
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
last_notified_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- RLS policies
|
||||
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE watchlist ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE recent_searches ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE price_alerts ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Users see own profile" ON profiles FOR ALL USING (auth.uid() = id);
|
||||
CREATE POLICY "Users see own watchlist" ON watchlist FOR ALL USING (auth.uid() = user_id);
|
||||
CREATE POLICY "Users see own searches" ON recent_searches FOR ALL USING (auth.uid() = user_id);
|
||||
CREATE POLICY "Users see own alerts" ON price_alerts FOR ALL USING (auth.uid() = user_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura server routes (Nitro)
|
||||
|
||||
Los server routes actuan como **proxy inteligente**: Flightics API -> cache en Supabase -> respuesta al frontend.
|
||||
|
||||
### Rutas existentes (refactored con cache)
|
||||
|
||||
| Ruta | Logica |
|
||||
|---|---|
|
||||
| `GET /api/locations` | Lee de tabla `airports`. Si vacia o >24h, llama a Flightics `getLocations()`, parsea y guarda. |
|
||||
| `GET /api/countries` | Lee de tabla `countries`. Si vacia o >24h, sincroniza. |
|
||||
| `GET /api/inspirations` | Lee de `inspirations_cache` si <1h. Si no, llama a Flightics, guarda en cache, retorna. |
|
||||
| `POST /api/search` | Proxy directo a Flightics `searchTrips`. Sin cache (resultados son efimeros). |
|
||||
| `POST /api/detail` | Proxy directo a Flightics. |
|
||||
| `POST /api/check` | Proxy directo a Flightics. |
|
||||
| `POST /api/weekend-search` | Proxy directo. |
|
||||
| `POST /api/route-flights` | Proxy directo (datos cambian frecuentemente). |
|
||||
| `POST /api/multi-city-inspirations` | Cache 1h en `multi_city_cache`, sino Flightics. |
|
||||
|
||||
### Rutas nuevas (usuario/Supabase)
|
||||
|
||||
| Ruta | Logica |
|
||||
|---|---|
|
||||
| `GET /api/user/profile` | Lee profile del usuario autenticado |
|
||||
| `PUT /api/user/profile` | Actualiza home_airports, passengers, locale |
|
||||
| `GET /api/user/watchlist` | Lista watchlist del usuario |
|
||||
| `POST /api/user/watchlist` | Anadir vuelo a watchlist |
|
||||
| `DELETE /api/user/watchlist/[id]` | Eliminar de watchlist |
|
||||
| `POST /api/user/watchlist/[id]/check` | Verificar precio: llama `checkTrip`, actualiza en DB |
|
||||
| `POST /api/user/watchlist/check-all` | Verificar todos los precios del usuario |
|
||||
| `GET /api/user/recent-searches` | Ultimas 10 busquedas |
|
||||
| `POST /api/user/recent-searches` | Guardar busqueda reciente |
|
||||
| `POST /api/sync/locations` | Fuerza sync de airports/countries/regions desde Flightics a Supabase |
|
||||
|
||||
---
|
||||
|
||||
## Paginas (9)
|
||||
|
||||
### `/` - Home (discovery hub)
|
||||
- Hero con SearchBar compacto inline
|
||||
- Tabs rapidos: Ida y vuelta | Solo ida | Multi-ciudad | Finde | Explorar
|
||||
- Inspiraciones desde home airports del usuario (profile o localStorage si no logueado)
|
||||
- Carrusel multi-city inspirations
|
||||
- Widget "Donde por X€?" con slider presupuesto
|
||||
- Busquedas recientes del usuario (si logueado)
|
||||
|
||||
### `/auth` - Login/Register
|
||||
- Login con email/password (Supabase Auth)
|
||||
- OAuth con Google
|
||||
- Registro
|
||||
- Redirect post-login
|
||||
|
||||
### `/search` - Busqueda completa
|
||||
- 5 modos: standard, solo ida, multi-city, finde, explorar
|
||||
- AirportInput con autocomplete (datos de Supabase `airports` table)
|
||||
- Al buscar, guarda en `recent_searches` si logueado
|
||||
|
||||
### `/results` - Resultados
|
||||
- Sort: precio, duracion, salida, escalas
|
||||
- Filtros: precio max, escalas, hora, aerolineas
|
||||
- Polling progresivo
|
||||
- Vista lista + compacta
|
||||
- Estrella watchlist (requiere login, o prompt to login)
|
||||
|
||||
### `/detail/[token]` - Detalle
|
||||
- Timeline itinerario
|
||||
- Verificar precio
|
||||
- Vuelos relacionados (getRouteFlights)
|
||||
- Compartir
|
||||
- Watchlist toggle
|
||||
- CTA booking
|
||||
|
||||
### `/explore` - Mapa explorador
|
||||
- Mapa Leaflet con aeropuertos de tabla `airports` (lat/lon)
|
||||
- Seleccionar origen -> inspiraciones como arcos
|
||||
- Slider presupuesto
|
||||
- Click destino -> popup
|
||||
- Toggle directos
|
||||
|
||||
### `/route/[from]-[to]` - Explorador de ruta
|
||||
- getRouteFlights sin fechas
|
||||
- Todas las opciones de vuelo
|
||||
- Comparativa aerolineas
|
||||
|
||||
### `/multi-city` - Inspiracion multi-ciudad
|
||||
- Input origenes -> getMultiCityInspirations
|
||||
- Tarjetas de itinerarios
|
||||
- Click -> busqueda multi-city
|
||||
|
||||
### `/watchlist` - Lista seguimiento (requiere auth)
|
||||
- Tabla desde Supabase `watchlist`
|
||||
- Verificar precios individual / bulk
|
||||
- Badges de estado: precio bajo (verde), subio (rojo), no disponible (gris)
|
||||
- Alertas de precio (target price)
|
||||
- Busquedas recientes
|
||||
|
||||
---
|
||||
|
||||
## Componentes (mismos del plan anterior + nuevos auth)
|
||||
|
||||
### Auth
|
||||
| Componente | Proposito |
|
||||
|---|---|
|
||||
| `auth/LoginForm.vue` | Email/password + Google OAuth |
|
||||
| `auth/UserMenu.vue` | Avatar + dropdown: perfil, watchlist, logout. O boton login si no auth. |
|
||||
| `auth/AuthGuard.vue` | Wrapper que redirige a /auth si no logueado |
|
||||
|
||||
### Busqueda (sin cambios del plan anterior)
|
||||
`search/SearchBar`, `search/AirportInput`, `search/SearchModeTabs`, `search/StopsConfigurator`, `search/MaxStopsFilter`, `search/BudgetSlider`, `search/DateRangePicker`, `search/StayDurationPicker`, `search/PassengerPicker` (en popover)
|
||||
|
||||
### Resultados
|
||||
`results/TripCard`, `results/TripCardCompact`, `results/ResultsToolbar`, `results/ResultsFilters`, `results/LoadingMore`
|
||||
|
||||
### Detalle
|
||||
`detail/ItineraryTimeline`, `detail/SegmentCard`, `detail/PriceVerifier`, `detail/RelatedFlights`, `detail/ShareButton`
|
||||
|
||||
### Mapa
|
||||
`map/FlightMap`, `map/MapControls`, `map/MapDestinationPopup`
|
||||
|
||||
### Inspiracion
|
||||
`inspiration/InspirationGrid`, `inspiration/MultiCityCard`, `inspiration/BudgetExplorer`
|
||||
|
||||
### Watchlist
|
||||
`watchlist/WatchlistItem`, `watchlist/PriceAlertSetter`
|
||||
|
||||
---
|
||||
|
||||
## Composables
|
||||
|
||||
### Existentes (refactored)
|
||||
- **`useFlightSearch`** - search con polling, sort, filter, weekend
|
||||
- **`useLocations`** - ahora lee de Supabase (tabla airports), con fallback a API
|
||||
- **`useInspirations`** - lee de cache Supabase, refresh si stale
|
||||
|
||||
### Nuevos
|
||||
- **`useAuth`** - wrapper de `useSupabaseUser()` + `useSupabaseClient()`. Login, logout, register, profile CRUD.
|
||||
- **`useWatchlist`** - CRUD contra Supabase `watchlist` table. `add`, `remove`, `checkPrice`, `checkAll`, `isWatched`.
|
||||
- **`useRecentSearches`** - ultimas busquedas del usuario desde Supabase
|
||||
- **`useUserPreferences`** - lee/escribe `profiles` table. Home airports, default passengers.
|
||||
- **`useRouteFlights`** - fetch route flights
|
||||
- **`useResultFilters`** - sort/filter computeds client-side
|
||||
|
||||
---
|
||||
|
||||
## Dependencias a instalar
|
||||
```
|
||||
pnpm add @nuxtjs/supabase leaflet @vue-leaflet/vue-leaflet
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Orden de implementacion
|
||||
|
||||
### Fase 0: Setup Supabase
|
||||
1. Crear proyecto Supabase (o usar existente)
|
||||
2. `pnpm add @nuxtjs/supabase`
|
||||
3. Configurar modulo en nuxt.config.ts + env vars
|
||||
4. Ejecutar SQL para crear tablas + RLS policies
|
||||
5. Crear server route `POST /api/sync/locations` para poblar airports/countries/regions
|
||||
|
||||
### Fase 1: Auth + Infraestructura
|
||||
6. `auth/LoginForm.vue` + `/auth` page
|
||||
7. `auth/UserMenu.vue` en app.vue header
|
||||
8. `useAuth` composable
|
||||
9. `useUserPreferences` composable (lee/escribe profiles)
|
||||
10. `useLocations` composable (lee de Supabase airports table)
|
||||
11. `search/AirportInput.vue` con autocomplete
|
||||
|
||||
### Fase 2: Busqueda completa
|
||||
12. `SearchModeTabs` + refactor SearchForm para 5 modos
|
||||
13. `StopsConfigurator`, `MaxStopsFilter`, `BudgetSlider`, `DateRangePicker`, `StayDurationPicker`
|
||||
14. `PassengerPicker` en popover
|
||||
15. `/search` page
|
||||
16. `useRecentSearches` + guardar busquedas en Supabase
|
||||
|
||||
### Fase 3: Resultados mejorados
|
||||
17. `useResultFilters` composable
|
||||
18. Refactor `useFlightSearch` con polling + sort/filter
|
||||
19. `ResultsToolbar` + `ResultsFilters`
|
||||
20. `TripCard` mejorado + `TripCardCompact`
|
||||
21. `LoadingMore` polling indicator
|
||||
|
||||
### Fase 4: Detalle + Watchlist
|
||||
22. `/detail/[token]` ruta dinamica
|
||||
23. `ItineraryTimeline` + `SegmentCard`
|
||||
24. `PriceVerifier`
|
||||
25. `useWatchlist` composable (Supabase CRUD)
|
||||
26. Watchlist toggle en TripCard y detail
|
||||
27. `/watchlist` page con verificacion de precios
|
||||
28. Server routes: `/api/user/watchlist/*`
|
||||
|
||||
### Fase 5: Discovery
|
||||
29. `pnpm add leaflet @vue-leaflet/vue-leaflet`
|
||||
30. `/explore` + `FlightMap` + controles
|
||||
31. `/route/[from]-[to]` + `useRouteFlights`
|
||||
32. `/multi-city` + `MultiCityCard`
|
||||
33. `RelatedFlights` en detail page
|
||||
|
||||
### Fase 6: Home + Polish
|
||||
34. Home refactored: SearchBar compacto, inspiraciones, multi-city carousel, BudgetExplorer, busquedas recientes
|
||||
35. Navegacion actualizada en app.vue (Buscar, Explorar, Multi-city, Watchlist)
|
||||
36. Cache de inspiraciones en Supabase
|
||||
37. Responsive polish
|
||||
|
||||
---
|
||||
|
||||
## Configuracion Nuxt
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxt/eslint', '@nuxt/ui', '@nuxtjs/supabase'],
|
||||
supabase: {
|
||||
redirectOptions: {
|
||||
login: '/auth',
|
||||
callback: '/auth/confirm',
|
||||
exclude: ['/', '/search', '/results', '/explore', '/route/*', '/multi-city'],
|
||||
}
|
||||
},
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
Las paginas publicas (home, search, results, explore, route, multi-city) funcionan sin auth. Watchlist y profile requieren login.
|
||||
|
||||
---
|
||||
|
||||
## Verificacion
|
||||
- `pnpm dev` compila sin errores
|
||||
- Supabase: tablas creadas, sync de locations funciona
|
||||
- Auth: registro, login, logout, profile update
|
||||
- Home: inspiraciones, multi-city carousel, budget explorer, busquedas recientes
|
||||
- `/search`: 5 modos, autocomplete de aeropuerto lee de Supabase
|
||||
- `/results`: polling, sort/filter, watchlist star (pide login si no auth)
|
||||
- `/detail/[token]`: timeline, verificar precio, related flights, compartir
|
||||
- `/explore`: mapa con aeropuertos, arcos de precio, slider presupuesto
|
||||
- `/route/MAD-NTE`: todos los vuelos sin fechas
|
||||
- `/multi-city`: itinerarios sugeridos
|
||||
- `/watchlist`: CRUD, verificar precios, badges estado
|
||||
- Mobile responsive en todas las paginas
|
||||
Reference in New Issue
Block a user