Skip to content

ProacTrip/Backend

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tecnologías Principales

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.

Arquitectura del Sistema

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.

Estructura de Carpetas

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.

Configuración Inicial para Desarrollo

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 backend

Paso 2: Configurar Variables de Entorno

Crea un archivo .env en la raíz de la carpeta backend copiando el ejemplo:

cp .env.example .env

Abre 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.go

Este 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.sql

Paso 5: Iniciar los Servicios

Ahora puedes iniciar todos los servicios con Docker Compose:

docker compose up -d

La 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 ps

Todos los servicios deberían mostrar estado "healthy" después de unos segundos. Para ver los logs en tiempo real:

docker compose logs -f api

La 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/health

MailHog estará disponible en http://localhost:8025 donde podrás ver todos los emails que la aplicación envía durante desarrollo.

Hot Reload en 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.

API Endpoints para Frontend

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.

Autenticación

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"
}

Usuarios y Perfiles

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.

Búsqueda de Viajes

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.

Reservas

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
  }
}

WebSockets para Notificaciones en Tiempo Real

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"
}

Estructura de Respuestas de Error

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álidos
  • AUTHENTICATION_ERROR: Token inválido o expirado
  • AUTHORIZATION_ERROR: Usuario no tiene permisos
  • NOT_FOUND: Recurso no encontrado
  • PAYMENT_ERROR: Error procesando pago
  • EXTERNAL_API_ERROR: Error con SerpAPI u otra API externa
  • INTERNAL_ERROR: Error interno del servidor

CORS y Seguridad

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ón
  • Content-Type: Para especificar el tipo de contenido
  • Accept: 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

Webhooks de Stripe

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 exitosamente
  • payment_intent.payment_failed: Pago rechazado
  • charge.refunded: Reembolso procesado

Testing de Emails en Desarrollo

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.

Comandos Útiles

Detener todos los servicios:

docker compose down

Ver logs de un servicio específico:

docker compose logs -f postgres
docker compose logs -f dragonfly
docker compose logs -f api

Reiniciar solo la API:

docker compose restart api

Limpiar todo y empezar desde cero (elimina también los datos):

docker compose down -v
docker compose up -d

Ejecutar migraciones manualmente:

go run cmd/migrate/main.go up

Revertir última migración:

go run cmd/migrate/main.go down

Resolución de Problemas Comunes

La 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.sql

Soporte y Documentación Adicional

Para 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:

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors