El backend utiliza un stack tecnológico moderno y minimalista diseñado para desarrollo rápido y escalabilidad:
Go 1.25.5: Lenguaje principal del backend. Elegido por su rendimiento, concurrencia nativa con goroutines y binarios compilados sin dependencias de runtime.
PostgreSQL 17.7: Base de datos relacional principal. Almacena usuarios, perfiles, reservas, pagos y toda la información transaccional. Incluye extensiones pg_trgm para búsqueda fuzzy de texto.
DragonflyDB 1.27.1: Cache de alto rendimiento compatible con Redis. Almacena sesiones de usuario, resultados de búsquedas de SerpAPI para reducir costos de API, y locks temporales durante el proceso de checkout.
Cloudflare R2: Object storage S3-compatible para almacenar documentos encriptados como pasaportes, fotos de perfil y archivos adjuntos. Sin cargos por egress bandwidth.
Stripe: Procesador de pagos en modo test. Maneja toda la lógica de cobros, reembolsos y gestión de métodos de pago.
MailHog: Servidor SMTP de desarrollo que captura todos los emails enviados por la aplicación sin realmente enviarlos. Útil para testing de notificaciones.
El backend sigue una arquitectura de servicios modulares donde cada dominio de negocio está encapsulado en su propio paquete dentro de la carpeta internal. Los servicios se comunican entre sí mediante interfaces bien definidas, permitiendo testing independiente y evolución sin acoplamiento.
La aplicación utiliza el patrón Repository para abstraer el acceso a datos, Service para lógica de negocio compleja, y Handler para la capa HTTP. Esta separación de responsabilidades facilita testing unitario y mantiene el código organizado conforme crece el proyecto.
Para autenticación utilizamos tokens PASETO que son más seguros que JWT tradicionales. Los tokens se validan en un middleware que ejecuta antes de cada endpoint protegido, extrayendo el user ID del token y pasándolo al contexto de la request para que los handlers puedan identificar al usuario autenticado.
backend/
├── cmd/ # Puntos de entrada de aplicaciones
│ ├── api/ # Aplicación principal del servidor API
│ └── main.go
├── internal/ # Código privado de la aplicación
│ ├── auth/ # Autenticación y generación de tokens
│ ├── user/ # Gestión de usuarios y perfiles
│ ├── search/ # Búsqueda con IA y integración SerpAPI
│ ├── booking/ # Creación y gestión de reservas
│ ├── payment/ # Procesamiento de pagos con Stripe
│ ├── notification/ # Emails, SMS y notificaciones push
│ ├── websocket/ # Conexiones en tiempo real
│ ├── admin/ # Panel administrativo interno
│ ├── document/ # OCR y almacenamiento de documentos
│ └── middleware/ # Middleware HTTP compartido
├── pkg/ # Código reutilizable y compartible
│ ├── database/ # Conexiones a PostgreSQL y DragonflyDB
│ ├── storage/ # Cliente de Cloudflare R2
│ ├── paseto/ # Generación y validación de tokens
│ └── config/ # Configuración de la aplicación
│
├── migrations/ # Migraciones a la base de datos
├── scripts/ # Utilidades y Scripts
└── tmp/ # Archivos temporales de compilación (ignorado en git)
La separación entre internal y pkg es importante. Todo en internal es privado y no puede ser importado por otros proyectos Go, mientras que pkg contiene código que podría ser reutilizado. Esta convención es estándar en la comunidad de Go.
Antes de comenzar, asegúrate de tener instalado Docker y Docker Compose en tu sistema. El proyecto utiliza contenedores para todas las dependencias, eliminando la necesidad de instalar PostgreSQL, Redis u otras herramientas localmente.
Paso 1: Clonar el Repositorio
git clone https://github.com/ProacTrip/Backend.git
cd backendPaso 2: Configurar Variables de Entorno
Crea un archivo .env en la raíz de la carpeta backend copiando el ejemplo:
cp .env.example .envAbre el archivo .env y configura las siguientes variables esenciales:
Paso 3: Generar Claves PASETO
Las claves PASETO son necesarias para firmar y verificar tokens de autenticación. Ejecuta el script generador:
go run scripts/generate-paseto-keys.goEste script imprimirá dos claves en base64. Copia la PASETO_PRIVATE_KEY y PASETO_PUBLIC_KEY y pégalas en tu archivo .env.
Paso 4: Crear las Tablas en la BD
docker exec -it postgres psql -U root -d db -f /app/migrations/001_initial_schema.sql
docker exec -it postgres psql -U root -d db -f /app/migrations/002_refresh_tokens.sql
docker exec -it postgres psql -U root -d db -f /app/migrations/003_email_verification.sql
docker exec -it postgres psql -U root -d db -f /app/migrations/004_password_reset.sqlPaso 5: Iniciar los Servicios
Ahora puedes iniciar todos los servicios con Docker Compose:
docker compose up -dLa bandera -d ejecuta los contenedores en modo detached, es decir en background. La primera vez que ejecutes este comando tomará varios minutos porque Docker necesita descargar todas las imágenes base y compilar la aplicación.
Paso 6: Verificar que Todo Esté Funcionando
Puedes verificar el estado de los servicios con:
docker compose psTodos los servicios deberían mostrar estado "healthy" después de unos segundos. Para ver los logs en tiempo real:
docker compose logs -f apiLa API estará disponible en http://localhost:8080. Puedes verificar que está corriendo visitando http://localhost:8080/health en tu navegador o con curl:
curl http://localhost:8080/healthMailHog estará disponible en http://localhost:8025 donde podrás ver todos los emails que la aplicación envía durante desarrollo.
El proyecto está configurado con Air, una herramienta de hot-reload para Go. Esto significa que cada vez que guardas cambios en cualquier archivo Go, Air detecta el cambio, recompila automáticamente la aplicación y reinicia el servidor. Todo este proceso toma usualmente menos de tres segundos.
Verás en los logs algo como esto cuando Air detecta cambios:
api | watching .
api | building...
api | running...
No necesitas detener y reiniciar manualmente Docker Compose cada vez que modificas código. El hot-reload funciona automáticamente mientras los contenedores estén corriendo.
La API sigue el estándar REST con versionado en la URL. Todos los endpoints comienzan con /api/v1/. Los endpoints están organizados por dominio de negocio.
POST /api/v1/auth/register
Registra un nuevo usuario con email y password.
Request body:
{
"email": "usuario@ejemplo.com",
"password": "contraseña_segura"
}Response 201:
{
"user_id": "uuid-del-usuario",
"email": "usuario@ejemplo.com",
"message": "Usuario creado. Revisa tu email para verificar tu cuenta."
}POST /api/v1/auth/login
Inicia sesión con email y password.
Request body:
{
"email": "usuario@ejemplo.com",
"password": "contraseña_segura"
}Response 200:
{
"access_token": "v2.local.token_paseto_aqui",
"token_type": "Bearer",
"expires_in": 3600,
"user": {
"id": "uuid-del-usuario",
"email": "usuario@ejemplo.com",
"email_verified": false
}
}POST /api/v1/auth/google
Inicia el flujo de autenticación con Google OAuth2. Redirige al usuario a la página de login de Google.
GET /api/v1/auth/google/callback
Endpoint de callback después de autenticación con Google. No llamar directamente desde el frontend, Google redirige aquí automáticamente.
POST /api/v1/auth/refresh
Renueva un access token usando un refresh token.
Request body:
{
"refresh_token": "token_de_refresco"
}GET /api/v1/users/me
Obtiene el perfil completo del usuario autenticado. Requiere token en header Authorization: Bearer token.
Response 200:
{
"id": "uuid-del-usuario",
"email": "usuario@ejemplo.com",
"profile": {
"first_name": "Juan",
"last_name": "Pérez",
"date_of_birth": "1990-05-15",
"nationality": "ES",
"phone": "+34612345678",
"avatar_url": "https://r2.cloudflare.com/...",
"preferred_language": "es",
"preferred_currency": "EUR",
"travel_preferences": {
"seat_preference": "window",
"meal_preference": "vegetarian"
}
}
}PUT /api/v1/users/me
Actualiza el perfil del usuario autenticado.
Request body (todos los campos son opcionales):
{
"first_name": "Juan",
"last_name": "Pérez",
"date_of_birth": "1990-05-15",
"phone": "+34612345678",
"preferred_language": "es",
"preferred_currency": "EUR",
"travel_preferences": {
"seat_preference": "window",
"meal_preference": "vegetarian",
"frequent_flyer": [
{"airline": "IB", "number": "123456"}
]
}
}GET /api/v1/profile
Obtiene los datos públicos y preferencias del usuario. Requiere token en header Authorization: Bearer token.
Response 200:
{
"id": "uuid...",
"user_id": "uuid...",
"first_name": "Aurelio",
"last_name": "Dev",
"display_name": "AureDev",
"nationality": "ESP",
"date_of_birth": "1995-05-20",
"avatar_url": "https://assets.proactrip.com/uuid-imagen.jpg",
"travel_preferences": {
"seat": "window",
"meal": "standard"
},
"created_at": "2026-02-08T..."
}PUT /api/v1/profile
Actualiza información básica.
Request body (todos los campos son opcionales):
{
"first_name": "NuevoNombre",
"phone": "+34600000000",
"travel_preferences": {
"seat": "aisle"
}
}POST /api/v1/avatar
Sube una imagen a Cloudflare R2 (Bucket Público). Headers:
Content-Type: multipart/form-data
Form Data:
avatar: (Archivo binario .jpg, .png)
{
"message": "Avatar actualizado exitosamente",
"avatar_url": "https://assets.proactrip.com/550e8400-e29b-41d4-a716-446655440000.jpg"
}GET /api/v1/medical
Recupera los datos médicos. El backend desencripta automáticamente el campo sensitive_notes.
Requiere token en header Authorization: Bearer token.
Response 200:
{
"id": "uuid-medico",
"blood_type": "O+",
"allergies": ["Polen", "Penicilina"], // Array nativo
"chronic_conditions": [],
"medications": ["Ibuprofeno"],
"emergency_contacts": { // JSON nativo
"Madre": "+34600123456"
},
"sensitive_notes": "PACIENTE VIP: Fobia a las alturas.", // DESENCRIPTADO (Texto plano)
"encrypted_data": "uZ+YRwvkm..." // Dato crudo (Solo para debug/storage)
}PUT /api/v1/medical
Crea o actualiza la ficha médica. Si se envía sensitive_notes, el backend lo encripta antes de guardar.
{
"blood_type": "A-",
"allergies": ["Gatos"],
"emergency_contacts": {
"Pareja": "+34611223344"
},
"sensitive_notes": "Texto privado que solo el usuario debe leer."
}GET /api/v1/users/me/documents
Lista todos los documentos del usuario (pasaportes, visas, etc).
Response 200:
{
"documents": [
{
"id": "uuid-del-documento",
"type": "passport",
"document_number": "ABC123456",
"issuing_country": "ES",
"issue_date": "2020-01-15",
"expiry_date": "2030-01-15",
"verified": true,
"file_url": "https://r2.cloudflare.com/..."
}
]
}POST /api/v1/users/me/documents
Sube un nuevo documento. Multipart/form-data con campos "file" y "type" (passport, visa, vaccination_certificate, id_card).
El backend automáticamente procesará el documento con OCR para extraer información como número de pasaporte, fechas de emisión y expiración, y país emisor.
POST /api/v1/search
Busca vuelos, hoteles y actividades usando lenguaje natural con IA.
Request body:
{
"query": "Quiero ir a París la próxima semana con mi familia, hotel céntrico y vuelo directo si es posible"
}Response 200:
{
"search_id": "uuid-de-busqueda",
"extracted_params": {
"destination": "Paris",
"origin": "Madrid",
"dates": {
"start": "2024-12-15",
"end": "2024-12-22"
},
"passengers": {
"adults": 2,
"children": 1
},
"preferences": {
"flight": ["direct"],
"hotel": ["central_location"]
}
},
"results": {
"flights": [
{
"id": "flight-result-1",
"airline": "Iberia",
"flight_number": "IB3201",
"departure": {
"airport": "MAD",
"time": "2024-12-15T10:00:00Z"
},
"arrival": {
"airport": "CDG",
"time": "2024-12-15T12:15:00Z"
},
"duration_minutes": 135,
"stops": 0,
"price": 250.00,
"currency": "EUR",
"cabin_class": "economy",
"baggage": {
"carry_on": 1,
"checked": 1
}
}
],
"hotels": [
{
"id": "hotel-result-1",
"name": "Hotel París Centro",
"address": "Rue de Rivoli, Paris",
"location": {
"latitude": 48.8566,
"longitude": 2.3522
},
"rating": 4.5,
"reviews_count": 1250,
"price_per_night": 120.00,
"currency": "EUR",
"amenities": ["wifi", "breakfast", "parking"],
"photos": [
"https://..."
]
}
],
"activities": [
{
"id": "activity-result-1",
"name": "Tour por el Louvre",
"description": "Visita guiada de 3 horas",
"price": 45.00,
"currency": "EUR",
"duration_minutes": 180
}
]
}
}Los resultados se cachean en DragonflyDB por quince minutos, por lo que búsquedas idénticas son instantáneas y no consumen cuota de SerpAPI.
POST /api/v1/bookings
Crea una nueva reserva con los componentes seleccionados (vuelos, hoteles, actividades).
Request body:
{
"components": [
{
"type": "flight",
"result_id": "flight-result-1"
},
{
"type": "hotel",
"result_id": "hotel-result-1",
"nights": 7
}
],
"passenger_details": {
"first_name": "Juan",
"last_name": "Pérez",
"date_of_birth": "1990-05-15",
"passport_number": "ABC123456",
"nationality": "ES"
}
}Response 201:
{
"booking_id": "uuid-de-reserva",
"booking_number": "PTR-2024-ABC123",
"status": "draft",
"components": [...],
"total_amount": 1390.00,
"currency": "EUR",
"breakdown": {
"subtotal": 1250.00,
"taxes": 125.00,
"service_fee": 15.00,
"total": 1390.00
},
"payment_intent_client_secret": "pi_xxx_secret_yyy"
}El payment_intent_client_secret debe usarse con Stripe Elements en el frontend para completar el pago.
GET /api/v1/bookings
Lista todas las reservas del usuario autenticado con paginación.
Query params: ?page=1&limit=20&status=confirmed
GET /api/v1/bookings/:id
Obtiene los detalles completos de una reserva específica.
POST /api/v1/bookings/:id/cancel
Cancela una reserva existente. El backend calcula automáticamente el monto de reembolso basándose en las políticas de cancelación de cada componente.
Response 200:
{
"booking_id": "uuid-de-reserva",
"status": "cancelled",
"refund": {
"amount": 1250.00,
"currency": "EUR",
"processing_time_days": 5,
"penalties": 140.00
}
}WS /api/v1/ws
Establece una conexión WebSocket para recibir notificaciones en tiempo real.
El frontend debe conectarse incluyendo el token de autenticación en la query string:
const ws = new WebSocket('ws://localhost:8080/api/v1/ws?token=tu_access_token');
ws.onmessage = (event) => {
const notification = JSON.parse(event.data);
console.log('Nueva notificación:', notification);
};El backend enviará mensajes cuando ocurran eventos relevantes como cambios de estado de vuelo, confirmaciones de pago, o recordatorios de viaje.
Ejemplo de mensaje recibido:
{
"type": "flight_delay",
"title": "Retraso de Vuelo",
"message": "Tu vuelo IB3201 tiene un retraso de 2 horas",
"data": {
"booking_id": "uuid-de-reserva",
"flight_number": "IB3201",
"new_departure_time": "2024-12-15T12:00:00Z",
"delay_minutes": 120
},
"timestamp": "2024-12-14T08:30:00Z"
}Todas las respuestas de error siguen un formato consistente para facilitar el manejo en el frontend:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Email ya está registrado",
"details": {
"field": "email",
"value": "usuario@ejemplo.com"
}
}
}Códigos de error comunes:
VALIDATION_ERROR: Datos de entrada inválidosAUTHENTICATION_ERROR: Token inválido o expiradoAUTHORIZATION_ERROR: Usuario no tiene permisosNOT_FOUND: Recurso no encontradoPAYMENT_ERROR: Error procesando pagoEXTERNAL_API_ERROR: Error con SerpAPI u otra API externaINTERNAL_ERROR: Error interno del servidor
El backend está configurado para aceptar requests desde el frontend en http://localhost:3000 durante desarrollo. Los headers CORS permitidos son:
Authorization: Para enviar el token de autenticaciónContent-Type: Para especificar el tipo de contenidoAccept: Para negociación de contenido
Todos los endpoints excepto login, register y los callbacks de OAuth requieren autenticación mediante token PASETO en el header Authorization.
Formato del header:
Authorization: Bearer v2.local.token_paseto_completo_aqui
Stripe enviará webhooks a http://localhost:8080/api/v1/webhooks/stripe cuando ocurran eventos de pago. No necesitas llamar este endpoint desde el frontend, es solo para comunicación entre Stripe y el backend.
Los eventos importantes que el backend maneja son:
payment_intent.succeeded: Pago completado exitosamentepayment_intent.payment_failed: Pago rechazadocharge.refunded: Reembolso procesado
Todos los emails que el backend envía durante desarrollo son capturados por MailHog. Puedes ver estos emails abriendo un navegador en http://localhost:8025.
MailHog muestra una interfaz web donde puedes ver todos los emails enviados, incluyendo el contenido HTML completo, headers, y attachments. Esto es útil para verificar que los templates de email se ven correctamente sin necesitar configurar un servidor SMTP real.
Detener todos los servicios:
docker compose downVer logs de un servicio específico:
docker compose logs -f postgres
docker compose logs -f dragonfly
docker compose logs -f apiReiniciar solo la API:
docker compose restart apiLimpiar todo y empezar desde cero (elimina también los datos):
docker compose down -v
docker compose up -dEjecutar migraciones manualmente:
go run cmd/migrate/main.go upRevertir última migración:
go run cmd/migrate/main.go downLa API no inicia y muestra error de conexión a PostgreSQL:
Asegúrate de que PostgreSQL esté completamente iniciado antes de que la API intente conectar. Docker Compose espera a que el health check de PostgreSQL pase, pero a veces necesitas esperar unos segundos adicionales. Ejecuta docker compose logs postgres para verificar que PostgreSQL muestra "database system is ready to accept connections".
Los cambios de código no se reflejan automáticamente:
Verifica que Air esté corriendo correctamente ejecutando docker compose logs api. Deberías ver mensajes de Air cuando detecta cambios. Si no ves estos mensajes, es posible que Air no esté observando los archivos correctamente. Intenta reiniciar el contenedor con docker compose restart api.
Errores de permisos en archivos:
Si desarrollas en Linux o WSL, es posible que encuentres problemas de permisos con la carpeta tmp. Asegúrate de que tu usuario tiene permisos de escritura ejecutando chmod -R 777 tmp desde la carpeta backend.
Migraciones fallan con error de extensiones:
Si las migraciones fallan diciendo que las extensiones uuid-ossp, pg_trgm o postgis no existen, verifica que el script init-extensions.sql se ejecutó correctamente. Este script debe ejecutarse automáticamente la primera vez que PostgreSQL inicia. Si no, puedes ejecutarlo manualmente conectándote a PostgreSQL:
docker compose exec postgres psql -U root -d db -f /docker-entrypoint-initdb.d/init-extensions.sqlPara preguntas sobre el backend o problemas que no puedas resolver, contacta al equipo de backend. También puedes revisar la documentación de las tecnologías utilizadas:
- Echo Framework: https://echo.labstack.com/
- PostgreSQL: https://www.postgresql.org/docs/
- Stripe Go SDK: https://github.com/stripe/stripe-go
- SerpAPI: https://serpapi.com/search-api