components:
  schemas:
    ActivityResponse:
      properties:
        docs:
          description: Array de actividades encontradas
          items:
            $ref: '#/components/schemas/UserActivity'
          type: array
        limit:
          description: Número de actividades por página
          example: 25
          type: integer
        page:
          description: Página actual
          example: 1
          type: integer
        pages:
          description: Número total de páginas disponibles
          example: 6
          type: integer
        total:
          description: Número total de actividades encontradas (sin paginación)
          example: 150
          type: integer
      type: object
    BillingModeUpdate:
      properties:
        code:
          description: Código del nuevo plan de precios a aplicar
          example: premium
          type: string
      required:
      - code
      type: object
    BlackSpot:
      description: Punto negro (zona de alta peligrosidad)
      properties:
        coordinates:
          description: Coordenadas [latitud, longitud]
          items:
            type: number
          maxItems: 2
          minItems: 2
          type: array
        dangerLevel:
          description: Nivel de peligrosidad (0-100)
          example: 75
          maximum: 100
          minimum: 0
          type: integer
        description:
          description: Descripción del punto negro
          type: string
        id:
          description: ID único del punto negro
          type: string
      type: object
    BlackSpotsResponse:
      properties:
        blackSpots:
          items:
            $ref: '#/components/schemas/BlackSpot'
          type: array
        metadata:
          properties:
            averageDangerLevel:
              description: Nivel de peligrosidad promedio
              type: integer
            maxDangerLevel:
              description: Nivel de peligrosidad máximo
              type: integer
            total:
              description: Total de puntos negros encontrados
              type: integer
          type: object
        request:
          properties:
            areaCovered:
              description: Área cubierta en km²
              type: number
            minDangerLevel:
              description: Nivel mínimo solicitado
              type: integer
            pointsCount:
              description: Número de puntos en la solicitud
              type: integer
          type: object
      type: object
    ChapterResult:
      description: "## \U0001F4CB Capítulo HS (Nivel de agrupación)\nRepresenta un\
        \ capítulo del código HS, que agrupa partidas relacionadas por tipo de producto.\
        \ Cada capítulo pertenece exactamente a una sección.\n\n### \U0001F4DD Campos\
        \ Principales\n- `_id`: Identificador único en la base de datos\n- `title`:\
        \ Título completo del capítulo (incluye número)\n- `label`: Array de etiquetas\
        \ descriptivas\n- `headings`: Lista de partidas hijas (puede estar vacía)\n\
        \n### \U0001F3AF Cuándo usar este nivel\n- Para navegación por categorías\
        \ de productos\n- Para análisis por sector económico\n- Para estadísticas\
        \ de comercio por capítulo\n\n### \U0001F4CA Ejemplo Real\n```json\n{\n  \"\
        _id\": \"6502e99001515afda2dbd5bf\",\n  \"title\": \"Capítulo 03 - Pescados\
        \ y crustáceos, moluscos y otros invertebrados acuáticos\",\n  \"label\":\
        \ [\n    \"Sección: III - Productos del reino animal\",\n    \"Notas: Incluye\
        \ pescados vivos, frescos, refrigerados, congelados, etc.\"\n  ],\n  \"headings\"\
        : [\n    {\n      \"_id\": \"6502e99001515afda2dbd5bf\",\n      \"title\"\
        : \"0302 - Pescados, frescos o refrigerados, excluyendo los filetes y demás\
        \ carne de la partida 0304\",\n      \"label\": [\n        \"- Pescados de\
        \ la especie Oncorhynchus spp.\"\n      ],\n      \"subheadings\": [\n   \
        \     {\n          \"_id\": \"6502e99101515afda2dbd5c1\",\n          \"title\"\
        : \"0302.11 - Truchas (Oncorhynchus spp.)\",\n          \"label\": [\n   \
        \         \"-- Salmo trutta\"\n          ]\n        }\n      ]\n    }\n  ]\n\
        }\n```\n"
      properties:
        _id:
          description: '**Identificador único MongoDB** del capítulo.


            **Formato**: ObjectId hexadecimal de 24 caracteres

            **Persistencia**: Este ID no cambia entre versiones

            '
          example: 6501a2b3c4d5e6f7a8b9c0d3
          type: string
        headings:
          description: '**Lista de partidas** asociadas a este capítulo.


            **IMPORTANTE**:

            - Puede estar vacía (`[]`) si el capítulo no tiene partidas definidas

            - Cada partida es única dentro del capítulo

            - El orden generalmente sigue la numeración HS

            '
          items:
            $ref: '#/components/schemas/HeadingResult'
          type: array
        label:
          description: '**Array de etiquetas descriptivas** con información contextual.


            **Contenido común**:

            - Referencia a la sección padre

            - Notas generales sobre el capítulo

            - Información sobre alcances y exclusiones

            '
          example:
          - 'Sección: XI - Textiles y sus manufacturas'
          items:
            type: string
          type: array
        title:
          description: '**Título completo del capítulo** incluyendo número y descripción
            oficial.


            **Formato**: `"Capítulo XX - Descripción completa"`

            **Ejemplo**: `"Capítulo 52 - Algodón"`


            **IMPORTANTE**: El número del capítulo (01-99) indica la categoría general
            del producto.

            '
          example: Capítulo 52 - Algodón
          type: string
      required:
      - _id
      - title
      - headings
      type: object
    CheckoutSessionRequest:
      properties:
        cancel_url:
          description: URL a la que redirigir si el usuario cancela
          example: https://app.example.com/cancel
          format: uri
          type: string
        subscription:
          description: Código del plan de suscripción a contratar
          example: premium
          type: string
        success_url:
          description: URL a la que redirigir tras pago exitoso
          example: https://app.example.com/success
          format: uri
          type: string
      required:
      - success_url
      - cancel_url
      - subscription
      type: object
    CityResponse:
      description: Información de la ciudad más cercana
      properties:
        adminCode:
          description: Código administrativo
          type: string
        country:
          description: Código de país (ISO 3166-1 alpha-2)
          type: string
        location:
          $ref: '#/components/schemas/POI/properties/location'
        name:
          description: Nombre de la ciudad
          type: string
      type: object
    ComplianceCheck:
      properties:
        complianceDetails:
          properties:
            calibrationValid:
              properties:
                compliant:
                  description: Indica si la calibración es reciente (<1 año)
                  type: boolean
                value:
                  description: Fecha de última calibración
                  format: date-time
                  type: string
              type: object
            cardValid:
              properties:
                compliant:
                  description: Indica si la tarjeta está vigente
                  type: boolean
                value:
                  description: Fecha de caducidad de la tarjeta
                  format: date-time
                  type: string
              type: object
            drivingTime:
              properties:
                compliant:
                  description: Indica si cumple el límite
                  type: boolean
                limit:
                  description: Límite permitido en minutos (540 = 9 horas)
                  type: number
                value:
                  description: Tiempo de conducción real en minutos
                  type: number
              type: object
            restTime:
              properties:
                compliant:
                  description: Indica si cumple el mínimo
                  type: boolean
                limit:
                  description: Límite mínimo en minutos (660 = 11 horas)
                  type: number
                value:
                  description: Tiempo de descanso real en minutos
                  type: number
              type: object
          type: object
        infractions:
          description: Lista de infracciones detectadas
          items:
            type: string
          type: array
        isValid:
          description: Indica si el registro es válido para circular
          type: boolean
      type: object
    Coordinates:
      properties:
        lat:
          $ref: '#/components/schemas/Latitude'
        lon:
          $ref: '#/components/schemas/Longitude'
      required:
      - lat
      - lon
      type: object
    CreateActivityRequest:
      properties:
        environment:
          description: Entorno de la actividad
          example: production
          required: true
          type: string
        feature:
          description: Funcionalidad utilizada
          example: user_profile
          required: true
          type: string
        idUser:
          description: ID del usuario (opcional si viene del JWT)
          example: 63d7907cbe76403b35da63df
          type: string
        ip:
          description: IP del usuario (opcional, se obtiene automáticamente)
          example: 192.168.1.100
          type: string
        o_system:
          description: Sistema operativo
          enum:
          - android
          - linux
          - windows
          - apple
          - etc
          example: windows
          required: true
          type: string
        origin:
          description: Origen de la solicitud
          example: web
          required: true
          type: string
        type:
          description: Tipo de actividad
          enum:
          - C
          - R
          - U
          - D
          example: C
          required: true
          type: string
        url:
          description: URL de la actividad
          example: /api/users/profile
          required: true
          type: string
      required:
      - environment
      - type
      - feature
      - origin
      - o_system
      - url
      type: object
    DateType:
      description: 'Configuración de fecha para la ruta.


        - `departure`: La fecha indica cuándo sale el conductor

        - `arrival`: La fecha indica cuándo debe llegar (el sistema calcula la salida)

        '
      properties:
        date:
          description: Fecha y hora (ISO 8601)
          example: '2025-01-15T08:00:00Z'
          format: date-time
          type: string
        type:
          description: Tipo de fecha
          enum:
          - departure
          - arrival
          example: departure
          type: string
      required:
      - type
      - date
      type: object
    Discount:
      properties:
        centsValue:
          description: Valor del descuento en céntimos
          example: 0.15
          format: float
          minimum: 0
          type: number
        createdAt:
          description: Fecha de creación del descuento
          example: '2025-04-29T10:30:00Z'
          format: date-time
          type: string
        id_discount:
          description: Identificador único del descuento
          example: '123456789'
          type: string
        isActive:
          description: Indica si el descuento está activo
          example: true
          type: boolean
        percentValue:
          description: Valor del descuento en porcentaje
          example: 10.0
          format: float
          maximum: 100
          minimum: 0
          type: number
        selected:
          description: Indica si el descuento está seleccionado
          example: false
          type: boolean
        station:
          description: Identificador de la estación asociada al descuento
          example: 5f8d3b7b3f6b8a1b9c3b7b3f
          type: string
        type:
          description: Tipo de descuento
          example: percentage
          type: string
        updatedAt:
          description: Fecha de última actualización del descuento
          example: '2025-04-29T10:30:00Z'
          format: date-time
          type: string
        userId:
          description: Identificador del usuario propietario del descuento
          example: user123
          type: string
      required:
      - id_discount
      - station
      - type
      type: object
    DiscountResponse:
      properties:
        data:
          oneOf:
          - $ref: '#/components/schemas/Discount'
          - items:
              $ref: '#/components/schemas/Discount'
            type: array
        message:
          description: Mensaje adicional de la operación
          example: Discount created successfully
          type: string
        pagination:
          properties:
            limit:
              description: Límite por página
              example: 10
              type: number
            page:
              description: Página actual
              example: 1
              type: number
            pages:
              description: Total de páginas
              example: 5
              type: number
            total:
              description: Total de documentos
              example: 50
              type: number
          type: object
        success:
          description: Indica si la operación fue exitosa
          example: true
          type: boolean
      type: object
    Driver:
      properties:
        _id:
          description: 'Identificador único del conductor en formato MongoDB ObjectId.

            **Formato**: 24 caracteres hexadecimales (ej: 5f8d3b7b3f6b8a1b9c3b7b3f)

            **Generado automáticamente** por el sistema al crear el conductor.

            **Requerido** para todas las operaciones que referencien un conductor
            específico.

            '
          example: 5f8d3b7b3f6b8a1b9c3b7b3f
          type: string
        currentPosition:
          description: 'Última posición conocida del conductor.

            **Actualizado automáticamente** cada 5 minutos cuando el conductor está
            en ruta.

            **Formato**: Objeto con coordenadas y timestamp.

            **Uso principal**: Mostrar ubicación en tiempo real en mapas.

            '
          properties:
            coordinates:
              description: 'Coordenadas GPS en formato [longitud, latitud].

                **Precisión mínima**: 6 decimales (~11cm).

                **Valores válidos**:

                - Latitud: -90 a 90

                - Longitud: -180 a 180

                '
              example:
              - -3.7038
              - 40.4168
              items:
                format: float
                type: number
              maxItems: 2
              minItems: 2
              type: array
            timestamp:
              description: 'Fecha y hora exacta de la posición en formato ISO 8601
                (UTC).

                **Actualizado automáticamente** cada vez que se reporta nueva posición.

                **Usado para**: Calcular velocidad, estimar tiempos de llegada, verificar
                actualidad.

                '
              example: '2025-04-30T10:30:00.000Z'
              format: date-time
              type: string
          type: object
        drivingHistory:
          description: 'Historial detallado de actividades del conductor.

            **Usado para**: Auditoría, análisis de productividad, cumplimiento normativo.

            **Retención**: 6 meses para fines legales.

            '
          items:
            properties:
              coordinates:
                description: 'Puntos significativos de la ruta durante esta actividad.

                  **Máximo**: 100 puntos por registro.

                  **Formato**: [[lng, lat], [lng, lat], ...]

                  '
                items:
                  items:
                    format: float
                    type: number
                  maxItems: 2
                  minItems: 2
                  type: array
                type: array
              durationMinutes:
                description: Duración total en minutos
                type: number
              endTime:
                description: Hora de fin de la actividad (null si está en curso)
                format: date-time
                type: string
              startTime:
                description: Hora de inicio de la actividad
                format: date-time
                type: string
              type:
                description: 'Tipo de actividad registrada.

                  - `driving`: Periodo de conducción activa

                  - `rest`: Periodo de descanso obligatorio

                  '
                enum:
                - driving
                - rest
                type: string
            type: object
          type: array
        enabled:
          description: 'Estado de habilitación del conductor.

            **Por defecto**: `true`

            **Uso**: Para deshabilitar temporalmente un conductor sin eliminarlo.

            **Efecto**: Conductores deshabilitados no aparecen en búsquedas ni pueden
            recibir rutas.

            '
          example: true
          type: boolean
        hoursStatus:
          description: 'Estado actual de las horas de conducción del conductor.

            **Actualizado automáticamente** con cada registro de actividad.

            **Usado para**: Cumplimiento normativo, prevención de infracciones.

            '
          properties:
            lastDrivingStart:
              description: 'Fecha y hora de inicio de la última sesión de conducción.

                **Usado para**: Calcular tiempo máximo de conducción ininterrumpida.

                **Regulación**: Máximo 4.5 horas sin descanso de 45 minutos.

                '
              example: '2025-04-30T08:00:00.000Z'
              format: date-time
              type: string
            lastRestStart:
              description: 'Fecha y hora de inicio del último periodo de descanso.

                **Usado para**: Calcular cumplimiento de descansos obligatorios.

                **Regulación**: Mínimo 11 horas de descanso diario.

                '
              example: '2025-04-30T21:00:00.000Z'
              format: date-time
              type: string
            remainingBiweeklyHours:
              description: 'Horas restantes disponibles en el periodo bimensual actual.

                **Regulación UE**: Máximo 90 horas en 2 semanas consecutivas.

                **Calculado automáticamente**.

                '
              example: 45.0
              type: number
            remainingDayHours:
              description: 'Horas restantes disponibles hoy.

                **Regulación UE**: Máximo 9 horas diarias (puede extenderse a 10 horas
                2 veces por semana).

                **Actualizado en tiempo real**.

                '
              example: 3.5
              type: number
            remainingWeeklyHours:
              description: 'Horas restantes disponibles esta semana (lunes a domingo).

                **Calculado automáticamente** basado en registros de conducción.

                **No puede ser negativo**.

                **Alerta**: Cuando < 4 horas restantes.

                '
              example: 12.5
              type: number
          type: object
        licenseNumber:
          description: 'Número de licencia de conducir válida.

            **Requerido** para crear un conductor.

            **Único** en el sistema, no se puede modificar después de creación.

            **Formato específico por país**:

            - España: 8 dígitos + 1 letra (ej: 12345678X)

            - Francia: 12 caracteres alfanuméricos

            - Alemania: 11 dígitos

            '
          example: 12345678X
          type: string
        name:
          description: 'Nombre completo del conductor según documento de identidad.

            **Requerido** para crear/actualizar un conductor.

            **Validación**: Mínimo 3 caracteres, máximo 100.

            **Formato**: Texto plano, sin caracteres especiales.

            '
          example: Juan Pérez García
          type: string
        owner:
          description: 'ID del usuario propietario del conductor.

            **Asignado automáticamente** basado en el token JWT del usuario autenticado.

            **No modificable** manualmente.

            Usado para aislamiento de datos entre usuarios.

            '
          example: 63d7907cbe76403b35da63df
          type: string
        positionHistory:
          description: 'Historial de posiciones del conductor (últimos 30 días).

            **Máximo**: 1000 posiciones almacenadas.

            **Purga automática**: Posiciones mayores a 30 días se eliminan.

            **Uso**: Análisis de rutas, auditoría, reconstrucción de trayectos.

            '
          items:
            properties:
              coordinates:
                items:
                  format: float
                  type: number
                maxItems: 2
                minItems: 2
                type: array
              timestamp:
                format: date-time
                type: string
            type: object
          type: array
      required:
      - _id
      - name
      - licenseNumber
      - enabled
      type: object
    DriverActivity:
      properties:
        coordinates:
          description: 'Puntos de ruta significativos durante la actividad.

            **Opcional**: Solo para actividades de tipo `driving`

            **Máximo**: 50 puntos

            **Uso**: Reconstrucción de ruta, cálculo de distancia

            '
          example:
          - - -3.7038
            - 40.4168
          - - -3.71
            - 40.42
          - - -3.715
            - 40.425
          items:
            items:
              format: float
              type: number
            maxItems: 2
            minItems: 2
            type: array
          type: array
        endTime:
          description: 'Fecha y hora de fin de la actividad.

            **Opcional**: Si no se proporciona, se asume actividad en curso

            **Validación**: Debe ser posterior a startTime

            '
          example: '2025-04-30T12:30:00.000Z'
          format: date-time
          type: string
        startTime:
          description: 'Fecha y hora de inicio de la actividad.

            **Requerido**: Sí

            **Formato**: ISO 8601 (UTC)

            **Validación**: No puede superponerse con actividades existentes

            '
          example: '2025-04-30T08:00:00.000Z'
          format: date-time
          type: string
        type:
          description: 'Tipo de actividad a registrar.

            - `driving`: Inicio/fin de periodo de conducción

            - `rest`: Inicio/fin de periodo de descanso

            **Requerido**: Sí

            '
          enum:
          - driving
          - rest
          example: driving
          type: string
      required:
      - type
      - startTime
      type: object
    Error:
      properties:
        error:
          description: Mensaje descriptivo del error en español
          example: Parámetros inválidos o faltantes
          type: string
      required:
      - error
      type: object
    ErrorResponse:
      properties:
        message:
          description: Mensaje descriptivo del error
          example: idUser is required
          type: string
      type: object
    GamificationMetrics:
      description: Métricas de gamificación del usuario
      properties:
        achievements:
          items:
            properties:
              description:
                type: string
              id:
                type: string
              name:
                type: string
              unlockedAt:
                format: date-time
                type: string
            type: object
          type: array
        co2Saved:
          description: CO2 ahorrado estimado en kg
          type: number
        fuelSaved:
          description: Combustible ahorrado estimado en litros
          type: number
        totalDistance:
          description: Distancia total recorrida en km
          type: number
        totalRoutes:
          description: Total de rutas calculadas
          type: integer
        totalTime:
          description: Tiempo total de conducción en horas
          type: number
      type: object
    GamificationSummary:
      description: Resumen de gamificación
      properties:
        level:
          description: Nivel actual del usuario
          type: integer
        nextLevelPoints:
          description: Puntos necesarios para el siguiente nivel
          type: integer
        points:
          description: Puntos acumulados
          type: integer
        rank:
          description: Rango del usuario
          example: Road Master
          type: string
        stats:
          $ref: '#/components/schemas/GamificationMetrics'
      type: object
    HeadingResult:
      description: "## \U0001F4CB Partida HS (Nivel intermedio)\nRepresenta una partida\
        \ del código HS, que agrupa subpartidas relacionadas técnicamente. Cada partida\
        \ pertenece exactamente a un capítulo.\n\n### \U0001F4DD Campos Principales\n\
        - `_id`: Identificador único en la base de datos\n- `title`: Descripción de\
        \ la partida\n- `label`: Array de etiquetas descriptivas\n- `subheadings`:\
        \ Lista de subpartidas hijas (puede estar vacía)\n\n### \U0001F3AF Cuándo\
        \ usar este nivel\n- Para búsquedas generales de productos\n- Cuando se necesita\
        \ una clasificación menos específica\n- Para análisis estadísticos por categoría\n\
        \n### \U0001F4CA Ejemplo Real\n```json\n{\n  \"_id\": \"6502e99001515afda2dbd5bf\"\
        ,\n  \"title\": \"0302.3 - Atunes (género thunnus)\",\n  \"label\": [\n  \
        \  \"- tunas (of the genus thunnus), skipjack tuna (stripe-bellied bonito)\
        \ (katsuwonus pelamis), excluding edible fish offal of subheadings 0302.91\
        \ to 0302.99 :\",\n    \"Sección: III - Pescados y crustáceos\"\n  ],\n  \"\
        subheadings\": [\n    {\n      \"_id\": \"6502e99101515afda2dbd5c1\",\n  \
        \    \"title\": \"0302.31 - Albacora o atún de aleta larga\",\n      \"label\"\
        : [\n        \"-- albacore or longfinned tunas (thunnus alalunga)\"\n    \
        \  ]\n    }\n  ]\n}\n```\n"
      properties:
        _id:
          description: '**Identificador único MongoDB** de la partida.


            **Formato**: ObjectId hexadecimal de 24 caracteres

            **Persistencia**: Este ID no cambia entre versiones

            '
          example: 6501a2b3c4d5e6f7a8b9c0d2
          type: string
        label:
          description: '**Array de etiquetas descriptivas** con información adicional.


            **Contenido común**:

            - Descripciones técnicas detalladas

            - Exclusiones o inclusiones específicas

            - Referencias a otras partidas/subpartidas

            '
          example:
          - '- tunas (of the genus thunnus), skipjack tuna (stripe-bellied bonito)
            (katsuwonus pelamis)'
          items:
            type: string
          type: array
        subheadings:
          description: '**Lista de subpartidas** asociadas a esta partida.


            **IMPORTANTE**:

            - Puede estar vacía (`[]`) si la partida no tiene subpartidas

            - El orden no tiene significado especial

            - Cada subpartida es única y no se repite en otras partidas

            '
          items:
            $ref: '#/components/schemas/SubheadingResult'
          type: array
        title:
          description: '**Nombre descriptivo** de la partida según la nomenclatura
            HS.


            **Formato**: `"Código - Descripción"`

            **Ejemplo**: `"0302.3 - Atunes (género thunnus)"`

            '
          example: Algodón
          type: string
      required:
      - _id
      - title
      - subheadings
      type: object
    HealthResponse:
      properties:
        dbStatus:
          description: Estado de la conexión a la base de datos
          enum:
          - connected
          - disconnected
          example: connected
          type: string
        status:
          description: Estado del servicio
          enum:
          - ok
          example: ok
          type: string
      type: object
    HistoricalDrivingAnalysis:
      properties:
        activityBreakdown:
          additionalProperties:
            type: number
          description: Desglose de actividades por tipo (minutos)
          type: object
        basicInfo:
          properties:
            driver:
              description: Nombre completo del conductor
              type: string
            period:
              description: Periodo de la sesión
              type: string
            vehicle:
              description: Matrícula del vehículo
              type: string
          type: object
        drivingAnalysis:
          properties:
            averageSpeed:
              description: Velocidad promedio en km/h
              type: number
            maxSpeed:
              description: Velocidad máxima en km/h
              type: number
            speedExcesses:
              description: Número de excesos de velocidad
              type: number
            totalDistance:
              description: Distancia total en km
              type: number
            totalTime:
              description: Tiempo total de conducción en minutos
              type: number
          type: object
        eventsSummary:
          properties:
            bySeverity:
              additionalProperties:
                type: number
              description: Eventos agrupados por gravedad
              type: object
            byType:
              additionalProperties:
                type: number
              description: Eventos agrupados por tipo
              type: object
            total:
              description: Total de eventos registrados
              type: number
          type: object
      type: object
    HistoricalDrivingRecord:
      properties:
        _id:
          description: ID único del registro de tacógrafo
          example: 5f8d3b7b3f6b8a1b9c3b7b3f
          type: string
        actividades:
          items:
            properties:
              duracionMinutos:
                description: Duración en minutos
                type: number
              fechaFin:
                format: date-time
                type: string
              fechaInicio:
                format: date-time
                type: string
              tipo:
                description: Tipo de actividad registrada
                enum:
                - CONDUCCION
                - TRABAJO
                - DISPONIBILIDAD
                - DESCANSO
                - DESCONOCIDO
                type: string
            type: object
          type: array
        archivoDescarga:
          properties:
            extension:
              description: Formato del archivo de tacógrafo
              enum:
              - TGD
              - DDD
              - C1B
              - V1B
              example: TGD
              type: string
            fechaDescarga:
              description: Fecha y hora de la descarga
              example: '2025-04-30T10:30:00.000Z'
              format: date-time
              type: string
            nombre:
              description: Nombre del archivo descargado del tacógrafo
              example: TACO_20250430_083000
              type: string
          type: object
        conduccion:
          properties:
            distanciaTotal:
              description: Distancia total recorrida en km
              example: 450.75
              type: number
            tiempoConduccionTotal:
              description: Tiempo total de conducción en minutos
              example: 480
              type: number
            velocidadMaxima:
              description: Velocidad máxima registrada en km/h
              example: 95.5
              type: number
            velocidadPromedio:
              description: Velocidad promedio en km/h
              example: 72.3
              type: number
          type: object
        conductor:
          properties:
            apellidos:
              description: Apellidos del conductor
              example: Pérez García
              type: string
            fechaCaducidad:
              description: Fecha de caducidad de la tarjeta
              example: '2026-12-31T23:59:59.000Z'
              format: date-time
              type: string
            nombre:
              description: Nombre del conductor
              example: Juan
              type: string
            numeroTarjeta:
              description: Número de tarjeta de conductor (16 dígitos)
              example: ES123456789012345
              type: string
          type: object
        eventos:
          items:
            properties:
              codigo:
                description: Código del evento
                type: string
              descripcion:
                description: Descripción del evento
                type: string
              fechaHora:
                format: date-time
                type: string
              gravedad:
                description: Gravedad del evento
                enum:
                - LEVE
                - GRAVE
                - MUY_GRAVE
                type: string
              tipo:
                description: Tipo de evento registrado
                enum:
                - FALLO_SENSOR
                - FALLO_TACOGRAFO
                - MANIPULACION
                - TARJETA_NO_INSERTADA
                - EXCESO_VELOCIDAD
                - EVENTO_PERSONALIZADO
                - ERROR_DATOS
                type: string
            type: object
          type: array
        resumenTiempos:
          properties:
            cumpleNormativa:
              description: Indica si cumple la normativa
              type: boolean
            infracciones:
              description: Lista de infracciones detectadas
              items:
                type: string
              type: array
            tiempoConduccion:
              description: Tiempo total de conducción en minutos
              type: number
            tiempoDescanso:
              description: Tiempo total de descanso en minutos
              type: number
          type: object
        sesion:
          properties:
            fechaExtraccion:
              description: Fecha de extracción de la tarjeta
              example: '2025-04-30T17:30:00.000Z'
              format: date-time
              type: string
            fechaInsercion:
              description: Fecha de inserción de la tarjeta en el tacógrafo
              example: '2025-04-30T08:00:00.000Z'
              format: date-time
              type: string
            vehiculo:
              properties:
                matricula:
                  description: Matrícula del vehículo
                  example: 1234ABC
                  type: string
              type: object
          type: object
      type: object
    Invoice:
      properties:
        amount_due:
          description: Monto total de la factura
          type: number
        created:
          description: Timestamp de creación
          type: number
        currency:
          description: Moneda
          type: string
        due_date:
          description: Timestamp de vencimiento
          type: number
        hosted_invoice_url:
          description: URL para ver la factura
          type: string
        id:
          description: ID de la factura en Stripe
          type: string
        number:
          description: Número de factura
          type: string
        status:
          description: Estado de la factura (paid, draft, open, etc.)
          type: string
      type: object
    Isochrone:
      properties:
        center:
          $ref: '#/components/schemas/Coordinates'
        createdAt:
          format: date-time
          type: string
        id:
          description: Identificador único de la isócrona
          type: string
        polygon:
          description: Polígono que define el área alcanzable
          items:
            $ref: '#/components/schemas/Coordinates'
          type: array
        travelTime:
          description: Tiempo de viaje en minutos
          type: number
      required:
      - id
      - center
      - travelTime
      - polygon
      type: object
    IsochroneResponse:
      description: Respuesta de cálculo de isocrona
      properties:
        features:
          items:
            properties:
              geometry:
                properties:
                  coordinates:
                    description: Coordenadas del polígono
                    type: array
                  type:
                    example: Polygon
                    type: string
                type: object
              properties:
                properties:
                  contour:
                    description: Valor del contorno (km o minutos)
                    type: number
                type: object
              type:
                example: Feature
                type: string
            type: object
          type: array
        type:
          example: FeatureCollection
          type: string
      type: object
    Language:
      default: es
      description: 'Idioma para los datos de respuesta


        **Idiomas disponibles:**

        - `es`: Español (predeterminado)

        - `en`: Inglés

        - `fr`: Francés

        - `de`: Alemán

        - `pt`: Portugués


        **Ejemplo de uso:**

        ```

        GET /events/nearby?lat=40.4168&lng=-3.7038&lang=en

        ```

        '
      enum:
      - es
      - en
      - fr
      - de
      - pt
      type: string
    Latitude:
      description: 'Coordenada de latitud geográfica en grados decimales (estándar
        WGS84)


        **Ejemplos:**

        - Madrid: 40.4168

        - Barcelona: 41.3851

        - Valencia: 39.4699

        '
      example: 40.4168
      format: float
      maximum: 90
      minimum: -90
      type: number
    Location:
      description: Ubicación guardada (cliente, almacén, etc.)
      properties:
        _id:
          type: string
        address:
          description: Dirección completa
          example: Calle Industrial 25
          type: string
        city:
          example: Madrid
          type: string
        closingTime:
          description: Hora de cierre (HH:MM)
          example: '18:00'
          type: string
        contactPerson:
          description: Persona de contacto
          type: string
        coordinates:
          items:
            type: number
          maxItems: 2
          minItems: 2
          type: array
        country:
          example: España
          type: string
        createdAt:
          format: date-time
          type: string
        customerId:
          description: ID del cliente (para integración)
          type: string
        email:
          description: Email de contacto
          format: email
          type: string
        latitude:
          description: Latitud como string
          example: '40.4168'
          type: string
        longitude:
          description: Longitud como string
          example: '-3.7038'
          type: string
        name:
          description: Nombre de la ubicación
          example: Almacén Central Madrid
          type: string
        openingTime:
          description: Hora de apertura (HH:MM)
          example: 08:00
          type: string
        owner:
          description: ID del usuario propietario
          type: string
        phone:
          description: Teléfono de contacto
          type: string
        province:
          example: Madrid
          type: string
        stopTime:
          description: Tiempo de parada habitual
          type: string
        updatedAt:
          format: date-time
          type: string
        zipcode:
          example: '28001'
          type: string
      type: object
    LocationCreate:
      properties:
        address:
          minLength: 1
          type: string
        city:
          minLength: 1
          type: string
        closingTime:
          description: Formato HH:MM
          pattern: ^([01]?[0-9]|2[0-3]):[0-5][0-9]$
          type: string
        contactPerson:
          type: string
        coordinates:
          items:
            type: number
          maxItems: 2
          minItems: 2
          type: array
        country:
          type: string
        customerId:
          type: string
        email:
          format: email
          type: string
        latitude:
          type: string
        longitude:
          type: string
        name:
          minLength: 1
          type: string
        openingTime:
          description: Formato HH:MM
          pattern: ^([01]?[0-9]|2[0-3]):[0-5][0-9]$
          type: string
        phone:
          type: string
        province:
          minLength: 1
          type: string
        zipcode:
          type: string
      required:
      - name
      - address
      - city
      - province
      - latitude
      - longitude
      - coordinates
      - openingTime
      - closingTime
      type: object
    LocationPoint:
      description: Punto de ubicación con metadatos
      properties:
        coordinates:
          description: Coordenadas [latitud, longitud]
          items:
            type: number
          maxItems: 2
          minItems: 2
          type: array
        locality:
          description: Localidad/ciudad
          type: string
        postalCode:
          description: Código postal
          type: string
      type: object
    LocationUpdate:
      properties:
        address:
          type: string
        city:
          type: string
        closingTime:
          type: string
        contactPerson:
          type: string
        coordinates:
          items:
            type: number
          maxItems: 2
          minItems: 2
          type: array
        country:
          type: string
        customerId:
          type: string
        email:
          format: email
          type: string
        latitude:
          type: string
        longitude:
          type: string
        name:
          type: string
        openingTime:
          type: string
        phone:
          type: string
        province:
          type: string
        zipcode:
          type: string
      type: object
    LoginRequest:
      properties:
        email:
          description: Email del usuario registrado
          format: email
          type: string
        password:
          description: Contraseña del usuario (mínimo 6 caracteres)
          type: string
      required:
      - email
      - password
      type: object
    LoginResponse:
      properties:
        token:
          description: Token JWT para autenticación
          type: string
        user:
          description: Información básica del usuario
          type: object
      type: object
    Longitude:
      description: 'Coordenada de longitud geográfica en grados decimales (estándar
        WGS84)


        **Ejemplos:**

        - Madrid: -3.7038

        - Barcelona: 2.1734

        - Valencia: -0.3763

        '
      example: -3.7038
      format: float
      maximum: 180
      minimum: -180
      type: number
    MainRoute:
      description: Ruta principal recomendada
      properties:
        maneuvers:
          description: Lista de maniobras de navegación
          items:
            $ref: '#/components/schemas/Maneuver'
          type: array
        shape:
          description: Polyline encoded de la geometría de la ruta
          type: string
        stops:
          items:
            $ref: '#/components/schemas/UnifiedStop'
          type: array
        summary:
          $ref: '#/components/schemas/RouteSummary'
      type: object
    Maneuver:
      description: Instrucción de navegación
      properties:
        begin_shape_index:
          description: Índice inicial en el polyline
          type: integer
        end_shape_index:
          description: Índice final en el polyline
          type: integer
        highway:
          description: Si este tramo es autopista
          type: boolean
        instruction:
          description: Instrucción de texto
          example: Gire a la derecha en Calle Mayor
          type: string
        length:
          description: Distancia del tramo en km
          type: number
        street_names:
          description: Nombres de calles
          items:
            type: string
          type: array
        time:
          description: Tiempo del tramo en segundos
          type: number
        toll:
          description: Si este tramo tiene peaje
          type: boolean
        type:
          description: Tipo de maniobra (código Valhalla)
          type: integer
      type: object
    MapLayers:
      description: 'Opciones de ruta y capas del mapa.

        Controla qué tipo de carreteras evitar y qué POIs mostrar.

        '
      properties:
        allowSecondary:
          default: false
          description: Permitir carreteras secundarias
          type: boolean
        avoidHighways:
          default: false
          description: Evitar autopistas
          type: boolean
        avoidTolls:
          default: false
          description: Evitar peajes
          type: boolean
        avoidTunnels:
          default: false
          description: Evitar túneles
          type: boolean
        cafeterias:
          default: false
          description: Mostrar cafeterías
          type: boolean
        calculateStops:
          default: true
          description: Calcular paradas de descanso automáticamente
          type: boolean
        gasStations:
          default: false
          description: Mostrar gasolineras
          type: boolean
        parkings:
          default: false
          description: Mostrar parkings para camiones
          type: boolean
        radars:
          default: false
          description: Mostrar radares
          type: boolean
        shortestRoute:
          default: false
          description: Calcular ruta más corta en lugar de más rápida
          type: boolean
        showers:
          default: false
          description: Mostrar duchas/áreas de descanso
          type: boolean
        supermarkets:
          default: false
          description: Mostrar supermercados
          type: boolean
        trafficDensity:
          default: false
          description: Considerar densidad de tráfico
          type: boolean
        weather:
          default: false
          description: Mostrar información meteorológica
          type: boolean
        workshops:
          default: false
          description: Mostrar talleres
          type: boolean
      type: object
    Merchandise:
      description: Información de la mercancía transportada
      properties:
        hsCode:
          description: Código arancelario HS de la mercancía
          example: '8703'
          type: string
        isADR:
          default: false
          description: Si la mercancía es peligrosa (ADR)
          type: boolean
        weight:
          description: Peso de la mercancía en kg
          example: 15000
          type: number
      type: object
    OilStation:
      properties:
        _id:
          description: Identificador único de la estación
          example: 5f8d3b7b3f6b8a1b9c3b7b3f
          type: string
        address:
          description: Dirección completa
          example: Calle Mayor, 123, 28013 Madrid
          type: string
        brand:
          description: Marca de la estación de servicio
          example: Repsol
          type: string
        city:
          description: Ciudad donde se encuentra la estación
          example: Madrid
          type: string
        country:
          description: País donde se encuentra la estación
          example: es
          type: string
        distance:
          description: Distancia desde el punto de referencia en metros
          example: 1250.5
          format: float
          type: number
        fuelTypes:
          items:
            properties:
              code:
                description: Código del tipo de combustible
                enum:
                - diesel_regular
                - diesel_premium
                - gasoline_95
                - gasoline_98
                - gnc
                - gnl
                - gpl
                - hydrogen
                - bioethanol
                - biodiesel
                example: diesel_regular
                type: string
              price:
                description: Precio por litro en euros
                example: 1.459
                format: float
                minimum: 0
                type: number
              updatedAt:
                description: Fecha de última actualización del precio
                example: '2025-04-29T10:30:00Z'
                format: date-time
                type: string
            type: object
          type: array
        label:
          description: Nombre comercial de la estación
          example: Repsol Calle Mayor
          type: string
        location:
          description: Coordenadas GPS en formato GeoJSON (longitud, latitud)
          properties:
            coordinates:
              example:
              - -3.7038
              - 40.4168
              items:
                format: float
                type: number
              type: array
            type:
              default: Point
              enum:
              - Point
              type: string
          type: object
        province:
          description: Provincia donde se encuentra la estación
          example: Madrid
          type: string
      type: object
    POI:
      description: Representa un Punto de Interés genérico
      properties:
        address:
          description: Dirección completa
          example: Calle Mayor, 1, 28013 Madrid
          type: string
        city:
          description: Nombre de la ciudad
          example: Madrid
          type: string
        country:
          description: Código de país (ISO 3166-1 alpha-2)
          example: ES
          type: string
        details:
          additionalProperties: true
          description: Detalles adicionales específicos del tipo de POI
          example:
            cuisine: Spanish
            fuel_types:
            - diesel
            - gasoline
            rating: 4.5
          type: object
        id:
          description: ID del POI
          example: poi-12345
          type: string
        location:
          properties:
            coordinates:
              description: Coordenadas como [longitud, latitud]
              example:
              - -3.70379
              - 40.416775
              items:
                format: double
                type: number
              maxItems: 2
              minItems: 2
              type: array
            type:
              enum:
              - Point
              example: Point
              type: string
          type: object
        name:
          description: Nombre del POI
          example: Restaurante El Buen Sabor
          type: string
        province:
          description: Provincia/estado
          example: Madrid
          type: string
        type:
          description: Tipo de POI
          enum:
          - parking
          - workshop
          - restaurant
          - shower
          example: restaurant
          type: string
        zipcode:
          description: Código postalal
          example: '28013'
          type: string
      type: object
    POIsResponse:
      description: Array de Puntos de Interés
      properties:
        pois:
          items:
            oneOf:
            - $ref: '#/components/schemas/Parking'
            - $ref: '#/components/schemas/Workshop'
            - $ref: '#/components/schemas/Restaurant'
            - $ref: '#/components/schemas/Shower'
          type: array
      type: object
    Parking:
      description: Representa una ubicación de aparcamiento de camiones
      properties:
        address:
          description: Dirección completa
          type: string
        city:
          description: Nombre de la ciudad
          type: string
        country:
          description: Código de país (ISO 3166-1 alpha-2)
          example: ES
          type: string
        features:
          description: Lista de características disponibles
          items:
            type: string
          type: array
        id:
          description: ID del aparcamiento
          type: string
        location:
          properties:
            coordinates:
              description: Coordenadas como [longitud, latitud]
              example:
              - -3.70379
              - 40.416775
              items:
                type: number
              maxItems: 2
              minItems: 2
              type: array
            type:
              enum:
              - Point
              type: string
          type: object
        name:
          description: Nombre del aparcamiento
          type: string
        province:
          description: Provincia/estado
          type: string
        zipcode:
          description: Código postal
          type: string
      type: object
    ParkingsResponse:
      description: Array de ubicaciones de aparcamiento
      properties:
        parkings:
          items:
            $ref: '#/components/schemas/Parking'
          type: array
      type: object
    PortalSessionRequest:
      properties:
        return_url:
          description: URL a la que redirigir al salir del portal de cliente
          example: https://app.example.com/account
          format: uri
          type: string
      required:
      - return_url
      type: object
    PositionUpdate:
      properties:
        coordinates:
          description: 'Coordenadas GPS actuales en formato [longitud, latitud].

            **Requerido**: Sí

            **Precisión**: Mínimo 6 decimales

            **Ejemplo válido**: [-3.7038, 40.4168] (Madrid, España)

            '
          example:
          - -3.7038
          - 40.4168
          items:
            format: float
            type: number
          maxItems: 2
          minItems: 2
          type: array
        timestamp:
          description: 'Fecha y hora exacta de la posición.

            **Requerido**: Sí

            **Formato**: ISO 8601 (UTC)

            **Restricción**: No puede ser fecha futura

            **Diferencia máxima**: 5 minutos con tiempo del servidor

            '
          example: '2025-04-30T10:30:00.000Z'
          format: date-time
          type: string
      required:
      - coordinates
      - timestamp
      type: object
    PricingTier:
      properties:
        _id:
          type: string
        basePrice:
          description: Precio base mensual/anual
          type: number
        code:
          description: 'Código único del plan, ejemplo: basic o premium'
          type: string
        excessRoutePrice:
          description: Costo por cada ruta adicional
          type: number
        includedRoutes:
          description: Cantidad de rutas incluidas en el precio base
          type: number
        isActive:
          description: Indica si el plan está disponible
          type: boolean
        name:
          description: Nombre descriptivo del plan
          type: string
        type:
          description: Tipo de plan (subscription, pay-per-use, etc.)
          type: string
      type: object
    Radar:
      properties:
        adminName2:
          description: Nombre del área administrativa
          type: string
        createdAt:
          format: date-time
          type: string
        id:
          description: Identificador único del radar
          type: string
        kilometers:
          description: Punto kilométrico donde se encuentra el radar
          type: number
        location:
          $ref: '#/components/schemas/Coordinates'
        maxSpeed:
          description: Límite de velocidad en km/h
          type: number
        roadName:
          description: Nombre de la carretera donde se encuentra el radar
          type: string
        sense:
          description: Sentido de dirección del radar
          enum:
          - UNKNOWN
          - POSITIVE
          - NEGATIVE
          - BOTH
          type: string
        type:
          description: Tipo de radar
          enum:
          - MOBILE
          - FIXED
          type: string
        updatedAt:
          format: date-time
          type: string
      required:
      - id
      - adminName2
      - roadName
      - type
      - sense
      - location
      type: object
    Radius:
      default: 25000
      description: 'Radio de búsqueda en metros desde el punto central


        **Escalas típicas:**

        - 1.000m: Área muy localizada (una intersección)

        - 5.000m: Barrio o zona urbana

        - 10.000m: Distrito o área metropolitana pequeña

        - 25.000m: Ciudad mediana o área extensa

        '
      maximum: 25000
      minimum: 1000
      type: number
    RegisterRequest:
      properties:
        company_cif:
          description: CIF/NIF de la empresa (requerido)
          type: string
        email:
          description: Email para el nuevo usuario
          format: email
          type: string
        password:
          description: Contraseña (mínimo 6 caracteres)
          type: string
      required:
      - email
      - password
      - company_cif
      type: object
    Restaurant:
      description: Representa un restaurante para camioneros
      properties:
        cuisine:
          description: Tipo de cocina
          type: string
        id:
          description: ID del restaurante
          type: string
        location:
          $ref: '#/components/schemas/POI/properties/location'
        name:
          description: Nombre del restaurante
          type: string
        priceRange:
          description: Indicador de rango de precios
          enum:
          - low
          - medium
          - high
          type: string
        rating:
          description: Valoración promedio
          format: float
          maximum: 5
          minimum: 0
          type: number
      type: object
    RoadRestriction:
      properties:
        createdAt:
          format: date-time
          type: string
        restrictionType:
          description: Tipo de restricción
          enum:
          - WEIGHT
          - HEIGHT
          - ENVIRONMENTAL
          - TIME
          type: string
        roadId:
          description: Identificador de la carretera afectada
          type: string
        schedule:
          description: Horario para restricciones basadas en tiempo
          properties:
            days:
              description: Días de la semana cuando aplica la restricción (0=Domingo)
              items:
                maximum: 6
                minimum: 0
                type: number
              type: array
            hours:
              description: Rango de tiempo cuando aplica la restricción
              type: string
          type: object
        updatedAt:
          format: date-time
          type: string
        value:
          description: Valor de la restricción (numérico o descriptivo)
          type:
            oneOf:
            - type: number
            - type: string
      required:
      - roadId
      - restrictionType
      - value
      type: object
    RouteCostResponse:
      description: "# \U0001F4CB Respuesta de Costos de Ruta\n\nRespuesta completa\
        \ con todos los costos calculados para una ruta de transporte.\nIncluye desglose\
        \ detallado de peajes, combustible, mantenimiento, tiempo y emisiones ambientales.\n\
        \n## \U0001F4B0 Campos Principales\n\n- **Costos Totales**: `total_cost` en\
        \ el objeto `costs`\n- **Duración**: Incluye descansos legales obligatorios\n\
        - **Distancia**: En metros para precisión\n- **Emisiones**: CO2 en kg para\
        \ reportes de sostenibilidad\n\n## \U0001F3AF Ejemplo de Uso\n\n```json\n\
        {\n  \"duration\": 343,\n  \"distance\": 621456,\n  \"departure\": \"2025-05-15T08:00:00+02:00\"\
        ,\n  \"arrival\": \"2025-05-15T15:43:00+02:00\",\n  \"total_tolls\": 87.25,\n\
        \  \"fuel\": 186.43,\n  \"co2\": 499.62,\n  \"fuel_type\": \"diesel\",\n \
        \ \"costs\": {\n    \"total_cost\": 542.89,\n    \"toll\": 87.25,\n    \"\
        fuel\": {\n      \"monetary\": 240.49,\n      \"fuel\": 186.43\n    },\n \
        \   // ... más componentes de costo\n  },\n  \"tolls\": [\n    {\n      \"\
        country\": \"ES\",\n      \"cia_name\": \"ASEFA\",\n      \"price\": 12.75,\n\
        \      \"currency\": \"EUR\",\n      \"paymentMethods\": [\"cash\", \"card\"\
        ]\n    }\n    // ... más peajes\n  ]\n}\n```\n"
      properties:
        arrival:
          description: '**🕔 Hora de Llegada Estimada**


            Fecha y hora estimada de llegada con zona horaria.

            Incluye el efecto de los descansos legales.

            '
          example: '2025-05-15T15:43:00+02:00'
          format: date-time
          type: string
        co2:
          description: '**🌱 Emisiones de CO2**


            Kilogramos de CO2 emitidos durante el viaje.

            Calculado según el tipo de combustible y distancia.

            '
          example: 499.62
          type: number
        costs:
          description: '# 💰 Desglose Completo de Costos


            Objeto con el desglose detallado de todos los costos operativos del viaje.


            ## 🛠️ Componentes de Mantenimiento


            Costos calculados basados en el desgaste por kilómetro de cada componente
            del vehículo.


            ## ⛽ Costos de Combustible


            Incluye tanto el costo monetario como el consumo en litros.


            ## 🏷️ Peajes


            Costo total de peajes (coincide con `total_tolls`).

            '
          properties:
            aire:
              description: Costo de filtro de aire por km
              example: 0.157
              type: number
            brake_liquid:
              description: Costo de líquido de frenos por km
              example: 0.31
              type: number
            brake_paste:
              description: Costo de pastillas de freno por km
              example: 0.31
              type: number
            cabine_filter:
              description: Costo de filtro de cabina por km
              example: 0.515
              type: number
            clutch:
              description: Costo de embrague por km
              example: 5.17
              type: number
            correa:
              description: Costo de correa de distribución por km
              example: 4.136
              type: number
            direccion:
              description: Costo de dirección por km
              example: 0.515
              type: number
            escape:
              description: Costo de escape por km
              example: 0.515
              type: number
            fuel:
              description: Costos relacionados con el combustible
              properties:
                fuel:
                  description: Litros totales de combustible consumidos
                  example: 186.43
                  type: number
                monetary:
                  description: Costo monetario total del combustible
                  example: 240.49
                  type: number
              type: object
            fuel_filter:
              description: Costo de filtro de combustible por km
              example: 0.414
              type: number
            oil:
              description: Costo de cambio de aceite por km
              example: 1.035
              type: number
            oil_filter:
              description: Costo de filtro de aceite por km
              example: 0.414
              type: number
            toll:
              description: Costo total de peajes
              example: 87.25
              type: number
            total_cost:
              description: '**💰 Costo Total del Viaje**


                Suma de todos los costos: mantenimiento + combustible + peajes.


                **Fórmula**: `sum(mantenimiento) + fuel.monetary + toll`

                '
              example: 542.89
              type: number
            wheels:
              description: Costo de ruedas por km (8 ruedas)
              example: 4.367
              type: number
          type: object
        departure:
          description: '**🕐 Hora de Salida Real**


            Fecha y hora de inicio del viaje con zona horaria.

            Puede diferir del parámetro `departure` si se calcula basado en `arrival`.

            '
          example: '2025-05-15T08:00:00+02:00'
          format: date-time
          type: string
        distance:
          description: '**📏 Distancia Total**


            Longitud total de la ruta en **metros**.


            **Conversión**: Dividir entre 1000 para obtener kilómetros

            '
          example: 621456
          type: number
        duration:
          description: '**⏱️ Duración Total del Viaje**


            Tiempo total en minutos, incluyendo **descansos legales obligatorios**.


            - Se añaden 45 minutos de descanso por cada 4.5 horas de conducción

            - Basado en normativa europea de transporte

            '
          example: 343
          type: number
        fuel:
          description: '**🛢️ Consumo de Combustible**


            Total de litros de combustible consumidos en el viaje.

            Afectado por `cargo_weight` y `avg_consumption`.

            '
          example: 186.43
          type: number
        fuelCost:
          description: '**💶 Costo Total de Combustible**


            Costo total del combustible consumido en EUR.

            Calculado como: `fuel * fuelPrice`

            '
          example: 240.49
          type: number
        fuelPrice:
          description: '**⛽ Precio del Combustible**


            Precio medio del combustible por litro en EUR.

            Basado en datos de mercado actualizados.

            '
          example: 1.29
          type: number
        fuel_type:
          description: '**🔥 Tipo de Combustible Usado**


            Tipo de combustible considerado para los cálculos.

            Coincide con el parámetro `fuel_type` o usa el valor por defecto.

            '
          example: diesel
          type: string
        timeCost:
          description: '**💰 Costo del Tiempo**


            Valor del tiempo de conducción en minutos.

            Usado para cálculos de productividad.

            '
          example: 343
          type: number
        tolls:
          description: '**📋 Lista Detallada de Peajes**


            Array con información individual de cada peaje en la ruta.

            Solo presente cuando `with_tolls=true`.


            **Orden**: Aparecen en el orden de la ruta

            '
          items:
            description: Información de un peaje individual
            properties:
              cia_name:
                description: Nombre de la compañía gestora del peaje
                example: ASEFA
                type: string
              country:
                description: Código ISO del país (ES, FR, PT, etc.)
                example: ES
                type: string
              currency:
                description: Moneda del peaje (siempre EUR)
                example: EUR
                type: string
              paymentMethods:
                description: Métodos de pago aceptados en el peaje
                example:
                - cash
                - card
                items:
                  type: string
                type: array
              price:
                description: Precio del peaje en EUR
                example: 12.75
                type: number
            type: object
          type: array
        total_tolls:
          description: '**🏷️ Costo Total de Peajes**


            Suma de todos los peajes en la ruta en EUR.

            Coincide con `costs.toll` en el desglose detallado.

            '
          example: 87.25
          type: number
      required:
      - duration
      - distance
      - fuelCost
      - costs
      - total_tolls
      - fuel
      - co2
      - fuel_type
      type: object
    RouteResponse:
      description: Respuesta completa del cálculo de ruta
      properties:
        alternatives:
          description: Rutas alternativas
          items:
            $ref: '#/components/schemas/MainRoute'
          type: array
        black_points:
          description: Puntos negros a lo largo de la ruta
          items:
            $ref: '#/components/schemas/BlackSpot'
          type: array
        has_route_limit:
          description: Límite de rutas mensuales del usuario
          example: 100
          type: number
        main:
          $ref: '#/components/schemas/MainRoute'
        stops:
          description: Todas las paradas de la ruta principal
          items:
            $ref: '#/components/schemas/UnifiedStop'
          type: array
        temp_code:
          description: Código temporal para compartir la ruta
          example: ABC123DEF
          type: string
      type: object
    RouteSummary:
      description: Resumen de la ruta calculada
      properties:
        arrivalDate:
          description: Fecha/hora de llegada
          format: date-time
          type: string
        cost:
          description: Coste estimado del viaje (combustible + peajes)
          example: 450.0
          type: number
        departureDate:
          description: Fecha/hora de salida (calculada si type=arrival)
          format: date-time
          type: string
        distance:
          description: Distancia total en km (alias de length)
          type: number
        has_ferry:
          description: Si la ruta usa ferry
          type: boolean
        has_highway:
          description: Si la ruta usa autopistas
          type: boolean
        has_time_restrictions:
          description: Si la ruta tiene restricciones horarias
          type: boolean
        has_toll:
          description: Si la ruta tiene peajes
          type: boolean
        length:
          description: Distancia total en km
          example: 620.5
          type: number
        max_lat:
          description: Latitud máxima del bounding box
          type: number
        max_lon:
          description: Longitud máxima del bounding box
          type: number
        min_lat:
          description: Latitud mínima del bounding box
          type: number
        min_lon:
          description: Longitud mínima del bounding box
          type: number
        remainingWeeklyDrivingHours:
          description: Horas de conducción restantes en la semana al finalizar
          type: number
        remainingWeeklyDrivingHoursFormatted:
          description: Horas restantes formateadas
          example: '45:30'
          type: string
        time:
          description: Tiempo de conducción puro (formato HH:MM)
          example: 06:30
          type: string
        timeInSeconds:
          description: Tiempo de conducción puro en segundos
          example: 23400
          type: integer
        timeWithBreaks:
          description: Tiempo total con descansos (formato HH:MM)
          example: '10:30'
          type: string
        timeWithBreaksInSeconds:
          description: Tiempo total incluyendo descansos en segundos
          example: 37800
          type: integer
      type: object
    RouteUsageRecord:
      properties:
        quantity:
          default: 1
          description: Cantidad de rutas utilizadas
          minimum: 1
          type: number
        timestamp:
          description: Timestamp Unix del momento de uso (opcional, usa actual si
            no se proporciona)
          type: number
        userId:
          description: ID del usuario que utiliza las rutas
          type: string
      required:
      - userId
      - quantity
      type: object
    SavedRoute:
      description: Ruta guardada por el usuario
      properties:
        _id:
          description: ID único de la ruta
          type: string
        createdAt:
          format: date-time
          type: string
        date:
          $ref: '#/components/schemas/DateType'
        description:
          description: Descripción de la ruta
          type: string
        destination:
          $ref: '#/components/schemas/LocationPoint'
        driver:
          properties:
            _id:
              type: string
            hoursStatus:
              properties:
                remainingBiweeklyHours:
                  type: number
                remainingWeeklyHours:
                  type: number
              type: object
          type: object
        mapLayers:
          $ref: '#/components/schemas/MapLayers'
        merchandise:
          $ref: '#/components/schemas/Merchandise'
        optimizedStops:
          type: boolean
        origin:
          $ref: '#/components/schemas/LocationPoint'
        owner:
          description: ID del usuario propietario
          type: string
        polyline:
          description: Datos de la polyline de la ruta
          properties:
            alternatives:
              description: Rutas alternativas
              type: array
            black_points:
              items:
                $ref: '#/components/schemas/BlackSpot'
              type: array
            main:
              description: Ruta principal
              type: object
            selectedRouteId:
              type: string
          type: object
        stops:
          items:
            $ref: '#/components/schemas/LocationPoint'
          type: array
        title:
          description: Título de la ruta
          example: Madrid - Barcelona semanal
          type: string
        updatedAt:
          format: date-time
          type: string
        vehicle:
          properties:
            _id:
              type: string
            brand:
              type: string
            consumption:
              type: number
            fuelType:
              type: string
            height:
              type: number
            model:
              type: string
            weight:
              type: number
            width:
              type: number
          type: object
        waypoints:
          items:
            $ref: '#/components/schemas/LocationPoint'
          type: array
      type: object
    SavedRouteCreate:
      properties:
        date:
          $ref: '#/components/schemas/DateType'
        description:
          maxLength: 1000
          type: string
        destination:
          $ref: '#/components/schemas/LocationPoint'
        driver:
          type: object
        mapLayers:
          $ref: '#/components/schemas/MapLayers'
        merchandise:
          $ref: '#/components/schemas/Merchandise'
        origin:
          $ref: '#/components/schemas/LocationPoint'
        polyline:
          type: object
        title:
          maxLength: 200
          minLength: 1
          type: string
        vehicle:
          type: object
        waypoints:
          items:
            $ref: '#/components/schemas/LocationPoint'
          type: array
      required:
      - title
      - origin
      - destination
      type: object
    SavedRouteUpdate:
      properties:
        date:
          $ref: '#/components/schemas/DateType'
        description:
          type: string
        destination:
          $ref: '#/components/schemas/LocationPoint'
        driver:
          type: object
        mapLayers:
          $ref: '#/components/schemas/MapLayers'
        merchandise:
          $ref: '#/components/schemas/Merchandise'
        origin:
          $ref: '#/components/schemas/LocationPoint'
        polyline:
          type: object
        title:
          type: string
        vehicle:
          type: object
        waypoints:
          items:
            $ref: '#/components/schemas/LocationPoint'
          type: array
      type: object
    SearchResponse:
      description: Resultados combinados de búsqueda para eventos y radares
      properties:
        events:
          description: Eventos de tráfico encontrados en el área de búsqueda
          items:
            $ref: '#/components/schemas/TrafficEvent'
          type: array
        radars:
          description: Radares encontrados en el área de búsqueda
          items:
            $ref: '#/components/schemas/Radar'
          type: array
      required:
      - events
      - radars
      type: object
    SearchResult:
      description: "## \U0001F4CB Sección HS (Nivel más alto)\nRepresenta una sección\
        \ del código HS, que es el nivel más alto de la jerarquía. Cada sección contiene\
        \ uno o más capítulos relacionados por sector económico.\n\n### \U0001F4DD\
        \ Campos Principales\n- `_id`: Identificador único en la base de datos\n-\
        \ `title`: Título completo de la sección (incluye número romano)\n- `label`:\
        \ Array de etiquetas descriptivas\n- `chapters`: Lista de capítulos hijos\
        \ (siempre tiene al menos uno)\n\n### \U0001F3AF Cuándo usar este nivel\n\
        - Para navegación de alto nivel\n- Para análisis macroeconómico\n- Para agrupación\
        \ por sectores industriales\n\n### \U0001F4CA Ejemplo Real\n```json\n{\n \
        \ \"_id\": \"6502e98f01515afda2dbd5a1\",\n  \"title\": \"Sección XI - Textiles\
        \ y sus manufacturas\",\n  \"label\": [\n    \"Alcance: Incluye fibras textiles,\
        \ hilados, tejidos, prendas de vestir, etc.\",\n    \"Exclusiones: Productos\
        \ de cuero (Sección VIII)\"\n  ],\n  \"chapters\": [\n    {\n      \"_id\"\
        : \"6502e98f01515afda2dbd5a2\",\n      \"title\": \"Capítulo 50 - Seda\",\n\
        \      \"label\": [\n        \"Sección: XI - Textiles y sus manufacturas\"\
        \n      ],\n      \"headings\": [\n        {\n          \"_id\": \"6502e98f01515afda2dbd5a3\"\
        ,\n          \"title\": \"5001 - Capullos de seda aptos para el devanado\"\
        ,\n          \"label\": [\n            \"- Crudos (sin blanquear)\"\n    \
        \      ],\n          \"subheadings\": [\n            {\n              \"_id\"\
        : \"6502e98f01515afda2dbd5a4\",\n              \"title\": \"5001.00 - Capullos\
        \ de seda aptos para el devanado\",\n              \"label\": [\n        \
        \        \"-- De Bombyx mori\"\n              ]\n            }\n         \
        \ ]\n        }\n      ]\n    }\n  ]\n}\n```\n"
      properties:
        _id:
          description: '**Identificador único MongoDB** de la sección.


            **Formato**: ObjectId hexadecimal de 24 caracteres

            **Persistencia**: Este ID no cambia entre versiones

            '
          example: 6501a2b3c4d5e6f7a8b9c0d4
          type: string
        chapters:
          description: '**Lista de capítulos** asociados a esta sección.


            **IMPORTANTE**:

            - Siempre contiene al menos un capítulo

            - Los capítulos están ordenados por número (01-99)

            - Cada capítulo pertenece a una sola sección

            '
          items:
            $ref: '#/components/schemas/ChapterResult'
          type: array
        label:
          description: '**Array de etiquetas descriptivas** con información general.


            **Contenido común**:

            - Alcance general de la sección

            - Exclusiones importantes

            - Notas sobre productos incluidos

            '
          example:
          - 'Alcance: Productos textiles y sus manufacturas'
          - 'Exclusiones: Calzado (Sección XII)'
          items:
            type: string
          type: array
        title:
          description: '**Título completo de la sección** incluyendo número romano
            y descripción.


            **Formato**: `"Sección XX - Descripción"`

            **Ejemplo**: `"Sección XI - Textiles y sus manufacturas"`


            **IMPORTANTE**: Hay 21 secciones en total (I a XXI).

            '
          example: Sección XI - Textiles
          type: string
      required:
      - _id
      - title
      - chapters
      type: object
    Shower:
      description: Representa una instalación de duchas para camioneros
      properties:
        genderSeparated:
          description: Si tiene instalaciones separadas por género
          type: boolean
        id:
          description: ID de la ducha
          type: string
        location:
          $ref: '#/components/schemas/POI/properties/location'
        name:
          description: Nombre de la instalación de duchas
          type: string
        price:
          description: Precio por uso
          format: float
          type: number
      type: object
    SubheadingResult:
      description: "## \U0001F4CB Subpartida HS (Nivel más granular)\nRepresenta una\
        \ subpartida específica del código HS, que es el nivel más detallado de la\
        \ clasificación arancelaria. Cada subpartida pertenece exactamente a una partida.\n\
        \n### \U0001F4DD Campos Principales\n- `_id`: Identificador único en la base\
        \ de datos\n- `title`: Descripción completa de la subpartida\n- `label`: Array\
        \ de etiquetas descriptivas (puede incluir información técnica adicional)\n\
        \n### \U0001F3AF Cuándo usar este nivel\n- Para declaraciones aduaneras precisas\n\
        - Cuando se necesita la clasificación más específica\n- Para cálculos de aranceles\
        \ exactos\n\n### \U0001F4CA Ejemplo Real\n```json\n{\n  \"_id\": \"6502e99101515afda2dbd5c1\"\
        ,\n  \"title\": \"0302.31 - Albacora o atún de aleta larga\",\n  \"label\"\
        : [\n    \"-- albacore or longfinned tunas (thunnus alalunga)\",\n    \"Código\
        \ HS: 0302.31.00\",\n    \"Arancel: 5%\"\n  ]\n}\n```\n"
      properties:
        _id:
          description: '**Identificador único MongoDB** de la subpartida.


            **Formato**: ObjectId hexadecimal de 24 caracteres

            **Ejemplo**: `507f1f77bcf86cd799439011`


            **IMPORTANTE**: Este ID es persistente y no cambia entre versiones de
            la base de datos.

            '
          example: 6501a2b3c4d5e6f7a8b9c0d1
          type: string
        label:
          description: '**Array de etiquetas descriptivas** que proporcionan información
            adicional.


            **Contenido común**:

            - Descripciones en otros idiomas (inglés, francés, etc.)

            - Notas técnicas específicas

            - Información sobre restricciones o requisitos

            - Referencias a regulaciones


            **IMPORTANTE**: Este campo puede estar vacío (`[]`) si no hay etiquetas
            adicionales.

            '
          example:
          - -- albacore or longfinned tunas (thunnus alalunga)
          - Fresh or chilled
          items:
            type: string
          type: array
        title:
          description: '**Nombre descriptivo completo** de la subpartida según la
            nomenclatura HS oficial.


            **Contenido típico**:

            - Código numérico de la subpartida

            - Descripción en español

            - Información técnica relevante


            **Formato**: `"Código - Descripción"`

            **Ejemplo**: `"0302.31 - Albacora o atún de aleta larga"`

            '
          example: Algodón sin cardar ni peinar
          type: string
      required:
      - _id
      - title
      type: object
    SubscriptionSummary:
      properties:
        pricingTier:
          properties:
            basePrice:
              description: Precio base
              type: number
            code:
              description: Código del plan
              type: string
            excessRoutePrice:
              description: Precio por ruta adicional
              type: number
            includedRoutes:
              description: Rutas incluidas en el plan
              type: number
            name:
              description: Nombre del plan
              type: string
            type:
              description: Tipo de plan
              type: string
          type: object
        subscription:
          properties:
            amount:
              description: Monto del período
              type: number
            cancelAt:
              description: Fecha de cancelación programada
              format: date-time
              type: string
            cancelReason:
              description: Motivo de cancelación
              type: string
            currency:
              description: 'Moneda, ejemplo: eur'
              type: string
            currentPeriodEnd:
              description: Fin del período de facturación
              format: date-time
              type: string
            currentPeriodStart:
              description: Inicio del período de facturación
              format: date-time
              type: string
            status:
              description: Estado de la suscripción (active, canceled, etc.)
              type: string
          type: object
        totalRoutes:
          description: Total de rutas utilizadas en el período
          type: number
      type: object
    SuccessResponse:
      properties:
        message:
          description: Mensaje descriptivo del resultado
          type: string
        success:
          description: Indica si la operación fue exitosa
          type: boolean
      type: object
    SyncDiscountRequest:
      properties:
        discounts:
          description: Array de descuentos a sincronizar
          items:
            properties:
              centsValue:
                description: Valor del descuento en céntimos
                example: 0.15
                format: float
                minimum: 0
                type: number
              id:
                description: Identificador del descuento
                example: '123456789'
                type: string
              isActive:
                description: Indica si el descuento está activo
                example: true
                type: boolean
              percentValue:
                description: Valor del descuento en porcentaje
                example: 10.0
                format: float
                maximum: 100
                minimum: 0
                type: number
              selected:
                description: Indica si el descuento está seleccionado
                example: false
                type: boolean
              station:
                description: Identificador de la estación
                example: 5f8d3b7b3f6b8a1b9c3b7b3f
                type: string
              type:
                description: Tipo de descuento
                example: percentage
                type: string
            type: object
          type: array
        userId:
          description: Identificador del usuario
          example: user123
          type: string
      required:
      - discounts
      - userId
      type: object
    TempRoute:
      description: Ruta temporal compartida por código
      properties:
        _id:
          type: string
        code:
          description: Código único para compartir
          type: string
        createdAt:
          format: date-time
          type: string
        date:
          $ref: '#/components/schemas/DateType'
        destination:
          $ref: '#/components/schemas/LocationPoint'
        merchandise:
          $ref: '#/components/schemas/Merchandise'
        optimizedStops:
          type: boolean
        origin:
          $ref: '#/components/schemas/LocationPoint'
        polyline:
          type: object
        stops:
          items:
            $ref: '#/components/schemas/UnifiedStop'
          type: array
        userId:
          description: ID del usuario que creó la ruta (opcional)
          type: string
        waypoints:
          items:
            $ref: '#/components/schemas/LocationPoint'
          type: array
      type: object
    TokenResponse:
      properties:
        expiresIn:
          description: Timestamp UNIX de expiración
          format: int64
          type: integer
        token:
          description: Token JWT firmado para autenticación
          type: string
      type: object
    TrafficEvent:
      properties:
        affectedVehicleTypes:
          description: Tipos de vehículos afectados por este evento
          items:
            type: string
          type: array
        createdAt:
          description: Cuando se creó el evento
          format: date-time
          type: string
        expectedDuration:
          description: Duración esperada en minutos
          type: number
        id:
          description: Identificador único del evento
          type: string
        location:
          $ref: '#/components/schemas/Coordinates'
        severity:
          description: Nivel de severidad (0-100)
          maximum: 100
          minimum: 0
          type: number
        type:
          description: Tipo de evento de tráfico
          enum:
          - ACCIDENT
          - ROAD_CLOSURE
          - SPECIAL_EVENT
          type: string
        updatedAt:
          description: Cuando se actualizó el evento por última vez
          format: date-time
          type: string
      required:
      - id
      - type
      - location
      - severity
      - expectedDuration
      - affectedVehicleTypes
      type: object
    TrafficFlowData:
      properties:
        polyline:
          description: Polilínea codificada para el segmento de carretera
          type: string
        roadClosure:
          description: Si la carretera está cerrada
          type: boolean
        speed_current:
          description: Velocidad actual en km/h
          type: number
        speed_normal:
          description: Velocidad normal/flujo libre en km/h
          type: number
        time_current:
          description: Tiempo de viaje actual en segundos
          type: number
        time_normal:
          description: Tiempo de viaje normal en segundos
          type: number
      type: object
    UnifiedStop:
      description: 'Parada unificada en la ruta (puede ser waypoint del usuario o
        parada calculada).

        Incluye información completa de tiempos y estado de horas.

        '
      properties:
        accumulatedMinutes:
          description: Minutos acumulados (conducción + descansos anteriores)
          type: number
        arrivalDate:
          description: Fecha/hora de llegada a la parada
          format: date-time
          type: string
        coordinates:
          description: Coordenadas [latitud, longitud]
          items:
            type: number
          maxItems: 2
          minItems: 2
          type: array
        departureDate:
          description: Fecha/hora de salida de la parada
          format: date-time
          type: string
        distanceFromStart:
          description: Distancia desde el inicio en km
          type: number
        durationMinutes:
          description: Duración de la parada en minutos
          type: integer
        hoursStatus:
          description: Estado de horas de conducción en este punto
          properties:
            exceedsDaily:
              description: Si excede el límite diario
              type: boolean
            needsBreak:
              description: Si necesita un descanso
              type: boolean
            remainingDaily:
              description: Horas restantes del día
              type: number
            remainingWeekly:
              description: Horas restantes de la semana
              type: number
          type: object
        isUserDefined:
          description: Si fue definido explícitamente por el usuario
          type: boolean
        isWaypoint:
          description: Si es un waypoint definido por usuario
          type: boolean
        pureDrivingMinutes:
          description: Minutos de conducción pura hasta esta parada
          type: number
        stopType:
          description: 'Tipo de parada:

            - WAYPOINT: Parada definida por usuario

            - SHORT_BREAK: Descanso corto (45 min)

            - LONG_BREAK: Descanso diario (11h)

            - WEEKLY_REST: Descanso semanal (45h)

            '
          enum:
          - WAYPOINT
          - SHORT_BREAK
          - LONG_BREAK
          - WEEKLY_REST
          type: string
      type: object
    UserActivity:
      properties:
        _id:
          description: ID único de MongoDB para la actividad
          example: 507f1f77bcf86cd799439011
          type: string
        createdAt:
          description: Fecha de creación del registro en la base de datos
          example: '2025-01-13T10:30:00.000Z'
          format: date-time
          type: string
        environment:
          description: Entorno donde se realizó la actividad (development, staging,
            production)
          example: production
          type: string
        feature:
          description: Nombre de la funcionalidad o módulo donde se realizó la actividad
          example: user_management
          type: string
        idUser:
          description: ID del usuario que realizó la actividad
          example: 63d7907cbe76403b35da63df
          type: string
        ip:
          description: Dirección IP del usuario que realizó la actividad
          example: 192.168.1.100
          type: string
        o_system:
          description: Sistema operativo del dispositivo del usuario
          enum:
          - android
          - linux
          - windows
          - apple
          - etc
          example: windows
          type: string
        origin:
          description: Origen de la solicitud (web, mobile, api, etc.)
          example: web
          type: string
        timestamp:
          description: Fecha y hora exacta cuando se realizó la actividad
          example: '2025-01-13T10:30:00.000Z'
          format: date-time
          type: string
        type:
          description: 'Tipo de actividad realizada:

            - C: Create (Creación)

            - R: Read (Lectura)

            - U: Update (Actualización)

            - D: Delete (Eliminación)

            '
          enum:
          - C
          - R
          - U
          - D
          example: C
          type: string
        updatedAt:
          description: Fecha de última actualización del registro
          example: '2025-01-13T10:30:00.000Z'
          format: date-time
          type: string
        url:
          description: URL completa donde se realizó la actividad
          example: /api/users/63d7907cbe76403b35da63df
          type: string
      required:
      - idUser
      - environment
      - type
      - feature
      - origin
      - ip
      - o_system
      - timestamp
      - url
      type: object
    Vehicle:
      description: 'Información del vehículo para cálculo de restricciones de ruta.

        Las dimensiones determinan qué carreteras puede usar el vehículo.

        '
      properties:
        avgSpeed:
          description: Velocidad media estimada en km/h
          example: 80
          type: number
        axleLoad:
          description: Carga máxima por eje en kg
          example: 11500
          type: number
        consumption:
          description: Consumo en litros/100km
          example: 32
          type: number
        height:
          description: Alto del vehículo en metros
          example: 4.0
          type: number
        length:
          description: Longitud del vehículo en metros
          example: 16.5
          type: number
        tankSize:
          description: Capacidad del tanque en litros
          example: 600
          type: number
        weight:
          description: Peso total del vehículo en kg
          example: 40000
          type: number
        width:
          description: Ancho del vehículo en metros
          example: 2.55
          type: number
      type: object
    VehicleType:
      properties:
        code:
          description: Código del tipo de vehículo
          enum:
          - r3c
          - tir
          - rt
          - r2c
          - r2d
          - van
          - frc
          - f2c
          - adr
          - ft
          - none
          type: string
        config:
          description: Configuración
          type: string
        image:
          description: Imagen en Base64
          type: string
        kg_max:
          description: Peso máximo en kg
          type: number
        kg_min:
          description: Peso mínimo en kg
          type: number
        length:
          description: Longitud
          type: number
        max:
          description: Valor máximo asociado
          type: number
        min:
          description: Valor mínimo asociado
          type: number
        mma:
          description: MMA
          type: number
        payload:
          description: Carga útil
          type: number
        volume:
          description: Volumen
          type: number
      type: object
    Waypoint:
      description: Punto intermedio en la ruta definido por el usuario
      properties:
        customBreakMinutes:
          default: 0
          description: Tiempo de parada en minutos
          example: 30
          type: integer
        position:
          description: Coordenadas [latitud, longitud]
          example:
          - 42.0
          - -7.5
          items:
            type: number
          maxItems: 2
          minItems: 2
          type: array
        stopType:
          default: WAYPOINT
          description: Tipo de parada
          enum:
          - WAYPOINT
          - SHORT_BREAK
          - LONG_BREAK
          - WEEKLY_REST
          type: string
      type: object
    Workshop:
      description: Representa una ubicación de taller de camiones
      properties:
        address:
          description: Dirección completa
          type: string
        emergencyService:
          description: Si proporciona servicio de emergencia
          type: boolean
        id:
          description: ID del taller
          type: string
        location:
          $ref: '#/components/schemas/POI/properties/location'
        name:
          description: Nombre del taller
          type: string
        openingHours:
          description: Información de horarios de apertura
          type: string
        services:
          description: Lista de servicios disponibles
          items:
            type: string
          type: array
      type: object
    securitySchemes:
      bearerAuth:
        bearerFormat: JWT
        description: "Autenticación mediante JWT. \nEl token debe ser obtenido del\
          \ servicio de autenticación de Transcend.\nFormato del header:\n```\nAuthorization:\
          \ Bearer {token}\n```\nEjemplo:\n```\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n\
          ```\n"
        scheme: bearer
        type: http
  securitySchemes:
    ApiKeyAuth:
      description: Clave API para autenticación alternativa
      in: header
      name: x-api-key
      type: apiKey
    BearerAuth:
      bearerFormat: JWT
      description: Token JWT de autenticación, obtenido de IAM
      scheme: bearer
      type: http
    apiKeyAuth:
      description: API Key proporcionada por Transcend
      in: header
      name: x-api-key
      type: apiKey
    bearerAuth:
      bearerFormat: JWT
      description: Token JWT para autenticación
      scheme: bearer
      type: http
info:
  description: 'API modular de TRANSCEND con 13 módulos: IAM, Drivers, Route, Vehicles,
    Traffic, Weather y más.'
  title: TRANSCEND API
  version: 1.0.0
openapi: 3.0.0
paths:
  /:
    get:
      description: "Obtiene la estructura completa del sistema armonizado, incluyendo\
        \ todas las secciones,\ncapítulos, partidas y subpartidas organizadas jerárquicamente.\n\
        \nLa respuesta incluye todos los niveles anidados en un solo request:\n- Secciones\
        \ (nivel más alto)\n- Capítulos (dentro de secciones)\n- Partidas (dentro\
        \ de capítulos) \n- Subpartidas (dentro de partidas)\n\nRequiere autenticación\
        \ mediante JWT en el header Authorization.\n"
      operationId: getAll
      parameters:
      - $ref: '#/components/parameters/authorization'
      responses:
        '200':
          content:
            application/json:
              example:
              - _id: 6501a2b3c4d5e6f7a8b9c0d4
                chapters:
                - _id: 6501a2b3c4d5e6f7a8b9c0d3
                  headings:
                  - _id: 6501a2b3c4d5e6f7a8b9c0d2
                    subheadings:
                    - _id: 6501a2b3c4d5e6f7a8b9c0d1
                      title: Algodón sin cardar ni peinar
                    title: Algodón
                  title: Capítulo 52 - Algodón
                title: Sección XI - Textiles
              schema:
                items:
                  $ref: '#/components/schemas/SearchResult'
                type: array
          description: 'Estructura completa recuperada exitosamente.

            Devuelve un array de secciones, cada una con sus capítulos anidados,

            que a su vez contienen partidas y subpartidas.

            '
        '401':
          content:
            application/json:
              examples:
                invalidToken:
                  value:
                    message: Invalid or expired token
                missingToken:
                  value:
                    message: Authentication token is required
              schema:
                $ref: '#/components/schemas/Error'
          description: "No autorizado. Ocurre cuando:\n- No se proporciona el header\
            \ Authorization\n- El token JWT es inválido o ha expirado\n- El formato\
            \ del token es incorrecto\n\nEjemplo de respuesta:\n```json\n{\n  \"message\"\
            : \"Invalid or expired token\"\n}\n```\n"
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: 'Error interno del servidor.

            Puede ocurrir si hay problemas al acceder a la base de datos.

            '
      security:
      - bearerAuth: []
      summary: Obtener toda la estructura jerárquica del HS Code
      tags:
      - profiles
  /account/create-checkout-session:
    post:
      description: Crea una sesión de pago en Stripe para que el usuario pueda suscribirse
        a un plan. Devuelve la URL del checkout de Stripe
      operationId: createCheckoutSession
      parameters:
      - $ref: '#/components/parameters/Module_Authorization'
      - $ref: '#/components/parameters/API_Key'
      - $ref: '#/components/parameters/UserId'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CheckoutSessionRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  url:
                    description: URL del checkout de Stripe
                    example: https://checkout.stripe.com/pay/cs_test_a1B2c3D4e5F6...
                    format: uri
                    type: string
                type: object
          description: Sesión de checkout creada exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Perfil de usuario no encontrado
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Crear sesión de checkout
      tags:
      - pay
  /account/create-portal-session:
    post:
      description: Crea una sesión del portal de cliente de Stripe para que el usuario
        pueda gestionar su suscripción, métodos de pago y facturas
      operationId: createPortalSession
      parameters:
      - $ref: '#/components/parameters/Module_Authorization'
      - $ref: '#/components/parameters/API_Key'
      - $ref: '#/components/parameters/UserId'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PortalSessionRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  url:
                    description: URL del portal de cliente de Stripe
                    example: https://billing.stripe.com/session/bps_1a2B3c4D5e6F...
                    format: uri
                    type: string
                type: object
          description: Sesión del portal creada exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Cliente de Stripe no encontrado para el usuario
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Crear sesión del portal de cliente
      tags:
      - pay
  /account/failed-payments:
    get:
      description: Recupera el listado de pagos fallidos para el cliente del usuario,
        útil para identificar problemas de facturación
      operationId: getFailedPayments
      parameters:
      - $ref: '#/components/parameters/Module_Authorization'
      - $ref: '#/components/parameters/API_Key'
      - $ref: '#/components/parameters/UserId'
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  description: Objeto de pago fallido de Stripe
                  type: object
                type: array
          description: Lista de pagos fallidos obtenida exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Cliente de Stripe no encontrado
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Obtener pagos fallidos
      tags:
      - pay
  /account/invoices:
    get:
      description: Recupera el historial de facturas del usuario, incluyendo estados
        y URLs para acceso
      operationId: getUserInvoices
      parameters:
      - $ref: '#/components/parameters/Module_Authorization'
      - $ref: '#/components/parameters/API_Key'
      - $ref: '#/components/parameters/UserId'
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Invoice'
                type: array
          description: Lista de facturas obtenida exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Cliente de Stripe no encontrado
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Obtener facturas del usuario
      tags:
      - pay
  /account/pricing-plans:
    get:
      description: Devuelve el listado completo de planes de precios activos, sus
        características y costos
      operationId: getPricingPlans
      parameters:
      - $ref: '#/components/parameters/Module_Authorization'
      - $ref: '#/components/parameters/API_Key'
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/PricingTier'
                type: array
          description: Lista de planes de precios obtenida exitosamente
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Obtener planes de precios disponibles
      tags:
      - pay
  /account/summary:
    get:
      description: Obtiene información completa sobre la suscripción actual del usuario,
        incluyendo estado, período de facturación, plan contratado y consumo de rutas
      operationId: getAccountSummary
      parameters:
      - $ref: '#/components/parameters/Module_Authorization'
      - $ref: '#/components/parameters/API_Key'
      - $ref: '#/components/parameters/UserId'
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionSummary'
          description: Resumen de suscripción obtenido exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: No se encontró suscripción activa para el usuario
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Obtener resumen de suscripción
      tags:
      - pay
  /aemet/alerts/area:
    get:
      description: Alertas meteorológicas de AEMET filtradas por área geográfica
      operationId: getAemetAlertsByArea
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/StartDate'
      - $ref: '#/components/parameters/EndDate'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Alertas por área recuperadas exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get weather alerts by area
      tags:
      - weather
  /aemet/alerts/current:
    get:
      description: Alertas meteorológicas actuales proporcionadas por AEMET para ubicaciones
        en España
      operationId: getAemetAlertsCurrent
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Alertas meteorológicas recuperadas exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get current AEMET alerts
      tags:
      - weather
  /aemet/alerts/range:
    get:
      description: Alertas meteorológicas históricas o futuras proporcionadas por
        AEMET para ubicaciones en España
      operationId: getAemetAlertsRange
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/StartDate'
      - $ref: '#/components/parameters/EndDate'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Alertas meteorológicas históricas o futuras recuperadas exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get AEMET alerts for date range
      tags:
      - weather
  /aemet/current:
    get:
      description: Alertas meteorológicas en tiempo real proporcionadas por AEMET
        para ubicaciones en España
      operationId: getAemetCurrent
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Alertas meteorológicas actuales recuperadas exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get current AEMET weather alerts
      tags:
      - weather
  /aemet/extreme:
    get:
      description: Valores extremos (máximos y mínimos) de los parámetros meteorológicos
        de AEMET
      operationId: getAemetExtremeValues
      parameters:
      - $ref: '#/components/parameters/Parametro'
      - $ref: '#/components/parameters/StationId'
      - $ref: '#/components/parameters/StartDate'
      - $ref: '#/components/parameters/EndDate'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Valores extremos recuperados exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get extreme weather values
      tags:
      - weather
  /aemet/forecast:
    get:
      description: Pronósticos meteorológicos proporcionados por AEMET para ubicaciones
        en España
      operationId: getAemetForecast
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/StartDate'
      - $ref: '#/components/parameters/EndDate'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Pronóstico meteorológico recuperado exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get AEMET weather forecast
      tags:
      - weather
  /aemet/forecast/station:
    get:
      description: Pronóstico meteorológico de AEMET para una estación meteorológica
        específica
      operationId: getAemetForecastStation
      parameters:
      - $ref: '#/components/parameters/StationId'
      - $ref: '#/components/parameters/StartDate'
      - $ref: '#/components/parameters/EndDate'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Pronóstico de la estación recuperado exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get weather forecast for specific station
      tags:
      - weather
  /aemet/forecast/stations:
    get:
      description: Pronóstico meteorológico de AEMET para todas las estaciones meteorológicas
      operationId: getAemetForecastAllStations
      parameters:
      - $ref: '#/components/parameters/StartDate'
      - $ref: '#/components/parameters/EndDate'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Pronóstico de todas las estaciones recuperado exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get weather forecast for all stations
      tags:
      - weather
  /aemet/stations:
    get:
      description: Lista completa de estaciones meteorológicas disponibles en AEMET
      operationId: getAemetStations
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Lista de estaciones recuperada exitosamente
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get AEMET weather stations list
      tags:
      - weather
  /aemet/stations/{stationId}:
    get:
      description: Información detallada de una estación meteorológica específica
        de AEMET
      operationId: getAemetStationDetails
      parameters:
      - $ref: '#/components/parameters/StationIdPath'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Detalles de la estación recuperados exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: ID de estación inválido o faltante
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get AEMET weather station details
      tags:
      - weather
  /alerts/current:
    get:
      description: Manténgase informado sobre las alertas meteorológicas activas en
        su zona.
      operationId: getAlertCurrent
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Alertas meteorológicas recuperadas exitosamente.
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes.
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error interno del servidor.
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get current weather alerts
      tags:
      - weather
  /alerts/range:
    get:
      description: Consultar alertas meteorológicas históricas o futuras para su ubicación.
      operationId: getAlertRange
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/StartDate'
      - $ref: '#/components/parameters/EndDate'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Alertas meteorológicas históricas o futuras recuperadas exitosamente.
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes.
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error interno del servidor.
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get weather alerts for date range
      tags:
      - weather
  /api/:
    get:
      description: 'Consulta las actividades realizadas por un usuario específico
        con soporte para paginación y filtros por fecha.


        **PARÁMETROS OBLIGATORIOS:**

        - `idUser`: ID del usuario cuyas actividades quieres consultar


        **PARÁMETROS OPCIONALES:**

        - `page`: Número de página (por defecto: 1)

        - `pageSize`: Actividades por página (10, 25, 50, 100 - por defecto: 25)

        - `startDate`: Fecha inicio filtro (formato ISO 8601, ej: "2025-01-01T00:00:00.000Z")

        - `endDate`: Fecha fin filtro (formato ISO 8601, ej: "2025-01-13T23:59:59.999Z")


        **COMPORTAMIENTO DE AUTENTICACIÓN:**

        - Si se proporciona JWT válido: Se pueden consultar actividades de cualquier
        usuario

        - Si no hay JWT: Solo se permiten consultas sin especificar idUser (retorna
        error)


        **FILTROS AUTOMÁTICOS:**

        - Si no se especifica startDate/endDate: Se filtra por los últimos 30 días

        - pageSize se ajusta automáticamente a valores permitidos (10, 25, 50, 100)


        **EJEMPLOS DE USO:**

        - Consultar actividades recientes: `GET /api/?idUser=63d7907cbe76403b35da63df`

        - Página específica: `GET /api/?idUser=63d7907cbe76403b35da63df&page=2&pageSize=50`

        - Rango de fechas: `GET /api/?idUser=63d7907cbe76403b35da63df&startDate=2025-01-01T00:00:00Z&endDate=2025-01-31T23:59:59Z`

        '
      operationId: getUserActivities
      parameters:
      - $ref: '#/components/parameters/Authorization'
      - $ref: '#/components/parameters/UserId'
      - $ref: '#/components/parameters/Page'
      - $ref: '#/components/parameters/PageSize'
      - $ref: '#/components/parameters/StartDate'
      - $ref: '#/components/parameters/EndDate'
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ActivityResponse'
          description: 'Actividades recuperadas exitosamente. La respuesta incluye:

            - `docs`: Array de actividades encontradas

            - `total`: Número total de actividades (sin paginación)

            - `limit`: Número de actividades por página

            - `page`: Página actual

            - `pages`: Número total de páginas disponibles

            '
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: 'Parámetros inválidos. Posibles causas:

            - idUser no proporcionado y sin autenticación JWT

            - Fechas en formato incorrecto (debe ser ISO 8601)

            - pageSize fuera del rango permitido

            '
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Token JWT inválido o expirado
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error interno del servidor
      summary: Obtener actividades de usuario
      tags:
      - activity
    post:
      description: "Registra una nueva actividad realizada por un usuario en el sistema.\n\
        \n**CAMPOS OBLIGATORIOS EN EL BODY:**\n- `environment`: Entorno (\"development\"\
        , \"staging\", \"production\")\n- `type`: Tipo de actividad (\"C\"=Create,\
        \ \"R\"=Read, \"U\"=Update, \"D\"=Delete)\n- `feature`: Nombre del módulo/funcionalidad\
        \ (ej: \"user_management\", \"dashboard\")\n- `origin`: Origen de la solicitud\
        \ (\"web\", \"mobile\", \"api\")\n- `o_system`: Sistema operativo (\"android\"\
        , \"linux\", \"windows\", \"apple\", \"etc\")\n- `url`: URL completa donde\
        \ se realizó la actividad\n\n**CAMPOS OPCIONALES:**\n- `idUser`: ID del usuario\
        \ (se obtiene automáticamente del JWT si está presente)\n\n**CAMPOS AUTOMÁTICOS\
        \ (NO INCLUIR EN REQUEST):**\n- `timestamp`: Se establece automáticamente\
        \ con la fecha/hora actual\n- `ip`: Se obtiene automáticamente de la solicitud\
        \ (soporte para proxies)\n\n**VALIDACIONES:**\n- `type` debe ser exactamente:\
        \ \"C\", \"R\", \"U\", o \"D\"\n- `o_system` debe ser uno de: \"android\"\
        , \"linux\", \"windows\", \"apple\", \"etc\"\n- Todos los campos requeridos\
        \ deben estar presentes y no vacíos\n\n**EJEMPLOS DE USO:**\n\n*Crear usuario:*\n\
        ```json\n{\n  \"environment\": \"production\",\n  \"type\": \"C\",\n  \"feature\"\
        : \"user_management\",\n  \"origin\": \"web\",\n  \"o_system\": \"windows\"\
        ,\n  \"url\": \"/api/users\"\n}\n```\n\n*Ver dashboard:*\n```json\n{\n  \"\
        environment\": \"production\",\n  \"type\": \"R\",\n  \"feature\": \"dashboard\"\
        ,\n  \"origin\": \"mobile\",\n  \"o_system\": \"android\",\n  \"url\": \"\
        /dashboard\"\n}\n```\n"
      operationId: createUserActivity
      parameters:
      - $ref: '#/components/parameters/Authorization'
      requestBody:
        content:
          application/json:
            examples:
              CreateUser:
                summary: Registro de creación de usuario
                value:
                  environment: production
                  feature: user_management
                  idUser: 63d7907cbe76403b35da63df
                  o_system: windows
                  origin: web
                  type: C
                  url: /api/users
              UpdateProfile:
                summary: Actualización de perfil de usuario
                value:
                  environment: production
                  feature: user_profile
                  o_system: android
                  origin: mobile
                  type: U
                  url: /api/users/profile
              ViewDashboard:
                summary: Acceso al dashboard principal
                value:
                  environment: production
                  feature: dashboard_main
                  o_system: linux
                  origin: web
                  type: R
                  url: /dashboard
            schema:
              $ref: '#/components/schemas/CreateActivityRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserActivity'
          description: 'Actividad registrada exitosamente. Retorna el objeto completo
            de la actividad creada

            incluyendo el ID generado por MongoDB y los campos automáticos (timestamp,
            ip, etc.)

            '
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: 'Datos inválidos en la solicitud. Posibles causas:

            - Campos requeridos faltantes (environment, type, feature, origin, o_system,
            url)

            - Valores enum inválidos (type debe ser C/R/U/D, o_system debe ser válido)

            - Formato de datos incorrecto

            - Campos vacíos o nulos

            '
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Token JWT inválido o expirado (si se requiere autenticación)
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error interno del servidor (problemas de base de datos, etc.)
      summary: Registrar nueva actividad
      tags:
      - activity
  /api/apikeys:
    delete:
      description: Elimina la API key del usuario
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          description: API key eliminada exitosamente
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Eliminar mi API key
      tags:
      - iam
    get:
      description: Retorna información de la API key del usuario
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Información de API key obtenida
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Obtener mi API key
      tags:
      - iam
    post:
      description: Genera una nueva API key para el usuario
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: API key creada exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Crear nueva API key
      tags:
      - iam
  /api/apikeys/verify:
    post:
      description: Verifica si una API key es válida
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: API key válida
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: API key inválida
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - ApiKeyAuth: []
      summary: Verificar API key
      tags:
      - iam
  /api/auth/check-internal-token:
    post:
      description: Verifica un token interno del sistema
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          description: Token interno válido
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Verificar token interno
      tags:
      - iam
  /api/auth/login:
    post:
      description: Autentica un usuario con email y contraseña, retorna token JWT
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
        required: true
      responses:
        '200':
          $ref: '#/components/responses/LoginSuccess'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Credenciales inválidas
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Demasiadas solicitudes - Rate limit excedido
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Iniciar sesión de usuario
      tags:
      - iam
  /api/auth/me:
    get:
      description: Retorna información del usuario autenticado
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Información del usuario obtenida
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Obtener información del usuario actual
      tags:
      - iam
  /api/auth/profile:
    put:
      description: Actualiza información del usuario autenticado
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          description: Perfil actualizado exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Actualizar perfil de usuario
      tags:
      - iam
  /api/auth/recovery:
    post:
      description: Inicia proceso de recuperación de contraseña
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          description: Email de recuperación enviado
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Solicitar recuperación de contraseña
      tags:
      - iam
  /api/auth/register:
    post:
      description: Crea un nuevo usuario y empresa asociada
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
          description: Registro exitoso
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Datos inválidos o duplicados
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Demasiados registros
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Registrar nuevo usuario y empresa
      tags:
      - iam
  /api/auth/renew-token:
    post:
      description: Renueva un token JWT válido
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
          description: Token renovado exitosamente
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Renovar token JWT
      tags:
      - iam
  /api/auth/subscription:
    post:
      description: Actualiza el tipo de suscripción del usuario autenticado
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          description: Suscripción actualizada exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Actualizar suscripción de usuario
      tags:
      - iam
  /api/auth/validate-token:
    get:
      description: Verifica si un token JWT es válido
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          description: Token válido
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Validar token JWT
      tags:
      - iam
  /api/auth/verify-service-token:
    post:
      description: Verifica un token de servicio
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          description: Token de servicio válido
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Verificar token de servicio
      tags:
      - iam
  /api/company:
    post:
      description: Crea una nueva empresa
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          description: Empresa creada exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Crear nueva empresa
      tags:
      - iam
    put:
      description: Actualiza información de la empresa
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Empresa actualizada exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Actualizar empresa
      tags:
      - iam
  /api/company/me:
    get:
      description: Retorna información de la empresa del usuario autenticado
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Información de empresa obtenida
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Obtener información de la empresa
      tags:
      - iam
  /api/company/users:
    get:
      description: Retorna lista de usuarios de la empresa
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Lista de usuarios obtenida
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Listar usuarios de la empresa
      tags:
      - iam
    post:
      description: Crea un nuevo usuario en la empresa
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          description: Usuario creado exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Crear usuario en la empresa
      tags:
      - iam
  /api/company/users/{userId}:
    delete:
      description: Elimina un usuario específico de la empresa
      parameters:
      - description: ID del usuario a eliminar
        in: path
        name: userId
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          description: Usuario eliminado exitosamente
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Eliminar usuario de la empresa
      tags:
      - iam
    put:
      description: Actualiza información de un usuario específico de la empresa
      parameters:
      - description: ID del usuario a actualizar
        in: path
        name: userId
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          description: Usuario actualizado exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Actualizar usuario de la empresa
      tags:
      - iam
  /api/company/webhook:
    put:
      description: Configura URL de webhook para notificaciones
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Webhook actualizado exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Actualizar webhook de empresa
      tags:
      - iam
  /api/gamification/metrics:
    get:
      description: Obtiene las métricas de gamificación del usuario autenticado
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GamificationMetrics'
          description: Métricas de gamificación
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: User ID requerido
      summary: Obtener métricas de gamificación
      tags:
      - route
  /api/gamification/metrics/refresh:
    post:
      description: Fuerza una actualización de las métricas de gamificación
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GamificationMetrics'
          description: Métricas actualizadas
      summary: Refrescar métricas de gamificación
      tags:
      - route
  /api/gamification/summary:
    get:
      description: Obtiene un resumen de los logros y estadísticas del usuario
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GamificationSummary'
          description: Resumen de gamificación
      summary: Obtener resumen de gamificación
      tags:
      - route
  /api/isochrone:
    get:
      description: 'Calcula el área alcanzable desde un punto dado en un tiempo o
        distancia determinada.

        Útil para análisis de cobertura de servicios.

        '
      parameters:
      - description: Latitud del punto central
        example: 40.4168
        in: query
        name: lat
        required: true
        schema:
          format: double
          type: number
      - description: Longitud del punto central
        example: -3.7038
        in: query
        name: lon
        required: true
        schema:
          format: double
          type: number
      - description: 'Distancia en km (si isTime=false) o minutos (si isTime=true).

          Máximo: 1000km o 120 minutos.

          '
        example: 50
        in: query
        name: km
        required: false
        schema:
          default: 100
          maximum: 1000
          type: number
      - description: Si true, km representa minutos en lugar de kilómetros
        in: query
        name: isTime
        required: false
        schema:
          default: false
          type: boolean
      - description: Información del vehículo (JSON-encoded)
        in: query
        name: vehicle
        required: false
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IsochroneResponse'
          description: Isocrona calculada
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Parámetros inválidos
      summary: Calcular isocronas
      tags:
      - route
  /api/locations:
    get:
      description: Obtiene todas las ubicaciones del usuario autenticado
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Location'
                type: array
          description: Lista de ubicaciones
      summary: Listar ubicaciones
      tags:
      - route
    post:
      description: Guarda una ubicación frecuente para el usuario (cliente, almacén,
        etc.)
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LocationCreate'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Location'
          description: Ubicación creada
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Datos inválidos
      summary: Crear ubicación
      tags:
      - route
  /api/locations/{id}:
    delete:
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  deleted:
                    example: true
                    type: boolean
                type: object
          description: Ubicación eliminada
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Ubicación no encontrada
      summary: Eliminar ubicación
      tags:
      - route
    get:
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Location'
          description: Ubicación
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Ubicación no encontrada
      summary: Obtener ubicación por ID
      tags:
      - route
    put:
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LocationUpdate'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Location'
          description: Ubicación actualizada
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Ubicación no encontrada
      summary: Actualizar ubicación
      tags:
      - route
  /api/route:
    get:
      description: "Calcula una ruta optimizada para camiones entre origen y destino,\
        \ \nincluyendo waypoints intermedios y paradas de descanso obligatorias.\n\
        \n## Tipos de fecha\n- `departure`: La fecha proporcionada es la hora de salida\
        \ (default)\n- `arrival`: La fecha proporcionada es la hora de llegada deseada.\
        \ El sistema\n  calcula automáticamente la hora de salida necesaria.\n\n##\
        \ Cálculo de paradas\nEl sistema calcula automáticamente las paradas necesarias\
        \ según:\n- Horas de conducción restantes del conductor\n- Normativa EU de\
        \ tiempos de conducción\n- Waypoints definidos por el usuario\n"
      parameters:
      - description: Latitud del punto de origen
        example: 42.2406
        in: query
        name: origin_lat
        required: true
        schema:
          format: double
          maximum: 90
          minimum: -90
          type: number
      - description: Longitud del punto de origen
        example: -8.7207
        in: query
        name: origin_lon
        required: true
        schema:
          format: double
          maximum: 180
          minimum: -180
          type: number
      - description: Latitud del punto de destino
        example: 41.3874
        in: query
        name: destiny_lat
        required: true
        schema:
          format: double
          maximum: 90
          minimum: -90
          type: number
      - description: Longitud del punto de destino
        example: 2.1686
        in: query
        name: destiny_lon
        required: true
        schema:
          format: double
          maximum: 180
          minimum: -180
          type: number
      - description: 'Array de waypoints intermedios (JSON-encoded).

          Cada waypoint puede tener un tiempo de parada personalizado.

          '
        example: '[{"position":[42.0,-7.5],"customBreakMinutes":30,"stopType":"WAYPOINT"}]'
        in: query
        name: waypoints
        required: false
        schema:
          type: string
      - description: 'Array de puntos a evitar (JSON-encoded).

          Cada punto se define como [latitud, longitud].

          El sistema creará zonas de evitación de 1km de radio alrededor de cada punto.

          '
        example: '[[42.0259,-7.185],[41.5,-2.3]]'
        in: query
        name: avoid_points
        required: false
        schema:
          type: string
      - description: 'Información del vehículo (JSON-encoded).

          Usado para restricciones de altura, peso, anchura en la ruta.

          '
        example: '{"width":2.55,"height":4.0,"weight":40000,"length":16.5,"axleLoad":11500,"tankSize":600,"consumption":32,"avgSpeed":80}'
        in: query
        name: vehicle
        required: false
        schema:
          type: string
      - description: 'Estado de horas del conductor (JSON-encoded).

          Usado para calcular cuándo necesita paradas de descanso.

          '
        example: '{"hoursStatus":{"remainingWeeklyHours":56,"remainingBiweeklyHours":90,"remainingDayHours":9}}'
        in: query
        name: driver
        required: false
        schema:
          type: string
      - description: 'Información de la mercancía (JSON-encoded).

          Usado para rutas ADR (mercancías peligrosas).

          '
        example: '{"hsCode":"8703","weight":15000,"isADR":false}'
        in: query
        name: merchandise
        required: false
        schema:
          type: string
      - description: 'Fecha y tipo de fecha (JSON-encoded).

          - `type: "departure"`: La fecha es hora de salida

          - `type: "arrival"`: La fecha es hora de llegada deseada

          '
        example: '{"type":"departure","date":"2025-01-15T08:00:00Z"}'
        in: query
        name: date
        required: false
        schema:
          type: string
      - description: 'Capas del mapa y opciones de ruta (JSON-encoded).

          Controla qué evitar y qué POIs mostrar.

          '
        example: '{"avoidTolls":false,"avoidHighways":false,"calculateStops":true,"shortestRoute":false}'
        in: query
        name: map_layers
        required: false
        schema:
          type: string
      - description: Incluir puntos negros de peligrosidad en la respuesta
        in: query
        name: show_black_points
        required: false
        schema:
          default: false
          type: boolean
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RouteResponse'
          description: Ruta calculada exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Parámetros inválidos
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: No se encontró ruta
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Error interno del servidor
      summary: Calcular ruta optimizada
      tags:
      - route
  /api/route/count:
    get:
      description: Obtiene el conteo de rutas calculadas en un intervalo de fechas
      parameters:
      - in: query
        name: startDate
        required: true
        schema:
          format: date-time
          type: string
      - in: query
        name: endDate
        required: true
        schema:
          format: date-time
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  count:
                    example: 150
                    type: integer
                type: object
          description: Conteo de rutas
      summary: Contar rutas en intervalo de tiempo
      tags:
      - route
  /api/saved-routes:
    get:
      description: Obtiene todas las rutas guardadas del usuario autenticado
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/SavedRoute'
                type: array
          description: Lista de rutas guardadas
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: User ID requerido
      summary: Listar rutas guardadas
      tags:
      - route
    post:
      description: Guarda una ruta calculada para el usuario autenticado
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SavedRouteCreate'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SavedRoute'
          description: Ruta guardada exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Datos inválidos o User ID requerido
      summary: Crear ruta guardada
      tags:
      - route
  /api/saved-routes/{id}:
    delete:
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  deleted:
                    example: true
                    type: boolean
                type: object
          description: Ruta eliminada
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Ruta no encontrada
      summary: Eliminar ruta guardada
      tags:
      - route
    get:
      parameters:
      - description: ID de la ruta guardada (MongoDB ObjectId)
        in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SavedRoute'
          description: Ruta guardada
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Ruta no encontrada
      summary: Obtener ruta guardada por ID
      tags:
      - route
    put:
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SavedRouteUpdate'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SavedRoute'
          description: Ruta actualizada
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Ruta no encontrada
      summary: Actualizar ruta guardada
      tags:
      - route
  /api/saved-routes/{id}/gpx:
    get:
      description: Descarga la ruta guardada en formato GPX para dispositivos GPS
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/gpx+xml:
              schema:
                format: binary
                type: string
          description: Archivo GPX
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Ruta no encontrada
      summary: Exportar ruta como GPX
      tags:
      - route
  /api/tempCode/{id}:
    get:
      description: 'Obtiene una ruta compartida mediante un código temporal.

        Las rutas temporales expiran después de un tiempo configurado.

        '
      parameters:
      - description: Código temporal de la ruta
        in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TempRoute'
          description: Ruta temporal
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Ruta temporal no encontrada o expirada
      summary: Obtener ruta por código temporal
      tags:
      - route
  /api/token/renew-token:
    post:
      deprecated: true
      description: '**⚠️ DEPRECATED:** Use `/api/auth/renew-token` instead.

        Alias para renovar token JWT.

        '
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
          description: Token renovado exitosamente
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Renovar token JWT (legacy)
      tags:
      - iam
  /api/token/validate-token:
    get:
      deprecated: true
      description: '**⚠️ DEPRECATED:** Use `/api/auth/validate-token` instead.

        Alias para validar token JWT.

        '
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          description: Token válido
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - BearerAuth: []
      summary: Validar token JWT (legacy)
      tags:
      - iam
  /api/traffic/blackspots/area:
    get:
      description: 'Retorna los puntos negros dentro de un radio alrededor de un punto
        central.

        '
      parameters:
      - description: Latitud del centro del área
        example: 40.4168
        in: query
        name: lat
        required: true
        schema:
          format: double
          type: number
      - description: Longitud del centro del área
        example: -3.7038
        in: query
        name: lon
        required: true
        schema:
          format: double
          type: number
      - description: Radio de búsqueda en metros (1000-25000)
        example: 10000
        in: query
        name: radius
        required: false
        schema:
          default: 25000
          maximum: 25000
          minimum: 1000
          type: integer
      - description: Nivel mínimo de peligrosidad (0-100)
        in: query
        name: minDangerLevel
        required: false
        schema:
          default: 30
          maximum: 100
          minimum: 0
          type: integer
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BlackSpotsResponse'
          description: Puntos negros en el área
      summary: Obtener puntos negros en un área
      tags:
      - route
  /api/traffic/blackspots/path:
    post:
      description: 'Retorna los puntos negros (zonas de alta peligrosidad) que se
        encuentran

        a lo largo de una ruta definida por una serie de puntos.

        '
      requestBody:
        content:
          application/json:
            schema:
              properties:
                minDangerLevel:
                  default: 30
                  description: Nivel mínimo de peligrosidad (0-100)
                  maximum: 100
                  minimum: 0
                  type: integer
                points:
                  description: Array de coordenadas [longitud, latitud] que definen
                    la ruta
                  example:
                  - - -3.7038
                    - 40.4168
                  - - -3.7138
                    - 40.4268
                  - - -3.7238
                    - 40.4368
                  items:
                    items:
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  minItems: 2
                  type: array
              required:
              - points
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BlackSpotsResponse'
          description: Puntos negros encontrados
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Parámetros inválidos
      summary: Obtener puntos negros a lo largo de una ruta
      tags:
      - route
  /api/traffic/health:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  service:
                    example: traffic-controller
                    type: string
                  status:
                    enum:
                    - healthy
                    - unhealthy
                    type: string
                  timestamp:
                    format: date-time
                    type: string
                  trafficService:
                    enum:
                    - connected
                    - disconnected
                    type: string
                type: object
          description: Servicio de tráfico saludable
        '503':
          content:
            application/json:
              schema:
                properties:
                  error:
                    type: string
                  status:
                    example: unhealthy
                    type: string
                type: object
          description: Servicio no disponible
      summary: Health check del servicio de tráfico
      tags:
      - route
  /billing/mode:
    put:
      description: Actualiza el plan de precios de un usuario existente (requiere
        autenticación del usuario)
      operationId: updateBillingMode
      parameters:
      - $ref: '#/components/parameters/Module_Authorization'
      - $ref: '#/components/parameters/API_Key'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BillingModeUpdate'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  message:
                    example: Billing mode updated
                    type: string
                type: object
          description: Modo de facturación actualizado exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Actualizar modo de facturación
      tags:
      - pay
  /billing/pricing:
    get:
      description: Recupera todos los planes de precios activos del sistema (endpoint
        público para IAM y otros servicios)
      operationId: getAllPricingTiers
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/PricingTier'
                type: array
          description: Lista de planes de precios obtenida exitosamente
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Obtener todas las tarifas
      tags:
      - pay
  /billing/routes/usage:
    post:
      description: Registra el consumo de rutas de un usuario para facturación. Las
        operaciones se encolan para procesamiento batch
      operationId: recordRouteUsage
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RouteUsageRecord'
        required: true
      responses:
        '202':
          content:
            application/json:
              schema:
                properties:
                  message:
                    example: Usage record queued for user 63d7907cbe76403b35da63df
                    type: string
                  queueSize:
                    description: Tamaño actual de la cola de procesamiento
                    type: number
                  status:
                    example: queued
                    type: string
                type: object
          description: Uso registrado y encolado exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Registrar uso de rutas
      tags:
      - pay
  /billing/users/{userId}/check-stripe:
    get:
      description: Verifica si el usuario tiene un cliente Stripe asociado. Si no
        existe el perfil, lo crea automáticamente
      operationId: checkUserHasStripe
      parameters:
      - $ref: '#/components/parameters/UserId_Path'
      - description: Email del usuario (requerido si no existe el perfil)
        in: query
        name: email
        required: false
        schema:
          example: user@example.com
          format: email
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  customerId:
                    description: ID del cliente Stripe (si existe)
                    type: string
                  hasStripe:
                    description: Indica si el usuario tiene cliente Stripe
                    type: boolean
                type: object
          description: Verificación completada exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: No se encontró cliente Stripe para el usuario
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Verificar si el usuario tiene cliente Stripe
      tags:
      - pay
  /billing/users/{userId}/subscription:
    get:
      description: Obtiene información sobre el tipo de suscripción de un usuario
        específico. Si no existe el perfil, lo crea automáticamente
      operationId: getUserSubscriptionType
      parameters:
      - $ref: '#/components/parameters/UserId_Path'
      - description: Email del usuario (requerido si no existe el perfil)
        in: query
        name: email
        required: false
        schema:
          example: user@example.com
          format: email
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  subscriptionName:
                    description: Nombre del plan ('none' si no tiene)
                    type: string
                  subscriptionType:
                    description: Código del plan de suscripción ('none' si no tiene)
                    type: string
                  userId:
                    description: ID del usuario
                    type: string
                type: object
          description: Tipo de suscripción obtenida exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Obtener tipo de suscripción de usuario
      tags:
      - pay
  /blackspots/area:
    get:
      description: 'Recupera puntos negros (lugares con alto nivel de peligrosidad)
        dentro de un área específica.


        **Ejemplo de uso:**

        ```

        GET /blackspots/area?lat=40.4168&lng=-3.7038&radius=5000&minDangerLevel=50

        ```


        Este endpoint es útil para identificar zonas de alto riesgo en rutas logísticas.

        '
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Radius'
      - description: Nivel mínimo de peligrosidad para filtrar (0-100)
        in: query
        name: minDangerLevel
        schema:
          default: 50
          maximum: 100
          minimum: 0
          type: number
      responses:
        '200':
          content:
            application/json:
              examples:
                ejemplo_exitoso:
                  summary: Puntos negros encontrados
                  value:
                  - coordinates:
                      lat: 40.4168
                      lon: -3.7038
                    createdAt: '2024-01-15T10:30:00Z'
                    dangerLevel: 75
                    description: Intersección con alta tasa de accidentes
                    id: bs_12345
                    updatedAt: '2024-01-15T10:30:00Z'
              schema:
                items:
                  $ref: '#/components/schemas/BlackSpot'
                type: array
          description: Lista de puntos negros en el área
        '400':
          description: Parámetros inválidos
        '401':
          description: No autorizado
        '500':
          description: Error interno del servidor
      security:
      - bearerAuth: []
      summary: Obtener puntos negros en un área
      tags:
      - traffic
  /blackspots/path:
    post:
      description: "Identifica puntos negros a lo largo de una ruta específica, calculando\
        \ el nivel de peligrosidad para cada segmento.\n\n**Ejemplo de uso:**\n```json\n\
        POST /blackspots/path\n{\n  \"points\": [\n    [-3.7038, 40.4168],\n    [-3.7138,\
        \ 40.4268]\n  ],\n  \"minDangerLevel\": 50\n}\n```\n"
      requestBody:
        content:
          application/json:
            schema:
              properties:
                minDangerLevel:
                  default: 50
                  description: Nivel mínimo de peligrosidad para filtrar
                  maximum: 100
                  minimum: 0
                  type: number
                points:
                  description: Array de pares de coordenadas [longitud, latitud]
                  items:
                    items:
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  minItems: 2
                  type: array
              required:
              - points
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/BlackSpot'
                type: array
          description: Puntos negros encontrados a lo largo de la ruta
        '400':
          description: Ruta inválida o parámetros incorrectos
        '401':
          description: No autorizado
        '500':
          description: Error interno del servidor
      security:
      - bearerAuth: []
      summary: Obtener puntos negros a lo largo de una ruta
      tags:
      - traffic
  /brands:
    get:
      description: Recupera todas las marcas de vehículos disponibles en el sistema
      operationId: getBrands
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  properties:
                    id:
                      format: uuid
                      type: string
                    name:
                      type: string
                  type: object
                type: array
          description: Lista de marcas recuperada exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get list of vehicle brands
      tags:
      - vehicles
  /brands/with-counts:
    get:
      description: Recupera todas las marcas de vehículos con el conteo de los modelos
        asociados
      operationId: getBrandsWithModelCount
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  properties:
                    id:
                      format: uuid
                      type: string
                    modelCount:
                      type: integer
                    name:
                      type: string
                  type: object
                type: array
          description: Lista de marcas con conteo de modelos recuperada exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get brands with model count
      tags:
      - vehicles
  /brands/{brandId}:
    get:
      description: Recupera los detalles de una marca específica por su ID
      operationId: getBrandById
      parameters:
      - description: ID de la marca
        in: path
        name: brandId
        required: true
        schema:
          format: uuid
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  id:
                    format: uuid
                    type: string
                  name:
                    type: string
                type: object
          description: Marca recuperada exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Marca no encontrada
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get brand by ID
      tags:
      - vehicles
  /city/getByName:
    get:
      description: Devuelve hasta 5 ciudades que coinciden con el término de búsqueda,
        incluyendo nombre, código de país y ubicación. La búsqueda no distingue mayúsculas
        y coincide con nombres parciales.
      parameters:
      - description: Nombre de ciudad o nombre parcial a buscar
        example: Madrid
        in: query
        name: search
        required: true
        schema:
          type: string
      responses:
        '200':
          $ref: '#/components/responses/200City'
        '400':
          description: Falta el parámetro requerido 'search'
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Search cities by name
      tags:
      - poi
  /city/getNear:
    get:
      description: Devuelve información sobre la ciudad más cercana a las coordenadas
        proporcionadas, incluyendo nombre, código de país y ubicación.
      parameters:
      - description: Coordenada de latitud del punto para el cual buscar la ciudad
          más cercana.
        example: 40.416775
        in: query
        name: lat
        required: true
        schema:
          format: double
          maximum: 90
          minimum: -90
          type: number
      - description: Coordenada de longitud del punto para el cual buscar la ciudad
          más cercana.
        example: -3.70379
        in: query
        name: lng
        required: true
        schema:
          format: double
          maximum: 180
          minimum: -180
          type: number
      responses:
        '200':
          $ref: '#/components/responses/200City'
        '400':
          description: Parámetros requeridos faltantes o inválidos. `lat` y/o `lng`
            deben ser proporcionados y ser coordenadas geográficas válidas.
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Find nearest city to coordinates
      tags:
      - poi
  /costs:
    get:
      description: '# 🛣️ Cálculo de Costos de Ruta


        Calcula los costos completos de una ruta de transporte, incluyendo:

        - **Peajes**: Costos detallados por peaje individual

        - **Combustible**: Consumo y costo basado en tipo de vehículo y carga

        - **Mantenimiento**: Desgaste de componentes del vehículo

        - **Tiempo**: Duración del viaje con descansos legales incluidos

        - **Emisiones**: Cálculo de CO2 emitido durante el trayecto


        ## 🎯 Casos de Uso


        - **Transportistas**: Presupuestar costos de operación

        - **Logística**: Comparar rutas alternativas

        - **Finanzas**: Predecir costos operativos

        - **Sostenibilidad**: Reportar huella de carbono


        ## 📝 Ejemplos Prácticos


        ### Ejemplo Básico (Sin Autenticación)

        ```http

        GET /costs?origin={"lat":41.3851,"lng":2.1734}&destination={"lat":40.4168,"lng":-3.7038}

        ```


        ### Ejemplo Avanzado (Con Autenticación)

        ```http

        GET /costs?origin={"lat":41.3851,"lng":2.1734}&destination={"lat":40.4168,"lng":-3.7038}&with_tolls=true&avg_consumption=35&cargo_weight=15000&fuel_type=diesel

        Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

        ```


        ### Ejemplo con Horarios Específicos

        ```http

        GET /costs?origin={"lat":41.3851,"lng":2.1734}&destination={"lat":40.4168,"lng":-3.7038}&departure=2025-05-15T08:00:00&arrival=2025-05-15T18:00:00

        ```


        ## ⚠️ Consideraciones Importantes


        - **Formato Coordenadas**: Usar formato JSON con lat/lng en grados decimales

        - **Distancia Máxima**: 1,000 km entre origen y destino

        - **Cache**: Resultados cacheados 24 horas para rutas idénticas

        - **Tiempos Legales**: Se incluyen descansos obligatorios cada 4.5 horas

        - **Moneda**: Todos los costos en EUR (Euros)

        '
      operationId: getRouteCosts
      parameters:
      - $ref: '#/components/parameters/AuthorizationHeader'
      - description: '**📍 Punto de Origen**


          Coordenadas de inicio de la ruta en formato JSON.


          **Formato**: `{"lat": número, "lng": número}`

          **Sistema**: WGS84 (grados decimales)

          **Precisión**: 6 decimales recomendados


          **Ejemplos**:

          - Barcelona: `{"lat":41.3851,"lng":2.1734}`

          - Madrid: `{"lat":40.4168,"lng":-3.7038}`

          - Valencia: `{"lat":39.4699,"lng":-0.3763}`


          **Límites**:

          - Latitud: -90 a 90

          - Longitud: -180 a 180

          '
        example: '{"lat":41.3851,"lng":2.1734}'
        in: query
        name: origin
        required: true
        schema:
          pattern: ^\{"lat":-?\d+(\.\d+)?,"lng":-?\d+(\.\d+)?\}$
          type: string
      - description: '**🎯 Punto de Destino**


          Coordenadas de destino de la ruta en formato JSON.


          Mismos requisitos y formato que el parámetro `origin`.


          **Validación**: La distancia entre origen y destino no puede exceder 1,000
          km.

          '
        example: '{"lat":40.4168,"lng":-3.7038}'
        in: query
        name: destination
        required: true
        schema:
          pattern: ^\{"lat":-?\d+(\.\d+)?,"lng":-?\d+(\.\d+)?\}$
          type: string
      - description: '**🕐 Hora de Salida**


          Fecha y hora de inicio del viaje. Usado para calcular peajes con tarifas
          variables por horario.


          **Formato**: ISO 8601 `YYYY-MM-DDTHH:MM:SS`

          **Zona Horaria**: Se asume la zona horaria del servidor si no se especifica

          **Valor por Defecto**: Hora actual del servidor


          **Ejemplos**:

          - Mañana a las 8:00: `2025-05-15T08:00:00`

          - Tarde a las 14:30: `2025-05-15T14:30:00`

          '
        example: '2025-05-15T08:00:00'
        in: query
        name: departure
        required: false
        schema:
          format: date-time
          pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$
          type: string
      - description: '**🕔 Hora de Llegada Estimada**


          Fecha y hora estimada de llegada. Si se especifica, se usa para cálculo
          de peajes nocturnos.


          **Nota**: No puede usarse simultáneamente con `departure`

          **Comportamiento**: Si se especifica `arrival`, se ignora `departure`

          '
        example: '2025-05-15T12:30:00'
        in: query
        name: arrival
        required: false
        schema:
          format: date-time
          pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$
          type: string
      - description: '**💰 Incluir Detalles de Peajes**


          Controla si se incluye información detallada de cada peaje en la respuesta.


          - `true`: Incluye array `tolls` con detalles de cada peaje

          - `false`: Solo incluye costo total en `total_tolls`


          **Recomendación**: Usar `true` para análisis detallado, `false` para respuestas
          más rápidas.

          '
        example: true
        in: query
        name: with_tolls
        required: false
        schema:
          default: true
          type: boolean
      - description: '**🔄 Incluir Puntos Intermedios**


          Incluye puntos de la ruta para visualización en mapas.


          **Costo**: Aumenta ligeramente el tiempo de respuesta

          **Uso**: Principalmente para interfaces gráficas

          '
        example: false
        in: query
        name: with_points
        required: false
        schema:
          default: false
          type: boolean
      - description: '**⛽ Consumo Medio de Combustible**


          Consumo del vehículo en litros por cada 100 km.


          **Valores Típicos**:

          - Camión ligero: 25-35 L/100km

          - Camión pesado: 35-45 L/100km

          - Tráiler: 40-55 L/100km


          **Impacto**: Afecta directamente los costos de combustible y mantenimiento

          '
        example: 35
        in: query
        name: avg_consumption
        required: false
        schema:
          default: 35
          format: float
          maximum: 100
          minimum: 20
          type: number
      - description: '**📦 Peso de la Carga**


          Peso total de la mercancía transportada en kilogramos.


          **Efecto**: Aumenta el consumo de combustible aproximadamente 0.02 L/100km
          por kg

          **Ejemplos**:

          - Carga ligera: 5,000 kg

          - Carga media: 15,000 kg

          - Carga completa: 25,000 kg

          '
        example: 15000
        in: query
        name: cargo_weight
        required: false
        schema:
          format: float
          maximum: 50000
          minimum: 0
          type: number
      - description: '**🔥 Tipo de Combustible**


          Tipo de combustible utilizado por el vehículo.


          **Opciones**:

          - `diesel`: Gasóleo (predeterminado)

          - `gasoline`: Gasolina

          - `gnv`: Gas Natural Vehicular


          **Impacto**: Afecta el cálculo de emisiones de CO2 y costos de combustible

          '
        example: diesel
        in: query
        name: fuel_type
        required: false
        schema:
          default: diesel
          enum:
          - diesel
          - gasoline
          - gnv
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RouteCostResponse'
          description: Cálculo exitoso de costos de peaje
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Parámetros inválidos o faltantes
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: No autorizado (token inválido o faltante)
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Error interno del servidor
      security:
      - bearerAuth: []
      summary: 📊 Calcular costos completos de ruta
      tags:
      - tolls
  /current:
    get:
      description: Obtener las condiciones meteorológicas en tiempo real para su ubicación
        exacta.
      operationId: getCurrent
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Condiciones meteorológicas actuales recuperadas exitosamente.
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get current weather
      tags:
      - weather
  /discounts:
    get:
      description: 'Recupera una lista paginada de descuentos con opciones de filtrado
        por estado, estación o usuario.

        '
      operationId: getAllDiscounts
      parameters:
      - description: Número de página para paginación
        in: query
        name: page
        required: false
        schema:
          default: 1
          example: 1
          minimum: 1
          type: integer
      - description: Límite de resultados por página
        in: query
        name: limit
        required: false
        schema:
          default: 10
          example: 10
          maximum: 100
          minimum: 1
          type: integer
      - description: Filtrar por estado activo/inactivo
        in: query
        name: isActive
        required: false
        schema:
          example: true
          type: boolean
      - description: Filtrar por identificador de estación
        in: query
        name: station
        required: false
        schema:
          example: 5f8d3b7b3f6b8a1b9c3b7b3f
          type: string
      - description: Filtrar por identificador de usuario
        in: query
        name: userId
        required: false
        schema:
          example: user123
          type: string
      responses:
        '200':
          content:
            application/json:
              example:
                data:
                - centsValue: 0
                  createdAt: '2025-04-29T10:30:00Z'
                  id_discount: '123456789'
                  isActive: true
                  percentValue: 10.0
                  selected: false
                  station: 5f8d3b7b3f6b8a1b9c3b7b3f
                  type: percentage
                  updatedAt: '2025-04-29T10:30:00Z'
                  userId: user123
                pagination:
                  limit: 10
                  page: 1
                  pages: 5
                  total: 50
                success: true
              schema:
                $ref: '#/components/schemas/DiscountResponse'
          description: Lista de descuentos recuperada exitosamente
        '500':
          content:
            application/json:
              example:
                error: Error fetching discounts
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al obtener los descuentos
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Obtener todos los descuentos
      tags:
      - stations
    post:
      description: 'Crea un nuevo descuento asociado a una estación de servicio. Los
        descuentos pueden ser de tipo porcentaje o céntimos y pueden estar activos
        o inactivos.

        '
      operationId: createDiscount
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              properties:
                centsValue:
                  description: Valor del descuento en céntimos
                  example: 0.15
                  format: float
                  minimum: 0
                  type: number
                id:
                  description: Identificador único del descuento (opcional, se genera
                    automáticamente)
                  example: '123456789'
                  type: string
                isActive:
                  description: Indica si el descuento está activo
                  example: true
                  type: boolean
                percentValue:
                  description: Valor del descuento en porcentaje
                  example: 10.0
                  format: float
                  maximum: 100
                  minimum: 0
                  type: number
                selected:
                  description: Indica si el descuento está seleccionado
                  example: false
                  type: boolean
                station:
                  description: Identificador de la estación asociada al descuento
                  example: 5f8d3b7b3f6b8a1b9c3b7b3f
                  type: string
                type:
                  description: Tipo de descuento
                  example: percentage
                  type: string
                userId:
                  description: Identificador del usuario propietario del descuento
                  example: user123
                  type: string
              required:
              - station
              - type
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              example:
                data:
                  centsValue: 0
                  createdAt: '2025-04-29T10:30:00Z'
                  id_discount: '123456789'
                  isActive: true
                  percentValue: 10.0
                  selected: false
                  station: 5f8d3b7b3f6b8a1b9c3b7b3f
                  type: percentage
                  updatedAt: '2025-04-29T10:30:00Z'
                  userId: user123
                message: Discount created successfully
                success: true
              schema:
                $ref: '#/components/schemas/DiscountResponse'
          description: Descuento creado exitosamente
        '400':
          content:
            application/json:
              example:
                error: Invalid discount data
              schema:
                $ref: '#/components/schemas/Error'
          description: Datos de descuento inválidos
        '500':
          content:
            application/json:
              example:
                error: Error creating discount
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al crear el descuento
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Crear un nuevo descuento
      tags:
      - stations
  /discounts/sync:
    post:
      description: 'Sincroniza múltiples descuentos desde el almacenamiento local
        del cliente al servidor. Útil para mantener la consistencia entre dispositivos.

        '
      operationId: syncDiscountsFromLocalStorage
      parameters: []
      requestBody:
        content:
          application/json:
            example:
              discounts:
              - centsValue: 0
                id: '123456789'
                isActive: true
                percentValue: 10.0
                selected: false
                station: 5f8d3b7b3f6b8a1b9c3b7b3f
                type: percentage
              userId: user123
            schema:
              $ref: '#/components/schemas/SyncDiscountRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              example:
                data:
                - centsValue: 0
                  createdAt: '2025-04-29T10:30:00Z'
                  id_discount: '123456789'
                  isActive: true
                  percentValue: 10.0
                  selected: false
                  station: 5f8d3b7b3f6b8a1b9c3b7b3f
                  type: percentage
                  updatedAt: '2025-04-29T10:30:00Z'
                  userId: user123
                message: Discounts synced successfully
                success: true
                synced: 1
              schema:
                $ref: '#/components/schemas/DiscountResponse'
          description: Descuentos sincronizados exitosamente
        '400':
          content:
            application/json:
              example:
                error: Discounts must be an array
              schema:
                $ref: '#/components/schemas/Error'
          description: Datos de sincronización inválidos
        '500':
          content:
            application/json:
              example:
                error: Error syncing discounts
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al sincronizar descuentos
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Sincronizar descuentos desde almacenamiento local
      tags:
      - stations
  /discounts/{id}:
    delete:
      description: 'Elimina permanentemente un descuento identificado por su ID.

        '
      operationId: deleteDiscount
      parameters:
      - description: Identificador único del descuento
        in: path
        name: id
        required: true
        schema:
          example: '123456789'
          type: string
      responses:
        '200':
          content:
            application/json:
              example:
                data:
                  centsValue: 0
                  createdAt: '2025-04-29T10:30:00Z'
                  id_discount: '123456789'
                  isActive: true
                  percentValue: 10.0
                  selected: false
                  station: 5f8d3b7b3f6b8a1b9c3b7b3f
                  type: percentage
                  updatedAt: '2025-04-29T10:30:00Z'
                  userId: user123
                message: Discount deleted successfully
                success: true
              schema:
                $ref: '#/components/schemas/DiscountResponse'
          description: Descuento eliminado exitosamente
        '404':
          content:
            application/json:
              example:
                error: Discount not found
              schema:
                $ref: '#/components/schemas/Error'
          description: Descuento no encontrado
        '500':
          content:
            application/json:
              example:
                error: Error deleting discount
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al eliminar el descuento
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Eliminar descuento
      tags:
      - stations
    get:
      description: 'Recupera un descuento específico por su identificador único.

        '
      operationId: getDiscountById
      parameters:
      - description: Identificador único del descuento
        in: path
        name: id
        required: true
        schema:
          example: '123456789'
          type: string
      responses:
        '200':
          content:
            application/json:
              example:
                data:
                  centsValue: 0
                  createdAt: '2025-04-29T10:30:00Z'
                  id_discount: '123456789'
                  isActive: true
                  percentValue: 10.0
                  selected: false
                  station: 5f8d3b7b3f6b8a1b9c3b7b3f
                  type: percentage
                  updatedAt: '2025-04-29T10:30:00Z'
                  userId: user123
                success: true
              schema:
                $ref: '#/components/schemas/DiscountResponse'
          description: Descuento encontrado
        '404':
          content:
            application/json:
              example:
                error: Discount not found
              schema:
                $ref: '#/components/schemas/Error'
          description: Descuento no encontrado
        '500':
          content:
            application/json:
              example:
                error: Error fetching discount
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al obtener el descuento
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Obtener descuento por ID
      tags:
      - stations
    put:
      description: 'Actualiza un descuento existente identificado por su ID.

        '
      operationId: updateDiscount
      parameters:
      - description: Identificador único del descuento
        in: path
        name: id
        required: true
        schema:
          example: '123456789'
          type: string
      requestBody:
        content:
          application/json:
            schema:
              properties:
                centsValue:
                  description: Valor del descuento en céntimos
                  example: 0.15
                  format: float
                  minimum: 0
                  type: number
                isActive:
                  description: Indica si el descuento está activo
                  example: true
                  type: boolean
                percentValue:
                  description: Valor del descuento en porcentaje
                  example: 10.0
                  format: float
                  maximum: 100
                  minimum: 0
                  type: number
                selected:
                  description: Indica si el descuento está seleccionado
                  example: false
                  type: boolean
                station:
                  description: Identificador de la estación asociada al descuento
                  example: 5f8d3b7b3f6b8a1b9c3b7b3f
                  type: string
                type:
                  description: Tipo de descuento
                  example: percentage
                  type: string
                userId:
                  description: Identificador del usuario propietario del descuento
                  example: user123
                  type: string
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              example:
                data:
                  centsValue: 0
                  createdAt: '2025-04-29T10:30:00Z'
                  id_discount: '123456789'
                  isActive: true
                  percentValue: 10.0
                  selected: false
                  station: 5f8d3b7b3f6b8a1b9c3b7b3f
                  type: percentage
                  updatedAt: '2025-04-29T10:30:00Z'
                  userId: user123
                message: Discount updated successfully
                success: true
              schema:
                $ref: '#/components/schemas/DiscountResponse'
          description: Descuento actualizado exitosamente
        '404':
          content:
            application/json:
              example:
                error: Discount not found
              schema:
                $ref: '#/components/schemas/Error'
          description: Descuento no encontrado
        '500':
          content:
            application/json:
              example:
                error: Error updating discount
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al actualizar el descuento
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Actualizar descuento existente
      tags:
      - stations
  /drivers:
    get:
      description: '**Obtiene una lista paginada de todos los conductores** asociados
        al usuario autenticado.


        ## 📋 Funcionalidades

        - **Paginación**: Soporta parámetros `page` y `pageSize`

        - **Ordenamiento**: Por `name`, `createdAt`, `updatedAt` (por defecto: más
        recientes primero)

        - **Filtros**: Por nombre, estado habilitado, etc.

        - **Aislamiento**: Solo muestra conductores del usuario propietario


        ## 🔍 Parámetros de consulta

        - `page`: Número de página (por defecto: 1)

        - `pageSize`: Resultados por página (máximo: 100)

        - `sort`: Orden (`asc`/`desc`)

        - `lang`: Idioma de respuesta (`es` por defecto)


        ## 📊 Respuesta

        Lista de objetos `Driver` con información completa incluyendo posición actual,
        estado de horas y historial reciente.

        '
      operationId: getDrivers
      parameters:
      - $ref: '#/components/parameters/Page'
      - $ref: '#/components/parameters/PageSize'
      - $ref: '#/components/parameters/Sort'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              schema:
                example:
                  data:
                  - _id: 5f8d3b7b3f6b8a1b9c3b7b3f
                    currentPosition:
                      coordinates:
                      - -3.7038
                      - 40.4168
                      timestamp: '2025-04-30T10:30:00.000Z'
                    enabled: true
                    hoursStatus:
                      remainingDayHours: 3.5
                      remainingWeeklyHours: 12.5
                    licenseNumber: 12345678X
                    name: Juan Pérez García
                    owner: 63d7907cbe76403b35da63df
                  - _id: 5f8d3b7b3f6b8a1b9c3b7b4f
                    currentPosition:
                      coordinates:
                      - -3.71
                      - 40.42
                      timestamp: '2025-04-30T09:15:00.000Z'
                    enabled: true
                    hoursStatus:
                      remainingDayHours: 2.0
                      remainingWeeklyHours: 8.0
                    licenseNumber: 87654321Y
                    name: María López Ruiz
                    owner: 63d7907cbe76403b35da63df
                  pagination:
                    page: 1
                    pageSize: 10
                    total: 2
                    totalPages: 1
                properties:
                  data:
                    items:
                      $ref: '#/components/schemas/Driver'
                    type: array
                  pagination:
                    properties:
                      page:
                        example: 1
                        type: integer
                      pageSize:
                        example: 10
                        type: integer
                      total:
                        example: 150
                        type: integer
                      totalPages:
                        example: 15
                        type: integer
                    type: object
                type: object
          description: Lista de conductores obtenida exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Listar conductores
      tags:
      - drivers
    post:
      description: '**Crea un nuevo conductor** en el sistema.


        ## 📝 Requisitos

        - Nombre completo (3-100 caracteres)

        - Número de licencia válido (único en el sistema)

        - El conductor se asocia automáticamente al usuario autenticado


        ## ✅ Validaciones

        - Número de licencia debe ser único

        - Formato de licencia válido según país

        - Máximo 1000 conductores por usuario


        ## 📊 Respuesta

        Objeto `Driver` completo con ID generado y campos por defecto.

        '
      operationId: createDriver
      parameters:
      - $ref: '#/components/parameters/Language'
      requestBody:
        content:
          application/json:
            example:
              licenseNumber: 12345678X
              name: Juan Pérez García
            schema:
              properties:
                licenseNumber:
                  example: 12345678X
                  maxLength: 20
                  minLength: 8
                  type: string
                name:
                  example: Juan Pérez García
                  maxLength: 100
                  minLength: 3
                  type: string
              required:
              - name
              - licenseNumber
              type: object
        required: true
      responses:
        '201':
          content:
            application/json:
              example:
                _id: 5f8d3b7b3f6b8a1b9c3b7b3f
                currentPosition: null
                drivingHistory: []
                enabled: true
                hoursStatus:
                  lastDrivingStart: null
                  lastRestStart: null
                  remainingBiweeklyHours: 90.0
                  remainingDayHours: 9.0
                  remainingWeeklyHours: 48.0
                licenseNumber: 12345678X
                name: Juan Pérez García
                owner: 63d7907cbe76403b35da63df
                positionHistory: []
              schema:
                $ref: '#/components/schemas/Driver'
          description: Conductor creado exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          content:
            application/json:
              example:
                error: El número de licencia ya está registrado
              schema:
                $ref: '#/components/schemas/Error'
          description: Conflicto - Número de licencia ya existe
        '429':
          content:
            application/json:
              example:
                error: Límite de consultas de posición excedido (10 por segundo)
              schema:
                $ref: '#/components/schemas/Error'
          description: Límite de consultas excedido
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Crear conductor
      tags:
      - drivers
  /drivers/{id}:
    delete:
      description: '**Elimina permanentemente un conductor del sistema**.


        ## ⚠️ Operación destructiva

        - **No se puede deshacer**

        - Elimina todos los datos asociados (posiciones, horas, historial)


        ## 🔒 Restricciones

        - Solo el propietario puede eliminar

        - Conductor debe existir

        - No elimina datos de registros históricos de tacógrafo


        ## 📋 Consecuencias

        - Se pierden todas las posiciones históricas

        - Se pierden todos los registros de horas

        - El conductor desaparece de todas las búsquedas

        '
      operationId: deleteDriver
      responses:
        '204':
          description: Conductor eliminado exitosamente (sin contenido)
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          content:
            application/json:
              example:
                error: Conductor no encontrado o sin permisos de acceso
              schema:
                $ref: '#/components/schemas/Error'
          description: Conductor no encontrado
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Eliminar conductor
      tags:
      - drivers
    get:
      description: '**Obtiene la información completa de un conductor específico**.


        ## 🔍 Detalles incluidos

        - Información personal y de licencia

        - Posición actual y estado de horas

        - Historial de posiciones (últimos 30 días)

        - Historial de conducción (últimos 6 meses)


        ## 🔒 Permisos

        - Solo el propietario del conductor puede acceder

        - Validación automática por campo `owner`

        '
      operationId: getDriverById
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Driver'
          description: Información del conductor obtenida exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          content:
            application/json:
              example:
                error: Conductor no encontrado o sin permisos de acceso
              schema:
                $ref: '#/components/schemas/Error'
          description: Conductor no encontrado
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Obtener conductor por ID
      tags:
      - drivers
    put:
      description: '**Actualiza la información de un conductor existente**.


        ## 📝 Campos actualizables

        - `name`: Nombre completo

        - `enabled`: Estado de habilitación

        - `licenseNumber`: **No se puede cambiar** (requiere eliminación y recreación)


        ## ✅ Validaciones

        - El conductor debe existir y pertenecer al usuario

        - Nombre debe tener 3-100 caracteres

        - Solo campos permitidos pueden actualizarse


        ## ⚠️ Consideraciones

        - Cambiar `enabled` a `false` deshabilita el conductor para nuevas rutas

        - No afecta datos históricos existentes

        '
      operationId: updateDriver
      requestBody:
        content:
          application/json:
            example:
              enabled: true
              name: Juan Pérez García
            schema:
              properties:
                enabled:
                  example: true
                  type: boolean
                name:
                  example: Juan Pérez García
                  maxLength: 100
                  minLength: 3
                  type: string
              required:
              - name
              - enabled
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Driver'
          description: Conductor actualizado exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          content:
            application/json:
              example:
                error: Conductor no encontrado o sin permisos de acceso
              schema:
                $ref: '#/components/schemas/Error'
          description: Conductor no encontrado
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Actualizar conductor
      tags:
      - drivers
  /drivers/{id}/daily-limits:
    get:
      description: '**Obtiene el estado actual de las horas de conducción del conductor**.


        ## ⏱️ Información proporcionada

        - Horas restantes disponibles hoy

        - Horas restantes esta semana (lunes-domingo)

        - Horas restantes en el periodo bimensual

        - Fecha del último descanso obligatorio

        - Fecha del último inicio de conducción


        ## 📊 Cálculos automáticos

        - Basados en registros de actividad de conducción

        - Cumplimiento normativo UE (Reglamento 561/2006)

        - Actualización en tiempo real con cada registro de actividad


        ## ⚠️ Alertas y límites

        - **Alerta**: Cuando quedan < 4 horas semanales

        - **Máximo diario**: 9 horas (10 horas máximo 2 veces por semana)

        - **Máximo semanal**: 48 horas (56 horas máximo 2 veces cada 4 semanas)

        - **Máximo bimensual**: 90 horas


        ## 🔄 Actualización

        - Se recalcula automáticamente con cada registro de actividad

        - Incluye pausas obligatorias de 45 minutos cada 4.5 horas

        - Descansos diarios mínimos de 11 horas

        '
      operationId: getDriverDailyLimits
      responses:
        '200':
          content:
            application/json:
              schema:
                example:
                  compliance:
                    isCompliant: true
                    warnings: []
                  lastDrivingStart: '2025-04-30T08:00:00.000Z'
                  lastRestStart: '2025-04-30T21:00:00.000Z'
                  remainingBiweeklyHours: 45.0
                  remainingDayHours: 3.5
                  remainingWeeklyHours: 12.5
                properties:
                  compliance:
                    properties:
                      isCompliant:
                        description: Cumple normativa actual
                        example: true
                        type: boolean
                      warnings:
                        description: Alertas de cumplimiento
                        example:
                        - Próximo descanso obligatorio en 2 horas
                        items:
                          type: string
                        type: array
                    type: object
                  lastDrivingStart:
                    description: Último inicio de conducción
                    example: '2025-04-30T08:00:00.000Z'
                    format: date-time
                    type: string
                  lastRestStart:
                    description: Último inicio de descanso
                    example: '2025-04-30T21:00:00.000Z'
                    format: date-time
                    type: string
                  remainingBiweeklyHours:
                    description: Horas restantes en periodo bimensual
                    example: 45.0
                    type: number
                  remainingDayHours:
                    description: Horas restantes hoy
                    example: 3.5
                    type: number
                  remainingWeeklyHours:
                    description: Horas restantes esta semana
                    example: 12.5
                    type: number
                type: object
          description: Límites diarios obtenidos exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          content:
            application/json:
              example:
                error: Conductor no encontrado o sin permisos de acceso
              schema:
                $ref: '#/components/schemas/Error'
          description: Conductor no encontrado
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Obtener límites diarios de horas del conductor
      tags:
      - drivers
  /drivers/{id}/hours-history:
    post:
      description: '**Registra una nueva actividad de conducción o descanso para el
        conductor**.


        ## 📝 Tipos de actividad

        - **`driving`**: Periodo de conducción activa

        - **`rest`**: Periodo de descanso obligatorio


        ## ⏱️ Requisitos de registro

        - **Timestamp de inicio**: Fecha y hora de inicio de la actividad

        - **Timestamp de fin**: Opcional, si la actividad continúa

        - **Coordenadas**: Opcional para actividades de conducción (máximo 50 puntos)

        - **Tipo de actividad**: `driving` o `rest`


        ## ✅ Validaciones

        - No superposición con actividades existentes

        - Duración máxima de conducción continua: 4.5 horas

        - Descanso mínimo diario: 11 horas

        - Coordenadas válidas si proporcionadas


        ## 🔄 Efectos automáticos

        - Recalcula límites de horas disponibles

        - Actualiza estado de cumplimiento normativo

        - Genera alertas si se aproximan límites

        - Registra en historial permanente

        '
      operationId: addDriverHoursHistory
      requestBody:
        content:
          application/json:
            example:
              coordinates:
              - - -3.7038
                - 40.4168
              - - -3.71
                - 40.42
              - - -3.715
                - 40.425
              endTime: '2025-04-30T12:30:00.000Z'
              startTime: '2025-04-30T08:00:00.000Z'
              type: driving
            schema:
              $ref: '#/components/schemas/DriverActivity'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                example:
                  activity:
                    coordinates:
                    - - -3.7038
                      - 40.4168
                    - - -3.71
                      - 40.42
                    endTime: '2025-04-30T12:30:00.000Z'
                    startTime: '2025-04-30T08:00:00.000Z'
                    type: driving
                  message: Actividad de conducción registrada correctamente
                  success: true
                  updatedLimits:
                    compliance:
                      isCompliant: true
                      warnings: []
                    remainingDayHours: 2.5
                    remainingWeeklyHours: 8.0
                properties:
                  activity:
                    $ref: '#/components/schemas/DriverActivity'
                  message:
                    example: Actividad de conducción registrada correctamente
                    type: string
                  success:
                    example: true
                    type: boolean
                  updatedLimits:
                    properties:
                      compliance:
                        properties:
                          isCompliant:
                            example: true
                            type: boolean
                          warnings:
                            example: []
                            items:
                              type: string
                            type: array
                        type: object
                      remainingDayHours:
                        example: 2.5
                        type: number
                      remainingWeeklyHours:
                        example: 8.0
                        type: number
                    type: object
                type: object
          description: Actividad registrada exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          content:
            application/json:
              example:
                error: Conductor no encontrado o sin permisos de acceso
              schema:
                $ref: '#/components/schemas/Error'
          description: Conductor no encontrado
        '409':
          content:
            application/json:
              example:
                error: La actividad se superpone con registros existentes
              schema:
                $ref: '#/components/schemas/Error'
          description: Conflicto de horarios
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Registrar actividad de horas del conductor
      tags:
      - drivers
  /drivers/{id}/position:
    get:
      description: '**Obtiene la posición GPS más reciente del conductor**.


        ## 📍 Información proporcionada

        - Coordenadas actuales (longitud, latitud)

        - Timestamp exacto de la última actualización

        - Precisión GPS (cuando disponible)


        ## ⏰ Actualización

        - Las posiciones se actualizan automáticamente cada 5 minutos

        - Si no hay posición reciente (>30 min), se devuelve `null`


        ## 📊 Uso típico

        - Mostrar ubicación en mapas en tiempo real

        - Calcular tiempos estimados de llegada

        - Verificar si el conductor está en ruta activa

        '
      operationId: getDriverPosition
      responses:
        '200':
          content:
            application/json:
              example:
                accuracy: 10
                coordinates:
                - -3.7038
                - 40.4168
                timestamp: '2025-04-30T10:30:00.000Z'
              schema:
                nullable: true
                properties:
                  accuracy:
                    description: Precisión GPS en metros (opcional)
                    example: 10
                    type: number
                  coordinates:
                    description: '[longitud, latitud]'
                    example:
                    - -3.7038
                    - 40.4168
                    items:
                      format: float
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  timestamp:
                    description: Fecha y hora de la posición
                    example: '2025-04-30T10:30:00.000Z'
                    format: date-time
                    type: string
                type: object
          description: Posición obtenida exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          content:
            application/json:
              example:
                error: Conductor no encontrado o sin permisos de acceso
              schema:
                $ref: '#/components/schemas/Error'
          description: Conductor no encontrado
        '429':
          content:
            application/json:
              example:
                error: Límite de consultas de posición excedido (10 por segundo)
              schema:
                $ref: '#/components/schemas/Error'
          description: Límite de consultas excedido
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Obtener posición actual del conductor
      tags:
      - drivers
    put:
      description: '**Actualiza la posición GPS actual del conductor**.


        ## 📍 Requisitos

        - Coordenadas válidas (latitud -90/+90, longitud -180/+180)

        - Timestamp no futuro (máximo 5 minutos de diferencia)

        - Precisión mínima: 6 decimales (~11cm)


        ## ⏱️ Limitaciones

        - **Máximo 1 actualización por minuto** por conductor

        - Las actualizaciones se almacenan automáticamente en el historial

        - Posiciones antiguas (>30 días) se purgan automáticamente


        ## 🔄 Efectos

        - Actualiza `currentPosition` del conductor

        - Agrega entrada al `positionHistory`

        - Recalcula estado de horas si está en actividad de conducción

        '
      operationId: updateDriverPosition
      requestBody:
        content:
          application/json:
            example:
              coordinates:
              - -3.7038
              - 40.4168
              timestamp: '2025-04-30T10:30:00.000Z'
            schema:
              $ref: '#/components/schemas/PositionUpdate'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  message:
                    example: Posición actualizada correctamente
                    type: string
                  position:
                    $ref: '#/components/schemas/PositionUpdate'
                  success:
                    example: true
                    type: boolean
                type: object
          description: Posición actualizada exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          content:
            application/json:
              example:
                error: Conductor no encontrado o sin permisos de acceso
              schema:
                $ref: '#/components/schemas/Error'
          description: Conductor no encontrado
        '429':
          content:
            application/json:
              example:
                error: Límite de actualizaciones de posición excedido (1 por minuto)
              schema:
                $ref: '#/components/schemas/Error'
          description: Límite de actualizaciones excedido
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Actualizar posición del conductor
      tags:
      - drivers
  /drivers/{id}/positions:
    get:
      description: '**Obtiene el historial completo de posiciones GPS del conductor**.


        ## 📊 Información incluida

        - Todas las posiciones registradas en el rango de fechas

        - Coordenadas y timestamps exactos

        - Información de precisión GPS (cuando disponible)


        ## 📅 Rangos de fecha

        - **Por defecto**: Últimas 24 horas

        - **Máximo**: 30 días por consulta

        - **Formato**: ISO 8601 (`YYYY-MM-DD` o `YYYY-MM-DDTHH:mm:ssZ`)


        ## 📈 Paginación

        - Máximo 1000 posiciones por página

        - Ordenadas por timestamp descendente (más recientes primero)

        - Total disponible en metadatos de paginación


        ## 💾 Retención

        - Las posiciones se retienen por **30 días**

        - Posiciones más antiguas se eliminan automáticamente

        - Datos históricos se usan para auditoría y reconstrucción de rutas

        '
      operationId: getDriverPositions
      responses:
        '200':
          content:
            application/json:
              schema:
                example:
                  data:
                  - accuracy: 10
                    coordinates:
                    - -3.7038
                    - 40.4168
                    timestamp: '2025-04-30T10:30:00.000Z'
                  - accuracy: 8
                    coordinates:
                    - -3.71
                    - 40.42
                    timestamp: '2025-04-30T10:25:00.000Z'
                  - accuracy: 12
                    coordinates:
                    - -3.715
                    - 40.425
                    timestamp: '2025-04-30T10:20:00.000Z'
                  pagination:
                    page: 1
                    pageSize: 100
                    total: 1250
                    totalPages: 13
                properties:
                  data:
                    items:
                      properties:
                        accuracy:
                          description: Precisión GPS en metros
                          example: 10
                          type: number
                        coordinates:
                          example:
                          - -3.7038
                          - 40.4168
                          items:
                            format: float
                            type: number
                          maxItems: 2
                          minItems: 2
                          type: array
                        timestamp:
                          example: '2025-04-30T10:30:00.000Z'
                          format: date-time
                          type: string
                      type: object
                    type: array
                  pagination:
                    properties:
                      page:
                        example: 1
                        type: integer
                      pageSize:
                        example: 100
                        type: integer
                      total:
                        example: 1250
                        type: integer
                      totalPages:
                        example: 13
                        type: integer
                    type: object
                type: object
          description: Historial de posiciones obtenido exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          content:
            application/json:
              example:
                error: Conductor no encontrado o sin permisos de acceso
              schema:
                $ref: '#/components/schemas/Error'
          description: Conductor no encontrado
        '429':
          content:
            application/json:
              example:
                error: Límite de consultas de posición excedido (10 por segundo)
              schema:
                $ref: '#/components/schemas/Error'
          description: Límite de consultas excedido
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Obtener historial de posiciones del conductor
      tags:
      - drivers
  /events:
    get:
      description: 'Obtiene eventos de tráfico en tiempo real cerca de una ubicación,
        con opciones de filtrado avanzado.


        **Ejemplo de uso completo:**

        ```

        GET /events?lat=40.4168&lng=-3.7038&radius=10000&types[]=ACCIDENT&types[]=ROAD_CLOSURE&minSeverity=30&vehicleTypes[]=TRUCK&lang=es

        ```


        **Filtros disponibles:**

        - `types`: Tipos de eventos a incluir

        - `minSeverity`: Severidad mínima (0-100)

        - `vehicleTypes`: Tipos de vehículos afectados

        - `lang`: Idioma de la respuesta


        **Ejemplo práctico:**

        Una empresa de logística filtra solo eventos que afecten a camiones con severidad
        alta.

        '
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Radius'
      - description: Tipos de eventos a incluir en la búsqueda
        in: query
        name: types
        schema:
          items:
            enum:
            - ACCIDENT
            - ROAD_CLOSURE
            - SPECIAL_EVENT
            type: string
          type: array
      - description: Severidad mínima del evento (0-100)
        in: query
        name: minSeverity
        schema:
          maximum: 100
          minimum: 0
          type: number
      - description: Tipos de vehículos afectados por el evento
        in: query
        name: vehicleTypes
        schema:
          items:
            type: string
          type: array
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              examples:
                eventos_actuales:
                  summary: Eventos actuales encontrados
                  value:
                  - affectedVehicleTypes:
                    - CAR
                    - BUS
                    createdAt: '2024-01-15T16:00:00Z'
                    expectedDuration: 240
                    id: evt_11111
                    location:
                      lat: 40.4168
                      lon: -3.7038
                    severity: 60
                    type: SPECIAL_EVENT
                    updatedAt: '2024-01-15T16:00:00Z'
              schema:
                items:
                  $ref: '#/components/schemas/TrafficEvent'
                type: array
          description: Lista de eventos de tráfico actuales
        '400':
          description: Parámetros inválidos
        '401':
          description: No autorizado
        '500':
          description: Error interno del servidor
      security:
      - bearerAuth: []
      summary: Consultar eventos de tráfico actuales
      tags:
      - traffic
  /events/all:
    get:
      description: 'Recupera todos los eventos de tráfico disponibles sin filtros
        de ubicación.


        **Advertencia:** Este endpoint puede devolver grandes volúmenes de datos.


        **Ejemplo de uso:**

        ```

        GET /events/all

        ```


        **Casos de uso:**

        - Dashboards de monitorización general

        - Análisis global del estado del tráfico

        - Sistemas que necesitan datos completos sin filtros

        '
      responses:
        '200':
          content:
            application/json:
              examples:
                todos_eventos:
                  summary: Todos los eventos disponibles
                  value:
                  - affectedVehicleTypes:
                    - TRUCK
                    - CAR
                    createdAt: '2024-01-15T17:30:00Z'
                    expectedDuration: 90
                    id: evt_22222
                    location:
                      lat: 39.4699
                      lon: -0.3763
                    severity: 85
                    type: ACCIDENT
                    updatedAt: '2024-01-15T17:30:00Z'
                  - affectedVehicleTypes:
                    - ALL
                    createdAt: '2024-01-15T18:00:00Z'
                    expectedDuration: 360
                    id: evt_33333
                    location:
                      lat: 41.3851
                      lon: 2.1734
                    severity: 95
                    type: ROAD_CLOSURE
                    updatedAt: '2024-01-15T18:00:00Z'
              schema:
                items:
                  $ref: '#/components/schemas/TrafficEvent'
                type: array
          description: Lista completa de eventos de tráfico
        '401':
          description: No autorizado
        '500':
          description: Error interno del servidor
      security:
      - bearerAuth: []
      summary: Consultar todos los eventos de tráfico
      tags:
      - traffic
  /events/getAlongPath:
    post:
      description: "Identifica eventos de tráfico a lo largo de una ruta específica\
        \ definida por puntos GPS.\n\n**Ejemplo de uso:**\n```json\nPOST /events/getAlongPath\n\
        {\n  \"points\": [\n    [-3.7038, 40.4168],  // Madrid\n    [-2.1734, 41.3851],\
        \  // Barcelona\n    [-0.3763, 39.4699]   // Valencia\n  ],\n  \"lang\": \"\
        es\"\n}\n```\n\n**Características:**\n- Optimizado para rutas largas\n- Filtra\
        \ puntos cada 15km para eficiencia\n- Elimina duplicados automáticamente\n\
        \n**Uso en navegación:**\nIdeal para aplicaciones de navegación que necesitan\
        \ alertas de tráfico en ruta.\n"
      requestBody:
        content:
          application/json:
            schema:
              properties:
                lang:
                  default: es
                  description: Idioma para los datos de respuesta
                  enum:
                  - es
                  - en
                  - fr
                  - de
                  - pt
                  type: string
                points:
                  description: Array de pares de coordenadas [longitud, latitud]
                  items:
                    items:
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  minItems: 2
                  type: array
              required:
              - points
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              examples:
                eventos_ruta:
                  summary: Eventos a lo largo de la ruta
                  value:
                  - affectedVehicleTypes:
                    - TRUCK
                    - BUS
                    createdAt: '2024-01-15T20:00:00Z'
                    expectedDuration: 180
                    id: evt_55555
                    location:
                      lat: 40.4168
                      lon: -3.7038
                    severity: 80
                    type: ROAD_CLOSURE
                    updatedAt: '2024-01-15T20:00:00Z'
              schema:
                items:
                  $ref: '#/components/schemas/TrafficEvent'
                type: array
          description: Eventos encontrados a lo largo de la ruta
        '400':
          description: Ruta inválida
        '401':
          description: No autorizado
        '500':
          description: Error interno del servidor
      security:
      - bearerAuth: []
      summary: Consultar eventos a lo largo de una ruta
      tags:
      - traffic
  /events/historical:
    get:
      description: 'Recupera eventos de tráfico que ocurrieron en un rango de fechas
        específico.


        **Ejemplo de uso:**

        ```

        GET /events/historical?from=2024-01-01&to=2024-01-31&lat=40.4168&lng=-3.7038&radius=10000

        ```


        **Casos de uso:**

        - Análisis de patrones de tráfico en fechas pasadas

        - Investigación de incidentes ocurridos

        - Planificación basada en datos históricos


        **Parámetros importantes:**

        - `from` y `to`: Fechas en formato YYYY-MM-DD

        - `lat` y `lng`: Opcionales para filtrar por ubicación

        - `radius`: Radio de búsqueda cuando se especifica ubicación

        '
      parameters:
      - description: Fecha de inicio del rango (YYYY-MM-DD)
        in: query
        name: from
        required: true
        schema:
          format: date
          type: string
      - description: Fecha de fin del rango (YYYY-MM-DD)
        in: query
        name: to
        required: true
        schema:
          format: date
          type: string
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Radius'
      responses:
        '200':
          content:
            application/json:
              examples:
                ejemplo_historico:
                  summary: Eventos históricos encontrados
                  value:
                  - affectedVehicleTypes:
                    - CAR
                    - TRUCK
                    createdAt: '2024-01-15T10:30:00Z'
                    expectedDuration: 120
                    id: evt_12345
                    location:
                      lat: 40.4168
                      lon: -3.7038
                    severity: 75
                    type: ACCIDENT
                    updatedAt: '2024-01-15T12:30:00Z'
              schema:
                items:
                  $ref: '#/components/schemas/TrafficEvent'
                type: array
          description: Lista de eventos de tráfico históricos
        '400':
          description: Parámetros inválidos
        '401':
          description: No autorizado
        '500':
          description: Error interno del servidor
      security:
      - bearerAuth: []
      summary: Consultar eventos de tráfico históricos
      tags:
      - traffic
  /events/isochrone:
    post:
      description: "Calcula áreas alcanzables (isocronas) desde puntos específicos\
        \ a lo largo de una ruta en un tiempo determinado.\n\n**Ejemplo de uso:**\n\
        ```json\nPOST /events/isochrone\n{\n  \"points\": [\n    [-3.7038, 40.4168],\n\
        \    [-3.7138, 40.4268],\n    [-3.7238, 40.4368]\n  ]\n}\n```\n\nÚtil para\
        \ planificar rutas y determinar áreas de cobertura en tiempo real.\n"
      requestBody:
        content:
          application/json:
            schema:
              properties:
                points:
                  description: Array de pares de coordenadas [longitud, latitud] que
                    definen la ruta
                  items:
                    items:
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  minItems: 2
                  type: array
              required:
              - points
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              examples:
                ejemplo_isocronas:
                  summary: Isocronas calculadas
                  value:
                  - center:
                      lat: 40.4168
                      lon: -3.7038
                    createdAt: '2024-01-15T10:30:00Z'
                    id: iso_12345
                    polygon:
                    - lat: 40.4268
                      lon: -3.6938
                    - lat: 40.4068
                      lon: -3.6938
                    - lat: 40.4068
                      lon: -3.7138
                    - lat: 40.4268
                      lon: -3.7138
                    travelTime: 30
              schema:
                items:
                  $ref: '#/components/schemas/Isochrone'
                type: array
          description: Isocronas calculadas para la ruta
        '400':
          description: Ruta inválida o parámetros incorrectos
        '401':
          description: No autorizado
        '500':
          description: Error interno del servidor
      security:
      - bearerAuth: []
      summary: Calcular isocronas para una ruta
      tags:
      - traffic
  /events/nearby:
    get:
      description: 'Versión simplificada para obtener eventos cerca de una ubicación
        sin filtros complejos.


        **Ejemplo de uso rápido:**

        ```

        GET /events/nearby?lat=40.4168&lng=-3.7038&radius=5000&lang=es

        ```


        **Diferencia con /events:**

        - Menos parámetros de filtrado

        - Más rápido para consultas simples

        - Ideal para aplicaciones móviles con necesidades básicas

        '
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Radius'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              examples:
                eventos_cercanos:
                  summary: Eventos cercanos encontrados
                  value:
                  - affectedVehicleTypes:
                    - CAR
                    createdAt: '2024-01-15T19:00:00Z'
                    expectedDuration: 60
                    id: evt_44444
                    location:
                      lat: 40.4268
                      lon: -3.7138
                    severity: 70
                    type: ACCIDENT
                    updatedAt: '2024-01-15T19:00:00Z'
              schema:
                items:
                  $ref: '#/components/schemas/TrafficEvent'
                type: array
          description: Lista de eventos cercanos
        '400':
          description: Parámetros inválidos
        '401':
          description: No autorizado
        '500':
          description: Error interno del servidor
      security:
      - bearerAuth: []
      summary: Consultar eventos cercanos
      tags:
      - traffic
  /events/type/{type}:
    get:
      description: 'Filtra eventos de tráfico por categoría específica (accidente,
        cierre de carretera, evento especial).


        **Tipos disponibles:**

        - `ACCIDENT`: Accidentes de tráfico

        - `ROAD_CLOSURE`: Cierres de carretera

        - `SPECIAL_EVENT`: Eventos especiales (conciertos, manifestaciones, etc.)


        **Ejemplo de uso:**

        ```

        GET /events/type/ACCIDENT?lat=40.4168&lng=-3.7038&radius=5000

        ```


        **Escenario típico:**

        Un gestor de flota quiere evitar zonas con accidentes recientes para sus vehículos.

        '
      parameters:
      - $ref: '#/components/parameters/EventType'
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Radius'
      responses:
        '200':
          content:
            application/json:
              examples:
                ejemplo_por_tipo:
                  summary: Eventos filtrados por tipo
                  value:
                  - affectedVehicleTypes:
                    - ALL
                    createdAt: '2024-01-15T14:20:00Z'
                    expectedDuration: 180
                    id: evt_67890
                    location:
                      lat: 41.3851
                      lon: 2.1734
                    severity: 90
                    type: ROAD_CLOSURE
                    updatedAt: '2024-01-15T14:20:00Z'
              schema:
                items:
                  $ref: '#/components/schemas/TrafficEvent'
                type: array
          description: Lista de eventos del tipo especificado
        '400':
          description: Parámetros inválidos
        '401':
          description: No autorizado
        '500':
          description: Error interno del servidor
      security:
      - bearerAuth: []
      summary: Consultar eventos por tipo específico
      tags:
      - traffic
  /forecast:
    get:
      description: Obtener pronósticos meteorológicos detallados para el período seleccionado.
      operationId: getForeCast
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/StartDate'
      - $ref: '#/components/parameters/EndDate'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Pronóstico meteorológico recuperado exitosamente.
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get weather forecast
      tags:
      - weather
  /health:
    get:
      description: Verifica que el servicio esté funcionando correctamente
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  status:
                    example: ok
                    type: string
                  timestamp:
                    format: date-time
                    type: string
                type: object
          description: Servicio saludable
      summary: Health check del servicio
      tags:
      - route
  /historical-driving:
    get:
      description: '**Obtiene una lista paginada de todos los registros históricos
        de tacógrafo**.


        ## 📋 Funcionalidades

        - **Paginación**: Soporta parámetros `page` y `pageSize`

        - **Filtros**: Por conductor, vehículo, rango de fechas

        - **Ordenamiento**: Por fecha de creación, conductor, vehículo

        - **Aislamiento**: Solo registros del usuario propietario


        ## 🔍 Parámetros de consulta

        - `page`: Número de página (por defecto: 1)

        - `pageSize`: Resultados por página (máximo: 100)

        - `startDate`/`endDate`: Rango de fechas

        - `driverCardNumber`: Filtrar por conductor

        - `licensePlate`: Filtrar por vehículo

        - `sort`: Orden (`asc`/`desc`)

        - `lang`: Idioma de respuesta (`es` por defecto)


        ## 📊 Respuesta

        Lista de objetos `HistoricalDrivingRecord` con metadatos de paginación.

        '
      operationId: getHistoricalDrivingRecords
      parameters:
      - $ref: '#/components/parameters/Language'
      - $ref: '#/components/parameters/StartDate'
      - $ref: '#/components/parameters/EndDate'
      - $ref: '#/components/parameters/Page'
      - $ref: '#/components/parameters/PageSize'
      - $ref: '#/components/parameters/Sort'
      - description: '**Filtrar por número de tarjeta del conductor**.

          **Formato**: ES12345678901234 (país + 14 dígitos)

          '
        in: query
        name: driverCardNumber
        required: false
        schema:
          example: ES12345678901234
          pattern: ^[A-Z]{2}[0-9]{14}$
          type: string
      - description: '**Filtrar por matrícula del vehículo**.

          **Longitud máxima**: 10 caracteres

          '
        in: query
        name: licensePlate
        required: false
        schema:
          example: 1234ABC
          maxLength: 10
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                example:
                  data:
                  - _id: 5f8d3b7b3f6b8a1b9c3b7b3f
                    archivoDescarga:
                      extension: TGD
                      fechaDescarga: '2025-04-30T10:30:00.000Z'
                      nombre: TACO_20250430_083000
                    conduccion:
                      distanciaTotal: 450.75
                      tiempoConduccionTotal: 480
                      velocidadMaxima: 95.5
                      velocidadPromedio: 72.3
                    conductor:
                      apellidos: Pérez García
                      fechaCaducidad: '2026-12-31T23:59:59.000Z'
                      nombre: Juan
                      numeroTarjeta: ES123456789012345
                    sesion:
                      fechaExtraccion: '2025-04-30T17:30:00.000Z'
                      fechaInsercion: '2025-04-30T08:00:00.000Z'
                      vehiculo:
                        matricula: 1234ABC
                  pagination:
                    page: 1
                    pageSize: 10
                    total: 1
                    totalPages: 1
                properties:
                  data:
                    items:
                      $ref: '#/components/schemas/HistoricalDrivingRecord'
                    type: array
                  pagination:
                    properties:
                      page:
                        example: 1
                        type: integer
                      pageSize:
                        example: 10
                        type: integer
                      total:
                        example: 150
                        type: integer
                      totalPages:
                        example: 15
                        type: integer
                    type: object
                type: object
          description: Registros obtenidos exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Listar registros históricos de tacógrafo
      tags:
      - drivers
    post:
      description: '**Crea un nuevo registro histórico a partir de datos de tacógrafo**.


        ## 📝 Requisitos

        - Archivo de tacógrafo descargado (TGD, DDD, C1B, V1B)

        - Información del conductor (tarjeta válida)

        - Información del vehículo

        - Datos de actividad parseados


        ## ✅ Validaciones

        - Archivo válido y no corrupto

        - Tarjeta de conductor vigente

        - Calibración del tacógrafo reciente (<1 año)

        - No duplicados (mismo archivo, conductor, fecha)


        ## 🔄 Procesamiento automático

        - Parseo de datos del tacógrafo

        - Cálculo de tiempos de conducción

        - Detección de infracciones

        - Generación de resumen y análisis

        - Almacenamiento de eventos registrados

        '
      operationId: createHistoricalDrivingRecord
      parameters:
      - $ref: '#/components/parameters/Language'
      requestBody:
        content:
          application/json:
            example:
              archivoDescarga:
                contenido: U0dEX0ZJTEVfREFUQQ==
                extension: TGD
                fechaDescarga: '2025-04-30T10:30:00.000Z'
                nombre: TACO_20250430_083000
              conductor:
                apellidos: Pérez García
                fechaCaducidad: '2026-12-31T23:59:59.000Z'
                nombre: Juan
                numeroTarjeta: ES123456789012345
              vehiculo:
                matricula: 1234ABC
            schema:
              properties:
                archivoDescarga:
                  properties:
                    contenido:
                      description: Contenido del archivo en base64
                      example: U0dEX0ZJTEVfREFUQQ==
                      type: string
                    extension:
                      enum:
                      - TGD
                      - DDD
                      - C1B
                      - V1B
                      example: TGD
                      type: string
                    fechaDescarga:
                      example: '2025-04-30T10:30:00.000Z'
                      format: date-time
                      type: string
                    nombre:
                      example: TACO_20250430_083000
                      type: string
                  required:
                  - nombre
                  - extension
                  - fechaDescarga
                  - contenido
                  type: object
                conductor:
                  properties:
                    apellidos:
                      example: Pérez García
                      type: string
                    fechaCaducidad:
                      example: '2026-12-31T23:59:59.000Z'
                      format: date-time
                      type: string
                    nombre:
                      example: Juan
                      type: string
                    numeroTarjeta:
                      example: ES123456789012345
                      pattern: ^[A-Z]{2}[0-9]{14}$
                      type: string
                  required:
                  - nombre
                  - apellidos
                  - numeroTarjeta
                  - fechaCaducidad
                  type: object
                vehiculo:
                  properties:
                    matricula:
                      example: 1234ABC
                      maxLength: 10
                      type: string
                  required:
                  - matricula
                  type: object
              required:
              - archivoDescarga
              - conductor
              - vehiculo
              type: object
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HistoricalDrivingRecord'
          description: Registro creado exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          content:
            application/json:
              example:
                error: Ya existe un registro para este conductor y fecha
              schema:
                $ref: '#/components/schemas/Error'
          description: Registro duplicado
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Crear registro histórico de tacógrafo
      tags:
      - drivers
  /historical-driving/{id}:
    delete:
      description: '**Elimina permanentemente un registro histórico del sistema**.


        ## ⚠️ Operación destructiva

        - **Requiere justificación legal para eliminación**

        - Elimina todos los datos asociados (actividades, eventos, análisis)

        - **No se puede deshacer**


        ## 🔒 Restricciones

        - Solo administradores pueden eliminar registros

        - Registros con más de 2 años requieren aprobación especial

        - Operación auditada y registrada


        ## 📋 Consecuencias

        - Se pierden datos de cumplimiento histórico

        - Afecta reportes y análisis retrospectivos

        - Requiere documentación legal de eliminación

        '
      operationId: deleteHistoricalDrivingRecord
      responses:
        '204':
          description: Registro eliminado exitosamente (sin contenido)
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          content:
            application/json:
              example:
                error: Solo administradores pueden eliminar registros históricos
              schema:
                $ref: '#/components/schemas/Error'
          description: Permisos insuficientes
        '404':
          content:
            application/json:
              example:
                error: Registro histórico no encontrado
              schema:
                $ref: '#/components/schemas/Error'
          description: Registro no encontrado
        '409':
          content:
            application/json:
              example:
                error: Registro no puede eliminarse por restricciones legales
              schema:
                $ref: '#/components/schemas/Error'
          description: No se puede eliminar
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Eliminar registro histórico
      tags:
      - drivers
    get:
      description: '**Obtiene la información completa de un registro histórico de
        tacógrafo**.


        ## 📋 Información incluida

        - Metadatos del archivo descargado

        - Información del conductor y vehículo

        - Sesión completa (inicio/fin)

        - Todas las actividades registradas

        - Resumen de conducción (distancia, tiempo, velocidades)

        - Lista completa de eventos registrados


        ## 🔒 Permisos

        - Solo el propietario del registro puede acceder

        - Validación por campo `owner` (asociado al usuario)


        ## 💾 Datos históricos

        - Registros se retienen indefinidamente para cumplimiento legal

        - Incluye datos parseados del tacógrafo original

        - Eventos categorizados por tipo y gravedad

        '
      operationId: getHistoricalDrivingRecord
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HistoricalDrivingRecord'
          description: Registro obtenido exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          content:
            application/json:
              example:
                error: Registro histórico no encontrado o sin permisos de acceso
              schema:
                $ref: '#/components/schemas/Error'
          description: Registro no encontrado
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Obtener registro histórico específico
      tags:
      - drivers
    put:
      description: '**Actualiza información de un registro histórico existente**.


        ## 📝 Campos actualizables

        - Información del conductor (si cambió la tarjeta)

        - Información del vehículo

        - Notas o comentarios adicionales

        - **No se pueden modificar datos del tacógrafo original**


        ## ✅ Validaciones

        - El registro debe existir y pertenecer al usuario

        - Solo campos permitidos pueden actualizarse

        - Mantiene integridad de datos originales


        ## ⚠️ Restricciones

        - Datos del tacógrafo son inmutables

        - Solo metadatos administrativos pueden cambiarse

        - Cambios se auditan automáticamente

        '
      operationId: updateHistoricalDrivingRecord
      requestBody:
        content:
          application/json:
            example:
              conductor:
                apellidos: Pérez García
                fechaCaducidad: '2026-12-31T23:59:59.000Z'
                nombre: Juan
                numeroTarjeta: ES123456789012345
              notas: Registro verificado manualmente - datos correctos
              vehiculo:
                matricula: 1234ABC
            schema:
              properties:
                conductor:
                  properties:
                    apellidos:
                      example: Pérez García
                      type: string
                    fechaCaducidad:
                      example: '2026-12-31T23:59:59.000Z'
                      format: date-time
                      type: string
                    nombre:
                      example: Juan
                      type: string
                    numeroTarjeta:
                      example: ES123456789012345
                      pattern: ^[A-Z]{2}[0-9]{14}$
                      type: string
                  type: object
                notas:
                  description: Notas administrativas opcionales
                  example: Registro verificado manualmente - datos correctos
                  maxLength: 500
                  type: string
                vehiculo:
                  properties:
                    matricula:
                      example: 1234ABC
                      maxLength: 10
                      type: string
                  type: object
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HistoricalDrivingRecord'
          description: Registro actualizado exitosamente
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          content:
            application/json:
              example:
                error: Registro histórico no encontrado o sin permisos de acceso
              schema:
                $ref: '#/components/schemas/Error'
          description: Registro no encontrado
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Actualizar registro histórico
      tags:
      - drivers
  /historical-driving/{id}/analysis:
    get:
      description: '**Obtiene un análisis completo del registro de tacógrafo**.

        Incluye métricas de conducción, eventos y cumplimiento normativo.

        '
      operationId: getHistoricalDrivingAnalysis
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HistoricalDrivingAnalysis'
          description: Análisis obtenido exitosamente
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Obtener análisis detallado del registro
      tags:
      - drivers
  /historical-driving/{id}/events:
    get:
      description: '**Obtiene la lista de eventos registrados en el tacógrafo**.

        Incluye eventos del tacógrafo y eventos manuales agregados.

        '
      operationId: getHistoricalDrivingEvents
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  data:
                    items:
                      properties:
                        descripcion:
                          type: string
                        fechaHora:
                          format: date-time
                          type: string
                        gravedad:
                          type: string
                        tipo:
                          type: string
                      type: object
                    type: array
                  pagination:
                    properties:
                      total:
                        type: integer
                    type: object
                type: object
          description: Eventos obtenidos exitosamente
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Listar eventos del registro histórico
      tags:
      - drivers
  /ipma/alerts/current:
    get:
      description: Alertas meteorológicas actuales proporcionadas por IPMA para ubicaciones
        en Portugal
      operationId: getIpmaAlertCurrent
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Alertas meteorológicas recuperadas exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get current IPMA weather alerts
      tags:
      - weather
  /ipma/alerts/range:
    get:
      description: Alertas meteorológicas históricas o futuras proporcionadas por
        IPMA para ubicaciones en Portugal
      operationId: getIpmaAlertRange
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/StartDate'
      - $ref: '#/components/parameters/EndDate'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Alertas meteorológicas recuperadas exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get IPMA alerts for date range
      tags:
      - weather
  /ipma/current:
    get:
      description: Condiciones meteorológicas actuales proporcionadas por IPMA para
        ubicaciones en Portugal
      operationId: getIpmaCurrent
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Clima actual recuperado exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get current IPMA weather
      tags:
      - weather
  /ipma/forecast:
    get:
      description: Pronósticos meteorológicos proporcionados por IPMA para ubicaciones
        en Portugal
      operationId: getIpmaForecast
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/StartDate'
      - $ref: '#/components/parameters/EndDate'
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Pronóstico meteorológico recuperado exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Parámetros inválidos o faltantes
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get IPMA weather forecast
      tags:
      - weather
  /models:
    get:
      description: Recupera todos los modelos disponibles con información de su marca
        asociada
      operationId: getAllModelsWithBrand
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  properties:
                    brand:
                      properties:
                        id:
                          format: uuid
                          type: string
                        name:
                          type: string
                      type: object
                    id:
                      format: uuid
                      type: string
                    name:
                      type: string
                  type: object
                type: array
          description: Modelos con información de marca recuperados exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get all models with brand info
      tags:
      - vehicles
  /models/search:
    get:
      description: Realiza una búsqueda de marcas y modelos de vehículos utilizando
        un término de búsqueda
      operationId: searchBrandsAndModels
      parameters:
      - description: Termo de búsqueda para filtrar marcas y modelos
        in: query
        name: q
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  brands:
                    items:
                      properties:
                        id:
                          format: uuid
                          type: string
                        name:
                          type: string
                      type: object
                    type: array
                  models:
                    items:
                      properties:
                        brand:
                          properties:
                            id:
                              format: uuid
                              type: string
                            name:
                              type: string
                          type: object
                        id:
                          format: uuid
                          type: string
                        name:
                          type: string
                      type: object
                    type: array
                type: object
          description: Resultados de la búsqueda recuperados exitosamente
        '400':
          description: Parámetro de búsqueda ausente o inválido
        '401':
          description: No autorizado - Token inválido o faltante
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Search brands and models
      tags:
      - vehicles
  /models/{brandId}:
    get:
      description: Recupera todos los modelos disponibles para una marca específica
      operationId: getModelsByBrand
      parameters:
      - description: ID de la marca
        in: path
        name: brandId
        required: true
        schema:
          format: uuid
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  properties:
                    id:
                      format: uuid
                      type: string
                    name:
                      type: string
                  type: object
                type: array
          description: Modelos recuperados exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Marca no encontrada
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get models by brand
      tags:
      - vehicles
  /models/{modelId}/specs:
    get:
      description: Recupera las especificaciones técnicas detalladas para un modelo
        específico
      operationId: getSpecsByModel
      parameters:
      - description: ID del modelo
        in: path
        name: modelId
        required: true
        schema:
          format: uuid
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  id:
                    format: uuid
                    type: string
                  specifications:
                    type: object
                type: object
          description: Especificaciones técnicas recuperadas exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Modelo no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get technical specifications by model
      tags:
      - vehicles
  /parking/getAlongPath:
    post:
      description: Devuelve aparcamientos cerca de una ruta especificada, definida
        por una serie de coordenadas.
      requestBody:
        content:
          application/json:
            schema:
              properties:
                category:
                  default: all
                  description: Categoría del aparcamiento para filtrar resultados
                  enum:
                  - LOW_SIDE
                  - SECURE
                  - FREE
                  - OTHER
                  - all
                  example: SECURE
                  type: string
                features:
                  description: Array de características específicas para filtrar
                  example:
                  - vigilancia
                  - iluminacion
                  items:
                    type: string
                  type: array
                path:
                  description: Array de puntos [longitud, latitud] que definen la
                    ruta. El sistema filtra automáticamente los puntos redundantes
                    manteniendo 25km de distancia mínima entre ellos.
                  example:
                  - - -3.70379
                    - 40.416775
                  - - -3.70123
                    - 40.41789
                  - - -3.69876
                    - 40.41901
                  items:
                    items:
                      format: double
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  type: array
                radius:
                  default: 25000
                  description: Radio de búsqueda en metros desde la ruta. Mín 1000m,
                    Máx 150000m. Por defecto 25000m (25km).
                  example: 25000
                  format: float
                  maximum: 150000
                  minimum: 1000
                  type: number
              required:
              - path
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Parking'
                type: array
          description: Operación exitosa
        '400':
          description: Cuerpo de petición o parámetros inválidos (ej., ruta faltante,
            puntos inválidos, radio fuera de rango)
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Find parkings along a path
      tags:
      - poi
  /parking/location:
    get:
      description: Devuelve aparcamientos dentro de un radio dado de coordenadas.
        Los resultados son procesados con el método .parse()
      parameters:
      - description: Latitude coordinate
        example: 40.416775
        in: query
        name: lat
        required: true
        schema:
          format: double
          maximum: 90
          minimum: -90
          type: number
      - description: Longitude coordinate
        example: -3.70379
        in: query
        name: lng
        required: true
        schema:
          format: double
          maximum: 180
          minimum: -180
          type: number
      - description: Radio de búsqueda en metros. Mín 1000m, Máx 250000m. Requerido.
        example: 25000
        in: query
        name: radius
        required: true
        schema:
          format: float
          maximum: 250000
          minimum: 1000
          type: number
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Parking'
                type: array
          description: Operación exitosa
        '400':
          description: Parámetros inválidos
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Find parkings by location
      tags:
      - poi
  /parking/nearby:
    get:
      description: Devuelve aparcamientos dentro de un radio especificado de coordenadas
        dadas. Por defecto 250km.
      parameters:
      - description: Latitude coordinate
        example: 40.416775
        in: query
        name: lat
        required: true
        schema:
          format: double
          maximum: 90
          minimum: -90
          type: number
      - description: Longitude coordinate
        example: -3.70379
        in: query
        name: lng
        required: true
        schema:
          format: double
          maximum: 180
          minimum: -180
          type: number
      - description: Radio de búsqueda en metros. Mín 1000m, Máx 250000m. Por defecto
          250000m (250km).
        example: 10000
        in: query
        name: radius
        schema:
          default: 250000
          format: float
          maximum: 250000
          minimum: 1000
          type: number
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Parking'
                type: array
          description: Operación exitosa
        '400':
          description: Parámetros inválidos
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Find parkings nearby a location
      tags:
      - poi
  /parking/province:
    get:
      description: Devuelve aparcamientos en la provincia especificada. Los resultados
        son procesados con el método .parse()
      parameters:
      - description: Nombre de provincia/estado
        example: Madrid
        in: query
        name: province
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Parking'
                type: array
          description: Operación exitosa
        '400':
          description: Parámetros inválidos
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Find parkings by province
      tags:
      - poi
  /parking/zipcode:
    get:
      description: Devuelve aparcamientos que coinciden con el código postal y país.
        Los resultados son procesados con el método .parse()
      parameters:
      - description: Código postal
        example: '28013'
        in: query
        name: zipcode
        required: true
        schema:
          type: string
      - description: 'Código de país (2 letras). Soportados: es, fr, de, pt. Requerido.'
        example: es
        in: query
        name: country
        required: true
        schema:
          enum:
          - es
          - fr
          - de
          - pt
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Parking'
                type: array
          description: Operación exitosa
        '400':
          description: Parámetros inválidos
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Find parkings by zipcode
      tags:
      - poi
  /radar/getAlongPath:
    post:
      description: Retrieve radar information along a specified route
      requestBody:
        content:
          application/json:
            schema:
              properties:
                lang:
                  default: es
                  description: Language for response data
                  enum:
                  - es
                  - en
                  - fr
                  - de
                  - pt
                  type: string
                points:
                  description: Array of coordinate pairs [longitude, latitude]
                  items:
                    items:
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  minItems: 2
                  type: array
              required:
              - points
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Radar'
                type: array
          description: List of radars along the path
        '400':
          description: Invalid route
        '401':
          description: Unauthorized
        '500':
          description: Internal server error
      security:
      - bearerAuth: []
      summary: Get radars along a path
      tags:
      - traffic
  /radar/nearby:
    get:
      description: Retrieve radar information near a location
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Radius'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Radar'
                type: array
          description: List of nearby radars
        '400':
          description: Invalid parameters
        '401':
          description: Unauthorized
        '500':
          description: Internal server error
      security:
      - bearerAuth: []
      summary: Get nearby radars
      tags:
      - traffic
  /restaurant/getAlongPath:
    post:
      description: Devuelve restaurantes cerca de una ruta especificada
      requestBody:
        content:
          application/json:
            schema:
              properties:
                path:
                  description: Array de puntos [longitud, latitud] que definen la
                    ruta
                  example:
                  - - -3.70379
                    - 40.416775
                  - - -3.70123
                    - 40.41789
                  - - -3.69876
                    - 40.41901
                  items:
                    items:
                      format: double
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  type: array
                radius:
                  default: 5000
                  description: Radio de búsqueda en metros desde la ruta. Mín 1000m,
                    Máx 25000m. Por defecto 5000m (5km).
                  format: float
                  maximum: 25000
                  minimum: 1000
                  type: number
              required:
              - path
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Restaurant'
                type: array
          description: Operación exitosa
        '400':
          description: Cuerpo de petición o parámetros inválidos
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Find restaurants along a path
      tags:
      - poi
  /restaurant/nearby:
    get:
      description: Devuelve restaurantes dentro de un radio dado de coordenadas. Por
        defecto 250km.
      parameters:
      - description: Latitude coordinate
        example: 40.416775
        in: query
        name: lat
        required: true
        schema:
          format: double
          maximum: 90
          minimum: -90
          type: number
      - description: Longitude coordinate
        example: -3.70379
        in: query
        name: lng
        required: true
        schema:
          format: double
          maximum: 180
          minimum: -180
          type: number
      - description: Radio de búsqueda en metros. Mín 1000m, Máx 250000m. Por defecto
          250000m (250km).
        example: 10000
        in: query
        name: radius
        schema:
          default: 250000
          format: float
          maximum: 250000
          minimum: 1000
          type: number
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Restaurant'
                type: array
          description: Operación exitosa
        '400':
          description: Parámetros inválidos
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Find restaurants nearby
      tags:
      - poi
  /route:
    post:
      description: 'Obtener las condiciones meteorológicas a lo largo de una ruta
        GPS con filtrado opcional por nivel de peligro.


        **Funcionalidad de filtrado por peligro:**

        - Si `dangerLevel` no se especifica, se incluyen todos los puntos (equivalente
        a dangerLevel=0)

        - Si `dangerLevel` se especifica, solo se devuelven los puntos con un nivel
        de peligro igual o superior al valor

        - El valor se ajusta automáticamente entre 0 y 100 (mínimo 0, máximo 100)


        **Procesamiento de la ruta:**

        - Los puntos GPS se filtran para conservar solo aquellos separados por al
        menos 25 km

        - Se obtienen datos climáticos y alertas para cada punto filtrado

        - Se calcula una puntuación de peligro basada en visibilidad, lluvia, nieve,
        viento, temperatura, nubes y condiciones extremas

        '
      operationId: getWeatherAlongPath
      parameters:
      - $ref: '#/components/parameters/Language'
      - $ref: '#/components/parameters/DangerLevel'
      requestBody:
        content:
          application/json:
            schema:
              properties:
                points:
                  description: Array de puntos GPS en el formato [[lat, lon], [lat,
                    lon], ...]
                  items:
                    example:
                    - 40.4168
                    - -3.7038
                    items:
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  type: array
              required:
              - points
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  alerts:
                    description: Alertas meteorológicas activas en los puntos de la
                      ruta
                    type: array
                  currentWeather:
                    description: 'Datos climáticos actuales, filtrados por nivel de
                      peligro.

                      Cada elemento incluye una puntuación de peligro (0-100) calculada
                      automáticamente.

                      '
                    type: array
                type: object
          description: Datos meteorológicos recuperados exitosamente
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Cuerpo de solicitud inválido o puntos GPS mal formados
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Error interno del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get weather along route with danger filtering
      tags:
      - weather
  /search:
    get:
      description: 'Realiza una búsqueda textual en todos los niveles de la jerarquía
        HS:

        - Secciones

        - Capítulos

        - Partidas

        - Subpartidas


        Devuelve solo los elementos que coincidan con el término de búsqueda,

        manteniendo la estructura jerárquica entre ellos.


        La búsqueda no distingue mayúsculas/minúsculas y busca coincidencias parciales.


        Requiere autenticación mediante JWT en el header Authorization.

        '
      operationId: search
      parameters:
      - description: 'Término de búsqueda para filtrar resultados.

          Mínimo 3 caracteres. Puede buscar por:

          - Números de capítulo/partida (ej. "52")

          - Descripciones (ej. "algodón")

          - Combinaciones (ej. "52 algodón")

          '
        example: algodón
        in: query
        name: search
        required: true
        schema:
          minLength: 3
          type: string
      - $ref: '#/components/parameters/authorization'
      responses:
        '200':
          content:
            application/json:
              example:
              - _id: 6501a2b3c4d5e6f7a8b9c0d4
                chapters:
                - _id: 6501a2b3c4d5e6f7a8b9c0d3
                  headings: []
                  title: Capítulo 52 - Algodón
                title: Sección XI - Textiles
              schema:
                items:
                  $ref: '#/components/schemas/SearchResult'
                type: array
          description: 'Búsqueda realizada exitosamente.

            Devuelve un array de secciones que contienen al menos un elemento

            que coincide con el término de búsqueda, con la estructura jerárquica

            completa desde la sección hasta el elemento coincidente.

            '
        '400':
          content:
            application/json:
              example:
                message: Search query is required and must be at least 3 characters
              schema:
                $ref: '#/components/schemas/Error'
          description: 'Parámetros de búsqueda inválidos.

            Ocurre cuando:

            - No se proporciona el parámetro ''search''

            - El término de búsqueda tiene menos de 3 caracteres

            '
        '401':
          content:
            application/json:
              examples:
                invalidToken:
                  value:
                    message: Invalid or expired token
                missingToken:
                  value:
                    message: Authentication token is required
              schema:
                $ref: '#/components/schemas/Error'
          description: "No autorizado. Ocurre cuando:\n- No se proporciona el header\
            \ Authorization\n- El token JWT es inválido o ha expirado\n- El formato\
            \ del token es incorrecto\n\nEjemplo de respuesta:\n```json\n{\n  \"message\"\
            : \"Invalid or expired token\"\n}\n```\n"
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: 'Error interno del servidor.

            Puede ocurrir si hay problemas al acceder a la base de datos.

            '
      security:
      - bearerAuth: []
      summary: Buscar en toda la estructura del HS Code
      tags:
      - profiles
  /search/getAlongPath:
    post:
      description: Combined search for traffic events and radars along a specified
        route
      requestBody:
        content:
          application/json:
            schema:
              properties:
                lang:
                  default: es
                  description: Language for response data
                  enum:
                  - es
                  - en
                  - fr
                  - de
                  - pt
                  type: string
                points:
                  description: Array of coordinate pairs [longitude, latitude]
                  items:
                    items:
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  minItems: 2
                  type: array
              required:
              - points
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchResponse'
          description: Combined search results along the path
        '400':
          description: Invalid route
        '401':
          description: Unauthorized
        '500':
          description: Internal server error
      security:
      - bearerAuth: []
      summary: Search events and radars along a path
      tags:
      - traffic
  /search/nearby:
    get:
      description: Combined search for traffic events and radars near a location
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Radius'
      - $ref: '#/components/parameters/Language'
      - description: Filter by event types
        in: query
        name: types
        schema:
          items:
            enum:
            - ACCIDENT
            - ROAD_CLOSURE
            - SPECIAL_EVENT
            type: string
          type: array
      - description: Minimum event severity level
        in: query
        name: minSeverity
        schema:
          maximum: 100
          minimum: 0
          type: number
      - description: Filter by affected vehicle types
        in: query
        name: vehicleTypes
        schema:
          items:
            type: string
          type: array
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchResponse'
          description: Combined search results
        '400':
          description: Invalid parameters
        '401':
          description: Unauthorized
        '500':
          description: Internal server error
      security:
      - bearerAuth: []
      summary: Search nearby events and radars
      tags:
      - traffic
  /series/{modelId}:
    get:
      description: Recupera todas las series disponibles para un modelo de vehículo
        dado
      operationId: getSeriesByModel
      parameters:
      - description: ID del modelo
        in: path
        name: modelId
        required: true
        schema:
          format: uuid
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  properties:
                    id:
                      format: uuid
                      type: string
                    name:
                      type: string
                  type: object
                type: array
          description: Series recuperadas exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Modelo no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get series by model
      tags:
      - vehicles
  /shower/getAlongPath:
    post:
      description: Devuelve duchas cerca de una ruta especificada
      requestBody:
        content:
          application/json:
            schema:
              properties:
                path:
                  description: Array de puntos [longitud, latitud] que definen la
                    ruta
                  example:
                  - - -3.70379
                    - 40.416775
                  - - -3.70123
                    - 40.41789
                  - - -3.69876
                    - 40.41901
                  items:
                    items:
                      format: double
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  type: array
                radius:
                  default: 5000
                  description: Radio de búsqueda en metros desde la ruta. Mín 1000m,
                    Máx 25000m. Por defecto 5000m (5km).
                  format: float
                  maximum: 25000
                  minimum: 1000
                  type: number
                type:
                  description: Tipo de ducha a buscar
                  example: PARKING
                  type: string
              required:
              - path
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Shower'
                type: array
          description: Operación exitosa
        '400':
          description: Cuerpo de petición o parámetros inválidos
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Find showers along a path
      tags:
      - poi
  /shower/nearby:
    get:
      description: Devuelve duchas dentro de un radio dado de coordenadas
      parameters:
      - description: Latitude coordinate
        example: 40.416775
        in: query
        name: lat
        required: true
        schema:
          format: double
          maximum: 90
          minimum: -90
          type: number
      - description: Longitude coordinate
        example: -3.70379
        in: query
        name: lng
        required: true
        schema:
          format: double
          maximum: 180
          minimum: -180
          type: number
      - description: Radio de búsqueda en metros. Mín 1000m, Máx 25000m. Por defecto
          5000m (5km).
        in: query
        name: radius
        schema:
          default: 5000
          format: float
          maximum: 25000
          minimum: 1000
          type: number
      - description: Tipo de ducha a buscar
        example: PARKING
        in: query
        name: type
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Shower'
                type: array
          description: Operación exitosa
        '400':
          description: Parámetros inválidos
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Find showers nearby
      tags:
      - poi
  /shower/types:
    get:
      description: Returns all available shower types/categories
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  type: string
                type: array
          description: Successful operation
        '500':
          description: Server error
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Get shower types
      tags:
      - poi
  /specs/keys:
    get:
      description: Recupera todas las claves de especificaciones técnicas disponibles
        en el sistema
      operationId: getAvailableSpecificationKeys
      responses:
        '200':
          content:
            application/json:
              schema:
                example:
                - engineType
                - horsepower
                - torque
                - fuelType
                - transmission
                items:
                  type: string
                type: array
          description: Claves de especificación recuperadas exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get available specification keys
      tags:
      - vehicles
  /specs/search:
    post:
      description: Realiza una búsqueda de vehículos basada en criterios de especificaciones
        técnicas
      operationId: searchBySpecifications
      requestBody:
        content:
          application/json:
            schema:
              properties:
                specifications:
                  description: Objeto con pares clave-valor de las especificaciones
                    a buscar
                  example:
                    engineType: Diesel
                    horsepower: 450
                  type: object
              required:
              - specifications
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  properties:
                    id:
                      format: uuid
                      type: string
                    name:
                      type: string
                    specifications:
                      type: object
                  type: object
                type: array
          description: Resultados de la búsqueda recuperados exitosamente
        '400':
          description: Criterios de búsqueda inválidos
        '401':
          description: No autorizado - Token inválido o faltante
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Search by specifications
      tags:
      - vehicles
  /stations/best-prices:
    get:
      description: 'Identifica y devuelve un listado de hasta 3 estaciones de servicio
        que ofrecen los precios más competitivos para un tipo de combustible específico
        dentro de un área geográfica determinada por latitud, longitud y radio. Es
        útil para optimizar costes de repostaje.

        **Limitaciones:** - Radio mínimo: 15km - Radio máximo: 150km - Máximo 3 resultados
        por petición

        '
      operationId: getBestPrices
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Radius'
      - $ref: '#/components/parameters/FuelType'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              example:
              - _id: 5f8d3b7b3f6b8a1b9c3b7b3f
                address: Poligono Industrial Sur, 1, 28906 Getafe
                fuelTypes:
                - code: diesel_regular
                  price: 1.42
                  updatedAt: '2025-04-29T09:00:00Z'
                label: Repsol Economica
                location:
                  coordinates:
                  - -3.732
                  - 40.3
                  type: Point
              - _id: 60a2c1b2d3e4f5a6b7c8d9e1
                address: Carretera de Toledo, km 15, 28905 Getafe
                fuelTypes:
                - code: diesel_regular
                  price: 1.425
                  updatedAt: '2025-04-29T09:15:00Z'
                label: Galp Ahorro
                location:
                  coordinates:
                  - -3.74
                  - 40.295
                  type: Point
              schema:
                items:
                  $ref: '#/components/schemas/OilStation'
                type: array
          description: Lista de hasta 3 estaciones con los mejores precios
        '400':
          content:
            application/json:
              examples:
                INVALID_COORDINATES:
                  summary: Coordenadas Inválidas
                  value:
                    error: Invalid coordinates
                INVALID_RADIUS:
                  summary: Radio Inválido
                  value:
                    error: Radius must be between 15000 and 150000 meters.
              schema:
                $ref: '#/components/schemas/Error'
          description: Radio fuera de límites o parámetros inválidos
        '503':
          content:
            application/json:
              example:
                error: Error fetching best prices
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al calcular los mejores precios
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Obtener estaciones con mejores precios en un área
      tags:
      - stations
  /stations/brand/{icon}:
    get:
      description: 'Devuelve el archivo de icono PNG asociado a una marca específica
        de estación de servicio. Los iconos están disponibles en formato PNG.

        '
      operationId: getStationBrandIcon
      parameters:
      - description: Identificador del icono de la marca
        in: path
        name: icon
        required: true
        schema:
          example: iconorepsol
          type: string
      responses:
        '200':
          content:
            image/png:
              schema:
                format: binary
                type: string
          description: Archivo PNG del icono de la marca
        '404':
          content:
            application/json:
              example:
                error: Icon not found
              schema:
                $ref: '#/components/schemas/Error'
          description: Icono no encontrado
        '503':
          content:
            application/json:
              example:
                error: Internal server error
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al obtener el icono
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Obtener icono de marca de estación de servicio
      tags:
      - stations
  /stations/brands:
    get:
      description: 'Devuelve una lista de todas las marcas de estaciones de servicio
        disponibles en el sistema, incluyendo sus nombres e iconos asociados.

        '
      operationId: getStationBrands
      parameters: []
      responses:
        '200':
          content:
            application/json:
              example:
              - icon: iconorepsol
                label: Repsol
              - icon: iconocepsa
                label: Cepsa
              schema:
                items:
                  properties:
                    icon:
                      description: Identificador del icono de la marca
                      example: iconorepsol
                      type: string
                    label:
                      description: Nombre de la marca
                      example: Repsol
                      type: string
                  type: object
                type: array
          description: Lista de marcas de estaciones de servicio
        '503':
          content:
            application/json:
              example:
                error: Error fetching station brands
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al obtener las marcas de estaciones
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Obtener listado de marcas de estaciones de servicio
      tags:
      - stations
  /stations/fuel-types:
    get:
      description: 'Devuelve una lista de todos los tipos de combustible válidos y
        actualmente gestionados por el sistema. Esta lista puede ser utilizada para
        filtrar búsquedas o presentar opciones al usuario.

        '
      operationId: getFuelTypes
      parameters:
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              example:
              - gnc
              - gnl
              - gpl
              - hydrogen
              - bioethanol
              - biodiesel
              - diesel_regular
              - diesel_premium
              - gasoline_95
              - gasoline_98
              schema:
                items:
                  type: string
                type: array
          description: Lista de tipos de combustible disponibles
        '503':
          content:
            application/json:
              example:
                error: Server error fetching fuel types.
              schema:
                $ref: '#/components/schemas/Error'
          description: Error del servidor al obtener los tipos de combustible.
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Obtener lista de tipos de combustible
      tags:
      - stations
  /stations/getAlongPath:
    post:
      description: 'Busca y devuelve una lista de estaciones de servicio que se encuentran
        cercanas a una secuencia de puntos GPS que definen una ruta. El sistema optimiza
        la búsqueda filtrando puntos redundantes (aquellos que están a menos de 25km
        entre sí) y es especialmente útil para la planificación de repostajes en rutas
        largas de transporte.

        **Casos de uso:** - Planificación de rutas para transportistas - Optimización
        de paradas para repostaje - Integración con sistemas de navegación

        **Características:** - Filtra puntos redundantes (más cercanos de 25km) -
        Devuelve estaciones cercanas a cada punto de la ruta - Optimizado para rutas
        largas de transporte - Opción de optimizar paradas (solo estaciones con mejores
        precios)

        '
      operationId: getAlongPath
      parameters:
      - $ref: '#/components/parameters/Language'
      requestBody:
        content:
          application/json:
            example:
              fuel_type: diesel_regular
              optimizedStops: true
              points:
              - - -3.7038
                - 40.4168
              - - -0.1278
                - 51.5074
            schema:
              properties:
                fuel_type:
                  default: diesel_regular
                  description: Tipo de combustible a buscar. Requerido para filtrar
                    las estaciones a lo largo de la ruta.
                  enum:
                  - diesel_regular
                  - diesel_premium
                  - gasoline_95
                  - gasoline_98
                  example: diesel_regular
                  type: string
                optimizedStops:
                  default: false
                  description: Si es `true`, solo devuelve las estaciones con los
                    precios más competitivos (en el 30% más bajo del rango encontrado).
                    Ideal para optimizar costes.
                  example: true
                  type: boolean
                points:
                  description: Array de puntos GPS en formato `[[lng,lat],...]` que
                    definen la ruta. Máximo 100 puntos.
                  items:
                    items:
                      format: float
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  maxItems: 100
                  type: array
              required:
              - points
              - fuel_type
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              example:
              - _id: 5f8d3b7b3f6b8a1b9c3b7b3f
                address: Autovia A1, Km 100, 09001 Burgos
                fuelTypes:
                - code: diesel_regular
                  price: 1.47
                  updatedAt: '2025-04-29T12:00:00Z'
                label: Repsol Ruta Norte
                location:
                  coordinates:
                  - -3.688
                  - 42.343
                  type: Point
              - _id: 60a2c1b2d3e4f5a6b7c8d9e2
                address: N-620, Km 50, 34001 Palencia
                fuelTypes:
                - code: gasoline_98
                  price: 1.65
                  updatedAt: '2025-04-29T12:30:00Z'
                label: Cepsa Parada Viajero
                location:
                  coordinates:
                  - -4.526
                  - 42.01
                  type: Point
              schema:
                items:
                  $ref: '#/components/schemas/OilStation'
                type: array
          description: Lista de estaciones encontradas a lo largo de la ruta
        '400':
          content:
            application/json:
              example:
                error: GPS points array is required
              schema:
                $ref: '#/components/schemas/Error'
          description: 'Error en los parámetros. Posibles causas:

            - Formato incorrecto de puntos GPS

            - Número de puntos excede el límite

            '
        '500':
          content:
            application/json:
              example:
                error: Unknown error
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al procesar la ruta
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Obtener estaciones a lo largo de una ruta
      tags:
      - stations
  /stations/nearby:
    get:
      description: 'Busca estaciones de servicio dentro de un radio especificado alrededor
        de unas coordenadas GPS. Ideal para encontrar opciones de repostaje inmediatas.

        **Ejemplo de uso:** - Planificación de rutas de transporte - Localizar opciones
        de repostaje cercanas - Integración con sistemas de navegación

        '
      operationId: getNearbyStations
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Radius'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              example:
              - _id: 5f8d3b7b3f6b8a1b9c3b7b3f
                address: Calle Mayor, 123, 28013 Madrid
                fuelTypes:
                - code: diesel_regular
                  price: 1.459
                  updatedAt: '2025-04-29T10:30:00Z'
                label: Repsol Calle Mayor
                location:
                  coordinates:
                  - -3.7038
                  - 40.4168
                  type: Point
              schema:
                items:
                  $ref: '#/components/schemas/OilStation'
                type: array
          description: Lista de estaciones encontradas ordenadas por distancia
        '400':
          content:
            application/json:
              examples:
                INVALID_COORDINATES:
                  summary: Coordenadas Inválidas
                  value:
                    error: Invalid coordinates
                RADIUS_OUT_OF_BOUNDS:
                  summary: Radio Fuera de Límites
                  value:
                    error: Radius must be between 1000 and 150000 meters.
              schema:
                $ref: '#/components/schemas/Error'
          description: 'Error en los parámetros de entrada. Posibles causas:

            - Coordenadas inválidas o fuera de rango

            - Radio de búsqueda fuera de límites

            '
        '503':
          content:
            application/json:
              example:
                error: Error fetching nearby stations
              schema:
                $ref: '#/components/schemas/Error'
          description: Error temporal del servidor. Intente nuevamente más tarde.
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Obtener estaciones cercanas por ubicación
      tags:
      - stations
  /stations/province:
    get:
      description: 'Analiza y devuelve los precios más bajos encontrados para un tipo
        de combustible específico en cada provincia del país seleccionado. Los resultados
        se presentan ordenados de menor a mayor precio, incluyendo el identificador
        de la provincia para facilitar su uso en integraciones.

        **Notas:** - Los resultados están ordenados de menor a mayor precio - Incluye
        identificador de provincia para facilitar integraciones

        '
      operationId: getCheapestProvince
      parameters:
      - $ref: '#/components/parameters/FuelType'
      - $ref: '#/components/parameters/Language'
      - $ref: '#/components/parameters/Country'
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  properties:
                    price:
                      description: Precio mínimo encontrado en la provincia
                      example: 1.429
                      format: float
                      type: number
                    province:
                      description: Nombre de la provincia
                      example: Madrid
                      type: string
                    provinceId:
                      description: Identificador único de la provincia
                      example: '28'
                      type: string
                  type: object
                type: array
          description: Lista de provincias con sus precios mínimos
        '503':
          content:
            application/json:
              example:
                error: Error fetching prices by province
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al calcular los precios por provincia
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Obtener precios más bajos por provincia
      tags:
      - stations
  /stations/search:
    get:
      description: 'Permite una búsqueda avanzada de estaciones de servicio aplicando
        múltiples criterios como tipo de combustible, ubicación geográfica (latitud,
        longitud, radio) y un término de búsqueda libre que puede coincidir con el
        nombre de la estación, dirección, ciudad o provincia. Los resultados se ordenan
        por distancia a las coordenadas proporcionadas.

        **Características especiales:** - Búsqueda por texto en dirección, ciudad,
        provincia o nombre - Filtrado por tipo de combustible disponible - Ordenación
        por distancia a las coordenadas proporcionadas

        '
      operationId: searchStations
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Radius'
      - $ref: '#/components/parameters/FuelType'
      - $ref: '#/components/parameters/Language'
      - $ref: '#/components/parameters/SearchQuery'
      responses:
        '200':
          content:
            application/json:
              example:
              - _id: 5f8d3b7b3f6b8a1b9c3b7b3f
                address: Calle Mayor, 123, 28013 Madrid
                fuelTypes:
                - code: diesel_regular
                  price: 1.459
                  updatedAt: '2025-04-29T10:30:00Z'
                label: Repsol Calle Mayor
                location:
                  coordinates:
                  - -3.7038
                  - 40.4168
                  type: Point
              - _id: 60a2c1b2d3e4f5a6b7c8d9e0
                address: Av. de América, 10, 28028 Madrid
                fuelTypes:
                - code: gasoline_95
                  price: 1.52
                  updatedAt: '2025-04-29T11:00:00Z'
                - code: diesel_premium
                  price: 1.55
                  updatedAt: '2025-04-29T11:05:00Z'
                label: Cepsa Av. de America
                location:
                  coordinates:
                  - -3.6738
                  - 40.4312
                  type: Point
              schema:
                items:
                  $ref: '#/components/schemas/OilStation'
                type: array
          description: Lista de estaciones que coinciden con los criterios de búsqueda
        '400':
          content:
            application/json:
              examples:
                INVALID_COORDINATES:
                  summary: Coordenadas Inválidas
                  value:
                    error: Invalid coordinates
                INVALID_FUEL_TYPE:
                  summary: Tipo de Combustible Inválido
                  value:
                    error: Invalid fuel type
              schema:
                $ref: '#/components/schemas/Error'
          description: Parámetros de búsqueda inválidos
        '404':
          content:
            application/json:
              example:
                error: No nearby stations found
              schema:
                $ref: '#/components/schemas/Error'
          description: No se encontraron estaciones que coincidan
        '503':
          content:
            application/json:
              example:
                error: Error searching for stations
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al procesar la búsqueda
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Buscar estaciones por filtros
      tags:
      - stations
  /stations/{id}:
    get:
      description: 'Recupera información detallada y actualizada de una estación de
        servicio específica, identificada por su ID único (MongoDB ObjectId). Esto
        incluye su ubicación precisa, la lista completa de combustibles que ofrece
        con sus precios actuales, y potencialmente horarios de apertura si están disponibles.

        **Incluye:** - Información de ubicación exacta - Lista completa de combustibles
        disponibles - Precios actualizados - Horarios de apertura (si disponibles)

        '
      operationId: getStationDetails
      parameters:
      - description: ID único de la estación (MongoDB ObjectId)
        in: path
        name: id
        required: true
        schema:
          example: 5f8d3b7b3f6b8a1b9c3b7b3f
          pattern: ^[0-9a-fA-F]{24}$
          type: string
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              example:
                _id: 5f8d3b7b3f6b8a1b9c3b7b3f
                address: Calle Mayor, 123, 28013 Madrid
                fuelTypes:
                - code: diesel_regular
                  price: 1.459
                  updatedAt: '2025-04-29T10:30:00Z'
                - code: gasoline_95
                  price: 1.589
                  updatedAt: '2025-04-29T10:32:00Z'
                label: Repsol Calle Mayor
                location:
                  coordinates:
                  - -3.7038
                  - 40.4168
                  type: Point
              schema:
                $ref: '#/components/schemas/OilStation'
          description: Detalles completos de la estación solicitada
        '400':
          content:
            application/json:
              example:
                error: Station ID required
              schema:
                $ref: '#/components/schemas/Error'
          description: ID de estación inválido o mal formado
        '404':
          content:
            application/json:
              example:
                error: Station not found
              schema:
                $ref: '#/components/schemas/Error'
          description: Estación no encontrada
        '503':
          content:
            application/json:
              example:
                error: Error fetching station details
              schema:
                $ref: '#/components/schemas/Error'
          description: Error al recuperar los detalles
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Obtener detalles de una estación específica
      tags:
      - stations
  /test:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  status:
                    example: ok
                    type: string
                type: object
          description: Servicio saludable
      summary: Alias de health check
      tags:
      - route
  /token:
    get:
      deprecated: true
      description: '**⚠️ DEPRECATED:** Use `/api/auth/login` instead.

        Endpoint legacy para obtener token JWT.

        '
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
          description: Token obtenido exitosamente
        '500':
          $ref: '#/components/responses/InternalServerError'
      summary: Obtener token JWT (legacy)
      tags:
      - iam
  /traffic/current:
    get:
      description: Retrieve current traffic flow information
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Radius'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrafficFlowData'
          description: Current traffic flow data
        '400':
          description: Invalid parameters
        '401':
          description: Unauthorized
        '500':
          description: Internal server error
      security:
      - bearerAuth: []
      summary: Get current traffic flow
      tags:
      - traffic
  /traffic/getAlongPath:
    post:
      description: Retrieve traffic flow information along a specified route
      requestBody:
        content:
          application/json:
            schema:
              properties:
                lang:
                  default: es
                  description: Language for response data
                  enum:
                  - es
                  - en
                  - fr
                  - de
                  - pt
                  type: string
                points:
                  description: Array of coordinate pairs [longitude, latitude]
                  items:
                    items:
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  minItems: 2
                  type: array
              required:
              - points
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/TrafficFlowData'
                type: array
          description: Traffic flow data along the path
        '400':
          description: Invalid route
        '401':
          description: Unauthorized
        '500':
          description: Internal server error
      security:
      - bearerAuth: []
      summary: Get traffic flow along a path
      tags:
      - traffic
  /traffic/nearby:
    get:
      description: Retrieve traffic flow information near a location
      parameters:
      - $ref: '#/components/parameters/Latitude'
      - $ref: '#/components/parameters/Longitude'
      - $ref: '#/components/parameters/Radius'
      - $ref: '#/components/parameters/Language'
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrafficFlowData'
          description: Traffic flow data near the location
        '400':
          description: Invalid parameters
        '401':
          description: Unauthorized
        '500':
          description: Internal server error
      security:
      - bearerAuth: []
      summary: Get nearby traffic flow
      tags:
      - traffic
  /trims/{seriesId}:
    get:
      description: Recupera todos los 'trims' (acabados) disponibles para una serie
        de vehículos específica
      operationId: getTrimsBySeries
      parameters:
      - description: ID de la serie
        in: path
        name: seriesId
        required: true
        schema:
          format: uuid
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  properties:
                    id:
                      format: uuid
                      type: string
                    name:
                      type: string
                    specifications:
                      type: object
                  type: object
                type: array
          description: Trims (acabados) recuperados exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Serie no encontrada
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get trims by series
      tags:
      - vehicles
  /trims/{trimId}:
    get:
      description: Recupera los detalles completos de un 'trim' (acabado) específico,
        incluyendo todas sus especificaciones técnicas
      operationId: getTrimById
      parameters:
      - description: ID del 'trim' (acabado)
        in: path
        name: trimId
        required: true
        schema:
          format: uuid
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  id:
                    format: uuid
                    type: string
                  name:
                    type: string
                  series:
                    properties:
                      id:
                        format: uuid
                        type: string
                      name:
                        type: string
                    type: object
                  specifications:
                    example:
                      other:
                        fuel_consumption: '40'
                    type: object
                type: object
          description: Trim (acabado) recuperado exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Trim (acabado) no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get trim by ID
      tags:
      - vehicles
  /types:
    get:
      description: Recupera todos los tipos de vehículos disponibles y sus configuraciones
      operationId: getVehicleTypes
      responses:
        '200':
          content:
            application/json:
              schema:
                additionalProperties:
                  $ref: '#/components/schemas/VehicleType'
                type: object
          description: Lista de tipos recuperada exitosamente
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get all vehicle types
      tags:
      - vehicles
  /vehicles:
    get:
      description: "Recupera todos los vehículos registrados en el sistema con paginación.\
        \ \nSe puede filtrar por estado usando el parámetro de consulta ?status=[active|maintenance|out_of_service].\n\
        Parámetros de paginación:\n- page: Número de página (por defecto 1)\n- limit:\
        \ Límite de elementos por página (por defecto 10)\n\nEjemplo de respuesta:\n\
        ```json\n[\n  {\n    \"id\": \"5f8d0d55b54764421b7156c3\",\n    \"model\"\
        : \"Volvo FH 540\",\n    \"licensePlate\": \"5678XYZ\",\n    \"status\": \"\
        active\",\n    \"maxWeight\": 40.0,\n    \"dimensions\": {\n      \"length\"\
        : 16.5,\n      \"width\": 2.55,\n      \"height\": 4.0\n    }\n  }\n]\n```\n"
      operationId: getVehicles
      parameters:
      - description: Filtrar los vehículos por estado operativo
        in: query
        name: status
        required: false
        schema:
          enum:
          - active
          - maintenance
          - out_of_service
          type: string
      - description: Número de página
        in: query
        name: page
        required: false
        schema:
          default: 1
          minimum: 1
          type: integer
      - description: Límite de elementos por página
        in: query
        name: limit
        required: false
        schema:
          default: 10
          maximum: 100
          minimum: 1
          type: integer
      responses:
        '200':
          content:
            application/json:
              example:
              - dimensions:
                  height: 4.0
                  length: 16.5
                  width: 2.55
                id: 5f8d0d55b54764421b7156c3
                licensePlate: 5678XYZ
                maxWeight: 40.0
                model: Volvo FH 540
                status: active
              - dimensions:
                  height: 3.9
                  length: 12.8
                  width: 2.5
                id: 6g9e1e66c65875532c8267d4
                licensePlate: 4321ABC
                maxWeight: 26.0
                model: MAN TGX 26.480
                status: maintenance
              schema:
                items:
                  $ref: '#/components/schemas/Vehicle'
                type: array
          description: Lista de vehículos recuperada exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get list of vehicles
      tags:
      - vehicles
    post:
      description: "Registra un nuevo vehículo en el sistema. Requiere autenticación\
        \ JWT con el rol 'fleet_manager'.\n\nEjemplo de uso:\n```json\n{\n  \"model\"\
        : \"Volvo FH 540\",\n  \"licensePlate\": \"5678XYZ\",\n  \"status\": \"active\"\
        ,\n  \"maxWeight\": 40.0,\n  \"dimensions\": {\n    \"length\": 16.5,\n  \
        \  \"width\": 2.55,\n    \"height\": 4.0\n  }\n}\n```\n"
      operationId: createVehicle
      requestBody:
        content:
          application/json:
            example:
              dimensions:
                height: 4.0
                length: 16.5
                width: 2.55
              licensePlate: 5678XYZ
              maxWeight: 40.0
              model: Volvo FH 540
              status: active
            schema:
              $ref: '#/components/schemas/Vehicle'
        required: true
      responses:
        '201':
          content:
            application/json:
              example:
                dimensions:
                  height: 4.0
                  length: 16.5
                  width: 2.55
                id: 5f8d0d55b54764421b7156c3
                licensePlate: 5678XYZ
                maxWeight: 40.0
                model: Volvo FH 540
                status: active
              schema:
                $ref: '#/components/schemas/Vehicle'
          description: Vehículo creado exitosamente
        '400':
          content:
            application/json:
              schema:
                properties:
                  error:
                    type: string
                type: object
          description: Datos de vehículo inválidos
        '401':
          description: No autorizado - Token inválido o faltante
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Create a new vehicle
      tags:
      - vehicles
  /vehicles/specs/model-id/{modelId}:
    get:
      description: Endpoint alternativo para recuperar especificaciones técnicas por
        ID de modelo
      operationId: getSpecsByModelIdAlt
      parameters:
      - description: ID del modelo
        in: path
        name: modelId
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Especificaciones recuperadas exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Modelo no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get specifications by model ID (alternative)
      tags:
      - vehicles
  /vehicles/specs/name/{modelName}:
    get:
      description: Recupera las especificaciones técnicas de un modelo específico
        por su nombre
      operationId: getSpecsByModelName
      parameters:
      - description: Nombre del modelo
        in: path
        name: modelName
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Especificaciones recuperadas exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Modelo no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get specifications by model name
      tags:
      - vehicles
  /vehicles/specs/{modelId}:
    get:
      description: Recupera las especificaciones técnicas de un modelo específico
        por su ID
      operationId: getSpecsByModelId
      parameters:
      - description: ID del modelo
        in: path
        name: modelId
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
          description: Especificaciones recuperadas exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Modelo no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get specifications by model ID
      tags:
      - vehicles
  /vehicles/vehicle-types/{id}:
    get:
      description: Recupera la etiqueta descriptiva de un tipo de vehículo por su
        ID
      operationId: getVehicleTypeLabel
      parameters:
      - description: ID del tipo de vehículo
        in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  id:
                    type: string
                  label:
                    type: string
                type: object
          description: Etiqueta de tipo de vehículo recuperada exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Tipo de vehículo no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get vehicle type label
      tags:
      - vehicles
  /vehicles/{id}:
    delete:
      description: Elimina permanentemente un vehículo del sistema
      operationId: deleteVehicle
      parameters:
      - $ref: '#/components/parameters/VehicleId'
      responses:
        '204':
          description: Vehículo eliminado exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Vehículo no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Delete vehicle
      tags:
      - vehicles
    get:
      description: Recupera los detalles de un vehículo específico por su ID
      operationId: getVehicleById
      parameters:
      - $ref: '#/components/parameters/VehicleId'
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Vehicle'
          description: Vehículo recuperado exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Vehículo no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get vehicle by ID
      tags:
      - vehicles
    put:
      description: Modifica los datos de un vehículo existente
      operationId: updateVehicle
      parameters:
      - $ref: '#/components/parameters/VehicleId'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Vehicle'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Vehicle'
          description: Vehículo actualizado exitosamente
        '400':
          description: Datos de vehículo inválidos
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Vehículo no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Update vehicle
      tags:
      - vehicles
  /vehicles/{id}/enabled:
    patch:
      description: Activa o desactiva el estado 'enabled' de un vehículo
      operationId: toggleVehicleEnabled
      parameters:
      - $ref: '#/components/parameters/VehicleId'
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  enabled:
                    type: boolean
                  id:
                    type: string
                type: object
          description: Estado del vehículo actualizado exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Vehículo no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Toggle vehicle 'enabled' status
      tags:
      - vehicles
  /vehicles/{id}/position:
    get:
      description: Recupera la última posición GPS registrada del vehículo
      operationId: getCurrentPosition
      parameters:
      - $ref: '#/components/parameters/VehicleId'
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  latitude:
                    type: number
                  longitude:
                    type: number
                  timestamp:
                    format: date-time
                    type: string
                  vehicleId:
                    type: string
                type: object
          description: Posición recuperada exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Vehículo o posición no encontrada
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get current vehicle position
      tags:
      - vehicles
    put:
      description: Registra una nueva posición GPS para el vehículo
      operationId: updatePosition
      parameters:
      - $ref: '#/components/parameters/VehicleId'
      requestBody:
        content:
          application/json:
            schema:
              properties:
                latitude:
                  description: Latitud de la nueva posición
                  maximum: 90
                  minimum: -90
                  type: number
                longitude:
                  description: Longitud de la nueva posición
                  maximum: 180
                  minimum: -180
                  type: number
              required:
              - latitude
              - longitude
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  latitude:
                    type: number
                  longitude:
                    type: number
                  timestamp:
                    format: date-time
                    type: string
                  vehicleId:
                    type: string
                type: object
          description: Posición actualizada exitosamente
        '400':
          description: Datos de posición inválidos
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Vehículo no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Update vehicle position
      tags:
      - vehicles
  /vehicles/{id}/position/history:
    get:
      description: Recupera el historial de posiciones GPS del vehículo
      operationId: getVehiclePositionHistory
      parameters:
      - $ref: '#/components/parameters/VehicleId'
      - description: Fecha de inicio del historial
        in: query
        name: startDate
        schema:
          format: date-time
          type: string
      - description: Fecha de fin del historial
        in: query
        name: endDate
        schema:
          format: date-time
          type: string
      - description: Número máximo de entradas a devolver
        in: query
        name: limit
        schema:
          default: 100
          maximum: 1000
          minimum: 1
          type: integer
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  properties:
                    latitude:
                      type: number
                    longitude:
                      type: number
                    timestamp:
                      format: date-time
                      type: string
                    vehicleId:
                      type: string
                  type: object
                type: array
          description: Historial de posiciones recuperado exitosamente
        '401':
          description: No autorizado - Token inválido o faltante
        '404':
          description: Vehículo no encontrado
        '500':
          description: Error del servidor
      security:
      - BearerAuth: []
      - ApiKeyAuth: []
      summary: Get vehicle position history
      tags:
      - vehicles
      x-docusaurus:
        sidebar_key: vehicles-get-position-history
  /workshop/getAlongPath:
    post:
      description: Devuelve talleres cerca de una ruta especificada
      requestBody:
        content:
          application/json:
            schema:
              properties:
                path:
                  description: Array de puntos [longitud, latitud] que definen la
                    ruta
                  example:
                  - - -3.70379
                    - 40.416775
                  - - -3.70123
                    - 40.41789
                  - - -3.69876
                    - 40.41901
                  items:
                    items:
                      format: double
                      type: number
                    maxItems: 2
                    minItems: 2
                    type: array
                  type: array
                radius:
                  default: 10000
                  description: Radio de búsqueda en metros desde la ruta. Mín 1000m,
                    Máx 50000m. Por defecto 10000m (10km).
                  format: float
                  maximum: 50000
                  minimum: 1000
                  type: number
              required:
              - path
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Workshop'
                type: array
          description: Operación exitosa
        '400':
          description: Cuerpo de petición o parámetros inválidos
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Find workshops along a path
      tags:
      - poi
  /workshop/nearby:
    get:
      description: Devuelve talleres dentro de un radio dado de coordenadas
      parameters:
      - description: Latitude coordinate
        example: 40.416775
        in: query
        name: lat
        required: true
        schema:
          format: double
          maximum: 90
          minimum: -90
          type: number
      - description: Longitude coordinate
        example: -3.70379
        in: query
        name: lng
        required: true
        schema:
          format: double
          maximum: 180
          minimum: -180
          type: number
      - description: Radio de búsqueda en metros. Mín 1000m, Máx 250000m. Por defecto
          250000m (250km).
        in: query
        name: radius
        schema:
          default: 250000
          format: float
          maximum: 250000
          minimum: 1000
          type: number
      responses:
        '200':
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Workshop'
                type: array
          description: Operación exitosa
        '400':
          description: Parámetros inválidos
        '500':
          description: Error del servidor
      security:
      - bearerAuth: []
      - apiKeyAuth: []
      summary: Find workshops nearby
      tags:
      - poi
security:
- BearerAuth: []
servers:
- description: Producción
  url: https://back.transcend.cargoffer.com
tags:
- description: 'API del módulo Activity de TransCend. Este módulo es responsable del
    registro y consulta de actividades de usuario dentro del sistema TransCend.


    **Propósito principal:**

    - Registrar todas las actividades realizadas por usuarios en el sistema

    - Proporcionar consultas paginadas de actividades por usuario

    - Facilitar auditoría y análisis de comportamiento de usuarios

    - Soporte para debugging y monitoreo de uso del sistema


    **Tipos de actividades registradas:**

    - C: Create (Creación de recursos)

    - R: Read (Lectura/Consulta de recursos)

    - U: Update (Actualización de recursos)

    - D: Delete (Eliminación de recursos)


    **Características principales:**

    - Autenticación JWT opcional (permite consultas públicas limitadas)

    - Paginación automática con mongoose-paginate-v2

    - Filtros por fecha (startDate/endDate)

    - Validación estricta de tipos de actividad y sistemas operativos

    '
  name: activity
- description: '# Transcend Drivers Module API - Documentación Completa


    Este API es parte de **TransCend** y corresponde al módulo de gestión de conductores.
    Proporciona operaciones CRUD para conductores, seguimiento de posición en tiempo
    real, gestión de horas de conducción y registro completo de historial de conducción
    con datos de tacógrafo.


    ## 🎯 Propósito Principal

    Centralizar toda la información relacionada con los conductores de la plataforma,
    incluyendo:

    - Datos personales y licencias

    - Ubicación actual y seguimiento GPS

    - Horas de servicio y cumplimiento normativo

    - Historial completo de conducción con datos de tacógrafo

    - Análisis de cumplimiento y detección de infracciones


    ## 🔐 Autenticación

    Todas las rutas requieren autenticación mediante:

    1. **JWT Token** (preferido): Token obtenido del módulo IAM

    2. **API Key**: Para integraciones de servicios externos


    ## 📋 Convenciones de Respuesta

    - Éxito: `200` o `201` con datos en formato JSON

    - Error: `4xx` o `5xx` con objeto `{ error: \"mensaje descriptivo\" }`

    - Validación: `400` para datos inválidos, `404` para recursos no encontrados


    ## ⚠️ Limitaciones y Cuotas

    - Máximo 1000 conductores por usuario

    - Actualización de posición: 1 por minuto por conductor

    - Consultas de posición: 10 por segundo por usuario

    - Historial de posiciones: Retenido por 30 días

    '
  name: drivers
- description: '# API de Búsqueda de Códigos HS - Transcend


    ## 🎯 Propósito Principal

    Este API es parte de la plataforma **Transcend** y proporciona acceso completo
    a la jerarquía del Sistema Armonizado (HS Code) utilizado en comercio internacional.
    Permite a desarrolladores integrar búsquedas de códigos aduaneros en sus aplicaciones.


    ## 📊 Estructura Jerárquica

    El sistema organiza los códigos HS en 4 niveles anidados:

    1. **Secciones** - Nivel más alto (21 secciones totales, ej. "Sección XI - Textiles")

    2. **Capítulos** - Dentro de secciones (99 capítulos, ej. "Capítulo 52 - Algodón")

    3. **Partidas** - Dentro de capítulos (ej. "Algodón")

    4. **Subpartidas** - Nivel más granular (ej. "Algodón sin cardar ni peinar")


    ## 🔐 Autenticación Dual

    **IMPORTANTE**: Esta API acepta **DOS métodos de autenticación** (usa solo uno):

    - **Método 1**: Token JWT (para usuarios humanos)

    - **Método 2**: API Key (para integraciones automáticas)


    ## 🚀 Casos de Uso Comunes

    - Integración en sistemas de facturación electrónica

    - Automatización de declaraciones aduaneras

    - Búsqueda de códigos para cotizaciones internacionales

    - Validación de clasificación arancelaria


    ## 📋 Requisitos Técnicos

    - Todas las respuestas son en formato JSON

    - Codificación: UTF-8

    - Timeout recomendado: 30 segundos

    - Rate limiting: 100 requests/minuto por cliente

    '
  name: hscode
- description: '# Sistema de Gestión de Identidad y Acceso (IAM) - Transcend


    Este API es parte del sistema **Transcend** y corresponde al módulo de Identity
    and Access Management (IAM).

    Proporciona endpoints completos para la gestión de usuarios, autenticación, empresas
    y API keys.


    ## Funcionalidades principales:


    ### 1. Autenticación y Gestión de Usuarios

    - Registro de nuevos usuarios con creación automática de empresa

    - Login con JWT tokens

    - Recuperación y reset de contraseñas

    - Gestión de perfiles de usuario

    - Validación y renovación de tokens


    ### 2. Gestión de Empresas

    - Creación y actualización de información de empresa

    - Gestión de usuarios dentro de la empresa

    - Configuración de webhooks para notificaciones


    ### 3. Gestión de API Keys

    - Generación de API keys para integraciones

    - Verificación de API keys desde otros sistemas

    - Activación/desactivación de API keys


    ## Flujos típicos de uso:


    ### Para usuarios nuevos:

    1. Registro en `/api/auth/register` con datos de empresa

    2. Login en `/api/auth/login` para obtener token JWT

    3. Configurar empresa en `/api/company/`

    4. Crear API key en `/api/apikeys/` para integraciones


    ### Para integraciones externas:

    1. Autenticación con API key en header `x-api-key`

    2. Verificación de API key en `/api/apikeys/verify`

    3. Uso de endpoints protegidos según permisos

    '
  name: iam
- description: 'Este API es parte de **Transcend** y corresponde al módulo de procesamiento
    de pagos y facturación.


    Su principal misión es gestionar todos los aspectos relacionados con los pagos,
    suscripciones,

    facturación y planes de precios para los servicios de Transcend. Proporciona endpoints
    para:

    - Gestión de suscripciones y planes de precios

    - Procesamiento de pagos mediante Stripe

    - Facturación y gestión de facturas

    - Seguimiento de uso y métricas de consumo

    - Gestión de perfiles de usuario y clientes Stripe

    '
  name: pay
- description: "Esta API es parte de **Transcend** y corresponde al módulo de Puntos\
    \ de Interés (POI).\n\nProporciona endpoints para buscar Puntos de Interés (POIs),\
    \ con un enfoque principal actual en aparcamientos para camiones. Las búsquedas\
    \ se pueden realizar por:\n- Ubicación (coordenadas)\n- Código postal (para aparcamientos)\n\
    - Provincia (para aparcamientos)\n- Proximidad a una ruta (path)\n- Cercanía a\
    \ una ciudad\n\nLa estructura de la API está diseñada para soportar diversos tipos\
    \ de POIs (ej. aparcamientos, gasolineras, restaurantes), aunque la implementación\
    \ actual se centra en aparcamientos para varias rutas.\n\n**Nota**: \n- Todos\
    \ los endpoints excepto /test y /health requieren autenticación mediante token\
    \ JWT.\n"
  name: poi
- description: "Este API es parte de **Transcend** y corresponde al módulo de gestión\
    \ de perfiles.\n\nSu principal función es proporcionar una interfaz para buscar\
    \ y navegar por la jerarquía completa\nde códigos HS, que incluye:\n- Secciones\n\
    - Capítulos\n- Partidas\n- Subpartidas\n\nLa API permite tanto obtener la estructura\
    \ completa como realizar búsquedas específicas\npor términos en cualquier nivel\
    \ de la jerarquía.\n\n**Nota**: \n- La API requiere autenticación mediante un\
    \ token JWT.\n- Los endpoints están protegidos y requieren autenticación.\n"
  name: profiles
- description: "# Transcend Route API - Documentación Completa\n\n## Descripción General\n\
    \nLa API de Transcend Route calcula rutas optimizadas para transporte de mercancías\
    \ por carretera, considerando restricciones de vehículos, normativa EU de tiempos\
    \ de conducción, y puntos negros de tráfico.\n\n## Autenticación\n\nLa API requiere\
    \ autenticación mediante:\n- **JWT Bearer Token**: `Authorization: Bearer <token>`\n\
    - **API Key**: `x-api-key: <api-key>`\n\n## Características principales\n- Cálculo\
    \ de rutas optimizadas para camiones\n- Gestión automática de paradas de descanso\
    \ según normativa EU\n- Integración con puntos negros de tráfico\n- Isocronas\
    \ para análisis de cobertura\n- Exportación de rutas en formato GPX\n\n## Normativa\
    \ EU de tiempos de conducción\nLa API calcula automáticamente las paradas obligatorias\
    \ según la normativa europea:\n- **Parada corta**: 45 minutos después de 4.5 horas\
    \ de conducción\n- **Descanso diario**: 11 horas (puede ser 9h en descanso reducido)\n\
    - **Descanso semanal**: 45 horas (puede ser 24h en descanso reducido)\n- **Máximo\
    \ semanal**: 56 horas de conducción\n- **Máximo bisemanal**: 90 horas de conducción\n\
    \n## Endpoints Principales\n\n### 1. Cálculo de Ruta (`GET /api/route`)\n\n**Descripción**:\
    \ Calcula una ruta optimizada entre origen y destino.\n\n**Parámetros principales**:\n\
    - `origin_lat`, `origin_lon`: Coordenadas de origen (requerido)\n- `destiny_lat`,\
    \ `destiny_lon`: Coordenadas de destino (requerido)\n- `waypoints`: Array JSON\
    \ de paradas intermedias\n- `vehicle`: Información del vehículo (dimensiones,\
    \ peso)\n- `driver`: Estado de horas del conductor\n- `date`: Fecha de salida/llegada\n\
    - `map_layers`: Opciones de ruta (evitar peajes, etc.)\n\n#### Ejemplo Básico\n\
    \n```bash\ncurl -X GET \"https://api.transcend.es/route?origin_lat=42.2406&origin_lon=-8.7207&destiny_lat=41.3874&destiny_lon=2.1686\"\
    \ \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\"\n```\n\n#### Ejemplo Completo\
    \ con Vehículo y Conductor\n\n```bash\ncurl -X GET \"https://api.transcend.es/route?\\\
    \norigin_lat=42.2406&\\\norigin_lon=-8.7207&\\\ndestiny_lat=41.3874&\\\ndestiny_lon=2.1686&\\\
    \nvehicle=%7B%22width%22%3A2.55%2C%22height%22%3A4.0%2C%22weight%22%3A40000%2C%22consumption%22%3A32%7D&\\\
    \ndriver=%7B%22hoursStatus%22%3A%7B%22remainingWeeklyHours%22%3A56%2C%22remainingDayHours%22%3A9%7D%7D&\\\
    \ndate=%7B%22type%22%3A%22departure%22%2C%22date%22%3A%222025-01-15T08%3A00%3A00Z%22%7D\"\
    \ \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\"\n```\n\n#### Ejemplo con Waypoints\
    \ y Mercancía ADR\n\n```bash\ncurl -X GET \"https://api.transcend.es/route?\\\n\
    origin_lat=40.4168&\\\norigin_lon=-3.7038&\\\ndestiny_lat=41.3851&\\\ndestiny_lon=2.1734&\\\
    \nwaypoints=%5B%7B%22position%22%3A%5B40.5%2C-3.5%5D%2C%22customBreakMinutes%22%3A30%7D%5D&\\\
    \nmerchandise=%7B%22hsCode%22%3A%223301%22%2C%22weight%22%3A20000%2C%22isADR%22%3Atrue%7D&\\\
    \nmap_layers=%7B%22avoidTolls%22%3Afalse%2C%22calculateStops%22%3Atrue%7D\" \\\
    \n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\"\n```\n\n### 2. Puntos Negros de\
    \ Tráfico (`POST /api/traffic/blackspots/path`)\n\n**Descripción**: Obtiene zonas\
    \ de alta peligrosidad a lo largo de una ruta.\n\n```bash\ncurl -X POST \"https://api.transcend.es/api/traffic/blackspots/path\"\
    \ \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer\
    \ YOUR_JWT_TOKEN\" \\\n  -d '{\n    \"points\": [\n      [-3.7038, 40.4168],\n\
    \      [-3.7138, 40.4268],\n      [-3.7238, 40.4368]\n    ],\n    \"minDangerLevel\"\
    : 30\n  }'\n```\n\n### 3. Isocronas (`GET /api/isochrone`)\n\n**Descripción**:\
    \ Calcula áreas alcanzables desde un punto.\n\n```bash\ncurl -X GET \"https://api.transcend.es/api/isochrone?lat=40.4168&lon=-3.7038&km=100&isTime=false\"\
    \ \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\"\n```\n\n## Casos de Uso Empresarial\n\
    \n### 1. Distribución Urbana con Múltiples Paradas\n\n**Escenario**: Camión de\
    \ reparto que debe visitar 5 clientes en Madrid.\n\n```bash\ncurl -X GET \"https://api.transcend.es/api/route?\\\
    \norigin_lat=40.4168&\\\norigin_lon=-3.7038&\\\ndestiny_lat=40.4168&\\\ndestiny_lon=-3.7038&\\\
    \nwaypoints=%5B\\\n%7B%22position%22%3A%5B40.421%2C-3.692%5D%2C%22customBreakMinutes%22%3A15%7D%2C\\\
    \n%7B%22position%22%3A%5B40.430%2C-3.710%5D%2C%22customBreakMinutes%22%3A20%7D%2C\\\
    \n%7B%22position%22%3A%5B40.415%2C-3.725%5D%2C%22customBreakMinutes%22%3A10%7D%2C\\\
    \n%7B%22position%22%3A%5B40.405%2C-3.715%5D%2C%22customBreakMinutes%22%3A25%7D\\\
    \n%5D&\\\nvehicle=%7B%22width%22%3A2.3%2C%22height%22%3A3.5%2C%22weight%22%3A12000%7D&\\\
    \ndate=%7B%22type%22%3A%22departure%22%2C%22date%22%3A%222025-01-15T08%3A00%3A00Z%22%7D\"\
    \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n```\n\n### 2. Transporte Internacional\
    \ con ADR\n\n**Escenario**: Mercancía peligrosa desde España a Francia.\n\n```bash\n\
    curl -X GET \"https://api.transcend.es/api/route?\\\norigin_lat=41.3851&\\\norigin_lon=2.1734&\\\
    \ndestiny_lat=43.7102&\\\ndestiny_lon=7.2620&\\\nmerchandise=%7B%22hsCode%22%3A%221201%22%2C%22weight%22%3A24000%2C%22isADR%22%3Atrue%7D&\\\
    \nmap_layers=%7B%22avoidTunnels%22%3Atrue%2C%22avoidHighways%22%3Afalse%2C%22trafficDensity%22%3Atrue%7D&\\\
    \nvehicle=%7B%22weight%22%3A44000%2C%22height%22%3A4.2%7D&\\\nshow_black_points=true\"\
    \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n```\n\n### 3. Ruta con Llegada Programada\n\
    \n**Escenario**: Conductor debe llegar a las 17:00 para descarga.\n\n```bash\n\
    curl -X GET \"https://api.transcend.es/api/route?\\\norigin_lat=37.3891&\\\norigin_lon=-5.9845&\\\
    \ndestiny_lat=40.4168&\\\ndestiny_lon=-3.7038&\\\ndate=%7B%22type%22%3A%22arrival%22%2C%22date%22%3A%222025-01-15T17%3A00%3A00Z%22%7D&\\\
    \ndriver=%7B%22hoursStatus%22%3A%7B%22remainingWeeklyHours%22%3A48%2C%22remainingDayHours%22%3A8%7D%7D\"\
    \ \\\n  -H \"x-api-key: YOUR_API_KEY\"\n```\n\n## Gestión de Rutas Guardadas\n\
    \n### Crear Ruta Guardada\n\n```bash\ncurl -X POST \"https://api.transcend.es/api/saved-routes\"\
    \ \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer\
    \ YOUR_JWT_TOKEN\" \\\n  -d '{\n    \"title\": \"Madrid - Barcelona semanal\"\
    ,\n    \"description\": \"Ruta habitual con parada en Zaragoza\",\n    \"origin\"\
    : {\n      \"coordinates\": [40.4168, -3.7038],\n      \"locality\": \"Madrid\"\
    \n    },\n    \"destination\": {\n      \"coordinates\": [41.3851, 2.1734],\n\
    \      \"locality\": \"Barcelona\"\n    },\n    \"waypoints\": [\n      {\n  \
    \      \"coordinates\": [41.6561, -0.8773],\n        \"locality\": \"Zaragoza\"\
    \n      }\n    ]\n  }'\n```\n\n### Listar Rutas Guardadas\n\n```bash\ncurl -X\
    \ GET \"https://api.transcend.es/api/saved-routes\" \\\n  -H \"Authorization:\
    \ Bearer YOUR_JWT_TOKEN\"\n```\n\n### Exportar Ruta como GPX\n\n```bash\ncurl\
    \ -X GET \"https://api.transcend.es/api/saved-routes/60f1b2b3c4d5e6f7g8h9i0j1/gpx\"\
    \ \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\" \\\n  --output route.gpx\n\
    ```\n\n## Gestión de Ubicaciones\n\n### Crear Ubicación\n\n```bash\ncurl -X POST\
    \ \"https://api.transcend.es/api/locations\" \\\n  -H \"Content-Type: application/json\"\
    \ \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\" \\\n  -d '{\n    \"name\"\
    : \"Almacén Central Madrid\",\n    \"address\": \"Calle Industrial 25\",\n   \
    \ \"city\": \"Madrid\",\n    \"province\": \"Madrid\",\n    \"latitude\": \"40.4168\"\
    ,\n    \"longitude\": \"-3.7038\",\n    \"coordinates\": [40.4168, -3.7038],\n\
    \    \"openingTime\": \"08:00\",\n    \"closingTime\": \"18:00\",\n    \"phone\"\
    : \"+34 91 123 4567\",\n    \"email\": \"almacen.madrid@empresa.com\"\n  }'\n\
    ```\n\n## Códigos de Error Comunes\n\n| Código | Descripción | Solución |\n|--------|-------------|----------|\n\
    | 400 | Parámetros inválidos | Verificar formato de coordenadas y JSON |\n| 401\
    \ | No autorizado | Verificar token JWT o API key |\n| 404 | Ruta no encontrada\
    \ | Verificar coordenadas y conectividad |\n| 429 | Too Many Requests | Esperar\
    \ antes de reintentar |\n| 500 | Error interno del servidor | Contactar soporte\
    \ técnico |\n\n## Límites de Uso\n\n- **Rutas por hora**: 1000 (por usuario)\n\
    - **Waypoints por ruta**: 50 máximo\n- **Distancia máxima**: 2000 km\n- **Tiempo\
    \ de respuesta**: < 30 segundos (normal), < 60 segundos (con optimización)\n\n\
    ## Formatos de Datos\n\n### Coordenadas\n- **Formato**: `[longitud, latitud]`\
    \ (GeoJSON standard)\n- **Rango**: Longitud -180 a 180, Latitud -90 a 90\n- **Precisión**:\
    \ Hasta 6 decimales (centímetros)\n\n### Fechas\n- **Formato**: ISO 8601 (`2025-01-15T08:00:00Z`)\n\
    - **Zona horaria**: UTC preferido\n\n### JSON Encoding\nLos parámetros complejos\
    \ deben estar URL-encoded:\n\n```javascript\n// JavaScript\nconst params = {\n\
    \  vehicle: { width: 2.55, height: 4.0 },\n  driver: { hoursStatus: { remainingWeeklyHours:\
    \ 56 } }\n};\nconst encoded = encodeURIComponent(JSON.stringify(params));\n//\
    \ Resultado: %7B%22vehicle%22%3A%7B%22width%22%3A2.55%2C%22height%22%3A4.0%7D%2C%22driver%22%3A%7B%22hoursStatus%22%3A%7B%22remainingWeeklyHours%22%3A56%7D%7D%7D\n\
    ```\n\n## SDKs y Librerías\n\n### JavaScript/TypeScript\n\n```javascript\nclass\
    \ TranscendRouteAPI {\n  constructor(apiKey, baseUrl = 'https://api.transcend.es')\
    \ {\n    this.apiKey = apiKey;\n    this.baseUrl = baseUrl;\n  }\n\n  async calculateRoute(origin,\
    \ destination, options = {}) {\n    const params = new URLSearchParams({\n   \
    \   origin_lat: origin.lat,\n      origin_lon: origin.lon,\n      destiny_lat:\
    \ destination.lat,\n      destiny_lon: destination.lon,\n      ...this.encodeOptions(options)\n\
    \    });\n\n    const response = await fetch(`${this.baseUrl}/api/route`, {\n\
    \      headers: {\n        'x-api-key': this.apiKey\n      }\n    });\n\n    return\
    \ response.json();\n  }\n\n  encodeOptions(options) {\n    const encoded = {};\n\
    \    Object.keys(options).forEach(key => {\n      if (typeof options[key] ===\
    \ 'object') {\n        encoded[key] = encodeURIComponent(JSON.stringify(options[key]));\n\
    \      } else {\n        encoded[key] = options[key];\n      }\n    });\n    return\
    \ encoded;\n  }\n}\n\n// Uso\nconst api = new TranscendRouteAPI('your-api-key');\n\
    const route = await api.calculateRoute(\n  { lat: 42.2406, lon: -8.7207 },\n \
    \ { lat: 41.3874, lon: 2.1686 },\n  {\n    vehicle: { width: 2.55, height: 4.0,\
    \ weight: 40000 },\n    driver: { hoursStatus: { remainingWeeklyHours: 56 } }\n\
    \  }\n);\n```\n\n### Python\n\n```python\nimport requests\nimport json\nfrom urllib.parse\
    \ import urlencode\n\nclass TranscendRouteAPI:\n    def __init__(self, api_key:\
    \ str, base_url='https://api.transcend.es'):\n        self.api_key = api_key\n\
    \        self.base_url = base_url\n        self.session = requests.Session()\n\
    \        self.session.headers.update({'x-api-key': api_key})\n\n    def calculate_route(self,\
    \ origin, destination, **options):\n        params = {\n            'origin_lat':\
    \ origin['lat'],\n            'origin_lon': origin['lon'],\n            'destiny_lat':\
    \ destination['lat'],\n            'destiny_lon': destination['lon']\n       \
    \ }\n\n        # Encode complex objects as JSON strings\n        for key, value\
    \ in options.items():\n            if isinstance(value, (dict, list)):\n     \
    \           params[key] = json.dumps(value)\n            else:\n             \
    \   params[key] = value\n\n        response = self.session.get(f'{self.base_url}/api/route',\
    \ params=params)\n        response.raise_for_status()\n        return response.json()\n\
    \n# Uso\napi = TranscendRouteAPI('your-api-key')\nroute = api.calculate_route(\n\
    \    {'lat': 42.2406, 'lon': -8.7207},\n    {'lat': 41.3874, 'lon': 2.1686},\n\
    \    vehicle={'width': 2.55, 'height': 4.0, 'weight': 40000},\n    driver={'hoursStatus':\
    \ {'remainingWeeklyHours': 56}}\n)\n```\n\n## Webhooks y Integraciones\n\n###\
    \ Eventos Disponibles\n- `route.calculated`: Nueva ruta calculada\n- `route.saved`:\
    \ Ruta guardada por usuario\n- `location.created`: Nueva ubicación creada\n\n\
    ### Configuración de Webhooks\n\n```bash\ncurl -X POST \"https://api.transcend.es/api/webhooks\"\
    \ \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer\
    \ YOUR_JWT_TOKEN\" \\\n  -d '{\n    \"url\": \"https://your-app.com/webhooks/route-events\"\
    ,\n    \"events\": [\"route.calculated\", \"route.saved\"],\n    \"secret\": \"\
    your-webhook-secret\"\n  }'\n```\n\n## Soporte y Contacto\n\n- **Documentación\
    \ completa**: [OpenAPI Spec](./openapi.yaml)\n- **Email soporte**: support@transcend.es\n\
    - **Portal desarrolladores**: https://developers.transcend.es\n- **Status page**:\
    \ https://status.transcend.es\n\n## Changelog\n\n### v1.0.0 (2025-01-15)\n- ✅\
    \ Cálculo de rutas con paradas automáticas\n- ✅ Soporte para tipos de fecha (departure/arrival)\n\
    - ✅ Integración con puntos negros de tráfico\n- ✅ Gestión de rutas guardadas\n\
    - ✅ API de ubicaciones\n- ✅ Exportación GPX\n- ✅ Gamificación básica\n- ✅ Rutas\
    \ temporales compartidas\n\n## Testing y Validación\n\n### Estrategia de Testing\n\
    \n#### Niveles de Testing\n\n##### 1. Unit Tests\n- **Cobertura**: Funciones individuales\
    \ y métodos\n- **Herramientas**: Jest, Mocha, Chai\n- **Enfoque**: Lógica de negocio,\
    \ cálculos, validaciones\n\n##### 2. Integration Tests\n- **Cobertura**: APIs\
    \ externas, bases de datos, servicios\n- **Herramientas**: Supertest, Testcontainers\n\
    - **Enfoque**: Flujos completos, integraciones\n\n##### 3. End-to-End Tests\n\
    - **Cobertura**: Flujos completos de usuario\n- **Herramientas**: Cypress, Playwright\n\
    - **Enfoque**: Experiencia completa del usuario\n\n##### 4. Performance Tests\n\
    - **Cobertura**: Carga, estrés, volumetría\n- **Herramientas**: k6, Artillery,\
    \ JMeter\n- **Enfoque**: Rendimiento bajo carga\n\n### Métricas de Calidad\n\n\
    ```yaml\n# Objetivos de calidad\ncoverage:\n  statements: 85%\n  branches: 80%\n\
    \  functions: 90%\n  lines: 85%\n\nperformance:\n  response_time_p95: 2000ms \
    \ # Percentil 95\n  throughput: 100 req/sec\n  error_rate: 0.1%          # Máximo\
    \ 0.1%\n\nreliability:\n  uptime: 99.9%\n  mttr: 15min               # Mean Time\
    \ To Recovery\n  mtbf: 720h               # Mean Time Between Failures\n```\n\n\
    ### Scripts de Testing Automatizado\n\n#### Script de CI/CD\n\n```yaml\n# .github/workflows/test.yml\n\
    name: Tests\n\non:\n  push:\n    branches: [ main, develop ]\n  pull_request:\n\
    \    branches: [ main ]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    services:\n\
    \      mongodb:\n        image: mongo:5.0\n        ports:\n          - 27017:27017\n\
    \n    steps:\n    - uses: actions/checkout@v3\n\n    - name: Setup Node.js\n \
    \     uses: actions/setup-node@v3\n      with:\n        node-version: '18'\n \
    \       cache: 'npm'\n\n    - name: Install dependencies\n      run: npm ci\n\n\
    \    - name: Run linting\n      run: npm run lint\n\n    - name: Run unit tests\n\
    \      run: npm run test:unit\n      env:\n        NODE_ENV: test\n\n    - name:\
    \ Run integration tests\n      run: npm run test:integration\n      env:\n   \
    \     NODE_ENV: test\n        MONGODB_URI: mongodb://localhost:27017/transcend_test\n\
    \n    - name: Generate coverage report\n      run: npm run coverage\n\n    - name:\
    \ Upload coverage to Codecov\n      uses: codecov/codecov-action@v3\n      with:\n\
    \        file: ./coverage/lcov.info\n\n  performance-test:\n    runs-on: ubuntu-latest\n\
    \    if: github.ref == 'refs/heads/main'\n\n    steps:\n    - uses: actions/checkout@v3\n\
    \n    - name: Setup Node.js\n      uses: actions/setup-node@v3\n      with:\n\
    \        node-version: '18'\n\n    - name: Install dependencies\n      run: npm\
    \ ci\n\n    - name: Run performance tests\n      run: npm run test:performance\n\
    \      env:\n        BASE_URL: ${{ secrets.PERF_TEST_URL }}\n        API_KEY:\
    \ ${{ secrets.PERF_TEST_API_KEY }}\n\n  security-test:\n    runs-on: ubuntu-latest\n\
    \    if: github.ref == 'refs/heads/main'\n\n    steps:\n    - uses: actions/checkout@v3\n\
    \n    - name: Run security scan\n      uses: securecodewarrior/github-actions-gosec@master\n\
    \      with:\n        args: './...'\n\n    - name: Run dependency check\n    \
    \  uses: dependency-check/Dependency-Check_Action@main\n      with:\n        project:\
    \ 'Transcend Route API'\n        path: '.'\n        format: 'ALL'\n```\n\n####\
    \ Script de Smoke Tests\n\n```bash\n#!/bin/bash\n# scripts/smoke-test.sh\n\nset\
    \ -e\n\necho \"\U0001F680 Running smoke tests for Transcend Route API\"\n\nBASE_URL=\"\
    ${BASE_URL:-http://localhost:8086}\"\nAPI_KEY=\"${API_KEY:-test-key}\"\n\necho\
    \ \"\U0001F4CD Testing basic route calculation...\"\ncurl -f -s \"${BASE_URL}/api/route?origin_lat=40.4168&origin_lon=-3.7038&destiny_lat=41.3851&destiny_lon=2.1734\"\
    \ \\\n  -H \"x-api-key: ${API_KEY}\" > /dev/null\necho \"✅ Route calculation OK\"\
    \n\necho \"\U0001F4CD Testing health check...\"\ncurl -f -s \"${BASE_URL}/health\"\
    \ > /dev/null\necho \"✅ Health check OK\"\n\necho \"\U0001F4CD Testing traffic\
    \ health...\"\ncurl -f -s \"${BASE_URL}/api/traffic/health\" \\\n  -H \"x-api-key:\
    \ ${API_KEY}\" > /dev/null\necho \"✅ Traffic health OK\"\n\necho \"\U0001F4CD\
    \ Testing black spots...\"\ncurl -f -s -X POST \"${BASE_URL}/api/traffic/blackspots/path\"\
    \ \\\n  -H \"Content-Type: application/json\" \\\n  -H \"x-api-key: ${API_KEY}\"\
    \ \\\n  -d '{\"points\":[[-3.7038,40.4168],[-3.7138,40.4268]],\"minDangerLevel\"\
    :30}' > /dev/null\necho \"✅ Black spots OK\"\n\necho \"\U0001F389 All smoke tests\
    \ passed!\"\n```\n\n### Tests de Integración Detallados\n\n#### Test Básico de\
    \ Ruta\n\n```javascript\n// test/integration/route.integration.test.js\nconst\
    \ request = require('supertest');\nconst app = require('../../src/index');\n\n\
    describe('Route API Integration Tests', () => {\n  describe('GET /api/route',\
    \ () => {\n    it('should calculate a basic route successfully', async () => {\n\
    \      const response = await request(app)\n        .get('/api/route')\n     \
    \   .query({\n          origin_lat: 40.4168,\n          origin_lon: -3.7038,\n\
    \          destiny_lat: 41.3851,\n          destiny_lon: 2.1734\n        })\n\
    \        .set('x-api-key', process.env.TEST_API_KEY)\n        .expect(200);\n\n\
    \      // Validar estructura de respuesta\n      expect(response.body).toHaveProperty('main');\n\
    \      expect(response.body).toHaveProperty('stops');\n      expect(response.body.main).toHaveProperty('summary');\n\
    \      expect(response.body.main.summary).toHaveProperty('time');\n      expect(response.body.main.summary).toHaveProperty('length');\n\
    \n      // Validar tipos de datos\n      expect(typeof response.body.main.summary.time).toBe('string');\n\
    \      expect(typeof response.body.main.summary.length).toBe('number');\n    \
    \  expect(response.body.main.summary.length).toBeGreaterThan(0);\n    });\n\n\
    \    it('should handle invalid coordinates', async () => {\n      const response\
    \ = await request(app)\n        .get('/api/route')\n        .query({\n       \
    \   origin_lat: 91,  // Latitud inválida\n          origin_lon: -3.7038,\n   \
    \       destiny_lat: 41.3851,\n          destiny_lon: 2.1734\n        })\n   \
    \     .set('x-api-key', process.env.TEST_API_KEY)\n        .expect(400);\n\n \
    \     expect(response.body).toHaveProperty('message');\n      expect(response.body.message).toMatch(/coordenadas/i);\n\
    \    });\n  });\n});\n```\n\n#### Test de Puntos Negros\n\n```javascript\n// test/integration/blackspots.integration.test.js\n\
    describe('Black Spots API Integration Tests', () => {\n  describe('POST /api/traffic/blackspots/path',\
    \ () => {\n    it('should return black spots along a route', async () => {\n \
    \     const routePoints = [\n        [-3.7038, 40.4168], // Madrid\n        [-3.7138,\
    \ 40.4268],\n        [-3.7238, 40.4368]\n      ];\n\n      const response = await\
    \ request(app)\n        .post('/api/traffic/blackspots/path')\n        .send({\n\
    \          points: routePoints,\n          minDangerLevel: 30\n        })\n  \
    \      .set('x-api-key', process.env.TEST_API_KEY)\n        .expect(200);\n\n\
    \      expect(response.body).toHaveProperty('blackSpots');\n      expect(response.body).toHaveProperty('metadata');\n\
    \      expect(Array.isArray(response.body.blackSpots)).toBe(true);\n    });\n\
    \  });\n});\n```\n\n## Integración con Sistemas ERP\n\n### Sincronización de Ubicaciones\n\
    \n```bash\n#!/bin/bash\n# Script para sincronizar ubicaciones desde ERP\n\nERP_API_URL=\"\
    https://erp.empresa.com/api\"\nTRANSCEND_API_URL=\"https://api.transcend.es\"\n\
    API_KEY=\"your-api-key\"\n\n# Obtener ubicaciones del ERP\nlocations=$(curl -s\
    \ \"${ERP_API_URL}/locations\" -H \"Authorization: Bearer ${ERP_TOKEN}\")\n\n\
    # Convertir y enviar a Transcend\necho \"$locations\" | jq -r '.[] | @json' |\
    \ while read -r location; do\n  # Transformar formato ERP a Transcend\n  transcend_location=$(echo\
    \ \"$location\" | jq '{\n    name: .name,\n    address: .address,\n    city: .city,\n\
    \    province: .province,\n    latitude: (.coordinates.lat | tostring),\n    longitude:\
    \ (.coordinates.lon | tostring),\n    coordinates: [.coordinates.lon, .coordinates.lat],\n\
    \    openingTime: .businessHours.open,\n    closingTime: .businessHours.close,\n\
    \    customerId: .id\n  }')\n\n  # Crear en Transcend\n  curl -X POST \"${TRANSCEND_API_URL}/api/locations\"\
    \ \\\n    -H \"Content-Type: application/json\" \\\n    -H \"x-api-key: ${API_KEY}\"\
    \ \\\n    -d \"$transcend_location\"\ndone\n```\n\n### Cálculo Automático de Rutas\
    \ para Pedidos\n\n```javascript\n// Node.js - Integración con sistema de pedidos\n\
    \nconst axios = require('axios');\n\nclass ERPRouteIntegration {\n  constructor(transcendApiKey)\
    \ {\n    this.transcend = axios.create({\n      baseURL: 'https://api.transcend.es',\n\
    \      headers: { 'x-api-key': transcendApiKey }\n    });\n  }\n\n  async calculateRouteForOrder(order)\
    \ {\n    // Extraer información del pedido\n    const origin = await this.getWarehouseLocation(order.warehouseId);\n\
    \    const destination = await this.getCustomerLocation(order.customerId);\n \
    \   const vehicle = await this.getVehicleForOrder(order);\n\n    // Calcular ruta\n\
    \    const routeParams = {\n      origin_lat: origin.lat,\n      origin_lon: origin.lon,\n\
    \      destiny_lat: destination.lat,\n      destiny_lon: destination.lon,\n  \
    \    vehicle: JSON.stringify(vehicle),\n      merchandise: JSON.stringify({\n\
    \        weight: order.totalWeight,\n        isADR: order.isHazardous\n      }),\n\
    \      date: JSON.stringify({\n        type: 'departure',\n        date: order.requestedDeliveryDate\n\
    \      })\n    };\n\n    const response = await this.transcend.get('/api/route',\
    \ { params: routeParams });\n\n    // Actualizar pedido con información de ruta\n\
    \    await this.updateOrderWithRoute(order.id, response.data);\n\n    return response.data;\n\
    \  }\n\n  async getWarehouseLocation(warehouseId) {\n    // Consultar ubicación\
    \ del almacén desde ERP\n    const response = await this.erp.get(`/warehouses/${warehouseId}`);\n\
    \    return {\n      lat: response.data.latitude,\n      lon: response.data.longitude\n\
    \    };\n  }\n\n  async getCustomerLocation(customerId) {\n    // Buscar ubicación\
    \ del cliente en Transcend o ERP\n    try {\n      const response = await this.transcend.get(`/api/locations/${customerId}`);\n\
    \      return {\n        lat: parseFloat(response.data.latitude),\n        lon:\
    \ parseFloat(response.data.longitude)\n      };\n    } catch (error) {\n     \
    \ // Si no existe en Transcend, buscar en ERP\n      const erpLocation = await\
    \ this.erp.get(`/customers/${customerId}/location`);\n      return {\n       \
    \ lat: erpLocation.data.lat,\n        lon: erpLocation.data.lon\n      };\n  \
    \  }\n  }\n}\n\n// Uso\nconst integration = new ERPRouteIntegration('your-api-key');\n\
    \n// Procesar pedido nuevo\nconst order = {\n  id: 'ORD-2025-001',\n  warehouseId:\
    \ 'WH-MAD',\n  customerId: 'CUST-123',\n  totalWeight: 15000,\n  isHazardous:\
    \ false,\n  requestedDeliveryDate: '2025-01-20T10:00:00Z'\n};\n\nconst route =\
    \ await integration.calculateRouteForOrder(order);\nconsole.log('Ruta calculada:',\
    \ route.summary);\n```\n\n## Optimización de Flotas\n\n### Asignación Óptima de\
    \ Vehículos\n\n```python\nimport requests\nimport json\nfrom typing import List,\
    \ Dict, Any\n\nclass FleetOptimizer:\n    def __init__(self, api_key: str):\n\
    \        self.api_key = api_key\n        self.base_url = \"https://api.transcend.es\"\
    \n        self.session = requests.Session()\n        self.session.headers.update({\"\
    x-api-key\": api_key})\n\n    def optimize_fleet_assignment(self, orders: List[Dict],\
    \ vehicles: List[Dict], depot: Dict) -> Dict:\n        assignments = {}\n\n  \
    \      # Ordenar pedidos por urgencia y distancia\n        sorted_orders = sorted(orders,\
    \ key=lambda x: (x.get('priority', 1), x['distance']))\n\n        for vehicle\
    \ in vehicles:\n            if not sorted_orders:\n                break\n\n \
    \           # Encontrar pedidos que este vehículo puede atender\n            suitable_orders\
    \ = [\n                order for order in sorted_orders\n                if self.vehicle_can_handle_order(vehicle,\
    \ order)\n            ]\n\n            if suitable_orders:\n                #\
    \ Asignar pedido más cercano\n                assignment = self.assign_order_to_vehicle(vehicle,\
    \ suitable_orders[0], depot)\n                assignments[vehicle['id']] = assignment\n\
    \                sorted_orders.remove(suitable_orders[0])\n\n        return {\n\
    \            'assignments': assignments,\n            'unassigned_orders': sorted_orders\n\
    \        }\n\n    def vehicle_can_handle_order(self, vehicle: Dict, order: Dict)\
    \ -> bool:\n        # Verificar capacidad de peso\n        if vehicle['max_weight']\
    \ < order['weight']:\n            return False\n\n        # Verificar restricciones\
    \ ADR\n        if order.get('isADR') and not vehicle.get('adr_certified', False):\n\
    \            return False\n\n        # Verificar dimensiones\n        if (vehicle['max_width']\
    \ < order.get('width', 0) or\n            vehicle['max_height'] < order.get('height',\
    \ 0)):\n            return False\n\n        return True\n\n    def assign_order_to_vehicle(self,\
    \ vehicle: Dict, order: Dict, depot: Dict) -> Dict:\n        # Calcular ruta para\
    \ este vehículo y pedido\n        route_params = {\n            'origin_lat':\
    \ depot['lat'],\n            'origin_lon': depot['lon'],\n            'destiny_lat':\
    \ order['destination']['lat'],\n            'destiny_lon': order['destination']['lon'],\n\
    \            'vehicle': json.dumps({\n                'width': vehicle['width'],\n\
    \                'height': vehicle['height'],\n                'weight': vehicle['weight']\
    \ + order['weight'],\n                'consumption': vehicle['consumption']\n\
    \            }),\n            'merchandise': json.dumps({\n                'weight':\
    \ order['weight'],\n                'isADR': order.get('isADR', False)\n     \
    \       }),\n            'date': json.dumps({\n                'type': 'departure',\n\
    \                'date': order.get('deadline', '2025-01-15T08:00:00Z')\n     \
    \       })\n        }\n\n        response = self.session.get('/api/route', params=route_params)\n\
    \        route_data = response.json()\n\n        return {\n            'vehicle_id':\
    \ vehicle['id'],\n            'order_id': order['id'],\n            'route': route_data,\n\
    \            'estimated_cost': route_data['main']['summary']['cost'],\n      \
    \      'estimated_time': route_data['main']['summary']['timeWithBreaks']\n   \
    \     }\n\n# Ejemplo de uso\noptimizer = FleetOptimizer(\"your-api-key\")\n\n\
    orders = [\n    {\n        'id': 'ORD-001',\n        'weight': 8000,\n       \
    \ 'width': 2.2,\n        'height': 2.8,\n        'isADR': False,\n        'priority':\
    \ 1,\n        'distance': 150,\n        'destination': {'lat': 40.5, 'lon': -3.5},\n\
    \        'deadline': '2025-01-15T17:00:00Z'\n    }\n]\n\nvehicles = [\n    {\n\
    \        'id': 'VEH-001',\n        'max_weight': 12000,\n        'max_width':\
    \ 2.5,\n        'max_height': 3.0,\n        'width': 2.3,\n        'height': 2.8,\n\
    \        'weight': 4000,  # Tara\n        'consumption': 28,\n        'adr_certified':\
    \ True\n    }\n]\n\ndepot = {'lat': 40.4168, 'lon': -3.7038}\n\nresult = optimizer.optimize_fleet_assignment(orders,\
    \ vehicles, depot)\nprint(\"Asignaciones óptimas:\", json.dumps(result, indent=2))\n\
    ```\n\n## Conclusión\n\nEsta documentación completa proporciona todo lo necesario\
    \ para integrar la API de Transcend Route en sistemas empresariales. Desde ejemplos\
    \ básicos hasta implementaciones avanzadas de optimización de flotas, la API está\
    \ diseñada para escalar con las necesidades del negocio del transporte.\n\nPara\
    \ más información, contacta al equipo de soporte en support@transcend.es.\n"
  name: route
- description: 'Este API es parte de **TransCend** y corresponde al módulo de estaciones
    de servicio. Proporciona información sobre ubicaciones, precios y disponibilidad
    de combustibles en estaciones de servicio.

    Su principal misión es ayudar a los transportistas a encontrar las mejores opciones
    de repostaje en sus rutas, considerando factores como ubicación, precios y tipos
    de combustible disponibles.

    '
  name: stations
- description: '# 🚛 Módulo de Gestión de Peajes


    Este API es parte de la plataforma **Transcend** y proporciona funcionalidades
    completas para calcular y gestionar costos de peajes en rutas de transporte de
    mercancías.


    ## 🎯 Casos de Uso Principales


    - **Planificación de Rutas**: Calcular costos totales de transporte incluyendo
    peajes, combustible y mantenimiento

    - **Presupuestos**: Obtener desglose detallado de costos para propuestas comerciales

    - **Optimización**: Identificar rutas más económicas considerando peajes y tiempos

    - **Análisis Ambiental**: Calcular emisiones de CO2 para reportes de sostenibilidad


    ## 🚀 Características Principales


    - **Cálculo Inteligente**: Costos de peajes, combustible, mantenimiento y tiempo
    de conducción

    - **Tiempos Legales**: Incluye tiempos de descanso obligatorios según normativa
    de transporte

    - **Múltiples Combustibles**: Soporte para diesel, gasolina y gas natural vehicular

    - **Peajes Detallados**: Información por peaje individual con métodos de pago

    - **Cache Optimizado**: Rutas frecuentes cacheadas por 24 horas

    - **Integración Geolocalización**: Google Maps, Here Maps y sistemas de navegación


    ## 📊 Límites Operativos


    | Parámetro | Límite | Descripción |

    |-----------|--------|-------------|

    | Distancia máxima | 1,000 km | Entre origen y destino |

    | Peajes por ruta | 50 | Máximo número de peajes procesados |

    | Actualización precios | 24h | Frecuencia de actualización de tarifas |

    | Tasa de requests | 100/min | Por cliente autenticado |

    | Coordenadas | WGS84 | Sistema de coordenadas estándar |


    ## 🔐 Autenticación


    La autenticación es **opcional** pero recomendada para acceso a funcionalidades
    avanzadas y límites elevados.


    ```http

    # Ejemplo de uso con autenticación

    GET /api/costs?origin={"lat":41.3851,"lng":2.1734}&destination={"lat":40.4168,"lng":-3.7038}

    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

    ```


    ## 🎪 Ejemplo Completo


    ```bash

    # Cálculo de ruta Barcelona -> Madrid con carga completa

    curl -X GET "https://back.transcend.cargoffer.com/tolls/costs?\

    origin={\"lat\":41.3851,\"lng\":2.1734}&\

    destination={\"lat\":40.4168,\"lng\":-3.7038}&\

    with_tolls=true&\

    avg_consumption=35&\

    cargo_weight=15000&\

    fuel_type=diesel" \

    -H "Authorization: Bearer your_jwt_token_here"

    ```

    '
  name: tolls
- description: 'Este API es parte de **Transcend** y corresponde con el módulo de
    tráfico, para consultar información en tiempo real sobre eventos de tráfico, radares,
    puntos negros y flujo de tráfico en carreteras del sector logístico.


    **¿Para qué sirve?**

    - Consultar eventos de tráfico actuales (accidentes, cierres de carretera, eventos
    especiales)

    - Localizar radares fijos y móviles en rutas específicas

    - Identificar puntos negros (zonas de alta peligrosidad)

    - Calcular isocronas (áreas alcanzables en tiempo determinado)

    - Obtener información de flujo de tráfico en tiempo real


    **Casos de uso típicos:**

    - Planificación de rutas seguras para transporte de mercancías

    - Evitación de zonas de alto riesgo y congestión

    - Optimización de tiempos de entrega

    - Gestión de flotas en tiempo real


    **Ejemplo práctico:**

    ```

    Un transportista necesita planificar una ruta desde Madrid a Barcelona.

    Usa este API para:

    1. Consultar eventos de tráfico en la ruta

    2. Identificar radares y puntos negros

    3. Verificar el flujo de tráfico actual

    4. Calcular áreas alcanzables en diferentes tiempos

    ```

    '
  name: traffic
- description: 'Este API es parte de **Transcend** y corresponde al módulo de gestión
    de vehículos. Proporciona operaciones CRUD completas para la flota de vehículos
    del sector logístico.


    Su principal misión es mantener un registro actualizado de todos los vehículos
    disponibles, sus características técnicas y estado operativo, permitiendo una
    gestión eficiente de la flota.

    '
  name: vehicles
- description: 'Esta API es parte de **Transcend** y corresponde al módulo de consulta
    meteorológica, para verificar la situación meteorológica en un punto dado de las
    rutas en el sector logístico.


    Su principal misión es verificar la climatología, además de una consulta habitual
    para complementar la información correspondiente a los otros servicios de TransCend;
    sirve para determinar si un punto de la ruta presenta problemas *(o representa
    un peligro)* para el transporte de mercancías, proporcionando un valor porcentual
    de peligrosidad y un nivel de alerta en relación con la climatología habitual.

    '
  name: weather
x-tagGroups:
- name: Core
  tags:
  - iam
  - profiles
  - activity
- name: Transport
  tags:
  - drivers
  - vehicles
  - route
  - stations
  - tolls
  - traffic
  - weather
- name: Commerce
  tags:
  - pay
  - poi
  - hscode
