Buscador inverso de viajes que compara vuelos, alojamiento y transporte para descubrir las mejores escapadas según origen, fechas, presupuesto y viajeros.
Iris cambia la pregunta habitual de un buscador de viajes. En vez de pedir un destino concreto, el usuario indica desde dónde sale, cuándo quiere viajar, cuántas personas viajan y cuánto quiere gastar. El sistema explora un catálogo de destinos, combina transporte y alojamiento, puntúa cada alternativa y muestra primero las escapadas con mejor equilibrio entre coste, distancia y tiempo.
Puede probar vuelos y alojamientos con datos simulados y deterministas o conectarse a Duffel, Google Routes y Redis para trabajar con información real. Los modos terrestres sólo aparecen cuando Google Routes está configurado.
- Búsqueda inversa por origen, fechas, viajeros y presupuesto.
- Comparación de vuelo, coche y transporte público cuando sus proveedores reales están disponibles.
- Precios de vuelos y alojamientos mediante Duffel.
- Rutas y tiempos de coche o transporte público mediante Google Routes.
- Detalle de vuelos: horarios, escalas, aeropuertos, terminales, operadores, avión, cabina, equipaje, tarifa, emisiones y condiciones de la oferta.
- Resultados destacados, tarjetas, tabla, filtros y ordenación.
- Enlaces de vuelos y paquetes en Skyscanner.
- Caché Redis opcional, fallback LRU en memoria, control de concurrencia y rate limiting.
- Interfaz oscura, responsive y preparada para escritorio y móvil.
- Despliegue full-stack en Vercel.
flowchart LR
U[Usuario] --> F[React]
F --> A[API Express]
A --> C[Catálogo de destinos]
C --> P[Pool de concurrencia]
P --> D[Duffel Flights y Stays]
P --> G[Google Routes]
D --> S[Normalización y score]
G --> S
S --> R[(Redis o caché LRU)]
S --> F
F --> K[Skyscanner]
- La API valida la búsqueda y calcula las noches de estancia.
- Recorre el catálogo de destinos candidatos con concurrencia limitada.
- Para cada destino obtiene transporte, alojamiento, distancia y duración.
- Normaliza las respuestas para que mocks y proveedores reales compartan el mismo contrato.
- Calcula el Iris Score, filtra por presupuesto y ordena los resultados.
- Guarda la respuesta en caché y genera enlaces externos de reserva.
El catálogo de IDs de Skyscanner es independiente del catálogo de búsqueda. Por eso se pueden añadir enlaces para más ciudades sin multiplicar las consultas de pago realizadas a Duffel.
| Capa | Tecnología |
|---|---|
| Frontend | React 19, TypeScript 6, Vite 8, Tailwind CSS 4, Lucide |
| Backend | Node.js, Express, CommonJS, Fetch API nativa |
| Vuelos y hoteles | Duffel Flights y Duffel Stays |
| Rutas | Google Routes API |
| Caché | Redis o LRU en memoria |
| Despliegue | Vercel Static + Functions |
| Pruebas | Node Test Runner |
- Node.js 20 o posterior.
- npm.
- Ninguna API key para usar el modo simulado.
npm install
npm --prefix frontend install
cp .env.example .env
npm run devLa configuración de ejemplo utiliza mocks para vuelo y hotel, por lo que arranca sin cuentas externas. Sin Google Routes, la interfaz muestra únicamente vuelos.
| Servicio | URL local |
|---|---|
| Frontend | http://localhost:5173 |
| API | http://localhost:3001 |
Vite redirige automáticamente las peticiones /api hacia Express.
npm run build
npm startExpress sirve el frontend compilado y la API desde http://localhost:3001.
Iris no necesita credenciales para probar la búsqueda de vuelos y hoteles en
modo mock. Los modos coche y transporte público permanecen ocultos hasta
configurar Google Routes.
| Variable | Necesaria para | Obligatoria |
|---|---|---|
DUFFEL_ACCESS_TOKEN |
Vuelos y alojamientos reales | Sí para Duffel |
GOOGLE_MAPS_API_KEY |
Coche y transporte público reales | Sí para Google Routes |
REDIS_URL |
Caché compartida entre instancias | No |
Duffel utiliza un único token para Flights y Stays.
- Crea una cuenta en Duffel.
- En el Dashboard abre More > Developers > Access tokens.
- Crea primero un test access token.
- Copia el token en
DUFFEL_ACCESS_TOKEN. - Solicita acceso a Duffel Stays desde tu cuenta si todavía no aparece habilitado. El acceso a vuelos y el acceso a alojamientos pueden tener permisos comerciales distintos.
Los tokens de prueba sirven para integrar y desarrollar, pero no emiten reservas reales. Para operar en producción, Duffel debe aprobar la cuenta y habilitar el modo live correspondiente. Consulta la guía oficial de Flights.
- Abre Google Cloud Console.
- Crea o selecciona un proyecto.
- Vincula una cuenta de facturación. Google Maps Platform requiere billing aunque el consumo pueda quedar dentro de sus créditos o límites vigentes.
- Habilita Routes API.
- Ve a APIs & Services > Credentials > Create credentials > API key.
- Restringe la clave a Routes API. Añade una restricción por IP sólo si el backend dispone de salida estática; las Functions de Vercel no la garantizan por defecto.
- Guarda la clave en
GOOGLE_MAPS_API_KEY.
La clave se utiliza sólo en el backend. No debe llevar restricciones de referrer pensadas para JavaScript en navegador. Configura cuotas, alertas de presupuesto y monitorización en Google Cloud para controlar el consumo. Consulta la configuración oficial de Routes API.
Redis es opcional. Sin REDIS_URL, Iris usa una caché LRU en memoria de hasta
500 entradas. En Vercel esa memoria no se comparte entre instancias ni persiste
durante nuevos despliegues.
Para una caché compartida:
- Añade Upstash desde Vercel Marketplace o crea una base en Upstash Console.
- Elige una región próxima a tu despliegue.
- Copia la URL de conexión Redis con TLS proporcionada por el servicio.
- Guárdala como
REDIS_URL.
También funciona cualquier proveedor compatible que entregue una URL
redis:// o rediss:// aceptada por el cliente oficial de Redis para Node.js.
FLIGHT_PROVIDER=duffel
HOTEL_PROVIDER=duffel
DUFFEL_ACCESS_TOKEN=duffel_test_xxxxxxxxx
ROUTE_PROVIDER=google
GOOGLE_MAPS_API_KEY=xxxxxxxxx
REDIS_URL=rediss://default:password@host:portNo publiques estos valores, no los incluyas en commits y no antepongas VITE_ a
las claves: hacerlo expondría las credenciales al navegador.
| Variable | Por defecto | Descripción |
|---|---|---|
PORT |
3001 |
Puerto de Express |
CURRENCY |
EUR |
Moneda esperada en el ranking |
FLIGHT_PROVIDER |
auto |
mock o duffel |
HOTEL_PROVIDER |
auto |
mock o duffel |
ROUTE_PROVIDER |
disabled |
disabled, auto o google |
PROVIDER_TIMEOUT_MS |
15000 |
Timeout por petición externa |
SEARCH_CONCURRENCY |
5 |
Máximo de tareas simultáneas |
CACHE_TTL_SECONDS |
900 |
Duración de una búsqueda cacheada |
REDIS_URL |
vacío | Activa la caché Redis compartida |
RATE_LIMIT_WINDOW_MS |
60000 |
Ventana del rate limit |
RATE_LIMIT_MAX |
60 |
Peticiones permitidas por IP y ventana |
CORS_ORIGIN |
cualquier origen | Orígenes permitidos, separados por comas |
Si vuelos u hoteles permanecen en auto, Iris activa Duffel al encontrar su
credencial y utiliza el mock en caso contrario. Para rutas, auto activa Google
únicamente cuando existe GOOGLE_MAPS_API_KEY; sin ella el proveedor queda
deshabilitado y la API rechaza modos terrestres.
Consulta .env.example para partir de una configuración segura.
{
"origin": "SVQ",
"startDate": "2026-09-04",
"endDate": "2026-09-06",
"passengers": 2,
"budget": 350,
"transportModes": ["FLIGHT", "DRIVE", "TRANSIT"]
}DRIVE y TRANSIT sólo se aceptan cuando Google Routes está activo. Sin
GOOGLE_MAPS_API_KEY, utiliza "transportModes": ["FLIGHT"].
La respuesta contiene:
results: alternativas ordenadas por score.highlights: mejor escapada, más barata, más rápida y mejor valor.meta: noches, candidatos evaluados y modos utilizados.transport: precio, fuente, itinerario y detalle completo de la oferta.hotel: precio, proveedor y alojamiento cuando esté disponible.bookingLinks: enlaces de vuelo y paquete en Skyscanner.
La cabecera X-Cache indica MISS o HIT.
| Método | Endpoint | Descripción |
|---|---|---|
GET |
/api/origins |
Orígenes disponibles |
GET |
/api/config |
Moneda, modos y proveedores activos |
GET |
/api/health |
Estado de la API |
El ranking pondera precio, distancia y duración con pesos 0.6 / 0.2 / 0.2.
Antes de aplicar los pesos, normaliza cada variable mediante min-max dentro del
conjunto actual de resultados:
Un score menor representa una escapada más conveniente. La normalización evita que las diferentes escalas de euros, kilómetros y minutos distorsionen el peso definido para cada factor.
Iris genera dos enlaces por resultado:
flightUrl: búsqueda de vuelos construida con códigos IATA.packageUrl: búsqueda de paquete con IDs internos verificados.
El mapa src/data/skyscannerPlaces.js contiene 78
códigos IATA verificados y no se usa para lanzar búsquedas a Duffel. Si no existe
una pareja de IDs válida, packageLinkType vale manual y el enlace lleva a la
búsqueda general; Iris nunca inventa identificadores.
El formato de Package Holidays no es una API pública contractual de Skyscanner y puede cambiar. Para un producto comercial deben revisarse su programa de afiliación, atribución y condiciones de uso.
El repositorio está preparado para desplegarse desde su raíz:
- Importa el repositorio en Vercel.
- No selecciones un framework manualmente; vercel.json define el build y el directorio de salida.
- Añade las credenciales en Project Settings > Environment Variables para Production, Preview y Development según corresponda.
- Despliega.
Vercel compila frontend/dist, publica los archivos estáticos en su CDN y
convierte src/app.js en una Function con un máximo de 60 segundos.
La configuración utiliza estos comandos; Vercel no necesita un Start Command:
Install Command: npm ci && npm --prefix frontend ci
Build Command: npm run build
Output Directory: frontend/dist
En producción se recomienda REDIS_URL. El rate limiter actual también opera por
instancia; si se necesita un límite global debe complementarse con Vercel
Firewall o un almacén distribuido para rate limiting.
.
├── frontend/
│ └── src/
│ ├── App.tsx # búsqueda, resultados y detalle de vuelos
│ └── App.css # diseño responsive
├── src/
│ ├── app.js # Express, CORS, rate limit y frontend
│ ├── config.js # selección de proveedores y límites
│ ├── data/ # orígenes, destinos e IDs externos
│ ├── routes/search.js # validación, endpoints y caché
│ ├── services/
│ │ ├── providers/ # Duffel y Google Routes
│ │ ├── cache.js # Redis con fallback LRU
│ │ ├── scoring.js # Iris Score
│ │ └── searchEngine.js # concurrencia y ranking multimodal
│ └── utils/concurrencyPool.js
├── test/api.test.js # pruebas HTTP de integración
└── vercel.json # despliegue full-stack
npm test
npm run lint
npm run buildLa suite cubre salud, validación, ocultación de rutas no configuradas, ranking, caché, enlaces externos y el contrato normalizado de itinerarios.
- Los precios reales dependen de permisos, cobertura y disponibilidad de los proveedores externos.
- Las ofertas de Duffel caducan y deben volver a consultarse antes de reservar.
- Coche y transporte público no aparecen sin Google Routes; Iris no genera datos terrestres simulados.
- Iris genera enlaces externos, pero no implementa todavía checkout ni emisión de reservas dentro de la aplicación.
- Sin Redis, caché y rate limiting son locales a cada instancia.
- Mantén
.envfuera de Git. - Usa tokens de prueba durante el desarrollo.
- Restringe las claves de Google por API y entorno.
- Rota inmediatamente cualquier credencial que se haya publicado.
- Configura secretos desde el panel del proveedor de despliegue.