openapi: 3.1.0
info:
  title: Cargoffer eCMR API
  description: API de gestión de documentos eCMR (digitales) de Cargoffer. Firmas
    digitales, plantillas, pallets, direcciones, usuarios y más.
  version: 1.0.0
  contact:
    name: Soporte Cargoffer
    email: support@cargoffer.com
servers:
- url: https://ecmr.api.pro.cargoffer.com
  description: Producción
- url: https://ecmr.api.release.cargoffer.com
  description: Release
tags: []
paths:
  /addresses/:
    get:
      operationId: listMyAddresses
      summary: Listar direcciones del usuario con paginación
      deprecated: false
      description: "## Propósito\nDevuelve una lista paginada de las direcciones asociadas\
        \ a la empresa\ndel usuario autenticado (ya sea perfil de empresa o de transportista).\n\
        \n## Objetivo\nPermitir a empresas y transportistas consultar todas sus direcciones\n\
        registradas, con información adicional que indica si cada dirección\npuede\
        \ ser eliminada y si es la dirección predeterminada.\n\n## Casos de uso\n\
        - Mostrar un selector de direcciones de origen/destino al crear un eCMR.\n\
        - Verificar qué direcciones están en uso (vía `can_be_deleted`) antes de intentar\
        \ eliminarlas.\n- Paginar la lista de direcciones en la interfaz de administración\
        \ de empresa.\n\n## Detalles técnicos\nLos resultados se ordenan alfabéticamente\
        \ por `name` y luego por `company_name`.\nCada dirección incluye dos campos\
        \ calculados mediante agregación:\n- `can_be_deleted`: `false` si la dirección\
        \ está referenciada como\n  origen (`etd_address`) o destino (`etl_address`)\
        \ en algún eCMR del propietario.\n- `isDefault`: `true` si coincide con `address_default`\
        \ de la empresa.\n\n## Autenticación\nSoporta JWT Bearer token y API Key.\n"
      tags:
      - addresses
      parameters:
      - name: page
        in: query
        description: 'Número de página (1-indexed). Si se omite, se usa `1`.

          '
        required: false
        example: 1
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: 'Elementos por página. Si se omite o es ≤ 0 se usa el valor configurado
          en el servidor (`ITEMS_PAGE`).

          '
        required: false
        example: 20
        schema:
          type: integer
          minimum: 1
      responses:
        '200':
          description: Lista paginada de direcciones con metadatos de paginación y
            campos calculados (`can_be_deleted`, `isDefault`)
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas de `returnOK`
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta
                    example: 200
                  data:
                    $ref: '#/components/schemas/PaginatedAddressList'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  docs:
                  - _id: 64917511ef73c37ccae60bc4
                    name: Congalsa
                    company_name: CONGALSA SL
                    phone: '981834400'
                    city: ribeira
                    country: españa
                    zipcode: '15960'
                    street_number: '117'
                    street_address: Estrada Deán Norte
                    province: A Coruña
                    location:
                      type: Point
                      coordinates:
                      - -8.9952131
                      - 42.5668541
                    placeId: ChIJLdx3TsE5Lw0Rmh7qsvQUqII
                    name_address: Estrada Deán Norte, 117, 15960 Ribeira, A Coruña,
                      España
                    destinations: []
                    can_be_deleted: true
                    isDefault: true
                  totalDocs: 1
                  limit: 20
                  totalPages: 1
                  page: 1
                  pagingCounter: 1
                  hasPrevPage: false
                  hasNextPage: false
                  prevPage: null
                  nextPage: null
          headers: {}
        '401':
          description: Error de autenticación por token/JWT o estado de cuenta bloqueada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se envió token ni apikey
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token enviado no es válido o expiró
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
                account_blocked:
                  summary: La cuenta autenticada está bloqueada
                  value:
                    status: 401
                    message: ACCOUNT_BLOCKED
          headers: {}
        '404':
          description: 'Recurso/contexto no encontrado durante autenticación o resolución
            de empresa/usuario en el servicio de direcciones.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                apikey_not_found:
                  summary: La apikey enviada no existe
                  value:
                    status: 404
                    message: APIKEY_NOT_FOUND
                cia_not_found:
                  summary: No se encontró la compañía asociada a la apikey
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
                company_not_found:
                  summary: No se encontró la empresa asociada al usuario autenticado
                  value:
                    status: 404
                    message: COMPANY_NOT_FOUND
                user_not_found:
                  summary: El usuario del token no existe en la base de datos
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                user_type_not_found:
                  summary: El token no corresponde a un tipo de cuenta válido (company/trucker)
                  value:
                    status: 404
                    message: USER_TYPE_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la consulta de direcciones
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_GET_MINE
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      operationId: createAddress
      summary: Crear nueva dirección
      deprecated: false
      description: "## Propósito\nCrea una nueva dirección y la asocia a la empresa\
        \ del usuario\nautenticado (empresa o transportista).\n\n## Objetivo\nPermitir\
        \ que empresas y transportistas registren puntos de carga y\ndescarga que\
        \ luego podrán usar como origen o destino en sus eCMRs.\n\n## Casos de uso\n\
        - Registrar una nueva nave de almacenaje como dirección de recogida.\n- Añadir\
        \ una dirección de cliente frecuente para entregas.\n- Configurar la dirección\
        \ por defecto de facturación de la empresa.\n\n## Flujo de validación\n```mermaid\n\
        flowchart TD\n  A[Recibir petición] --> B{¿Usuario autenticado?}\n  B -->|No|\
        \ C[401 Unauthorized]\n  B -->|Sí| D{¿Tipo de cuenta válido?}\n  D -->|No|\
        \ E[404 USER_TYPE_NOT_FOUND]\n  D -->|Sí| F{¿Se envía addressGoogleMaps?}\n\
        \  F -->|Sí| G[Parsear address_components y geometry]\n  F -->|No| H[Usar\
        \ solo name y company_name]\n  G --> I[Crear documento Address]\n  H --> I\n\
        \  I --> J{¿isDefault = true?}\n  J -->|Sí| K[Actualizar address_default de\
        \ la empresa]\n  J -->|No| L[No modificar address_default]\n  K --> M[200\
        \ OK - Dirección creada]\n  L --> M\n```\n\n## Detalles técnicos\nSi se envía\
        \ `addressGoogleMaps`, el servidor extrae automáticamente:\ncalle (`route`),\
        \ número (`street_number`), ciudad (`locality`),\ncódigo postal (`postal_code`),\
        \ provincia (`administrative_area_level_2`),\npaís (`country`), coordenadas\
        \ GeoJSON y `placeId` a partir de los\n`address_components` y `geometry` de\
        \ Google Places.\n\n**Campos obligatorios**: `name`, `company_name`.\n**Campos\
        \ opcionales**: `phone`, `addressGoogleMaps`, `isDefault`.\n\n## Autenticación\n\
        Soporta JWT Bearer token y API Key.\n"
      tags:
      - addresses
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddressCreate'
            examples:
              simple:
                summary: Creación básica con geocodificación (Golden Path)
                value:
                  name: Almacén Madrid
                  company_name: Cargoffer Logistics SL
                  phone: '+34910000000'
                  isDefault: true
                  addressGoogleMaps:
                    place_id: ChIJLdx3TsE5Lw0Rmh7qsvQUqII
                    formatted_address: Calle de la Logística, 123, 28045 Madrid, España
                    address_components:
                    - long_name: '123'
                      short_name: '123'
                      types:
                      - street_number
                    - long_name: Calle de la Logística
                      short_name: C. de la Logística
                      types:
                      - route
                    - long_name: Madrid
                      short_name: Madrid
                      types:
                      - locality
                      - political
                    - long_name: Comunidad de Madrid
                      short_name: MD
                      types:
                      - administrative_area_level_1
                      - political
                    - long_name: España
                      short_name: ES
                      types:
                      - country
                      - political
                    - long_name: '28045'
                      short_name: '28045'
                      types:
                      - postal_code
                    geometry:
                      location:
                        lat: 40.39242
                        lng: -3.69462
                    types:
                    - street_address
              minimal:
                summary: Creación mínima sin geocodificación
                value:
                  name: Oficina Barcelona
                  company_name: Cargoffer Logistics SL
        required: true
      responses:
        '200':
          description: Dirección creada correctamente. Devuelve el documento persistido
            con los campos derivados de la geocodificación
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas de `returnOK`
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta
                    example: 200
                  data:
                    $ref: '#/components/schemas/Address'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 64917511ef73c37ccae60bc4
                  name: Almacén Madrid
                  company_name: Cargoffer Logistics SL
                  phone: '+34910000000'
                  city: madrid
                  country: españa
                  zipcode: '28045'
                  street_number: '123'
                  street_address: Calle de la Logística
                  province: Comunidad de Madrid
                  location:
                    type: Point
                    coordinates:
                    - -3.69462
                    - 40.39242
                  placeId: ChIJLdx3TsE5Lw0Rmh7qsvQUqII
                  name_address: Calle de la Logística, 123, 28045 Madrid, España
                  destinations: []
                  createdAt: '2024-10-02T18:24:37.606Z'
                  deleted: false
          headers: {}
        '401':
          description: Error de autenticación por token/JWT o estado de cuenta bloqueada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se envió token ni apikey
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token enviado no es válido o expiró
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
                account_blocked:
                  summary: La cuenta autenticada está bloqueada
                  value:
                    status: 401
                    message: ACCOUNT_BLOCKED
          headers: {}
        '404':
          description: 'Recurso/contexto no encontrado durante autenticación o creación.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                apikey_not_found:
                  summary: La apikey enviada no existe
                  value:
                    status: 404
                    message: APIKEY_NOT_FOUND
                cia_not_found:
                  summary: No se encontró la compañía asociada a la apikey
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
                company_not_found:
                  summary: No se encontró la empresa asociada al usuario autenticado
                  value:
                    status: 404
                    message: COMPANY_NOT_FOUND
                user_not_found:
                  summary: El usuario del token no existe en la base de datos
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                user_type_not_found:
                  summary: El token no corresponde a un tipo de cuenta válido (company/trucker)
                  value:
                    status: 404
                    message: USER_TYPE_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la creación de la dirección
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_CREATE
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /addresses/{id}:
    put:
      operationId: updateAddress
      summary: Actualizar dirección existente
      deprecated: false
      description: "## Propósito\nActualiza los datos de una dirección existente que\
        \ pertenece a la\nempresa del usuario autenticado.\n\n## Objetivo\nPermitir\
        \ corregir o actualizar la información de una dirección ya\nregistrada (nombre,\
        \ teléfono, ubicación) sin tener que eliminarla\ny recrearla.\n\n## Casos\
        \ de uso\n- Corregir el nombre o teléfono de contacto de un punto de entrega.\n\
        - Actualizar las coordenadas GPS mediante un nuevo objeto `addressGoogleMaps`.\n\
        - Establecer o quitar una dirección como predeterminada de la empresa.\n\n\
        ## Flujo de validación\n```mermaid\nflowchart TD\n  A[Recibir PUT /addresses/:id]\
        \ --> B{¿Usuario autenticado?}\n  B -->|No| C[401 Unauthorized]\n  B -->|Sí|\
        \ D{¿Dirección existe?}\n  D -->|No| E[404 NOT_FOUND]\n  D -->|Sí| F{¿Dirección\
        \ pertenece a la empresa?}\n  F -->|No| G[403 NOT_ALLOWED]\n  F -->|Sí| H{¿Se\
        \ envía addressGoogleMaps?}\n  H -->|Sí| I[Recalcular campos de ubicación]\n\
        \  H -->|No| J[Mantener ubicación actual]\n  I --> K{¿isDefault?}\n  J -->\
        \ K\n  K -->|true| L[Establecer como address_default]\n  K -->|false/omitido|\
        \ M{¿Era la predeterminada?}\n  M -->|Sí| N[Eliminar address_default]\n  M\
        \ -->|No| O[Sin cambios en default]\n  L --> P[200 OK]\n  N --> P\n  O -->\
        \ P\n```\n\n## Detalles técnicos\nEl servidor verifica que el `id` exista\
        \ y esté incluido en el array\n`addresses` de la empresa; de lo contrario\
        \ devuelve `403 NOT_ALLOWED`.\n\nCuando se envía `addressGoogleMaps`, los\
        \ campos de ubicación (`city`,\n`street_address`, `street_number`, `zipcode`,\
        \ `province`, `country`,\n`location`, `placeId`, `name_address`) se recalculan\
        \ desde los\n`address_components` de Google. Los campos que no están presentes\
        \ en\nel nuevo body se eliminan del documento si pertenecen a la lista de\n\
        campos de dirección editables.\n\n**Comportamiento de `isDefault`**:\n- `true`\
        \ → establece esta dirección como `address_default` de la empresa.\n- `false`\
        \ o no enviado → si la dirección **era** la predeterminada,\n  se elimina\
        \ `address_default` de la empresa (se des-marca).\n\n## Autenticación\nSoporta\
        \ JWT Bearer token y API Key.\n"
      tags:
      - addresses
      parameters:
      - name: id
        in: path
        description: ObjectId (24 caracteres hexadecimales) de la dirección a actualizar.
          Debe pertenecer a la empresa del usuario
        required: true
        example: 693978e10fe7ba19d690757b
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddressUpdate'
            examples:
              update_details:
                summary: Actualizar nombre, empresa y teléfono
                value:
                  name: Pasatras
                  company_name: pasatras SL
                  phone: '981834400'
                  isDefault: true
              update_location:
                summary: Actualizar ubicación vía Google Maps
                value:
                  addressGoogleMaps:
                    place_id: ChIJLdx3TsE5Lw0Rmh7qsvQUqII
                    formatted_address: Estrada Deán Norte, 117, 15960 Ribeira, A Coruña,
                      España
                    address_components:
                    - long_name: '117'
                      short_name: '117'
                      types:
                      - street_number
                    - long_name: Estrada Deán Norte
                      short_name: Estrada Deán Nte.
                      types:
                      - route
                    - long_name: Ribeira
                      short_name: Ribeira
                      types:
                      - locality
                      - political
                    - long_name: Galicia
                      short_name: GA
                      types:
                      - administrative_area_level_1
                      - political
                    - long_name: España
                      short_name: ES
                      types:
                      - country
                      - political
                    - long_name: '15960'
                      short_name: '15960'
                      types:
                      - postal_code
                    geometry:
                      location:
                        lat: 42.5668541
                        lng: -8.9952131
        required: true
      responses:
        '200':
          description: Dirección actualizada correctamente. Devuelve el documento
            completo con los campos de ubicación recalculados si se proporcionó `addressGoogleMaps`
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas de `returnOK`
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta
                    example: 200
                  data:
                    $ref: '#/components/schemas/Address'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 64917511ef73c37ccae60bc4
                  name: Pasatras
                  company_name: pasatras SL
                  phone: '981834400'
                  city: ribeira
                  country: españa
                  zipcode: '15960'
                  street_number: '117'
                  street_address: Estrada Deán Norte
                  province: Galicia
                  location:
                    type: Point
                    coordinates:
                    - -8.9952131
                    - 42.5668541
                  placeId: ChIJLdx3TsE5Lw0Rmh7qsvQUqII
                  name_address: Estrada Deán Norte, 117, 15960 Ribeira, A Coruña,
                    España
                  destinations: []
                  createdAt: '2024-10-02T18:24:37.606Z'
                  deleted: false
          headers: {}
        '401':
          description: Error de autenticación por token/JWT o estado de cuenta bloqueada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se envió token ni apikey
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token enviado no es válido o expiró
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
                account_blocked:
                  summary: La cuenta autenticada está bloqueada
                  value:
                    status: 401
                    message: ACCOUNT_BLOCKED
          headers: {}
        '403':
          description: La dirección existe pero no pertenece al array `addresses`
            de la empresa del usuario autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: NOT_ALLOWED
          headers: {}
        '404':
          description: 'La dirección con el ID proporcionado no existe, el usuario
            del token no se encontró, o el tipo de cuenta no pudo resolverse.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                address_not_found:
                  summary: No existe una dirección con el ObjectId proporcionado
                  value:
                    status: 404
                    message: NOT_FOUND
                apikey_not_found:
                  summary: La apikey enviada no existe
                  value:
                    status: 404
                    message: APIKEY_NOT_FOUND
                cia_not_found:
                  summary: No se encontró la compañía asociada a la apikey
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
                company_not_found:
                  summary: No se encontró la empresa asociada al usuario autenticado
                  value:
                    status: 404
                    message: COMPANY_NOT_FOUND
                user_not_found:
                  summary: El usuario del token no existe en la base de datos
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                user_type_not_found:
                  summary: El token no corresponde a un tipo de cuenta válido (company/trucker)
                  value:
                    status: 404
                    message: USER_TYPE_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la actualización de la dirección
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_EDIT
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    delete:
      operationId: deleteAddress
      summary: Eliminar dirección
      deprecated: false
      description: "## Propósito\nElimina permanentemente una dirección de la empresa\
        \ del usuario\nautenticado.\n\n## Objetivo\nPermitir limpiar direcciones obsoletas\
        \ o erróneas que ya no son\nnecesarias para la operativa de la empresa.\n\n\
        ## Casos de uso\n- Eliminar una dirección de un almacén cerrado.\n- Limpiar\
        \ direcciones duplicadas creadas por error.\n- Retirar puntos de entrega de\
        \ clientes con los que ya no se trabaja.\n\n## Flujo de validación\n```mermaid\n\
        flowchart TD\n  A[Recibir DELETE /addresses/:id] --> B{¿Usuario autenticado?}\n\
        \  B -->|No| C[401 Unauthorized]\n  B -->|Sí| D{¿Dirección existe?}\n  D -->|No|\
        \ E[404 NOT_FOUND]\n  D -->|Sí| F{¿Pertenece a la empresa?}\n  F -->|No| G[403\
        \ NOT_ALLOWED]\n  F -->|Sí| H[Eliminar ref del array empresa]\n  H --> I[Borrar\
        \ documento Address]\n  I --> J[200 OK]\n```\n\n## Detalles técnicos\nLa eliminación\
        \ es irreversible: se borra el documento de la colección\n`Address` (soft-delete\
        \ vía `mongoose-delete`) y se retira la referencia\ndel array `addresses`\
        \ de la empresa.\n\n> **⚠️ Importante:** Este endpoint **no** valida si la\
        \ dirección está\n> en uso como origen o destino en algún eCMR. Usa el campo\n\
        > `can_be_deleted` del listado (`GET /addresses/`) para verificarlo\n> antes\
        \ de invocar esta operación.\n\n## Autenticación\nSoporta JWT Bearer token\
        \ y API Key.\n"
      tags:
      - addresses
      parameters:
      - name: id
        in: path
        description: ObjectId (24 caracteres hexadecimales) de la dirección a eliminar.
          Debe pertenecer a la empresa del usuario
        required: true
        example: 693978520fe7ba19d6907560
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
      responses:
        '200':
          description: Dirección eliminada exitosamente. Devuelve el `_id` del registro
            borrado
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas de `returnOK`
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta
                    example: 200
                  data:
                    type: object
                    properties:
                      _id:
                        type: string
                        description: ObjectId de la dirección que fue eliminada
                        example: 693978520fe7ba19d6907560
                    required:
                    - _id
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 507f1f77bcf86cd799439011
          headers: {}
        '401':
          description: Error de autenticación por token/JWT o estado de cuenta bloqueada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se envió token ni apikey
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token enviado no es válido o expiró
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
                account_blocked:
                  summary: La cuenta autenticada está bloqueada
                  value:
                    status: 401
                    message: ACCOUNT_BLOCKED
          headers: {}
        '403':
          description: La dirección no pertenece al array `addresses` de la empresa
            del usuario autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: NOT_ALLOWED
          headers: {}
        '404':
          description: 'La dirección con el ID proporcionado no existe, el usuario
            del token no se encontró, o el tipo de cuenta no pudo resolverse.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                address_not_found:
                  summary: No existe una dirección con el ObjectId proporcionado
                  value:
                    status: 404
                    message: NOT_FOUND
                apikey_not_found:
                  summary: La apikey enviada no existe
                  value:
                    status: 404
                    message: APIKEY_NOT_FOUND
                cia_not_found:
                  summary: No se encontró la compañía asociada a la apikey
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
                company_not_found:
                  summary: No se encontró la empresa asociada al usuario autenticado
                  value:
                    status: 404
                    message: COMPANY_NOT_FOUND
                user_not_found:
                  summary: El usuario del token no existe en la base de datos
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                user_type_not_found:
                  summary: El token no corresponde a un tipo de cuenta válido (company/trucker)
                  value:
                    status: 404
                    message: USER_TYPE_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la eliminación de la dirección
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_DELETE
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /apikeys:
    get:
      operationId: listMyApiKeys
      summary: Listar claves API del usuario
      deprecated: false
      description: '## Propósito

        Devuelve la lista de claves API activas asociadas a la empresa del

        usuario autenticado (empresa o transportista).


        ## Objetivo

        Permitir a los usuarios visualizar sus claves API existentes para

        gestionar integraciones, identificar claves por su `temp_code` y

        verificar los roles asignados.


        ## Casos de uso

        - Consultar las claves API activas en el panel de configuración de la empresa.

        - Identificar una clave específica por su `temp_code` antes de revocarla.

        - Verificar el rol (`dev`, `gestor`, `admin`) asignado a cada clave.


        ## Detalles técnicos

        Las claves se devuelven **enmascaradas**: todos los caracteres excepto

        los últimos 4 se reemplazan por `*` (ej. `******a1b2`). La clave

        completa solo se muestra en la respuesta de creación (`POST /apikeys`).


        En el estado actual, el middleware de autenticación unificado puebla

        `req.company` (también para cuentas trucker). El controlador conserva un

        fallback legacy sobre `req.trucker`, pero la rama principal operativa es

        `req.company`.


        Cada clave incluye:

        - `apikey`: Versión enmascarada para identificación visual.

        - `temp_code`: Código temporal de 15 caracteres usado para eliminar la clave.

        - `role`: Nivel de permisos (`dev`, `gestor`, `admin`).

        - `createdAt`: Fecha de generación en formato ISO 8601.


        ## Autenticación

        Requiere JWT Bearer token (`isLoged` middleware). También acepta API Key.

        '
      tags:
      - apikeys
      parameters: []
      responses:
        '200':
          description: Lista de claves API obtenida exitosamente con las claves enmascaradas
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyListResponse'
              example:
                '0': 0
                status: 200
                data:
                  apikeys:
                  - apikey: '******a1b2'
                    temp_code: aB3dE5fG7hI9jKl
                    role: admin
                    createdAt: '2025-01-15T10:30:00.000Z'
                  - apikey: '******x9y8'
                    temp_code: mN4oP6qR8sT0uVw
                    role: dev
                    createdAt: '2025-02-20T14:00:00.000Z'
          headers: {}
        '401':
          description: Token JWT ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key en la petición
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token JWT proporcionado es inválido o está expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: 'No se encontró el usuario, la empresa o el tipo de cuenta
            asociado al token.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  summary: El usuario del token no existe en la base de datos
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  summary: No se encontró la empresa asociada al usuario
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
                not_found:
                  summary: No se pudo resolver el tipo de cuenta del token (company/trucker)
                  value:
                    status: 404
                    message: NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la consulta de claves API
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      operationId: createApiKey
      summary: Crear nueva clave API
      deprecated: false
      description: "## Propósito\nGenera una nueva clave API y la asocia a la empresa\
        \ del usuario\nautenticado (empresa o transportista).\n\n## Objetivo\nPermitir\
        \ que las empresas creen credenciales de acceso programático\na la API de\
        \ ECMR, con diferentes niveles de permisos según el rol\nasignado.\n\n## Casos\
        \ de uso\n- Generar una clave API para integrar un sistema ERP con la API\
        \ de ECMR.\n- Crear una clave con rol `dev` para entornos de prueba.\n- Emitir\
        \ una clave `admin` para herramientas internas de gestión.\n\n## Flujo de\
        \ generación\n```mermaid\nflowchart TD\n  A[Recibir POST /apikeys] --> B{¿Usuario\
        \ autenticado?}\n  B -->|No| C[401 Unauthorized]\n  B -->|Sí| D{¿Tipo de cuenta\
        \ válido?}\n  D -->|No| E[404 NOT_FOUND]\n  D -->|Sí| F[Generar apikey con\
        \ prefijo T/C + 9 chars]\n  F --> G[Generar temp_code de 15 chars]\n  G -->\
        \ H[Crear documento Apikey con role]\n  H --> I[Añadir referencia al array\
        \ apikeys de la empresa]\n  I --> J[200 OK - Devolver apikey completa]\n```\n\
        \n## Detalles técnicos\n- La clave generada tiene formato: prefijo (`T` para\
        \ trucker, `C` para\n  company) + 9 caracteres alfanuméricos aleatorios =\
        \ **10 caracteres**.\n- Se genera un `temp_code` de **15 caracteres** alfanuméricos\
        \ que se\n  usa como identificador seguro para la eliminación posterior.\n\
        - El campo `type` del body determina el `role` de la clave.\n- **La clave\
        \ completa solo se devuelve en esta respuesta**. En listados\n  posteriores\
        \ (`GET /apikeys`) se devuelve enmascarada.\n\n**Roles válidos**: `dev`, `gestor`,\
        \ `admin` (default: `admin`).\n\n## Autenticación\nRequiere JWT Bearer token\
        \ (`isLoged` middleware). También acepta API Key.\n"
      tags:
      - apikeys
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApiKeyCreate'
            examples:
              admin_key:
                summary: Crear clave con rol admin (valor por defecto)
                value:
                  type: admin
              dev_key:
                summary: Crear clave con rol dev para entorno de pruebas
                value:
                  type: dev
              gestor_key:
                summary: Crear clave con rol gestor para operaciones limitadas
                value:
                  type: gestor
        required: true
      responses:
        '200':
          description: 'Clave API creada exitosamente. La clave completa se devuelve
            solo en esta respuesta; en listados posteriores aparecerá enmascarada.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyCreatedResponse'
              example:
                '0': 0
                status: 200
                data:
                  apikey: Ca1B2c3D4e
          headers: {}
        '401':
          description: Token JWT ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key en la petición
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token JWT proporcionado es inválido o está expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: 'No se encontró el usuario, la empresa o el tipo de cuenta
            asociado al token.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  summary: El usuario del token no existe en la base de datos
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  summary: No se encontró la empresa asociada al usuario
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
                not_found:
                  summary: No se pudo resolver el tipo de cuenta del token (company/trucker)
                  value:
                    status: 404
                    message: NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la generación de la clave API
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /apikeys/{tempCode}:
    delete:
      operationId: deleteApiKey
      summary: Eliminar clave API
      deprecated: false
      description: "## Propósito\nRevoca y elimina permanentemente una clave API identificada\
        \ por su\n`temp_code`.\n\n## Objetivo\nPermitir a los usuarios revocar claves\
        \ API comprometidas, obsoletas\no que ya no son necesarias para sus integraciones.\n\
        \n## Casos de uso\n- Revocar una clave API que ha sido expuesta accidentalmente\
        \ en un repositorio.\n- Eliminar claves de prueba tras finalizar el desarrollo\
        \ de una integración.\n- Rotar credenciales eliminando la clave antigua y\
        \ creando una nueva.\n\n## Flujo de validación\n```mermaid\nflowchart TD\n\
        \  A[Recibir DELETE /apikeys/:tempCode] --> B{¿Usuario autenticado?}\n  B\
        \ -->|No| C[401 Unauthorized]\n  B -->|Sí| D{¿Existe apikey con este temp_code?}\n\
        \  D -->|No| E[404 APIKEY_NOT_FOUND]\n  D -->|Sí| F{¿Pertenece al array apikeys\
        \ de la empresa?}\n  F -->|No| G[404 APIKEY_NOT_FOUND]\n  F -->|Sí| H[Eliminar\
        \ documento Apikey]\n  H --> I[200 OK]\n```\n\n## Detalles técnicos\n- Se\
        \ usa el `temp_code` como identificador en lugar del `_id` de MongoDB\n  o\
        \ de la propia API Key. Esto evita exponer claves activas en URLs,\n  logs\
        \ o historiales del navegador.\n- La eliminación es mediante soft-delete (`mongoose-delete`).\n\
        - La clave deja de ser válida para autenticación inmediatamente.\n- **La acción\
        \ es irreversible.**\n\n> **⚠️ Importante:** Cualquier integración que esté\
        \ usando esta clave\n> dejará de autenticarse inmediatamente tras la eliminación.\n\
        \n## Autenticación\nRequiere JWT Bearer token (`isLoged` middleware). También\
        \ acepta API Key.\n"
      tags:
      - apikeys
      parameters:
      - name: tempCode
        in: path
        description: 'Código temporal alfanumérico de 15 caracteres que identifica
          de forma segura la clave API a eliminar. Se obtiene del listado `GET /apikeys`.

          '
        required: true
        example: aB3dE5fG7hI9jKl
        schema:
          type: string
          minLength: 15
          maxLength: 15
          pattern: ^[a-zA-Z0-9]{15}$
      responses:
        '200':
          description: Clave API eliminada exitosamente. Devuelve un objeto vacío
            en `data`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyDeleteResponse'
              example:
                '0': 0
                status: 200
                data: {}
          headers: {}
        '401':
          description: Token JWT ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key en la petición
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token JWT proporcionado es inválido o está expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: 'No se encontró la clave API con el `temp_code` proporcionado,
            o la clave no pertenece a la empresa del usuario autenticado.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                apikey_not_found:
                  summary: No existe una clave API con el temp_code proporcionado
                    o no pertenece a la empresa
                  value:
                    status: 404
                    message: APIKEY_NOT_FOUND
                user_not_found:
                  summary: El usuario del token no existe en la base de datos
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  summary: No se encontró la empresa asociada al usuario
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la eliminación de la clave API
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /auth/account_type:
    get:
      operationId: getAccountTypes
      summary: Obtener tipos de cuenta disponibles
      deprecated: false
      description: '## Propósito

        Devuelve los tipos de cuenta disponibles en el sistema ECMR.


        ## Objetivo

        Permitir que el formulario de registro consulte dinámicamente qué

        tipos de cuenta están activos (empresa o transportista) para

        mostrar las opciones correspondientes al usuario.


        ## Casos de uso

        - Cargar las opciones del selector de tipo de cuenta en el formulario de registro.

        - Verificar qué tipos de cuenta están habilitados en el sistema.


        ## Detalles técnicos

        Consulta la colección `AccountType` y devuelve todos los registros.

        Cada tipo tiene un `id`, un `value` (`company` o `trucker`) y un

        flag `active` que indica si acepta nuevos registros.


        ## Troubleshooting

        Si en entorno demo "no devuelve nada", suele significar que la

        colección `account_types` está vacía. En ese caso la respuesta

        sigue siendo `200 OK` con `data: []`.


        ## Autenticación

        No requiere autenticación.

        '
      tags:
      - auth
      parameters: []
      responses:
        '200':
          description: Lista de tipos de cuenta con su estado de activación
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
              examples:
                with_data:
                  summary: Tipos de cuenta configurados
                  value:
                    '0': 0
                    status: 200
                    data:
                    - id: 1
                      value: company
                      active: true
                    - id: 2
                      value: trucker
                      active: true
                empty_data:
                  summary: Colección account_types vacía
                  value:
                    '0': 0
                    status: 200
                    data: []
          headers: {}
        '400':
          description: Error inesperado al consultar los tipos de cuenta
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: Error message
          headers: {}
      security: []
  /auth/activate:
    post:
      operationId: resendActivationEmail
      summary: Reenviar email de activación
      deprecated: false
      description: "## Propósito\nReenvía el email de activación de cuenta a un usuario\
        \ que aún no\nha verificado su dirección de email.\n\n## Objetivo\nPermitir\
        \ que usuarios que no recibieron el email de activación\noriginal (o lo perdieron)\
        \ puedan solicitar un nuevo envío para\ncompletar la activación de su cuenta.\n\
        \n## Casos de uso\n- Un usuario nuevo no recibió el email de activación original.\n\
        - El administrador de la empresa reenvía la activación a un usuario específico.\n\
        - El propio usuario autenticado solicita un reenvío para su cuenta.\n\n##\
        \ Flujo de reenvío\n```mermaid\nflowchart TD\n  A[Recibir POST /auth/activate]\
        \ --> B{¿Usuario autenticado?}\n  B -->|No| C[401 Unauthorized]\n  B -->|Sí|\
        \ D{¿Se proporciona id o email?}\n  D -->|Sí| E[Buscar usuario por id o email]\n\
        \  D -->|No| F[Usar ID del usuario autenticado]\n  E --> G{¿Usuario encontrado?}\n\
        \  F --> G\n  G -->|No| H[404 NOT_FOUND]\n  G -->|Sí| I[Generar nuevo recovery_token\
        \ de 20 chars]\n  I --> J[Enviar email de activación]\n  J --> K[200 OK -\
        \ true]\n```\n\n## Detalles técnicos\n- El usuario objetivo se resuelve con\
        \ prioridad:\n  1. `body.id` o `query.id` o `params.id`\n  2. `body.email`\
        \ o `query.email` o `params.email`\n  3. ID del usuario autenticado actual\
        \ (`req.userData || req.company`)\n- Genera un nuevo `recovery_token` aleatorio\
        \ de 20 caracteres.\n- Envía el email de activación usando la plantilla `sendActive`.\n\
        - El enlace de activación del email permite al usuario verificar\n  su cuenta\
        \ y activarla.\n\n## Aclaración de flujo\n- `POST /auth/activate`: reenvío\
        \ de email de activación (requiere auth).\n- Activación real por token: `GET\
        \ /users/activate/{token}` (ruta pública).\n\n## Autenticación\nRequiere JWT\
        \ Bearer token o API Key (`isLoged` middleware).\n"
      tags:
      - auth
      parameters:
      - name: id
        in: query
        description: 'ObjectId del usuario al que reenviar la activación. Opcional
          si se proporciona `email` o se desea reenviar al usuario autenticado.

          '
        required: false
        example: 507f1f77bcf86cd799439011
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
      - name: email
        in: query
        description: 'Email del usuario al que reenviar la activación. Alternativa
          a `id`. Se usa si no se proporciona `id`.

          '
        required: false
        example: usuario@empresa.com
        schema:
          type: string
          format: email
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                id:
                  type: string
                  description: ObjectId del usuario a activar (alternativa al parámetro
                    query)
                  example: 507f1f77bcf86cd799439011
                email:
                  type: string
                  format: email
                  description: Email del usuario a activar (alternativa al parámetro
                    query)
                  example: usuario@empresa.com
            examples:
              by_email:
                summary: Reenviar activación por email
                value:
                  email: usuario@empresa.com
              by_id:
                summary: Reenviar activación por ObjectId
                value:
                  id: 507f1f77bcf86cd799439011
      responses:
        '200':
          description: Email de activación reenviado exitosamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
              example:
                '0': 0
                status: 200
                data: true
          headers: {}
        '400':
          description: Error al generar el token de activación o al enviar el email
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: Error message
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key en la petición
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token JWT proporcionado es inválido o está expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: 'No se encontró el usuario con el id o email proporcionado.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  summary: No existe un usuario con el id o email proporcionado
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                not_found:
                  summary: No se encontró el recurso solicitado
                  value:
                    status: 404
                    message: NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /auth/login:
    post:
      operationId: loginUser
      summary: Iniciar sesión
      deprecated: false
      description: "## Propósito\nAutentica a un usuario y devuelve un token JWT válido\
        \ para acceder\na los endpoints protegidos de la API.\n\n## Objetivo\nPermitir\
        \ que usuarios registrados (empresa o transportista) inicien\nsesión y obtengan\
        \ un token JWT que incluye información del perfil,\ntipo de cuenta y estado\
        \ de métodos de pago.\n\n## Casos de uso\n- Inicio de sesión desde la aplicación\
        \ web o móvil.\n- Obtener un JWT fresco cuando el token anterior ha expirado.\n\
        - Primera autenticación tras activar la cuenta por email.\n\n## Flujo de autenticación\n\
        ```mermaid\nflowchart TD\n  A[Recibir POST /auth/login] --> B{¿Email proporcionado?}\n\
        \  B -->|No| C[400 FORM_DATA_NOT_VALID]\n  B -->|Sí| D{¿Usuario existe?}\n\
        \  D -->|No| E[400 WRONG_CREDENTIALS]\n  D -->|Sí| F{¿Password proporcionado?}\n\
        \  F -->|No| G[400 FORM_DATA_NOT_VALID]\n  F -->|Sí| H{¿Password válido?}\n\
        \  H -->|No| I[400 WRONG_CREDENTIALS]\n  H -->|Sí| J{¿Empresa/transportista\
        \ asociada?}\n  J -->|No| K[401 CIA_NOT_FOUND]\n  J -->|Sí| L[Sincronizar\
        \ Stripe si company]\n  L --> M[Registrar lastSignInAt e IP]\n  M --> N[Registrar\
        \ acceso asíncrono]\n  N --> O[Generar JWT con datos del perfil]\n  O -->\
        \ P[200 OK - Token JWT]\n```\n\n## Detalles técnicos\n- Busca el usuario en\
        \ `CompanyUser` por `email`.\n- Busca la empresa asociada por `users` y, como\
        \ fallback, por `truckers`.\n- Si la empresa no es `trucker`, sincroniza cliente\
        \ en Stripe si no\n  existe aún un registro de billing.\n- Registra `lastSignInAt`\
        \ y `lastSignInIp` en el usuario.\n- Guarda un registro de acceso asíncrono\
        \ (`accessService.setAccess`).\n- El JWT incluye: datos del usuario, `accountType`,\
        \ `hasSign`,\n  `hasPaymentMethod`; y para truckers: `hasVehicles`, `hasDrivers`.\n\
        \n**Campos obligatorios**: `email`, `password`.\n\n## Autenticación\nNo requiere\
        \ autenticación previa.\n"
      tags:
      - auth
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
            examples:
              company_login:
                summary: Login como empresa
                value:
                  email: juan.perez@empresa.com
                  password: SecurePass123!
                  accountType: company
              trucker_login:
                summary: Login como transportista
                value:
                  email: conductor@transportes.com
                  password: TruckPass456!
                  accountType: trucker
        required: true
      responses:
        '200':
          description: Login exitoso. Devuelve el token JWT en el campo `data`
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas de `returnOK`
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta
                    example: 200
                  data:
                    type: string
                    description: Token JWT para autenticación en requests posteriores.
                      Incluye datos de perfil, tipo de cuenta y estado de pagos
                    example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
          headers: {}
        '400':
          description: 'Credenciales inválidas o datos del formulario incompletos
            (email o password faltantes).

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                wrong_credentials:
                  summary: Email no registrado o contraseña incorrecta
                  value:
                    status: 400
                    message: WRONG_CREDENTIALS
                form_data_not_valid:
                  summary: Email o password no proporcionados en el body
                  value:
                    status: 400
                    message: FORM_DATA_NOT_VALID
          headers: {}
        '401':
          description: Usuario no activo, bloqueado o sin empresa/transportista asociada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                cia_not_found:
                  summary: Usuario sin empresa o transportista asociada
                  value:
                    status: 401
                    message: CIA_NOT_FOUND
                user_not_active:
                  summary: Usuario existente pero aún no activado por email
                  value:
                    status: 401
                    message: USER_NOT_ACTIVE
                account_blocked:
                  summary: Cuenta bloqueada temporalmente
                  value:
                    status: 401
                    message: ACCOUNT_BLOCKED
          headers: {}
        '500':
          description: Error interno no controlado durante el proceso de autenticación
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
          headers: {}
      security: []
  /auth/recovery:
    post:
      operationId: requestPasswordRecovery
      summary: Solicitar recuperación de contraseña
      deprecated: false
      description: "## Propósito\nInicia el proceso de recuperación de contraseña\
        \ generando un token\núnico y enviando un email con el enlace de recuperación.\n\
        \n## Objetivo\nPermitir que usuarios que han olvidado su contraseña puedan\n\
        restablecerla de forma segura a través de un enlace único enviado\na su email\
        \ registrado.\n\n## Casos de uso\n- Un usuario ha olvidado su contraseña e\
        \ introduce su email para recuperarla.\n- El administrador de una empresa\
        \ solicita el restablecimiento del acceso.\n\n## Flujo de recuperación\n```mermaid\n\
        flowchart TD\n  A[Recibir POST /auth/recovery] --> B{¿Email proporcionado?}\n\
        \  B -->|No| C[401 NOT_VALID]\n  B -->|Sí| D{¿Usuario existe con ese email?}\n\
        \  D -->|No| E[200 OK - respuesta vacía por seguridad]\n  D -->|Sí| F[Generar\
        \ token único de recuperación]\n  F --> G[Guardar recovery_token en el usuario]\n\
        \  G --> H[Enviar email con enlace de recuperación]\n  H --> I[200 OK]\n```\n\
        \n## Detalles técnicos\n- El email se normaliza a minúsculas antes de la búsqueda.\n\
        - Si el email no existe en el sistema, se devuelve `200 OK` con\n  body vacío\
        \ **por seguridad** (no se revela si el email está registrado).\n- El `recovery_token`\
        \ se genera con `tools.generatePass()`.\n- El email de recuperación se envía\
        \ en el idioma indicado por `query.lang`\n  o el idioma del perfil del usuario.\n\
        \n**Campos obligatorios**: `email`.\n\n## Autenticación\nNo requiere autenticación.\n"
      tags:
      - auth
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecoveryRequest'
            example:
              email: usuario@empresa.com
        required: true
      responses:
        '200':
          description: 'Solicitud procesada. Si el email existe, se envía un enlace
            de recuperación. Devuelve siempre `200` por seguridad (no revela si el
            email está registrado).

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
              example:
                '0': 0
                status: 200
                data: {}
          headers: {}
        '400':
          description: Error al guardar el token de recuperación en la base de datos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: Error message
          headers: {}
        '401':
          description: El campo email no fue proporcionado o está vacío
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NOT_VALID
          headers: {}
      security: []
  /auth/recovery/{token}:
    get:
      operationId: renderPasswordRecoveryPage
      summary: Página de cambio de contraseña
      deprecated: false
      description: "## Propósito\nRenderiza una página HTML con un formulario para\
        \ cambiar la\ncontraseña usando el token de recuperación recibido por email.\n\
        \n## Objetivo\nProporcionar una interfaz web para que el usuario pueda establecer\n\
        una nueva contraseña de forma segura tras solicitar la recuperación.\n\n##\
        \ Casos de uso\n- El usuario hace clic en el enlace de recuperación recibido\
        \ por email.\n- Se necesita cambiar la contraseña desde un dispositivo sin\
        \ la app instalada.\n\n## Detalles técnicos\n- Es una **ruta web** (no API\
        \ REST): devuelve `text/html`.\n- Valida que el `token` corresponda a un usuario\
        \ existente.\n- Si el token no es válido o no se encuentra el usuario, renderiza\n\
        \  la página `page_user_not_found`.\n- Si es válido, renderiza la página `change_password`\
        \ con los datos\n  necesarios para enviar el formulario.\n- El idioma de la\
        \ página se controla con `query.lang` (default: `en`).\n\n## Autenticación\n\
        No requiere autenticación.\n"
      tags:
      - auth
      parameters:
      - name: token
        in: path
        description: Token de recuperación único recibido por email. Se almacena como
          `recovery_token` del usuario
        required: true
        example: abc123def456ghi789
        schema:
          type: string
      - name: lang
        in: query
        description: 'Idioma en el que renderizar la página HTML. Si se omite, se
          usa `en` por defecto.

          '
        required: false
        example: es
        schema:
          type: string
          enum:
          - en
          - es
          default: en
      responses:
        '200':
          description: Página HTML con el formulario de cambio de contraseña (o página
            de error si el token no es válido)
          content:
            text/html:
              schema:
                type: string
                description: HTML renderizado del formulario de cambio de contraseña
          headers: {}
        '503':
          description: Error interno al renderizar la plantilla HTML (fallo en el
            motor de templates)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 503
                message: TEMPLATE_RENDER_ERROR
          headers: {}
      security: []
  /auth/recovery_password:
    post:
      operationId: changePasswordByToken
      summary: Cambiar contraseña con token de recuperación
      deprecated: false
      description: "## Propósito\nCambia la contraseña del usuario utilizando el token\
        \ de recuperación\npreviamente generado por `POST /auth/recovery`.\n\n## Objetivo\n\
        Completar el flujo de recuperación de contraseña, permitiendo al\nusuario\
        \ establecer una nueva contraseña de forma segura.\n\n## Casos de uso\n- El\
        \ usuario envía el formulario de cambio de contraseña desde la\n  página renderizada\
        \ por `GET /auth/recovery/{token}`.\n\n## Flujo de cambio de contraseña\n\
        ```mermaid\nflowchart TD\n  A[Recibir POST /auth/recovery_password] --> B{¿Token\
        \ y password presentes?}\n  B -->|No| C[Renderizar página de error]\n  B -->|Sí|\
        \ D{¿Existe usuario con ese recovery_token?}\n  D -->|No| E[Renderizar página\
        \ de error]\n  D -->|Sí| F[Hashear nueva contraseña]\n  F --> G[Limpiar recovery_token]\n\
        \  G --> H[Guardar usuario]\n  H --> I[Renderizar página de éxito]\n```\n\n\
        ## Detalles técnicos\n- El token se puede enviar por `body.token`, `params.token`\
        \ o `query.token`.\n- Valida que la contraseña no esté vacía ni solo contenga\
        \ espacios.\n- La nueva contraseña se hashea con `User.getPassword()`.\n-\
        \ Tras el cambio, el `recovery_token` se limpia (se establece a `\"\"`).\n\
        - **Devuelve HTML** (no JSON): renderiza `email_password_ok` en éxito o\n\
        \  `email_password_ko` en error.\n\n## Autenticación\nNo requiere autenticación\
        \ (el token de recuperación actúa como credencial).\n"
      tags:
      - auth
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecoveryPasswordRequest'
            example:
              token: abc123def456ghi789
              password: NewSecurePass123!
        required: true
      responses:
        '200':
          description: 'Página HTML de resultado: muestra éxito si la contraseña se
            cambió correctamente, o error si el token es inválido o la contraseña
            está vacía.

            '
          content:
            text/html:
              schema:
                type: string
                description: HTML renderizado con el resultado del cambio de contraseña
          headers: {}
        '400':
          description: Error al persistir la nueva contraseña en la base de datos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: Error message
          headers: {}
        '503':
          description: Error interno al renderizar la plantilla HTML
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 503
                message: TEMPLATE_RENDER_ERROR
          headers: {}
      security: []
  /auth/register:
    post:
      operationId: registerAccount
      summary: Registrar nueva cuenta
      deprecated: false
      description: "## Propósito\nCrea una nueva cuenta de empresa o transportista\
        \ en el sistema ECMR,\nincluyendo la empresa, el usuario administrador y la\
        \ dirección principal.\n\n## Objetivo\nPermitir el alta de nuevas organizaciones\
        \ en la plataforma ECMR con\ntodo lo necesario para empezar a operar: datos\
        \ fiscales, usuario\nadministrador, dirección de facturación y registro en\
        \ Stripe.\n\n## Casos de uso\n- Una empresa de transporte se registra para\
        \ empezar a gestionar eCMRs.\n- Un transportista autónomo se da de alta en\
        \ la plataforma.\n- Un referido usa un código de invitación (`ref_code`) al\
        \ registrarse.\n\n## Flujo de registro\n```mermaid\nflowchart TD\n  A[Recibir\
        \ POST /auth/register] --> B{¿NIF/CIF proporcionado?}\n  B -->|No| C[406 NIF_REQUIRED]\n\
        \  B -->|Sí| D{¿Datos de usuario proporcionados?}\n  D -->|No| E[406 USER_REQUIRED]\n\
        \  D -->|Sí| F{¿NIF/CIF ya registrado?}\n  F -->|Sí| G[403 COMPANY_EXISTS]\n\
        \  F -->|No| H{¿Email ya registrado?}\n  H -->|Sí| I[403 COMPANY_EXISTS]\n\
        \  H -->|No| J[Crear dirección con geocodificación]\n  J --> K[Crear empresa\
        \ con datos fiscales]\n  K --> L[Crear usuario admin con contraseña hasheada]\n\
        \  L --> M[Registrar cliente en Stripe]\n  M --> N[Guardar usuario y empresa]\n\
        \  N --> O[Enviar email de bienvenida + activación]\n  O --> P[200 OK - Cuenta\
        \ creada inactiva]\n```\n\n## Detalles técnicos\n- Valida unicidad de `invoice_data.taxid`\
        \ (NIF/CIF) y `user.email`.\n- Crea una dirección parseando `address` con\
        \ `AddressGoogleMaps` si se\n  proporcionan datos de geocodificación.\n- El\
        \ usuario se crea con rol `admin` y un `recovery_token` para activación.\n\
        - Se intenta registrar un cliente en Stripe (no bloquea si falla).\n- Se envían\
        \ dos emails: bienvenida y activación de cuenta.\n- **La cuenta queda inactiva**\
        \ hasta que el usuario haga clic en el\n  enlace de activación recibido por\
        \ email.\n- Si `accountType` es `\"trucker\"`, la empresa se crea como transportista.\n\
        \n**Campos obligatorios del body**:\n- `invoice_data.taxid`: NIF/CIF de la\
        \ empresa\n- `user.name`, `user.lastname`, `user.email`, `user.password`\n\
        - `socialName`: Razón social\n- `address`: Datos de dirección\n\n**Campos\
        \ opcionales**: `lang`, `ref_code` (o `coupon`), `accountType`.\n\n## Nota\
        \ para pruebas en demo\nLos campos `invoice_data.taxid` y `user.email` deben\
        \ ser únicos por\nintento. Si se reutilizan valores de un alta previa, el\
        \ backend puede\nresponder `403 COMPANY_EXISTS` (comportamiento esperado).\n\
        \n## Autenticación\nNo requiere autenticación.\n"
      tags:
      - auth
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
            examples:
              company_register:
                summary: Registro de empresa (valores de prueba únicos)
                value:
                  accountType: company
                  socialName: Demo Company QA 20260304
                  invoice_data:
                    taxid: BQA20260304001
                    phone: +34 912 345 678
                  user:
                    name: Juan
                    lastname: Pérez García
                    email: qa.company.20260304.001@demo.local
                    password: SecurePass123!
                  address:
                    street: Calle Mayor 10
                    city: Madrid
                    state: Madrid
                    country: España
                    postalCode: '28013'
                    phone: +34 912 345 678
                    email: contacto@empresa.com
                  lang: es
                  ref_code: REF123
              trucker_register:
                summary: Registro de transportista (valores de prueba únicos)
                value:
                  accountType: trucker
                  socialName: Demo Trucker QA 20260304
                  invoice_data:
                    taxid: TQA20260304001
                    phone: +34 600 123 456
                  user:
                    name: Pedro
                    lastname: García López
                    email: qa.trucker.20260304.001@demo.local
                    password: TruckPass456!
                  address:
                    street: Polígono Industrial Norte 5
                    city: Valencia
                    state: Valencia
                    country: España
                    postalCode: '46001'
                    phone: +34 600 123 456
                    email: pedro@transportes.com
                  lang: es
        required: true
      responses:
        '200':
          description: 'Cuenta creada exitosamente. La cuenta queda inactiva hasta
            la activación por email. Devuelve los datos de la empresa creada.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          headers: {}
        '400':
          description: Error al crear la empresa, la dirección o el usuario (validación
            de datos o error de persistencia)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: Error creating company
          headers: {}
        '403':
          description: Ya existe una empresa con el mismo NIF/CIF o un usuario con
            el mismo email
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                company_exists:
                  summary: NIF/CIF o email ya registrado en el sistema
                  value:
                    status: 403
                    message: COMPANY_EXISTS
                trucker_exists:
                  summary: Alta trucker rechazada por taxid o email ya existente
                  value:
                    status: 403
                    message: COMPANY_EXISTS
          headers: {}
        '406':
          description: Datos obligatorios faltantes en el body de la petición
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                nif_required:
                  summary: El campo invoice_data.taxid es obligatorio
                  value:
                    status: 406
                    message: NIF_REQUIRED
                user_required:
                  summary: El objeto user con name, lastname, email y password es
                    obligatorio
                  value:
                    status: 406
                    message: USER_REQUIRED
          headers: {}
      security: []
  /billing/check-stripe:
    get:
      operationId: checkBillingStripeStatus
      summary: Consultar estado de suscripción Stripe del usuario autenticado
      deprecated: false
      description: '## Propósito

        Obtener el estado de suscripción actual del usuario autenticado en

        billing ECMR.


        ## Objetivo

        Permitir que la app determine si un usuario tiene ciclo activo,

        suspendido o cancelado y adaptar funcionalidades premium.


        ## Casos de uso

        - Mostrar estado del plan en dashboard de billing.

        - Bloquear/desbloquear funcionalidades según suscripción.

        - Forzar sincronización ligera desde Stripe si no hay ciclo local activo.


        ## Detalles técnicos

        Usa `tools.getUserContext(req)` para resolver `userId`.

        Si hay email disponible, asegura `UserProfile` antes de consultar.

        Devuelve `BillingCycle` poblado con `pricingTier` o `null` si no existe

        perfil/ciclo.

        En `catch`, usa `returnKO` sin código explícito (`418`).


        ## Autenticación

        Requiere JWT Bearer token o API key (`isLoged` middleware).

        '
      tags:
      - billing
      parameters: []
      responses:
        '200':
          description: Estado de suscripción obtenido correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingCheckStripeResponse'
        '401':
          description: Token/API key ausente, inválido o cuenta bloqueada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
        '404':
          description: Usuario o compañía no encontrada para el contexto autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
        '418':
          description: Error funcional al consultar o sincronizar estado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 418
                message: UserProfile not found
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /billing/create-checkout-session:
    post:
      operationId: createBillingCheckoutSession
      summary: Crear sesión de Stripe Checkout para suscripción ECMR
      deprecated: false
      description: '## Propósito

        Crear una sesión de Stripe Checkout para que el usuario inicie o cambie

        su suscripción ECMR.


        ## Objetivo

        Devolver una URL segura de Stripe para completar el flujo de pago desde

        frontend sin exponer lógica sensible de servidor.


        ## Casos de uso

        - Contratar un nuevo plan ECMR.

        - Migrar de tier al seleccionar otro `priceId`.

        - Reintentar alta de suscripción tras fallo previo.


        ## Detalles técnicos

        Requiere `priceId`, `successUrl` y `cancelUrl`.

        Si `priceId` es ObjectId de Mongo, el backend intenta resolver su

        `stripePriceId`.

        El backend asegura `UserProfile` antes de crear checkout y responde

        `data.url` con el link de Stripe.

        En `catch`, usa `returnKO` sin código explícito (`418`).


        ## Autenticación

        Requiere JWT Bearer token o API key (`isLoged` middleware).

        '
      tags:
      - billing
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BillingCheckoutSessionRequest'
            example:
              priceId: price_1ABCDxyz
              successUrl: https://app.example.com/billing/success
              cancelUrl: https://app.example.com/billing/cancel
      responses:
        '200':
          description: Sesión de checkout creada correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingSessionUrlResponse'
        '401':
          description: Token/API key ausente, inválido o cuenta bloqueada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Usuario o compañía no encontrada para el contexto autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '418':
          description: Error funcional al crear la sesión de checkout
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 418
                message: UserProfile not found. Please ensure profile is created first.
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /billing/create-portal-session:
    post:
      operationId: createBillingPortalSession
      summary: Crear sesión de Stripe Billing Portal
      deprecated: false
      description: '## Propósito

        Generar una sesión de Stripe Billing Portal para que el usuario

        autenticado gestione su suscripción.


        ## Objetivo

        Redirigir al usuario a un portal seguro de Stripe para actualizar método

        de pago, revisar plan o cancelar suscripción.


        ## Casos de uso

        - Abrir área de gestión de suscripción desde la app.

        - Actualizar tarjeta o método de pago.

        - Gestionar cancelación/reactivación según reglas de Stripe.


        ## Detalles técnicos

        Requiere `returnUrl` para volver a la app tras salir del portal.

        El backend resuelve `customerId` desde `UserProfile` y retorna

        `data.url` de la sesión de portal.

        En `catch`, usa `returnKO` sin código explícito (`418`).


        ## Autenticación

        Requiere JWT Bearer token o API key (`isLoged` middleware).

        '
      tags:
      - billing
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BillingPortalSessionRequest'
            example:
              returnUrl: https://app.example.com/billing
      responses:
        '200':
          description: Sesión de portal creada correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingSessionUrlResponse'
        '401':
          description: Token/API key ausente, inválido o cuenta bloqueada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Usuario o compañía no encontrada para el contexto autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '418':
          description: Error funcional al crear la sesión de portal
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 418
                message: UserProfile not found
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /billing/invoices:
    get:
      operationId: getBillingInvoices
      summary: Listar facturas Stripe del usuario autenticado
      deprecated: false
      description: '## Propósito

        Devolver facturas Stripe simplificadas del usuario autenticado con

        soporte de paginación por cursor.


        ## Objetivo

        Permitir construir histórico de facturación en frontend sin exponer

        toda la estructura nativa de Stripe.


        ## Casos de uso

        - Renderizar listado de facturas en panel de billing.

        - Cargar más resultados con cursor (`starting_after`).

        - Acceder a URL PDF y hosted invoice para descarga/visualización.


        ## Detalles técnicos

        `limit` se parsea como entero y por defecto toma `10`.

        `starting_after` se usa como cursor Stripe.

        La respuesta devuelve `data[]` de facturas normalizadas, `hasMore` y

        `lastId`.

        En `catch`, usa `returnKO` sin código explícito (`418`).


        ## Autenticación

        Requiere JWT Bearer token o API key (`isLoged` middleware).

        '
      tags:
      - billing
      parameters:
      - name: limit
        in: query
        description: Máximo de facturas por página (default 10)
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 10
        example: 10
      - name: starting_after
        in: query
        description: Cursor Stripe para paginación (id de factura)
        required: false
        schema:
          type: string
        example: in_1ABCXYZ
      responses:
        '200':
          description: Facturas obtenidas correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingInvoicesResponse'
        '401':
          description: Token/API key ausente, inválido o cuenta bloqueada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Usuario o compañía no encontrada para el contexto autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '418':
          description: Error funcional al listar facturas
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 418
                message: UserProfile not found
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /billing/pricing-tiers:
    get:
      operationId: getBillingPricingTiers
      summary: Obtener planes y tarifas de billing ECMR
      deprecated: false
      description: '## Propósito

        Exponer el catálogo de planes de facturación ECMR disponibles para

        iniciar una suscripción.


        ## Objetivo

        Permitir al frontend cargar los tiers de precio y mostrar al usuario

        los límites/capacidades asociados antes de contratar.


        ## Casos de uso

        - Pintar página de pricing pública.

        - Refrescar catálogo de planes antes de checkout.

        - Validar que un `priceId` pertenece a un plan conocido.


        ## Detalles técnicos

        Consulta `PricingTiersECMR` ordenado por `priceCents` ascendente y

        responde usando el envelope estándar `returnOK`.

        En caso de error, el controlador usa `returnKO` sin código explícito,

        por lo que el status por defecto es `418`.


        ## Autenticación

        No requiere autenticación.

        '
      tags:
      - billing
      parameters: []
      responses:
        '200':
          description: Lista de tiers de pricing de ECMR
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingPricingTiersResponse'
        '418':
          description: Error funcional durante la consulta de tiers
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 418
                message: Error message
      security: []
  /billing/webhook/stripe:
    post:
      operationId: handleBillingStripeWebhook
      summary: Recibir eventos de webhook Stripe de billing ECMR
      deprecated: false
      description: '## Propósito

        Recibir y validar eventos enviados por Stripe para mantener sincronizado

        el estado de suscripciones y facturas en ECMR.


        ## Objetivo

        Procesar eventos como `checkout.session.completed`,

        `customer.subscription.updated` o eventos de invoice para actualizar

        ciclos de facturación y estado interno del usuario.


        ## Casos de uso

        - Confirmar una suscripción completada tras checkout.

        - Sincronizar cambios de estado de suscripción.

        - Registrar pagos o fallos de pago de facturas.


        ## Detalles técnicos

        Requiere cabecera `stripe-signature` para verificar firma con

        `STRIPE_WEBHOOK_SECRET`.

        El endpoint usa `req.rawBody` (o fallback `req.body`) para construir

        el evento con Stripe y no usa `returnOK/returnKO`; responde JSON simple

        en éxito y `text/plain` en errores de firma/handler.


        ## Autenticación

        No requiere JWT ni API key. La autenticación es criptográfica por

        firma Stripe (`stripe-signature`).

        '
      tags:
      - billing
      parameters:
      - name: stripe-signature
        in: header
        description: Firma Stripe para validación del webhook
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
            example:
              id: evt_1Qwerty123
              type: customer.subscription.updated
              data:
                object:
                  id: sub_123
                  customer: cus_123
      responses:
        '200':
          description: Evento aceptado y procesado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingWebhookReceived'
              example:
                received: true
        '400':
          description: Firma inválida o payload no verificable
          content:
            text/plain:
              schema:
                type: string
              example: 'Webhook Error: No signatures found matching the expected signature
                for payload'
        '500':
          description: Error interno al procesar el evento validado
          content:
            text/plain:
              schema:
                type: string
              example: Server Error
      security: []
  /categories/find:
    get:
      operationId: searchCategories
      summary: Buscar categorías de carga
      deprecated: false
      description: "## Propósito\nBusca categorías de carga (headings y subheadings\
        \ del sistema\narmonizado) por un término de búsqueda de texto libre.\n\n\
        ## Objetivo\nPermitir que los usuarios encuentren rápidamente la categoría\
        \ de\nmercancía adecuada al crear o editar un eCMR, mostrando resultados\n\
        combinados de capítulos (headings) y subcapítulos (subheadings).\n\n## Casos\
        \ de uso\n- Buscar \"animal\" para encontrar categorías relacionadas con animales\
        \ vivos.\n- Filtrar categorías de carga al rellenar el campo de mercancía\
        \ de un eCMR.\n- Autocompletar un selector de tipo de mercancía en la interfaz.\n\
        \n## Flujo de búsqueda\n```mermaid\nflowchart TD\n  A[Recibir GET /categories/find?term=X]\
        \ --> B{¿term proporcionado y no vacío?}\n  B -->|No| C[404 TERM_NOT_FOUND]\n\
        \  B -->|Sí| D[Buscar en cargo_heading por regex en title/label]\n  D -->\
        \ E[Buscar en cargo_subheading por regex en title/label]\n  E --> F[Combinar\
        \ resultados - máx 20 de cada colección]\n  F --> G[Limpiar títulos y unir\
        \ etiquetas]\n  G --> H[Ordenar alfabéticamente por title]\n  H --> I[200\
        \ OK - Lista de categorías]\n```\n\n## Detalles técnicos\n- La búsqueda se\
        \ ejecuta con `$regex` y opción `i` (case-insensitive)\n  sobre los campos\
        \ `title` y `label` de ambas colecciones.\n- Se limita a **20 resultados**\
        \ de `cargo_heading` y **20 resultados**\n  de `cargo_subheading`, que se\
        \ combinan y ordenan alfabéticamente\n  por `title` (máximo 40 resultados\
        \ totales).\n- Los títulos se limpian eliminando el prefijo `\"heading \"\
        `.\n- Las etiquetas (`label`, almacenadas como array) se unen con espacios\n\
        \  y se eliminan guiones (`-`) y dos puntos (`:`).\n- El campo `url` se devuelve\
        \ recortado; puede estar vacío.\n\n## Autenticación\nRequiere JWT Bearer token\
        \ o API Key (`isLoged` middleware).\n"
      tags:
      - categories
      parameters:
      - name: term
        in: query
        description: 'Término de búsqueda para filtrar categorías. Se busca con regex
          case-insensitive en los campos `title` y `label` de headings y subheadings.
          No puede estar vacío.

          '
        required: true
        example: animal
        schema:
          type: string
          minLength: 1
      responses:
        '200':
          description: Lista de categorías encontradas, combinadas de headings y subheadings,
            ordenadas alfabéticamente por título
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas de `returnOK`
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta
                    example: 200
                  data:
                    $ref: '#/components/schemas/CategoryList'
                required:
                - status
                - data
                - '0'
              examples:
                with_results:
                  summary: Búsqueda con resultados encontrados
                  value:
                    '0': 0
                    status: 200
                    data:
                    - title: live animals
                      label: animales vivos
                      url: https://example.com/live-animals
                    - title: meat and edible meat offal
                      label: carne y despojos comestibles
                      url: https://example.com/meat
                    - title: fish and crustaceans
                      label: pescado y crustáceos
                      url: ''
                empty_results:
                  summary: Búsqueda sin coincidencias (array vacío)
                  value:
                    '0': 0
                    status: 200
                    data: []
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key en la petición
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token JWT proporcionado es inválido o está expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
                account_blocked:
                  summary: La cuenta del usuario está bloqueada
                  value:
                    status: 401
                    message: ACCOUNT_BLOCKED
          headers: {}
        '404':
          description: 'El parámetro `term` no fue proporcionado o está vacío

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                term_not_found:
                  summary: El parámetro term es obligatorio y no puede estar vacío
                  value:
                    status: 404
                    message: TERM_NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /company_data/:
    get:
      operationId: getMyCompanyData
      summary: Obtener datos de la empresa
      deprecated: false
      description: "## Propósito\nDevuelve los datos parseados de la empresa asociada\
        \ al usuario\nautenticado (empresa o transportista).\n\n## Objetivo\nPermitir\
        \ que la aplicación cargue los datos de la empresa para\nmostrar en la configuración\
        \ del perfil, en cabeceras de eCMRs,\no para verificar datos fiscales y de\
        \ contacto.\n\n## Casos de uso\n- Cargar la pantalla de configuración de empresa\
        \ con los datos actuales.\n- Obtener la firma digital para previsualizar en\
        \ la interfaz.\n- Consultar datos fiscales (NIF, razón social) para facturación.\n\
        \n## Detalles técnicos\n- Resuelve usuario y empresa vía `toolsAccountType`.\n\
        - Devuelve los datos parseados con `getCompanyDataByType`, que adapta\n  el\
        \ formato según si es empresa o transportista.\n- Los datos incluyen: `_id`,\
        \ `name`, `socialName`, `status`, `reason`,\n  `hasSign`, `sign`, dirección,\
        \ datos de facturación, persona de contacto, etc.\n\n## Autenticación\nRequiere\
        \ JWT Bearer token o API Key.\n"
      tags:
      - company_data
      parameters: []
      responses:
        '200':
          description: Datos completos de la empresa del usuario autenticado
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas `returnOK`
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta
                    example: 200
                  data:
                    $ref: '#/components/schemas/CompanyData'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 63d7907cbe76403b35da63dd
                  name: CIA Testing 01
                  socialName: Cia Testing 01 S.L.
                  status: false
                  reason: PENDING
                  hasSign: true
                  sign: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAS...
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key en la petición
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token JWT proporcionado es inválido o está expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: 'No se encontró el usuario, el tipo de cuenta o la empresa
            asociada al token.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_type_not_found:
                  summary: No se pudo resolver el tipo de cuenta del token (company/trucker)
                  value:
                    status: 404
                    message: USER_TYPE_NOT_FOUND
                user_not_found:
                  summary: El usuario del token no existe en la base de datos
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                not_found:
                  summary: No se pudo determinar el tipo de cuenta (controlador)
                  value:
                    status: 404
                    message: NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado al obtener los datos de la empresa
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_GET_MINE
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    put:
      operationId: updateMyCompanyData
      summary: Actualizar datos de la empresa
      deprecated: false
      description: "## Propósito\nActualiza los datos de la empresa del usuario autenticado\
        \ (empresa o\ntransportista).\n\n## Objetivo\nPermitir que los administradores\
        \ modifiquen la razón social, dirección,\ndatos de facturación y persona de\
        \ contacto de su empresa desde el\npanel de configuración.\n\n## Casos de\
        \ uso\n- Actualizar la razón social o el NIF/CIF de la empresa.\n- Cambiar\
        \ la dirección fiscal o de contacto.\n- Modificar los datos de la persona\
        \ de contacto (nombre, email, teléfono).\n- Actualizar la zona horaria de\
        \ la empresa.\n\n## Flujo de actualización\n```mermaid\nflowchart TD\n  A[Recibir\
        \ PUT /company_data/] --> B{¿Usuario autenticado?}\n  B -->|No| C[401 Unauthorized]\n\
        \  B -->|Sí| D{¿Tipo de cuenta válido?}\n  D -->|No| E[404 NOT_FOUND]\n  D\
        \ -->|Sí| F[Parsear body.companyData]\n  F --> G[Actualizar datos con updateDataByCompanyAndType]\n\
        \  G --> H{¿Tiene método de pago?}\n  H -->|No| I[Crear cliente de pago en\
        \ Stripe]\n  H -->|Sí| J[Sin cambios en pago]\n  I --> K[200 OK - Datos actualizados]\n\
        \  J --> K\n```\n\n## Detalles técnicos\n- Los datos a actualizar se envían\
        \ dentro de `body.companyData` (no en\n  el body raíz).\n- Tras actualizar,\
        \ verifica si la empresa tiene método de pago (`isPaymentMethodSet`).\n  Si\
        \ no lo tiene, crea automáticamente un cliente de pago (Stripe).\n- Devuelve\
        \ los datos de empresa actualizados y parseados.\n\n**Estructura del body**:\
        \ `{ companyData: { ... } }`.\n\n## Autenticación\nRequiere JWT Bearer token\
        \ o API Key.\n"
      tags:
      - company_data
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                companyData:
                  $ref: '#/components/schemas/CompanyDataUpdate'
              required:
              - companyData
              additionalProperties: false
            examples:
              update_basic:
                summary: Actualizar nombre social y dirección principal
                value:
                  companyData:
                    socialName: Mi Empresa SL
                    email: contacto@empresa.com
                    phone: '+34912345678'
                    address:
                      street_address: Calle Mayor
                      street_number: '10'
                      city: Madrid
                      state: Madrid
                      country: esp
                      zipcode: '28013'
                      timezone: Europe/Madrid
              update_contact_only:
                summary: Actualizar solo contacto principal (válido)
                value:
                  companyData:
                    email: nuevo-contacto@empresa.com
                    phone: '+34999999888'
        required: true
      responses:
        '200':
          description: Datos de empresa actualizados correctamente. Devuelve los datos
            parseados tras la actualización
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas `returnOK`
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta
                    example: 200
                  data:
                    $ref: '#/components/schemas/CompanyData'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 63d7907cbe76403b35da63dd
                  socialName: Mi Empresa SL
                  hasSign: true
                  sign: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAS...
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key en la petición
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token JWT proporcionado es inválido o está expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: 'No se encontró el usuario, el tipo de cuenta o la empresa
            asociada al token.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_type_not_found:
                  summary: No se pudo resolver el tipo de cuenta del token (company/trucker)
                  value:
                    status: 404
                    message: USER_TYPE_NOT_FOUND
                user_not_found:
                  summary: El usuario del token no existe en la base de datos
                  value:
                    status: 404
                    message: USER_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la actualización de los datos de empresa
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_UPDATE_MINE
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /company_data/editSign:
    put:
      operationId: updateCompanySign
      summary: Actualizar firma digital de la empresa
      deprecated: false
      description: "## Propósito\nActualiza la firma digital de la empresa del usuario\
        \ autenticado.\n\n## Objetivo\nPermitir que las empresas suban o actualicen\
        \ su firma digital, que\nse usa para firmar eCMRs electrónicamente.\n\n##\
        \ Casos de uso\n- Subir la firma digital por primera vez al configurar la\
        \ empresa.\n- Actualizar una firma expirada o incorrecta.\n- Cambiar la firma\
        \ tras un cambio de representante legal.\n\n## Flujo de actualización de firma\n\
        ```mermaid\nflowchart TD\n  A[Recibir PUT /company_data/editSign] --> B{¿Usuario\
        \ autenticado?}\n  B -->|No| C[401 Unauthorized]\n  B -->|Sí| D{¿Se envía\
        \ archivo image?}\n  D -->|Sí| E[Convertir imagen a data-url base64]\n  D\
        \ -->|No| F{¿Se envía body.sign?}\n  F -->|Sí| G[Usar body.sign directamente]\n\
        \  F -->|No| H[500 ERROR_NOT_SIGNED]\n  E --> I[Guardar en cia.sign + hasSign=true]\n\
        \  G --> I\n  I --> J[200 OK]\n```\n\n## Detalles técnicos\n- Acepta **dos\
        \ formatos** de entrada:\n  1. **Archivo**: campo `image` como `multipart/form-data`\
        \ (procesado por multer `upload.single`).\n     El servidor convierte la imagen\
        \ a `data:<mimeType>;base64,...`.\n  2. **Data URL**: campo `sign` en el body\
        \ JSON (fallback si no se envía archivo).\n- Si no se proporciona ni archivo\
        \ ni `sign`, devuelve `500 ERROR_NOT_SIGNED`.\n- Establece `cia.hasSign =\
        \ true` y guarda la firma como string data-url.\n- Si el archivo reside en\
        \ S3 (sin buffer local), lo descarga, convierte y elimina.\n\n## Autenticación\n\
        Requiere JWT Bearer token o API Key.\n"
      tags:
      - company_data
      parameters: []
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                image:
                  type: string
                  format: binary
                  description: Archivo de imagen de la firma (PNG, JPEG). Procesado
                    por multer como campo `image`
                sign:
                  type: string
                  description: Firma como data-url base64 (fallback si no se envía
                    archivo)
                  example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
          application/json:
            schema:
              type: object
              properties:
                sign:
                  type: string
                  description: Firma como data-url base64
                  example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
              required:
              - sign
        required: true
      responses:
        '200':
          description: Firma actualizada correctamente
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas `returnOK`
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta
                    example: 200
                  data:
                    type: string
                    description: Confirmación de éxito
                    example: OK
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data: OK
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key en la petición
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token JWT proporcionado es inválido o está expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: 'No se encontró el usuario, el tipo de cuenta o la empresa
            asociada al token.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_type_not_found:
                  summary: No se pudo resolver el tipo de cuenta del token (company/trucker)
                  value:
                    status: 404
                    message: USER_TYPE_NOT_FOUND
                user_not_found:
                  summary: El usuario del token no existe en la base de datos
                  value:
                    status: 404
                    message: USER_NOT_FOUND
          headers: {}
        '500':
          description: 'Error al procesar la firma o al guardar en base de datos.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                error_edit_sign:
                  summary: Error genérico al actualizar la firma
                  value:
                    status: 500
                    message: ERROR_EDIT_SIGN
                error_not_signed:
                  summary: No se proporcionó ni archivo ni campo sign en el body
                  value:
                    status: 500
                    message: ERROR_NOT_SIGNED
                error_saving:
                  summary: Error al convertir la imagen a base64 o al guardar en BD
                  value:
                    status: 500
                    message: ERROR_SAVING
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /company_data/isComplete:
    get:
      operationId: checkProfileCompleteness
      summary: Verificar completitud del perfil
      deprecated: false
      description: "## Propósito\nDevuelve el estado de completitud del perfil de\
        \ la empresa del usuario\nautenticado (empresa o transportista).\n\n## Objetivo\n\
        Permitir que la aplicación determine si el perfil de la empresa está\ncompleto\
        \ y si tiene un método de pago configurado, para mostrar\navisos o bloquear\
        \ funcionalidades que requieran perfil completo.\n\n## Casos de uso\n- Mostrar\
        \ un banner de \"Completa tu perfil\" en el dashboard.\n- Bloquear la creación\
        \ de eCMRs hasta que el perfil esté completo.\n- Verificar si el método de\
        \ pago está configurado antes de activar facturación.\n\n## Detalles técnicos\n\
        - Resuelve el usuario y la empresa del token JWT.\n- Evalúa `isProfileCompletedByType`\
        \ según el tipo de cuenta.\n- Verifica `isPaymentMethodSet`; si no tiene método\
        \ de pago, intenta\n  crear un cliente de pago automáticamente (Stripe).\n\
        - Devuelve `isCompleted` (bool) e `isPaymentMethodSet` (bool).\n\n## Autenticación\n\
        Requiere JWT Bearer token o API Key.\n"
      tags:
      - company_data
      parameters: []
      responses:
        '200':
          description: Estado de completitud del perfil con indicador de método de
            pago
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas `returnOK`
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta
                    example: 200
                  data:
                    $ref: '#/components/schemas/ProfileComplete'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  isCompleted: true
                  isPaymentMethodSet: true
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key en la petición
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: El token JWT proporcionado es inválido o está expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: 'No se encontró el usuario, el tipo de cuenta o la empresa
            asociada al token.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_type_not_found:
                  summary: No se pudo resolver el tipo de cuenta del token (company/trucker)
                  value:
                    status: 404
                    message: USER_TYPE_NOT_FOUND
                user_not_found:
                  summary: El usuario del token no existe en la base de datos
                  value:
                    status: 404
                    message: USER_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado al evaluar la completitud del perfil
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_PROFILE_COMPLETE
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /country/:
    get:
      operationId: getCountriesCatalog
      summary: Obtener catálogo de países
      deprecated: false
      description: "## Propósito\nDevuelve el catálogo paginado de países disponibles\
        \ en el sistema.\n\n## Objetivo\nProporcionar una lista de países para usar\
        \ en selectores de\nformularios (registro, dirección, datos de facturación)\
        \ con sus\ncódigos ISO y nombres oficiales.\n\n## Casos de uso\n- Rellenar\
        \ un selector de país en el formulario de registro.\n- Obtener la lista de\
        \ países habilitados para validar direcciones.\n- Mostrar nombres oficiales\
        \ de países en documentos eCMR.\n\n## Detalles técnicos\n- Usa `Country.paginate()`\
        \ sin filtros ni parámetros de paginación,\n  devolviendo la primera página\
        \ con el límite por defecto de\n  `mongoose-paginate-v2` (10 docs).\n- El\
        \ modelo tiene `strict: false`, por lo que los documentos pueden\n  contener\
        \ campos adicionales como `name.common` y `name.official`\n  que no están\
        \ definidos en el schema estricto.\n- No requiere autenticación.\n\n## Autenticación\n\
        No requiere autenticación (endpoint público).\n"
      tags:
      - country
      parameters: []
      responses:
        '200':
          description: Catálogo paginado de países con códigos ISO, nombres y estado
            de habilitación
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas `returnOK`
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta
                    example: 200
                  data:
                    $ref: '#/components/schemas/PaginatedCountryList'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  docs:
                  - code: ES
                    enabled: true
                    name:
                      common: Spain
                      official: Kingdom of Spain
                  - code: FR
                    enabled: true
                    name:
                      common: France
                      official: French Republic
                  - code: PT
                    enabled: true
                    name:
                      common: Portugal
                      official: Portuguese Republic
                  totalDocs: 195
                  limit: 10
                  totalPages: 20
                  page: 1
                  pagingCounter: 1
                  hasPrevPage: false
                  hasNextPage: true
                  prevPage: null
                  nextPage: 2
          headers: {}
        '400':
          description: Error inesperado al ejecutar la consulta paginada de países
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: Error message from Country.paginate
          headers: {}
      security: []
  /drivers/:
    get:
      operationId: listMyDrivers
      summary: Listar conductores
      deprecated: false
      description: "## Propósito\nDevuelve la lista paginada de conductores asociados\
        \ a la empresa\ndel usuario autenticado (solo transportistas).\n\n## Objetivo\n\
        Permitir que los gestores de flotas consulten y busquen sus\nconductores registrados\
        \ para asignarlos a eCMRs o gestionar\nsu información.\n\n## Casos de uso\n\
        - Listar todos los conductores de la empresa en el panel de gestión.\n- Buscar\
        \ un conductor por nombre para asignarlo a un eCMR.\n- Autocompletar un selector\
        \ de conductores en un formulario.\n\n## Detalles técnicos\n- No utiliza `mongoose-paginate`:\
        \ implementa paginación manual sobre\n  el array `cia.truckers` con `populate`\
        \ + `skip` + `limit`.\n- Los filtros `search` y `autocomplete` se combinan\
        \ con `$and` y\n  aplican regex case-insensitive sobre el campo `name`.\n\
        - Campos devueltos por conductor: `_id`, `name`, `lastname`, `phone`,\n  `email`,\
        \ `taxid`, `address`, `accountType`, `default_vehicle`,\n  `emailVerified`.\n\
        - Parámetros `isSign` y `extra` se aceptan por compatibilidad pero\n  **no\
        \ tienen impacto** en la lógica actual.\n\n## Autenticación\nRequiere JWT\
        \ Bearer token o API Key.\n"
      tags:
      - drivers
      parameters:
      - name: page
        in: query
        description: Número de página (empieza en 1)
        required: false
        example: 1
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Número de conductores por página
        required: false
        example: 10
        schema:
          type: integer
          minimum: 1
          default: 10
      - name: search
        in: query
        description: 'Filtro por nombre del conductor (regex case-insensitive). Se
          combina con `autocomplete` usando operador AND.

          '
        required: false
        example: aure
        schema:
          type: string
      - name: autocomplete
        in: query
        description: 'Filtro adicional por nombre (regex case-insensitive). Se combina
          con `search` usando operador AND.

          '
        required: false
        example: a
        schema:
          type: string
      - name: isSign
        in: query
        description: Parámetro legacy aceptado por compatibilidad — sin efecto actual
        required: false
        schema:
          type: string
          deprecated: true
      - name: extra
        in: query
        description: Parámetro legacy aceptado por compatibilidad — sin efecto actual
        required: false
        schema:
          type: string
          deprecated: true
      responses:
        '200':
          description: Lista paginada de conductores de la empresa
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas `returnOK`
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta
                    example: 200
                  data:
                    $ref: '#/components/schemas/DriverList'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  docs:
                  - _id: 643700268a290ac6df9237cf
                    name: Aureliano
                    lastname: Dariel
                    phone: '644333555'
                    email: trucker@testing.com
                    taxid: 33222444K
                    address: rua nova de abaixo, 7
                    accountType: default
                    default_vehicle: 69241a6793ebf2871d670bbe
                    emailVerified: true
                  totalDocs: 1
                  limit: 10
                  totalPages: 1
                  page: 1
                  pagingCounter: 1
                  hasPrevPage: false
                  hasNextPage: false
                  prevPage: null
                  nextPage: null
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token JWT inválido o expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: No se encontró el usuario o la empresa del token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_type_not_found:
                  summary: No se pudo resolver el tipo de cuenta
                  value:
                    status: 404
                    message: USER_TYPE_NOT_FOUND
                user_not_found:
                  summary: El usuario del token no existe
                  value:
                    status: 404
                    message: USER_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado al obtener la lista de conductores
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_GET_MINE
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      operationId: createDriver
      summary: Crear conductor
      deprecated: false
      description: "## Propósito\nCrea un nuevo conductor asociado a la empresa del\
        \ usuario autenticado.\n\n## Objetivo\nPermitir que los gestores de flotas\
        \ añadan nuevos conductores a su\nempresa para asignarlos a eCMRs y gestionar\
        \ entregas.\n\n## Casos de uso\n- Dar de alta un nuevo conductor en la empresa.\n\
        - Registrar un conductor con sus datos personales y vehículo por defecto.\n\
        \n## Flujo de creación\n```mermaid\nflowchart TD\n  A[Recibir POST /drivers/]\
        \ --> B{¿Email proporcionado?}\n  B -->|No| C[400 FORM_DATA_NOT_VALID]\n \
        \ B -->|Sí| D{¿Email ya registrado?}\n  D -->|Sí| E[406 USER_ALREADY_EXIST]\n\
        \  D -->|No| F[Validar datos con validateData]\n  F -->|Error| G[400 Error\
        \ de validación]\n  F -->|OK| H[Crear usuario con contraseña auto-generada]\n\
        \  H --> I[Guardar imagen si se envió]\n  I --> J[Guardar conductor en BD]\n\
        \  J --> K[Enviar email con credenciales]\n  K --> L[Añadir a cia.truckers]\n\
        \  L --> M[200 OK - Conductor creado]\n```\n\n## Detalles técnicos\n- Valida\
        \ unicidad del `email` en la colección `CompanyUser`.\n- Los datos se validan\
        \ con `TruckerUser.validateData()`.\n- Se genera una contraseña aleatoria\
        \ con `tools.generatePass()`.\n- El usuario se crea con `status: true` (activo\
        \ inmediatamente).\n- Se envía un email con las credenciales al nuevo conductor\n\
        \  (`mail.createNewTrucker`).\n- El ID del conductor se añade al array `cia.truckers`.\n\
        - El idioma del email se hereda del usuario que crea el conductor.\n\n**Campos\
        \ obligatorios**: al menos `email`. El resto depende de\n`validateData()`\
        \ (name, lastname).\n\n## Autenticación\nRequiere JWT Bearer token o API Key.\n"
      tags:
      - drivers
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DriverCreate'
            example:
              name: Pedro
              lastname: García López
              email: pedro.garcia@transportes.com
              phone: '644333555'
              taxid: 33222444K
              address: Polígono Industrial Norte 5, Valencia
              default_vehicle: 69241a6793ebf2871d670bbe
              country: esp
              timezone: europe/madrid
              birthDate: '1995-08-11'
        required: true
      responses:
        '200':
          description: Conductor creado exitosamente. Se envía email con credenciales
            al nuevo conductor
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/Driver'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 643700268a290ac6df9237cf
                  name: Pedro
                  lastname: García López
                  email: pedro.garcia@transportes.com
                  phone: '644333555'
                  taxid: 33222444K
                  default_vehicle: 69241a6793ebf2871d670bbe
                  emailVerified: false
                  country: esp
                  timezone: europe/madrid
                  i18n: es
                  birthDate: '1995-08-11T00:00:00.000Z'
                  createdAt: '2024-01-16T11:56:26.895Z'
                  image: null
          headers: {}
        '400':
          description: Datos del formulario inválidos o faltantes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                form_data_not_valid:
                  summary: El campo email es obligatorio
                  value:
                    status: 400
                    message: FORM_DATA_NOT_VALID
                not_valid_birthday:
                  summary: La fecha de nacimiento no es válida
                  value:
                    status: 400
                    message: NOT_VALID_BIRTHDAY
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token JWT inválido o expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: No se encontró el usuario o la empresa del token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  summary: El usuario del token no existe
                  value:
                    status: 404
                    message: USER_NOT_FOUND
          headers: {}
        '406':
          description: Ya existe un usuario registrado con ese email
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 406
                message: USER_ALREADY_EXIST
          headers: {}
        '500':
          description: Error inesperado al crear el conductor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_CREATE_DRIVER
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /drivers/activate/{id}:
    post:
      operationId: activateDriver
      summary: Activar conductor (reenviar credenciales)
      deprecated: false
      description: "## Propósito\nReactiva un conductor generando una nueva contraseña\
        \ y reenviando\nel email con las credenciales de acceso.\n\n## Objetivo\n\
        Permitir que un conductor que ha perdido sus credenciales o cuya\ncuenta necesita\
        \ reactivación reciba un nuevo email con contraseña\ngenerada automáticamente.\n\
        \n## Casos de uso\n- Un conductor olvidó sus credenciales y el gestor las\
        \ regenera.\n- Se necesita reactivar el acceso de un conductor tras un periodo\n\
        \  de inactividad.\n- El email de bienvenida original no llegó al conductor.\n\
        \n## Flujo de activación\n```mermaid\nflowchart TD\n  A[Recibir POST /drivers/activate/:id]\
        \ --> B{¿Conductor existe?}\n  B -->|No| C[404 NOT_FOUND]\n  B -->|Sí| D{¿Pertenece\
        \ a la empresa?}\n  D -->|No| E[403 NOT_ALLOWED]\n  D -->|Sí| F[Generar nueva\
        \ contraseña aleatoria]\n  F --> G[Hashear y guardar contraseña]\n  G -->\
        \ H[Enviar email con nuevas credenciales]\n  H --> I[200 OK - Datos del conductor]\n\
        ```\n\n## Detalles técnicos\n- Verifica que el conductor exista y pertenezca\
        \ a `cia.truckers`.\n- Genera una contraseña aleatoria con `tools.generatePass()`.\n\
        - La contraseña se hashea y se actualiza en el usuario.\n- Se envía un email\
        \ `createNewTrucker` con las nuevas credenciales\n  (misma plantilla que la\
        \ creación).\n- Devuelve los datos parseados del conductor tras la activación.\n\
        - **No modifica el estado `status`** del conductor; solo regenera\n  la contraseña.\n\
        \n## Autenticación\nRequiere JWT Bearer token o API Key.\n"
      tags:
      - drivers
      parameters:
      - name: id
        in: path
        description: ObjectId del conductor a activar
        required: true
        example: 643700268a290ac6df9237cf
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
      responses:
        '200':
          description: Conductor activado con nueva contraseña. Se envía email con
            credenciales
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/Driver'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 643700268a290ac6df9237cf
                  name: Aureliano
                  lastname: Dariel
                  email: trucker@testing.com
                  phone: '644333555'
                  emailVerified: false
                  country: esp
                  timezone: europe/madrid
                  i18n: es
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token JWT inválido o expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '403':
          description: El conductor no pertenece al array `truckers` de la empresa
            autenticada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: NOT_ALLOWED
          headers: {}
        '404':
          description: No se encontró un conductor con el ID proporcionado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado al activar el conductor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_ACTIVATE_DRIVER
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /drivers/{id}:
    put:
      operationId: updateDriver
      summary: Actualizar conductor
      deprecated: false
      description: "## Propósito\nActualiza los datos de un conductor existente asociado\
        \ a la empresa.\n\n## Objetivo\nPermitir modificar los datos personales de\
        \ un conductor (nombre,\nteléfono, email, dirección, vehículo por defecto,\
        \ etc.).\n\n## Casos de uso\n- Corregir datos incorrectos de un conductor.\n\
        - Actualizar el teléfono o email de un conductor.\n- Cambiar el vehículo por\
        \ defecto del conductor.\n\n## Flujo de actualización\n```mermaid\nflowchart\
        \ TD\n  A[Recibir PUT /drivers/:id] --> B{¿ID proporcionado?}\n  B -->|No|\
        \ C[404 NOT_FOUND]\n  B -->|Sí| D{¿Conductor pertenece a la empresa?}\n  D\
        \ -->|No| E[403 NOT_ALLOWED]\n  D -->|Sí| F{¿Se cambia el email?}\n  F -->|Sí|\
        \ G{¿Nuevo email ya registrado?}\n  G -->|Sí| H[406 USER_ALREADY_EXIST]\n\
        \  G -->|No| I[Validar datos con validateData]\n  F -->|No| I\n  I -->|Error|\
        \ J[400 Error de validación]\n  I -->|OK| K[Actualizar y guardar]\n  K -->\
        \ L[200 OK - Conductor actualizado]\n```\n\n## Detalles técnicos\n- Verifica\
        \ que el conductor (`id`) pertenezca al array `cia.truckers`\n  de la empresa\
        \ autenticada. Si no, devuelve `403 NOT_ALLOWED`.\n- Si se cambia el `email`,\
        \ valida que el nuevo email no esté ya\n  registrado en otra cuenta.\n- Los\
        \ datos se validan con `TruckerUser.validateData()`.\n- Actualiza los campos\
        \ con `TruckerUser.updateData()`.\n\n## Autenticación\nRequiere JWT Bearer\
        \ token o API Key.\n"
      tags:
      - drivers
      parameters:
      - name: id
        in: path
        description: ObjectId del conductor a actualizar
        required: true
        example: 643700268a290ac6df9237cf
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DriverUpdate'
            example:
              name: Aureliano
              lastname: Dariel Martínez
              phone: '644999888'
              email: aureliano.nuevo@testing.com
              timezone: europe/madrid
        required: true
      responses:
        '200':
          description: Conductor actualizado exitosamente
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/Driver'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 643700268a290ac6df9237cf
                  name: Aureliano
                  lastname: Dariel Martínez
                  email: aureliano.nuevo@testing.com
                  phone: '644999888'
                  taxid: 33222444K
                  country: esp
                  timezone: europe/madrid
                  i18n: es
                  emailVerified: false
          headers: {}
        '400':
          description: Error de validación de datos (fecha de nacimiento inválida
            u otro campo)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: NOT_VALID_BIRTHDAY
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token JWT inválido o expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '403':
          description: El conductor no pertenece al array `truckers` de la empresa
            autenticada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: NOT_ALLOWED
          headers: {}
        '404':
          description: No se encontró el conductor con el ID proporcionado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                not_found:
                  summary: No se proporcionó ID o no se encontró el conductor
                  value:
                    status: 404
                    message: NOT_FOUND
                user_not_found:
                  summary: El conductor existe pero no se pudo cargar su perfil
                  value:
                    status: 404
                    message: USER_NOT_FOUND
          headers: {}
        '406':
          description: El nuevo email ya está registrado por otro usuario
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 406
                message: USER_ALREADY_EXIST
          headers: {}
        '500':
          description: Error inesperado al actualizar el conductor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_EDIT_DRIVER
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    delete:
      operationId: deleteDriver
      summary: Eliminar conductor
      deprecated: false
      description: "## Propósito\nElimina (soft-delete) un conductor de la empresa\
        \ del usuario autenticado.\n\n## Objetivo\nPermitir que los gestores de flotas\
        \ den de baja conductores que ya\nno trabajan con la empresa.\n\n## Casos\
        \ de uso\n- Un conductor deja la empresa y se elimina su acceso.\n- Se dio\
        \ de alta por error y se necesita eliminar.\n\n## Flujo de eliminación\n```mermaid\n\
        flowchart TD\n  A[Recibir DELETE /drivers/:id] --> B{¿Conductor existe?}\n\
        \  B -->|No| C[404 NOT_FOUND]\n  B -->|Sí| D{¿Pertenece a la empresa?}\n \
        \ D -->|No| E[403 NOT_ALLOWED]\n  D -->|Sí| F[Soft-delete del conductor]\n\
        \  F --> G[Eliminar ID del array cia.truckers]\n  G --> H[Guardar empresa]\n\
        \  H --> I[200 OK - ID del conductor eliminado]\n```\n\n## Detalles técnicos\n\
        - Verifica que el conductor exista y pertenezca al array `cia.truckers`.\n\
        - Utiliza **soft-delete** (`mongoose-delete`): el registro no se\n  borra\
        \ físicamente, se marca como eliminado.\n- Elimina el ID del conductor del\
        \ array `cia.truckers` y guarda la empresa.\n- Devuelve solo el `_id` del\
        \ conductor eliminado como confirmación.\n\n## Autenticación\nRequiere JWT\
        \ Bearer token o API Key.\n"
      tags:
      - drivers
      parameters:
      - name: id
        in: path
        description: ObjectId del conductor a eliminar
        required: true
        example: 643700268a290ac6df9237cf
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
      responses:
        '200':
          description: Conductor eliminado (soft-delete). Devuelve el ID como confirmación
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      _id:
                        type: string
                        description: ObjectId del conductor eliminado
                        example: 643700268a290ac6df9237cf
                    required:
                    - _id
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 643700268a290ac6df9237cf
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token JWT inválido o expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '403':
          description: El conductor no pertenece al array `truckers` de la empresa
            autenticada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: NOT_ALLOWED
          headers: {}
        '404':
          description: No se encontró un conductor con el ID proporcionado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado al eliminar el conductor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_DELETE_DRIVER
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/company-info/{service_code}:
    put:
      summary: Update Company Information
      deprecated: false
      description: '## Propósito

        Actualizar los datos de empresa vinculados al eCMR indicado.


        ## Objetivo

        Update Company Information.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `PUT /ecmr/company-info/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Actualiza información específica de la empresa
        cargadora. Permite modificar datos administrativos o de contacto por parte
        de la empresa creadora.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Assignment
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              description: Datos de la empresa
              properties:
                company_name:
                  type: string
                  description: Nombre de la empresa
                  example: Empresa Transportes SL
                contact_name:
                  type: string
                  description: Nombre del contacto
                  example: Juan Pérez
                contact_email:
                  type: string
                  description: Email del contacto
                  example: contacto@empresa.com
                contact_phone:
                  type: string
                  description: Teléfono del contacto
                  example: +34 912 345 678
            example:
              company_name: Transportes Rápidos SL
              contact_name: María García
              contact_email: maria.garcia@transportes.com
              contact_phone: +34 987 654 321
      responses:
        '200':
          description: Detalles del ECMR
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ECMR'
              example:
                _id: 691375bc640c7afd73ec429f
                geolocationPickup:
                  type: Point
                  coordinates:
                  - -8.7426208
                  - 42.2245427
                geolocationDelivery:
                  type: Point
                  coordinates:
                  - -8.7426208
                  - 42.2245427
                geolocationCreation:
                  type: Point
                  coordinates:
                  - 42.2245427
                  - -8.7426208
                payment:
                  paid: false
                status: delivered
                price: 150
                owner: company
                had_etl_cargo_method: false
                had_etd_cargo_method: false
                description: Mercancia general paletizada
                info_extra: ''
                plate_full_trailer: ''
                pallets_num: 0
                is_fresh: false
                fresh_cargo_temp: 0
                linear_meters: 0
                cargo_height: 0
                cargo_type: full
                hscode: '-'
                cargo_weight: 150
                is_imperial_measure: false
                service_code: RIBMADqNhRP
                custom_code: ''
                etd_time_start: ''
                etd_time_end: ''
                etl_time_start: ''
                etl_time_end: ''
                etl_photos: []
                etl_comment: ''
                etd_photos: []
                etd_comment: ''
                sign_image_sender: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAQAElEQVR4AexdCXwbxbn/vpF21w6B0qRAOErfK1AKrxwlRCsHAtbKTQg0QB8FCn3cd6GUG8rxSMsd7ldajhQILUdLCoVCGgjWyiQk1sqkUAq0FGjLUXC4IQRLu9J871vHIXYsyZKsYyWvflrt7M7MN9/3n5n/zrkS4H98BHwEfAQaBAGfsBoko3w1a4kA1jIxP60SEBhOWHXNq7omXgJsftDmRoCa27wGtm44YdU1r+qaeANnY3Gq+6F8BBodgeGE1egWFdTfb8EVhGdMeDZwGWhg1StVtMYYYfktuEoVnMaV08BloIFVr1R5aQ7CquiTp6LCKpVPvpwmRqAqJa5J8WoOwqrok6eiwipcbPyiXWFAPSHOyyXOEwANUqI5CGuQQc3t9FDR9rmzuYtapayrcDkZY4RVAfQqIKJSZaGucorhzgpjVWFxdYWvERMvC/9iykkJYIwxwioSvUIAVkBEIfFN5VdhrCosruZQl1Xha65l/gS9gP8YI6z8meH7+AhUGwEvVPhibKw5sZaQoE9YxeRgf5gSUO0P7//4CDQmAjUn1hISHERYfoUsXLxKQLWwoMbybfpi0VjZMda1HURYY7RCjvUSMJL9frEYCaEC/j7bFwCnLK9BhFVWfD+Sj4CPQF4EfLbPC02ZHj5hlQmcH81HoP4IjL0W3CgJq/5Z5mvgIzB2EahGC87bJFgnwvI2KE1RAXyIa5ONTYdzNUiwclkxhLBqh33poNROt8qBW1BSPoPy3S8oLIdn6RDnEOLfGhGBGuFcqWIxoj1FBaifNkMIq0bYFwXJuoG8rNu6uhZ1nc+gfPf7hdavoABAvwb+T30QKFgsaq5S/bQZQlg1t9tPsEQE6ldQ+hX1+bIfBv+nfghUgbD8Ul2/7KxyynXmyypb54tvAASqQFh+qW6AfPdV9BEoiIBXPatAWF411dfLR8BHoNERGDuE5fdUPVdW/SzxXJZ4XqGxQ1gN1VNtxKpcus79WVJ6NM9XqoZScNT4j1pASXAVJqza6lKS4kUFrpP+RelWMFB/VS4YwnueZeo8OFrD5pf3cqNoKAfjX5YZoxZQUqqFCau2upSkeFGBG13/fiOLLnr9oRv6pynyyxs50KxQFiYsb2A/xrWoZdEbQ+ToZVO9rFuda6NPWHXOAG8lX0tyrLPllTK1GmZ4Wbdq2FuCzCoQlv94KAF/P6iPQIUQGBv1rgqE5T8eKlQCfTFjFYGyuKe4eleWaA/lQxUIy0PWFaNKnhwMR5yFww7DWaRHnTm6YR8Witg7AVCe2MUk7OEwNbKqRsl4GOg8qhXHPXkiF75dRdGFE66Qb30Jq0JGjEpMnhzMiuyN6x4E2T+ihF4iiCLCHUxe8/g4UzdS06e0fzppVHp4KXIeTCqtYo2SWav2YIYc7F4boiKuKoquiH6NLMQnrDy51xNrecw9nh44u27LbLkhEVeuS8bVI4WgIxFoEUq5MRGeHhDq3dzyuqbNsEN5RPq3640ADVJgsHvQ7Uo4qyi6Euo1tAyfsEbIvnyFr7tT+0sipt2TiGvnBhX1CBT4OCDZkuj8cCR16pRI+r9GEO17+wj4CJSIQNMQlj7dnhwynD/oRuqsNiPzHXeMabfd3l2/RDzKCr5sEb7T3alc3ZpVL2cBdxKIrQII5+uRzNHhdvoPvlfDr1c7JJWFYGxYWVnMmkFaXsJquALh0AHcRVsPMbCRpOz3AeFOR/vCY7qR/nXYcGYzgR2utzu7T55Bm1Yr47q68FMrrj0MQrlcEixAkFNJOOeHo5mDp02jjaqV7lC5NPSySa9qZ2XD1YQmzfHVZuUlrNoViNWKjPYXIfAsEL2biCnnMml8NxhM78UD5GcSBRcSySwPkkdA0GVBx3lcN5zfh6L2tXrUPjlspGaGovS19nYKQoU+...
                sign_image_cia: ''
                sign_pickup: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAQAElEQVR4Aex9CZwUxfX/e9XTPQsoEdRfojHRJB7RqD/NstOzgDDTs6AE8QAx/Ew8QAU1eOAVwQPwh4oao/EWE0w0+jdRUEGD4k7PBIWdnmX9qeAVNcYj3hAPZHe6e+r9Xy+C67oLe8zszu5Wf7q2r6pXr741/d33XlV3C1CLQkAhoBDoIQgowuohHaXUVAgoBAAUYalfgUJAIdBjEFCE1WO6qvOKKgkKgZ6OgCKsnt6DSn+FQB9CQBFWH+ps1VSFQE9HQBFWT+9Bpb9CoCUEeuk5RVi9tGNVsxQCvREBRVi9sVdVmxQCvRQBRVi9tGO31Swz5kZjMQptK5+6rhAoJQQUYbXUG33gHAq4k5upCItBKJW18tBPB0fjrhlN+L+IWt6cqOX+NhpvOAJgjrpPv+wkBcSXQPS1DQH8mNvsc1JrNyDA1u12lVXuwdF47tio5V0cibt/zHv9lhLC9QRyNKcBRPAtQm1axJp1RjRWv0c3qFlyVSrCKrkuUQr1NgTGjKHwkFhu/4iVOzoSz10YsRrurNf8pZJgASEeRSjDiJBkq/c8XzYc4SSNEx07fIGTMk4myt/KePyEhPhVRbx+JO/36VURVh/tfgR4mZuuXEJgFAq5ziExJN6wT6TKH2ta7jlmwr1lfc5bqgm8Fwl+wcQ0CEnUSPJn+653hGMbxznJ8GW8vcepNjJ16YEfN1Unmyp7TEq6BUCsQ9TOYJnHDx9Og5rm6Uv7irD6Um83aatyCZuA0YndyGj6gWl5o5mYfmVa7g3mCnephuIhlDQVAHcjkmsk5a/2hT/OSYWPcezwTCdlLKy1+62oe2rAe9CGZXU6vDaUC90CRI8A0BjfcH8ViblD2lC012VRhNXrulQ1qFgImDHazUx4cTPuTuXtNablPYS+t5RIzuB40z78T+B1geImDfRxjq0fyen8rF12e22qX7Kuuv9bndFr5Ur8PL+d8TAKfIpAjACEc2IxKuuMzJ5YVhFWT+y1AuiMyiXcKopmgr5txrzhUcs/KRr3ruDA+F9B85Yi0cXs1h3M+L2PgAvRp4n9yRiXtY2zON1Uk9QfX2Xj61sV3o6L5VX0LSbHeDThnattcBdICT9HoDcB8Ll0Ghugjy2dJKw+hlYvai5bA2qUkPtz2LCPth8SBMTjDWPZcjojEs9dzZbTIiBvCQi4AkgO56D452xF/YXdsROxXh+XsY3TM0n9txk7tDSzIvwSE0dBR1vLY7STaTWMZpL8tSbdBSBpFqv6bRTwqCQ5LWMbp2ZT+rV8rs+tirD6XJdvajD2MQsrmOMUTCOIJHJHmQn37Kjl/ta0cov88LdSmoD7BOJpiGJfTh8xSb0OBGdAODQukzJOcezwfCcVXuQkw8/X1GD9JgQL+7f8UNolwqQZtbxLNMEkBXgO6zEQhfZXqTFJJfVfc/33r06VvVLYmnuWNEVYPau/CqZtb7WwAleu0nIjEcufyJbS+VHLvSlieUuk1y/F7tSdSOJ4Hq3bnRDe5HSPEHCq0I1Yxg6Py9ihMx1b/w2T04VOyqhzluFnBQO8BUHDquj7kYBA495czfPvQNQ4UC9DgrQ/Sj8/LZMKX+xUhxbVVpf9s4XiffKUIqw+2e0A2H4LC0ppMTkAzrGlYZVx/7hIPDfLjLt38PEyAC8lEX6HJCdw2pmD4S8B5e/IS/pFKPdpnAlpQsY2znWSxu+yyfDDNdXG/9U8geu7qm1DrYYfmQn/GDORuzKfZ5IC8Ut2OV0BeJuUoWkZOzwnkwotqV3R/+2u0qkn1SN6krJK18Ih0CMsrDkkgmkD0SovxpbSSRErN9u03IWckiTcNCFdJTE/BhD6cyB8NWH++ryURw829JiTMiaxhfJr3t4azGUKpgasXLnz54VDsO2SKmO5H7PF9z+mlbtWgnYHu3...
                sign_delivery: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAQAElEQVR4AeydCZxT1b3H//9zc5OZAZW6V2xr+3i+LtrS4iQz4DK5QRSX1qVaayvF5dmi4L5QUVTcsECfWpfWulDts8+9vqpUmCSDipObEfvqgopUrdTW2iIiApPc3PN//zsSmhlCZsnMZPvfzz2T5Ozn+8/9zf+ce3OvAtmEgBAQAhVCQASrQgwl3RQCQgBABEu+BUJACFQMARGsijFV8R2VGoRApRMQwap0C0r/hUANERDBqiFjy1CFQKUTEMGqdAtK/4VAPgJVGieCVaWGlWEJgWokIIJVjVaVMQmBKiUgglWlhpVhCYFqJCCClc+qEicEhEBZEhDBKkuzSKeEgBDIR0AEKx8ViRMCQqAsCYhglaVZpFPDR0BaqiQCIliVZC3pqxCocQIiWDX+BZDhC4FKIiCCVUnWkr4KgRonUKRg1Tg9Gb4QEALDSkAEa1hxS2NCQAgUQ0AEa2i4Sq1CQAgMAQERrCGAKlUKASEwNAREsIaGq9RaKwRknMNKQARrWHFLY0JACBRDQASrGHpSVggIgWElIII1rLilMSEgBIohUFrBKqbnUlYICIGaIyCClcfkY1tolBc97khqaJzY+YVgxGkORlJHBSPpH4YizuxQJH1LyHKeaAo7iwqFUMSJF0rPTetr3r7m8+ruV17LeYzzzwmG01OaLWf8+Em0q8dAghAoJwIiWDnW2K8ltU+TlT7Nbzg388H7jG9D+o+GVosV0XxFaooiGIsEisPLAPoFV7k3FgpE0FooPTetr3n7ms+ruz95AfUrRDqFCGFNdL3OOI+HrPR9zKGbiDUfQjuCbEKgRARqXrDGtqwd1RzpPLTJcq4wDJjPB3kji9Na1DBTaZpspMyvJ2L+CYmYeQy/TuPXK/j1NjsWuKwjWvf7QiEZM68plJ6bls2rGwJPA8JKBGMdAtYhqM8pMIIK1JGKjBksmvsb2ji7t6DAuFgR9CmvVxeAalak9kfC3RGwEwAdIPwSEJ2ASNdqoMfdTGaldtIvMasXg1Z6KYdbWdBme55nMJJhD9Rp9jzSr06iEcBbSwvV8cvmXV6EQPEEalqwmsPOQQEccY0GYzqjzCCq+fVknp+I+WYk4uay9ra6VcuW4XpOG9Q91EJ7BiMbm4NW6jiegp0bjDgLQlbKm5IV7dV5npUXWHj77N3lyb/ARfcqrdwfa9RnaaTTtOKA7mxCeEGj/isC7AKAO3MYDQRfQ6KTkGAealxcn3H+L2Sln92EzsImKz2Dp6cTmptX10MFb/vvT58KHdz572yrZhbnI3hcU1msz2e7Xcdjvb0pnCq4PMAMFnH+rZYIsnHeazCSXsgi76tgTEPe9ZoVrMaWj3dnr+EYYAJKZ+YlYubViVazta0NPy6W+pjJFGhupvqmiakvNVmdk/nL/aOglZrbZDmPNFnpZaicZUC++Yh4LCKM5oN9Nbe5EjXMLNary3ptWY8t+7m3117ztwYe4TpuSkYDR/qM9BQFahZ7X6uB9OcB6O+a4NZ67ZvYoP1jDdCHKsCLAOE9APw8sYfm1u12W1O48yw+cMtSvPLZK2Q5j4as9NMcXnH8zmvgqsfZVguA6HQCOAgIdgcA9oQhSUolPeEvFPL9E/Hi+B/A7UT0NySMcH2yFyCgCqRVdVJH28j3WCwmmagXtMfrlxY72MksUsEIL85b6ek7p5y7dIMTI40PEhpnsleyL5BaoxHeIg3nk85MSPI00476T7Bj/gs43GDHAhcOpVdX7Phyyz+3eLv322O+...
                sign_pickup_date: '2025-11-11T18:32:46.443Z'
                sign_delivery_date: '2025-11-11T18:32:59.471Z'
                signed_by_sender: true
                signed_by_company: false
                confirmed: false
                payment_method: carriage_paid
                truckers_sucesives: []
                deleted: false
                pallets_data: []
                etd_date: '2025-11-10T23:00:00.000Z'
                etl_date: '2025-11-10T23:00:00.000Z'
                documents: []
                etl_address: 64917511ef73c37ccae60bc4
                etd_address: 6491769cef73c37ccae60c18
                company_user: 63d7907cbe76403b35da63df
                company: 63d7907cbe76403b35da63dd
                createdAt: '2025-11-11T17:43:24.830Z'
                updatedAt: '2025-11-11T18:32:59.472Z'
                __v: 0
                temp_token: nFnPftf49q2t2r97hgpLPmh2hFFT7fp9hHB7b982GR4TMmBnTj26
                trucker_cia: 69130610d8983feafddd1b76
                trucker_user: 69130610d8983feafddd1b74
                trucker_vehicle: 691380b0c52022c07aeeaa80
                sign_image_sender_data:
                  taxid: 52930684W
                  name: Jorge
                  email: josemariapiga+87@gmail.com
                  lastname: Parada
                etl_europallets: 0
                etd_europallets: 0
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '2':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '3':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '4':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '5':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/contact-info/{service_code}:
    get:
      summary: Obtener información de contacto
      deprecated: false
      description: '## Propósito

        Consultar la información de contacto asociada a un eCMR específico.


        ## Objetivo

        Obtener información de contacto.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `GET /ecmr/contact-info/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Obtiene la información de contacto de la contraparte.
        - Si solicita un **Trucker**: Devuelve nombre, teléfono y email de la **Company**
        (persona de contacto). - Si solicita una **Company**: Devuelve nombre, teléfono
        y...


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Collaboration
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: XXXXXXhbMFp
        schema:
          type: string
      responses:
        '200':
          description: Información de contacto
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactInfo'
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '2':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '3':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '4':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/customcode/{service_code}:
    put:
      summary: Save custom code
      deprecated: false
      description: '## Propósito

        Definir o modificar el código personalizado asociado a un eCMR.


        ## Objetivo

        Save custom code.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `PUT /ecmr/customcode/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Guarda o actualiza un código de referencia interno
        (Custom Code) para el ECMR. Este código es útil para que la empresa identifique
        el envío con sus propios sistemas ERP/TMS.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Collaboration
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                custom_code:
                  type: string
                  description: Código de referencia interno
                  example: REF-2025-001
              required:
              - custom_code
            example:
              custom_code: ERP-REF-2025-001
        required: true
      responses:
        '200':
          description: Operación realizada correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '2':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '3':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '4':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/link-company/{service_code}:
    post:
      summary: Link company to ECMR
      deprecated: false
      description: '## Propósito

        Vincular una empresa al eCMR para habilitar su colaboración en la operación.


        ## Objetivo

        Link company to ECMR.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `POST /ecmr/link-company/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Vincula una empresa al ECMR mediante correo electrónico.
        - Si el ECMR ya tiene token temporal, envía el enlace de acceso por email.
        - Si no tiene token, genera uno nuevo y luego envía el enlace. Permite dar
        acceso a e...


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Collaboration
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkCompanyRequest'
        required: true
      responses:
        '200':
          description: Operación realizada correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '2':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '3':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '4':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/payment-method/{service_code}:
    put:
      summary: Update ECMR Payment Method
      deprecated: false
      description: '## Propósito

        Configurar o actualizar el método de pago asociado a un eCMR.


        ## Objetivo

        Update ECMR Payment Method.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `PUT /ecmr/payment-method/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Actualiza el método de pago del ECMR (e.g., ''carriage_paid'',
        ''carriage_forward''). **Restricciones:** - No se puede cambiar el método
        de pago en ECMRs con estado ''delivered'' u otros estados finales. **Status**:
        Functio...


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Assignment
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                payment_method:
                  type: string
                  description: Método de pago (e.g., 'carriage_paid', 'carriage_forward')
                  example: carriage_paid
              required:
              - payment_method
        required: true
      responses:
        '200':
          description: Detalles del ECMR
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ECMR'
              example:
                _id: 691375bc640c7afd73ec429f
                geolocationPickup:
                  type: Point
                  coordinates:
                  - -8.7426208
                  - 42.2245427
                geolocationDelivery:
                  type: Point
                  coordinates:
                  - -8.7426208
                  - 42.2245427
                geolocationCreation:
                  type: Point
                  coordinates:
                  - 42.2245427
                  - -8.7426208
                payment:
                  paid: false
                status: delivered
                price: 150
                owner: company
                had_etl_cargo_method: false
                had_etd_cargo_method: false
                description: Mercancia general paletizada
                info_extra: ''
                plate_full_trailer: ''
                pallets_num: 0
                is_fresh: false
                fresh_cargo_temp: 0
                linear_meters: 0
                cargo_height: 0
                cargo_type: full
                hscode: '-'
                cargo_weight: 150
                is_imperial_measure: false
                service_code: RIBMADqNhRP
                custom_code: ''
                etd_time_start: ''
                etd_time_end: ''
                etl_time_start: ''
                etl_time_end: ''
                etl_photos: []
                etl_comment: ''
                etd_photos: []
                etd_comment: ''
                sign_image_sender: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAQAElEQVR4AexdCXwbxbn/vpF21w6B0qRAOErfK1AKrxwlRCsHAtbKTQg0QB8FCn3cd6GUG8rxSMsd7ldajhQILUdLCoVCGgjWyiQk1sqkUAq0FGjLUXC4IQRLu9J871vHIXYsyZKsYyWvflrt7M7MN9/3n5n/zrkS4H98BHwEfAQaBAGfsBoko3w1a4kA1jIxP60SEBhOWHXNq7omXgJsftDmRoCa27wGtm44YdU1r+qaeANnY3Gq+6F8BBodgeGE1egWFdTfb8EVhGdMeDZwGWhg1StVtMYYYfktuEoVnMaV08BloIFVr1R5aQ7CquiTp6LCKpVPvpwmRqAqJa5J8WoOwqrok6eiwipcbPyiXWFAPSHOyyXOEwANUqI5CGuQQc3t9FDR9rmzuYtapayrcDkZY4RVAfQqIKJSZaGucorhzgpjVWFxdYWvERMvC/9iykkJYIwxwioSvUIAVkBEIfFN5VdhrCosruZQl1Xha65l/gS9gP8YI6z8meH7+AhUGwEvVPhibKw5sZaQoE9YxeRgf5gSUO0P7//4CDQmAjUn1hISHERYfoUsXLxKQLWwoMbybfpi0VjZMda1HURYY7RCjvUSMJL9frEYCaEC/j7bFwCnLK9BhFVWfD+Sj4CPQF4EfLbPC02ZHj5hlQmcH81HoP4IjL0W3CgJq/5Z5mvgIzB2EahGC87bJFgnwvI2KE1RAXyIa5ONTYdzNUiwclkxhLBqh33poNROt8qBW1BSPoPy3S8oLIdn6RDnEOLfGhGBGuFcqWIxoj1FBaifNkMIq0bYFwXJuoG8rNu6uhZ1nc+gfPf7hdavoABAvwb+T30QKFgsaq5S/bQZQlg1t9tPsEQE6ldQ+hX1+bIfBv+nfghUgbD8Ul2/7KxyynXmyypb54tvAASqQFh+qW6AfPdV9BEoiIBXPatAWF411dfLR8BHoNERGDuE5fdUPVdW/SzxXJZ4XqGxQ1gN1VNtxKpcus79WVJ6NM9XqoZScNT4j1pASXAVJqza6lKS4kUFrpP+RelWMFB/VS4YwnueZeo8OFrD5pf3cqNoKAfjX5YZoxZQUqqFCau2upSkeFGBG13/fiOLLnr9oRv6pynyyxs50KxQFiYsb2A/xrWoZdEbQ+ToZVO9rFuda6NPWHXOAG8lX0tyrLPllTK1GmZ4Wbdq2FuCzCoQlv94KAF/P6iPQIUQGBv1rgqE5T8eKlQCfTFjFYGyuKe4eleWaA/lQxUIy0PWFaNKnhwMR5yFww7DWaRHnTm6YR8Witg7AVCe2MUk7OEwNbKqRsl4GOg8qhXHPXkiF75dRdGFE66Qb30Jq0JGjEpMnhzMiuyN6x4E2T+ihF4iiCLCHUxe8/g4UzdS06e0fzppVHp4KXIeTCqtYo2SWav2YIYc7F4boiKuKoquiH6NLMQnrDy51xNrecw9nh44u27LbLkhEVeuS8bVI4WgIxFoEUq5MRGeHhDq3dzyuqbNsEN5RPq3640ADVJgsHvQ7Uo4qyi6Euo1tAyfsEbIvnyFr7tT+0sipt2TiGvnBhX1CBT4OCDZkuj8cCR16pRI+r9GEO17+wj4CJSIQNMQlj7dnhwynD/oRuqsNiPzHXeMabfd3l2/RDzKCr5sEb7T3alc3ZpVL2cBdxKIrQII5+uRzNHhdvoPvlfDr1c7JJWFYGxYWVnMmkFaXsJquALh0AHcRVsPMbCRpOz3AeFOR/vCY7qR/nXYcGYzgR2utzu7T55Bm1Yr47q68FMrrj0MQrlcEixAkFNJOOeHo5mDp02jjaqV7lC5NPSySa9qZ2XD1YQmzfHVZuUlrNoViNWKjPYXIfAsEL2biCnnMml8NxhM78UD5GcSBRcSySwPkkdA0GVBx3lcN5zfh6L2tXrUPjlspGaGovS19nYKQoU+...
                sign_image_cia: ''
                sign_pickup: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAQAElEQVR4Aex9CZwUxfX/e9XTPQsoEdRfojHRJB7RqD/NstOzgDDTs6AE8QAx/Ew8QAU1eOAVwQPwh4oao/EWE0w0+jdRUEGD4k7PBIWdnmX9qeAVNcYj3hAPZHe6e+r9Xy+C67oLe8zszu5Wf7q2r6pXr741/d33XlV3C1CLQkAhoBDoIQgowuohHaXUVAgoBAAUYalfgUJAIdBjEFCE1WO6qvOKKgkKgZ6OgCKsnt6DSn+FQB9CQBFWH+ps1VSFQE9HQBFWT+9Bpb9CoCUEeuk5RVi9tGNVsxQCvREBRVi9sVdVmxQCvRQBRVi9tGO31Swz5kZjMQptK5+6rhAoJQQUYbXUG33gHAq4k5upCItBKJW18tBPB0fjrhlN+L+IWt6cqOX+NhpvOAJgjrpPv+wkBcSXQPS1DQH8mNvsc1JrNyDA1u12lVXuwdF47tio5V0cibt/zHv9lhLC9QRyNKcBRPAtQm1axJp1RjRWv0c3qFlyVSrCKrkuUQr1NgTGjKHwkFhu/4iVOzoSz10YsRrurNf8pZJgASEeRSjDiJBkq/c8XzYc4SSNEx07fIGTMk4myt/KePyEhPhVRbx+JO/36VURVh/tfgR4mZuuXEJgFAq5ziExJN6wT6TKH2ta7jlmwr1lfc5bqgm8Fwl+wcQ0CEnUSPJn+653hGMbxznJ8GW8vcepNjJ16YEfN1Unmyp7TEq6BUCsQ9TOYJnHDx9Og5rm6Uv7irD6Um83aatyCZuA0YndyGj6gWl5o5mYfmVa7g3mCnephuIhlDQVAHcjkmsk5a/2hT/OSYWPcezwTCdlLKy1+62oe2rAe9CGZXU6vDaUC90CRI8A0BjfcH8ViblD2lC012VRhNXrulQ1qFgImDHazUx4cTPuTuXtNablPYS+t5RIzuB40z78T+B1geImDfRxjq0fyen8rF12e22qX7Kuuv9bndFr5Ur8PL+d8TAKfIpAjACEc2IxKuuMzJ5YVhFWT+y1AuiMyiXcKopmgr5txrzhUcs/KRr3ruDA+F9B85Yi0cXs1h3M+L2PgAvRp4n9yRiXtY2zON1Uk9QfX2Xj61sV3o6L5VX0LSbHeDThnattcBdICT9HoDcB8Ll0Ghugjy2dJKw+hlYvai5bA2qUkPtz2LCPth8SBMTjDWPZcjojEs9dzZbTIiBvCQi4AkgO56D452xF/YXdsROxXh+XsY3TM0n9txk7tDSzIvwSE0dBR1vLY7STaTWMZpL8tSbdBSBpFqv6bRTwqCQ5LWMbp2ZT+rV8rs+tirD6XJdvajD2MQsrmOMUTCOIJHJHmQn37Kjl/ta0cov88LdSmoD7BOJpiGJfTh8xSb0OBGdAODQukzJOcezwfCcVXuQkw8/X1GD9JgQL+7f8UNolwqQZtbxLNMEkBXgO6zEQhfZXqTFJJfVfc/33r06VvVLYmnuWNEVYPau/CqZtb7WwAleu0nIjEcufyJbS+VHLvSlieUuk1y/F7tSdSOJ4Hq3bnRDe5HSPEHCq0I1Yxg6Py9ihMx1b/w2T04VOyqhzluFnBQO8BUHDquj7kYBA495czfPvQNQ4UC9DgrQ/Sj8/LZMKX+xUhxbVVpf9s4XiffKUIqw+2e0A2H4LC0ppMTkAzrGlYZVx/7hIPDfLjLt38PEyAC8lEX6HJCdw2pmD4S8B5e/IS/pFKPdpnAlpQsY2znWSxu+yyfDDNdXG/9U8geu7qm1DrYYfmQn/GDORuzKfZ5IC8Ut2OV0BeJuUoWkZOzwnkwotqV3R/+2u0qkn1SN6krJK18Ih0CMsrDkkgmkD0SovxpbSSRErN9u03IWckiTcNCFdJTE/BhD6cyB8NWH++ryURw829JiTMiaxhfJr3t4azGUKpgasXLnz54VDsO2SKmO5H7PF9z+mlbtWgnYHu3...
                sign_delivery: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAQAElEQVR4AeydCZxT1b3H//9zc5OZAZW6V2xr+3i+LtrS4iQz4DK5QRSX1qVaayvF5dmi4L5QUVTcsECfWpfWulDts8+9vqpUmCSDipObEfvqgopUrdTW2iIiApPc3PN//zsSmhlCZsnMZPvfzz2T5Ozn+8/9zf+ce3OvAtmEgBAQAhVCQASrQgwl3RQCQgBABEu+BUJACFQMARGsijFV8R2VGoRApRMQwap0C0r/hUANERDBqiFjy1CFQKUTEMGqdAtK/4VAPgJVGieCVaWGlWEJgWokIIJVjVaVMQmBKiUgglWlhpVhCYFqJCCClc+qEicEhEBZEhDBKkuzSKeEgBDIR0AEKx8ViRMCQqAsCYhglaVZpFPDR0BaqiQCIliVZC3pqxCocQIiWDX+BZDhC4FKIiCCVUnWkr4KgRonUKRg1Tg9Gb4QEALDSkAEa1hxS2NCQAgUQ0AEa2i4Sq1CQAgMAQERrCGAKlUKASEwNAREsIaGq9RaKwRknMNKQARrWHFLY0JACBRDQASrGHpSVggIgWElIII1rLilMSEgBIohUFrBKqbnUlYICIGaIyCClcfkY1tolBc97khqaJzY+YVgxGkORlJHBSPpH4YizuxQJH1LyHKeaAo7iwqFUMSJF0rPTetr3r7m8+ruV17LeYzzzwmG01OaLWf8+Em0q8dAghAoJwIiWDnW2K8ltU+TlT7Nbzg388H7jG9D+o+GVosV0XxFaooiGIsEisPLAPoFV7k3FgpE0FooPTetr3n7ms+ruz95AfUrRDqFCGFNdL3OOI+HrPR9zKGbiDUfQjuCbEKgRARqXrDGtqwd1RzpPLTJcq4wDJjPB3kji9Na1DBTaZpspMyvJ2L+CYmYeQy/TuPXK/j1NjsWuKwjWvf7QiEZM68plJ6bls2rGwJPA8JKBGMdAtYhqM8pMIIK1JGKjBksmvsb2ji7t6DAuFgR9CmvVxeAalak9kfC3RGwEwAdIPwSEJ2ASNdqoMfdTGaldtIvMasXg1Z6KYdbWdBme55nMJJhD9Rp9jzSr06iEcBbSwvV8cvmXV6EQPEEalqwmsPOQQEccY0GYzqjzCCq+fVknp+I+WYk4uay9ra6VcuW4XpOG9Q91EJ7BiMbm4NW6jiegp0bjDgLQlbKm5IV7dV5npUXWHj77N3lyb/ARfcqrdwfa9RnaaTTtOKA7mxCeEGj/isC7AKAO3MYDQRfQ6KTkGAealxcn3H+L2Sln92EzsImKz2Dp6cTmptX10MFb/vvT58KHdz572yrZhbnI3hcU1msz2e7Xcdjvb0pnCq4PMAMFnH+rZYIsnHeazCSXsgi76tgTEPe9ZoVrMaWj3dnr+EYYAJKZ+YlYubViVazta0NPy6W+pjJFGhupvqmiakvNVmdk/nL/aOglZrbZDmPNFnpZaicZUC++Yh4LCKM5oN9Nbe5EjXMLNary3ptWY8t+7m3117ztwYe4TpuSkYDR/qM9BQFahZ7X6uB9OcB6O+a4NZ67ZvYoP1jDdCHKsCLAOE9APw8sYfm1u12W1O48yw+cMtSvPLZK2Q5j4as9NMcXnH8zmvgqsfZVguA6HQCOAgIdgcA9oQhSUolPeEvFPL9E/Hi+B/A7UT0NySMcH2yFyCgCqRVdVJH28j3WCwmmagXtMfrlxY72MksUsEIL85b6ek7p5y7dIMTI40PEhpnsleyL5BaoxHeIg3nk85MSPI00476T7Bj/gs43GDHAhcOpVdX7Phyyz+3eLv322O+...
                sign_pickup_date: '2025-11-11T18:32:46.443Z'
                sign_delivery_date: '2025-11-11T18:32:59.471Z'
                signed_by_sender: true
                signed_by_company: false
                confirmed: false
                payment_method: carriage_paid
                truckers_sucesives: []
                deleted: false
                pallets_data: []
                etd_date: '2025-11-10T23:00:00.000Z'
                etl_date: '2025-11-10T23:00:00.000Z'
                documents: []
                etl_address: 64917511ef73c37ccae60bc4
                etd_address: 6491769cef73c37ccae60c18
                company_user: 63d7907cbe76403b35da63df
                company: 63d7907cbe76403b35da63dd
                createdAt: '2025-11-11T17:43:24.830Z'
                updatedAt: '2025-11-11T18:32:59.472Z'
                __v: 0
                temp_token: nFnPftf49q2t2r97hgpLPmh2hFFT7fp9hHB7b982GR4TMmBnTj26
                trucker_cia: 69130610d8983feafddd1b76
                trucker_user: 69130610d8983feafddd1b74
                trucker_vehicle: 691380b0c52022c07aeeaa80
                sign_image_sender_data:
                  taxid: 52930684W
                  name: Jorge
                  email: josemariapiga+87@gmail.com
                  lastname: Parada
                etl_europallets: 0
                etd_europallets: 0
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '2':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '3':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '4':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '5':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/send:
    post:
      summary: Enviar ECMR por email
      deprecated: false
      description: '## Propósito

        Enviar por correo electrónico el PDF del eCMR a una lista de destinatarios.


        ## Objetivo

        Facilitar el reenvío de documentación de transporte a clientes,

        operadores o contactos administrativos desde la plataforma.


        ## Casos de uso

        - Reenviar un eCMR a nuevos destinatarios tras una actualización.

        - Compartir el PDF con equipos de operaciones o facturación.

        - Notificar a múltiples contactos en un mismo envío.


        ## Detalles técnicos

        Requiere `service_code` y `emails[]` (1 a 10 elementos). El backend

        valida formato de email, permisos de acceso al eCMR y genera/obtiene

        el PDF antes del envío.


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - ECMR - Collaboration
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                service_code:
                  type: string
                  description: Código de servicio del ECMR a reenviar
                  example: XXXXXXQgmdd
                emails:
                  type: array
                  description: Lista de correos destinatarios
                  minItems: 1
                  maxItems: 10
                  items:
                    type: string
                    format: email
                    example: operaciones@cliente.com
              required:
              - service_code
              - emails
            example:
              service_code: XXXXXXQgmdd
              emails:
              - operaciones@cliente.com
              - logistica@partner.com
        required: true
      responses:
        '200':
          description: Operación realizada correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          headers: {}
        '400':
          description: Error de validación
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                '1':
                  summary: service_code_required
                  value:
                    status: 400
                    message: SERVICE_CODE_REQUIRED
                '2':
                  summary: emails_required
                  value:
                    status: 400
                    message: EMAILS_REQUIRED
                '3':
                  summary: too_many_recipients
                  value:
                    status: 400
                    message: TOO_MANY_RECIPIENTS
                '4':
                  summary: invalid_email_format
                  value:
                    status: 400
                    message: INVALID_EMAIL_FORMAT
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '2':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '3':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '4':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
        '404':
          description: Recurso no encontrado
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '5':
                  summary: address_not_found
                  value:
                    error: Dirección no encontrada
                '6':
                  summary: user_not_found
                  value:
                    error: Usuario no encontrado
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/trucker-assign/{service_code}:
    put:
      summary: Update Carrier Restricted Assignment
      deprecated: false
      description: '## Propósito

        Asignar o reasignar un transportista a un eCMR específico.


        ## Objetivo

        Update Carrier Restricted Assignment.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `PUT /ecmr/trucker-assign/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Permite la asignación restringida de datos de
        transportista. Este endpoint valida si el estado del ECMR es ''planned'' antes
        de permitir la asignación. Utilizado para asignar recursos (conductor/vehículo)
        antes de que e...


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Assignment
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: XXXXXX4LrN3
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              description: Datos de transportista permitidos en este flujo
              properties:
                plate:
                  type: string
                  description: Matrícula del vehículo asignado
                  example: ABC-1234
                driver_id:
                  type: string
                  description: ID del conductor asignado
                  example: 643700268a290ac6df9237cf
                vehicle_id:
                  type: string
                  description: ID del vehículo asignado
                  example: 6450d58755656096b9a92355
            example:
              plate: XYZ-5678
              driver_id: 643700268a290ac6df9237cf
              vehicle_id: 6450d58755656096b9a92355
      responses:
        '200':
          description: Detalles del ECMR
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ECMR'
              example:
                _id: 691375bc640c7afd73ec429f
                geolocationPickup:
                  type: Point
                  coordinates:
                  - -8.7426208
                  - 42.2245427
                geolocationDelivery:
                  type: Point
                  coordinates:
                  - -8.7426208
                  - 42.2245427
                geolocationCreation:
                  type: Point
                  coordinates:
                  - 42.2245427
                  - -8.7426208
                payment:
                  paid: false
                status: delivered
                price: 150
                owner: company
                had_etl_cargo_method: false
                had_etd_cargo_method: false
                description: Mercancia general paletizada
                info_extra: ''
                plate_full_trailer: ''
                pallets_num: 0
                is_fresh: false
                fresh_cargo_temp: 0
                linear_meters: 0
                cargo_height: 0
                cargo_type: full
                hscode: '-'
                cargo_weight: 150
                is_imperial_measure: false
                service_code: RIBMADqNhRP
                custom_code: ''
                etd_time_start: ''
                etd_time_end: ''
                etl_time_start: ''
                etl_time_end: ''
                etl_photos: []
                etl_comment: ''
                etd_photos: []
                etd_comment: ''
                sign_image_sender: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAQAElEQVR4AexdCXwbxbn/vpF21w6B0qRAOErfK1AKrxwlRCsHAtbKTQg0QB8FCn3cd6GUG8rxSMsd7ldajhQILUdLCoVCGgjWyiQk1sqkUAq0FGjLUXC4IQRLu9J871vHIXYsyZKsYyWvflrt7M7MN9/3n5n/zrkS4H98BHwEfAQaBAGfsBoko3w1a4kA1jIxP60SEBhOWHXNq7omXgJsftDmRoCa27wGtm44YdU1r+qaeANnY3Gq+6F8BBodgeGE1egWFdTfb8EVhGdMeDZwGWhg1StVtMYYYfktuEoVnMaV08BloIFVr1R5aQ7CquiTp6LCKpVPvpwmRqAqJa5J8WoOwqrok6eiwipcbPyiXWFAPSHOyyXOEwANUqI5CGuQQc3t9FDR9rmzuYtapayrcDkZY4RVAfQqIKJSZaGucorhzgpjVWFxdYWvERMvC/9iykkJYIwxwioSvUIAVkBEIfFN5VdhrCosruZQl1Xha65l/gS9gP8YI6z8meH7+AhUGwEvVPhibKw5sZaQoE9YxeRgf5gSUO0P7//4CDQmAjUn1hISHERYfoUsXLxKQLWwoMbybfpi0VjZMda1HURYY7RCjvUSMJL9frEYCaEC/j7bFwCnLK9BhFVWfD+Sj4CPQF4EfLbPC02ZHj5hlQmcH81HoP4IjL0W3CgJq/5Z5mvgIzB2EahGC87bJFgnwvI2KE1RAXyIa5ONTYdzNUiwclkxhLBqh33poNROt8qBW1BSPoPy3S8oLIdn6RDnEOLfGhGBGuFcqWIxoj1FBaifNkMIq0bYFwXJuoG8rNu6uhZ1nc+gfPf7hdavoABAvwb+T30QKFgsaq5S/bQZQlg1t9tPsEQE6ldQ+hX1+bIfBv+nfghUgbD8Ul2/7KxyynXmyypb54tvAASqQFh+qW6AfPdV9BEoiIBXPatAWF411dfLR8BHoNERGDuE5fdUPVdW/SzxXJZ4XqGxQ1gN1VNtxKpcus79WVJ6NM9XqoZScNT4j1pASXAVJqza6lKS4kUFrpP+RelWMFB/VS4YwnueZeo8OFrD5pf3cqNoKAfjX5YZoxZQUqqFCau2upSkeFGBG13/fiOLLnr9oRv6pynyyxs50KxQFiYsb2A/xrWoZdEbQ+ToZVO9rFuda6NPWHXOAG8lX0tyrLPllTK1GmZ4Wbdq2FuCzCoQlv94KAF/P6iPQIUQGBv1rgqE5T8eKlQCfTFjFYGyuKe4eleWaA/lQxUIy0PWFaNKnhwMR5yFww7DWaRHnTm6YR8Witg7AVCe2MUk7OEwNbKqRsl4GOg8qhXHPXkiF75dRdGFE66Qb30Jq0JGjEpMnhzMiuyN6x4E2T+ihF4iiCLCHUxe8/g4UzdS06e0fzppVHp4KXIeTCqtYo2SWav2YIYc7F4boiKuKoquiH6NLMQnrDy51xNrecw9nh44u27LbLkhEVeuS8bVI4WgIxFoEUq5MRGeHhDq3dzyuqbNsEN5RPq3640ADVJgsHvQ7Uo4qyi6Euo1tAyfsEbIvnyFr7tT+0sipt2TiGvnBhX1CBT4OCDZkuj8cCR16pRI+r9GEO17+wj4CJSIQNMQlj7dnhwynD/oRuqsNiPzHXeMabfd3l2/RDzKCr5sEb7T3alc3ZpVL2cBdxKIrQII5+uRzNHhdvoPvlfDr1c7JJWFYGxYWVnMmkFaXsJquALh0AHcRVsPMbCRpOz3AeFOR/vCY7qR/nXYcGYzgR2utzu7T55Bm1Yr47q68FMrrj0MQrlcEixAkFNJOOeHo5mDp02jjaqV7lC5NPSySa9qZ2XD1YQmzfHVZuUlrNoViNWKjPYXIfAsEL2biCnnMml8NxhM78UD5GcSBRcSySwPkkdA0GVBx3lcN5zfh6L2tXrUPjlspGaGovS19nYKQoU+...
                sign_image_cia: ''
                sign_pickup: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAQAElEQVR4Aex9CZwUxfX/e9XTPQsoEdRfojHRJB7RqD/NstOzgDDTs6AE8QAx/Ew8QAU1eOAVwQPwh4oao/EWE0w0+jdRUEGD4k7PBIWdnmX9qeAVNcYj3hAPZHe6e+r9Xy+C67oLe8zszu5Wf7q2r6pXr741/d33XlV3C1CLQkAhoBDoIQgowuohHaXUVAgoBAAUYalfgUJAIdBjEFCE1WO6qvOKKgkKgZ6OgCKsnt6DSn+FQB9CQBFWH+ps1VSFQE9HQBFWT+9Bpb9CoCUEeuk5RVi9tGNVsxQCvREBRVi9sVdVmxQCvRQBRVi9tGO31Swz5kZjMQptK5+6rhAoJQQUYbXUG33gHAq4k5upCItBKJW18tBPB0fjrhlN+L+IWt6cqOX+NhpvOAJgjrpPv+wkBcSXQPS1DQH8mNvsc1JrNyDA1u12lVXuwdF47tio5V0cibt/zHv9lhLC9QRyNKcBRPAtQm1axJp1RjRWv0c3qFlyVSrCKrkuUQr1NgTGjKHwkFhu/4iVOzoSz10YsRrurNf8pZJgASEeRSjDiJBkq/c8XzYc4SSNEx07fIGTMk4myt/KePyEhPhVRbx+JO/36VURVh/tfgR4mZuuXEJgFAq5ziExJN6wT6TKH2ta7jlmwr1lfc5bqgm8Fwl+wcQ0CEnUSPJn+653hGMbxznJ8GW8vcepNjJ16YEfN1Unmyp7TEq6BUCsQ9TOYJnHDx9Og5rm6Uv7irD6Um83aatyCZuA0YndyGj6gWl5o5mYfmVa7g3mCnephuIhlDQVAHcjkmsk5a/2hT/OSYWPcezwTCdlLKy1+62oe2rAe9CGZXU6vDaUC90CRI8A0BjfcH8ViblD2lC012VRhNXrulQ1qFgImDHazUx4cTPuTuXtNablPYS+t5RIzuB40z78T+B1geImDfRxjq0fyen8rF12e22qX7Kuuv9bndFr5Ur8PL+d8TAKfIpAjACEc2IxKuuMzJ5YVhFWT+y1AuiMyiXcKopmgr5txrzhUcs/KRr3ruDA+F9B85Yi0cXs1h3M+L2PgAvRp4n9yRiXtY2zON1Uk9QfX2Xj61sV3o6L5VX0LSbHeDThnattcBdICT9HoDcB8Ll0Ghugjy2dJKw+hlYvai5bA2qUkPtz2LCPth8SBMTjDWPZcjojEs9dzZbTIiBvCQi4AkgO56D452xF/YXdsROxXh+XsY3TM0n9txk7tDSzIvwSE0dBR1vLY7STaTWMZpL8tSbdBSBpFqv6bRTwqCQ5LWMbp2ZT+rV8rs+tirD6XJdvajD2MQsrmOMUTCOIJHJHmQn37Kjl/ta0cov88LdSmoD7BOJpiGJfTh8xSb0OBGdAODQukzJOcezwfCcVXuQkw8/X1GD9JgQL+7f8UNolwqQZtbxLNMEkBXgO6zEQhfZXqTFJJfVfc/33r06VvVLYmnuWNEVYPau/CqZtb7WwAleu0nIjEcufyJbS+VHLvSlieUuk1y/F7tSdSOJ4Hq3bnRDe5HSPEHCq0I1Yxg6Py9ihMx1b/w2T04VOyqhzluFnBQO8BUHDquj7kYBA495czfPvQNQ4UC9DgrQ/Sj8/LZMKX+xUhxbVVpf9s4XiffKUIqw+2e0A2H4LC0ppMTkAzrGlYZVx/7hIPDfLjLt38PEyAC8lEX6HJCdw2pmD4S8B5e/IS/pFKPdpnAlpQsY2znWSxu+yyfDDNdXG/9U8geu7qm1DrYYfmQn/GDORuzKfZ5IC8Ut2OV0BeJuUoWkZOzwnkwotqV3R/+2u0qkn1SN6krJK18Ih0CMsrDkkgmkD0SovxpbSSRErN9u03IWckiTcNCFdJTE/BhD6cyB8NWH++ryURw829JiTMiaxhfJr3t4azGUKpgasXLnz54VDsO2SKmO5H7PF9z+mlbtWgnYHu3...
                sign_delivery: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAQAElEQVR4AeydCZxT1b3H//9zc5OZAZW6V2xr+3i+LtrS4iQz4DK5QRSX1qVaayvF5dmi4L5QUVTcsECfWpfWulDts8+9vqpUmCSDipObEfvqgopUrdTW2iIiApPc3PN//zsSmhlCZsnMZPvfzz2T5Ozn+8/9zf+ce3OvAtmEgBAQAhVCQASrQgwl3RQCQgBABEu+BUJACFQMARGsijFV8R2VGoRApRMQwap0C0r/hUANERDBqiFjy1CFQKUTEMGqdAtK/4VAPgJVGieCVaWGlWEJgWokIIJVjVaVMQmBKiUgglWlhpVhCYFqJCCClc+qEicEhEBZEhDBKkuzSKeEgBDIR0AEKx8ViRMCQqAsCYhglaVZpFPDR0BaqiQCIliVZC3pqxCocQIiWDX+BZDhC4FKIiCCVUnWkr4KgRonUKRg1Tg9Gb4QEALDSkAEa1hxS2NCQAgUQ0AEa2i4Sq1CQAgMAQERrCGAKlUKASEwNAREsIaGq9RaKwRknMNKQARrWHFLY0JACBRDQASrGHpSVggIgWElIII1rLilMSEgBIohUFrBKqbnUlYICIGaIyCClcfkY1tolBc97khqaJzY+YVgxGkORlJHBSPpH4YizuxQJH1LyHKeaAo7iwqFUMSJF0rPTetr3r7m8+ruV17LeYzzzwmG01OaLWf8+Em0q8dAghAoJwIiWDnW2K8ltU+TlT7Nbzg388H7jG9D+o+GVosV0XxFaooiGIsEisPLAPoFV7k3FgpE0FooPTetr3n7ms+ruz95AfUrRDqFCGFNdL3OOI+HrPR9zKGbiDUfQjuCbEKgRARqXrDGtqwd1RzpPLTJcq4wDJjPB3kji9Na1DBTaZpspMyvJ2L+CYmYeQy/TuPXK/j1NjsWuKwjWvf7QiEZM68plJ6bls2rGwJPA8JKBGMdAtYhqM8pMIIK1JGKjBksmvsb2ji7t6DAuFgR9CmvVxeAalak9kfC3RGwEwAdIPwSEJ2ASNdqoMfdTGaldtIvMasXg1Z6KYdbWdBme55nMJJhD9Rp9jzSr06iEcBbSwvV8cvmXV6EQPEEalqwmsPOQQEccY0GYzqjzCCq+fVknp+I+WYk4uay9ra6VcuW4XpOG9Q91EJ7BiMbm4NW6jiegp0bjDgLQlbKm5IV7dV5npUXWHj77N3lyb/ARfcqrdwfa9RnaaTTtOKA7mxCeEGj/isC7AKAO3MYDQRfQ6KTkGAealxcn3H+L2Sln92EzsImKz2Dp6cTmptX10MFb/vvT58KHdz572yrZhbnI3hcU1msz2e7Xcdjvb0pnCq4PMAMFnH+rZYIsnHeazCSXsgi76tgTEPe9ZoVrMaWj3dnr+EYYAJKZ+YlYubViVazta0NPy6W+pjJFGhupvqmiakvNVmdk/nL/aOglZrbZDmPNFnpZaicZUC++Yh4LCKM5oN9Nbe5EjXMLNary3ptWY8t+7m3117ztwYe4TpuSkYDR/qM9BQFahZ7X6uB9OcB6O+a4NZ67ZvYoP1jDdCHKsCLAOE9APw8sYfm1u12W1O48yw+cMtSvPLZK2Q5j4as9NMcXnH8zmvgqsfZVguA6HQCOAgIdgcA9oQhSUolPeEvFPL9E/Hi+B/A7UT0NySMcH2yFyCgCqRVdVJH28j3WCwmmagXtMfrlxY72MksUsEIL85b6ek7p5y7dIMTI40PEhpnsleyL5BaoxHeIg3nk85MSPI00476T7Bj/gs43GDHAhcOpVdX7Phyyz+3eLv322O+...
                sign_pickup_date: '2025-11-11T18:32:46.443Z'
                sign_delivery_date: '2025-11-11T18:32:59.471Z'
                signed_by_sender: true
                signed_by_company: false
                confirmed: false
                payment_method: carriage_paid
                truckers_sucesives: []
                deleted: false
                pallets_data: []
                etd_date: '2025-11-10T23:00:00.000Z'
                etl_date: '2025-11-10T23:00:00.000Z'
                documents: []
                etl_address: 64917511ef73c37ccae60bc4
                etd_address: 6491769cef73c37ccae60c18
                company_user: 63d7907cbe76403b35da63df
                company: 63d7907cbe76403b35da63dd
                createdAt: '2025-11-11T17:43:24.830Z'
                updatedAt: '2025-11-11T18:32:59.472Z'
                __v: 0
                temp_token: nFnPftf49q2t2r97hgpLPmh2hFFT7fp9hHB7b982GR4TMmBnTj26
                trucker_cia: 69130610d8983feafddd1b76
                trucker_user: 69130610d8983feafddd1b74
                trucker_vehicle: 691380b0c52022c07aeeaa80
                sign_image_sender_data:
                  taxid: 52930684W
                  name: Jorge
                  email: josemariapiga+87@gmail.com
                  lastname: Parada
                etl_europallets: 0
                etd_europallets: 0
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '2':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '3':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '4':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '5':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/trucker-info/{service_code}:
    put:
      summary: Update Carrier Information
      deprecated: false
      description: '## Propósito

        Actualizar la información del transportista asociada al eCMR indicado.


        ## Objetivo

        Update Carrier Information.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `PUT /ecmr/trucker-info/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Actualiza información específica del transportista
        (vehículo, conductor asignado). Permite actualizar la matrícula del vehículo
        (`plate`) y reasignar el conductor interno. Solo usuarios con rol ''trucker''
        vinculados al...


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Assignment
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Datos de transportista permitidos en este flujo
              properties:
                plate:
                  type: string
                  description: Matrícula del vehículo asignado
                  example: ABC-1234
                driver_id:
                  type: string
                  description: ID del conductor asignado
                  example: 643700268a290ac6df9237cf
                vehicle_id:
                  type: string
                  description: ID del vehículo asignado
                  example: 6450d58755656096b9a92355
            example:
              plate: XYZ-5678
              driver_id: 643700268a290ac6df9237cf
              vehicle_id: 6450d58755656096b9a92355
      responses:
        '200':
          description: Detalles del ECMR
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ECMR'
              example:
                _id: 691375bc640c7afd73ec429f
                geolocationPickup:
                  type: Point
                  coordinates:
                  - -8.7426208
                  - 42.2245427
                geolocationDelivery:
                  type: Point
                  coordinates:
                  - -8.7426208
                  - 42.2245427
                geolocationCreation:
                  type: Point
                  coordinates:
                  - 42.2245427
                  - -8.7426208
                payment:
                  paid: false
                status: delivered
                price: 150
                owner: company
                had_etl_cargo_method: false
                had_etd_cargo_method: false
                description: Mercancia general paletizada
                info_extra: ''
                plate_full_trailer: ''
                pallets_num: 0
                is_fresh: false
                fresh_cargo_temp: 0
                linear_meters: 0
                cargo_height: 0
                cargo_type: full
                hscode: '-'
                cargo_weight: 150
                is_imperial_measure: false
                service_code: RIBMADqNhRP
                custom_code: ''
                etd_time_start: ''
                etd_time_end: ''
                etl_time_start: ''
                etl_time_end: ''
                etl_photos: []
                etl_comment: ''
                etd_photos: []
                etd_comment: ''
                sign_image_sender: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAQAElEQVR4AexdCXwbxbn/vpF21w6B0qRAOErfK1AKrxwlRCsHAtbKTQg0QB8FCn3cd6GUG8rxSMsd7ldajhQILUdLCoVCGgjWyiQk1sqkUAq0FGjLUXC4IQRLu9J871vHIXYsyZKsYyWvflrt7M7MN9/3n5n/zrkS4H98BHwEfAQaBAGfsBoko3w1a4kA1jIxP60SEBhOWHXNq7omXgJsftDmRoCa27wGtm44YdU1r+qaeANnY3Gq+6F8BBodgeGE1egWFdTfb8EVhGdMeDZwGWhg1StVtMYYYfktuEoVnMaV08BloIFVr1R5aQ7CquiTp6LCKpVPvpwmRqAqJa5J8WoOwqrok6eiwipcbPyiXWFAPSHOyyXOEwANUqI5CGuQQc3t9FDR9rmzuYtapayrcDkZY4RVAfQqIKJSZaGucorhzgpjVWFxdYWvERMvC/9iykkJYIwxwioSvUIAVkBEIfFN5VdhrCosruZQl1Xha65l/gS9gP8YI6z8meH7+AhUGwEvVPhibKw5sZaQoE9YxeRgf5gSUO0P7//4CDQmAjUn1hISHERYfoUsXLxKQLWwoMbybfpi0VjZMda1HURYY7RCjvUSMJL9frEYCaEC/j7bFwCnLK9BhFVWfD+Sj4CPQF4EfLbPC02ZHj5hlQmcH81HoP4IjL0W3CgJq/5Z5mvgIzB2EahGC87bJFgnwvI2KE1RAXyIa5ONTYdzNUiwclkxhLBqh33poNROt8qBW1BSPoPy3S8oLIdn6RDnEOLfGhGBGuFcqWIxoj1FBaifNkMIq0bYFwXJuoG8rNu6uhZ1nc+gfPf7hdavoABAvwb+T30QKFgsaq5S/bQZQlg1t9tPsEQE6ldQ+hX1+bIfBv+nfghUgbD8Ul2/7KxyynXmyypb54tvAASqQFh+qW6AfPdV9BEoiIBXPatAWF411dfLR8BHoNERGDuE5fdUPVdW/SzxXJZ4XqGxQ1gN1VNtxKpcus79WVJ6NM9XqoZScNT4j1pASXAVJqza6lKS4kUFrpP+RelWMFB/VS4YwnueZeo8OFrD5pf3cqNoKAfjX5YZoxZQUqqFCau2upSkeFGBG13/fiOLLnr9oRv6pynyyxs50KxQFiYsb2A/xrWoZdEbQ+ToZVO9rFuda6NPWHXOAG8lX0tyrLPllTK1GmZ4Wbdq2FuCzCoQlv94KAF/P6iPQIUQGBv1rgqE5T8eKlQCfTFjFYGyuKe4eleWaA/lQxUIy0PWFaNKnhwMR5yFww7DWaRHnTm6YR8Witg7AVCe2MUk7OEwNbKqRsl4GOg8qhXHPXkiF75dRdGFE66Qb30Jq0JGjEpMnhzMiuyN6x4E2T+ihF4iiCLCHUxe8/g4UzdS06e0fzppVHp4KXIeTCqtYo2SWav2YIYc7F4boiKuKoquiH6NLMQnrDy51xNrecw9nh44u27LbLkhEVeuS8bVI4WgIxFoEUq5MRGeHhDq3dzyuqbNsEN5RPq3640ADVJgsHvQ7Uo4qyi6Euo1tAyfsEbIvnyFr7tT+0sipt2TiGvnBhX1CBT4OCDZkuj8cCR16pRI+r9GEO17+wj4CJSIQNMQlj7dnhwynD/oRuqsNiPzHXeMabfd3l2/RDzKCr5sEb7T3alc3ZpVL2cBdxKIrQII5+uRzNHhdvoPvlfDr1c7JJWFYGxYWVnMmkFaXsJquALh0AHcRVsPMbCRpOz3AeFOR/vCY7qR/nXYcGYzgR2utzu7T55Bm1Yr47q68FMrrj0MQrlcEixAkFNJOOeHo5mDp02jjaqV7lC5NPSySa9qZ2XD1YQmzfHVZuUlrNoViNWKjPYXIfAsEL2biCnnMml8NxhM78UD5GcSBRcSySwPkkdA0GVBx3lcN5zfh6L2tXrUPjlspGaGovS19nYKQoU+...
                sign_image_cia: ''
                sign_pickup: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAQAElEQVR4Aex9CZwUxfX/e9XTPQsoEdRfojHRJB7RqD/NstOzgDDTs6AE8QAx/Ew8QAU1eOAVwQPwh4oao/EWE0w0+jdRUEGD4k7PBIWdnmX9qeAVNcYj3hAPZHe6e+r9Xy+C67oLe8zszu5Wf7q2r6pXr741/d33XlV3C1CLQkAhoBDoIQgowuohHaXUVAgoBAAUYalfgUJAIdBjEFCE1WO6qvOKKgkKgZ6OgCKsnt6DSn+FQB9CQBFWH+ps1VSFQE9HQBFWT+9Bpb9CoCUEeuk5RVi9tGNVsxQCvREBRVi9sVdVmxQCvRQBRVi9tGO31Swz5kZjMQptK5+6rhAoJQQUYbXUG33gHAq4k5upCItBKJW18tBPB0fjrhlN+L+IWt6cqOX+NhpvOAJgjrpPv+wkBcSXQPS1DQH8mNvsc1JrNyDA1u12lVXuwdF47tio5V0cibt/zHv9lhLC9QRyNKcBRPAtQm1axJp1RjRWv0c3qFlyVSrCKrkuUQr1NgTGjKHwkFhu/4iVOzoSz10YsRrurNf8pZJgASEeRSjDiJBkq/c8XzYc4SSNEx07fIGTMk4myt/KePyEhPhVRbx+JO/36VURVh/tfgR4mZuuXEJgFAq5ziExJN6wT6TKH2ta7jlmwr1lfc5bqgm8Fwl+wcQ0CEnUSPJn+653hGMbxznJ8GW8vcepNjJ16YEfN1Unmyp7TEq6BUCsQ9TOYJnHDx9Og5rm6Uv7irD6Um83aatyCZuA0YndyGj6gWl5o5mYfmVa7g3mCnephuIhlDQVAHcjkmsk5a/2hT/OSYWPcezwTCdlLKy1+62oe2rAe9CGZXU6vDaUC90CRI8A0BjfcH8ViblD2lC012VRhNXrulQ1qFgImDHazUx4cTPuTuXtNablPYS+t5RIzuB40z78T+B1geImDfRxjq0fyen8rF12e22qX7Kuuv9bndFr5Ur8PL+d8TAKfIpAjACEc2IxKuuMzJ5YVhFWT+y1AuiMyiXcKopmgr5txrzhUcs/KRr3ruDA+F9B85Yi0cXs1h3M+L2PgAvRp4n9yRiXtY2zON1Uk9QfX2Xj61sV3o6L5VX0LSbHeDThnattcBdICT9HoDcB8Ll0Ghugjy2dJKw+hlYvai5bA2qUkPtz2LCPth8SBMTjDWPZcjojEs9dzZbTIiBvCQi4AkgO56D452xF/YXdsROxXh+XsY3TM0n9txk7tDSzIvwSE0dBR1vLY7STaTWMZpL8tSbdBSBpFqv6bRTwqCQ5LWMbp2ZT+rV8rs+tirD6XJdvajD2MQsrmOMUTCOIJHJHmQn37Kjl/ta0cov88LdSmoD7BOJpiGJfTh8xSb0OBGdAODQukzJOcezwfCcVXuQkw8/X1GD9JgQL+7f8UNolwqQZtbxLNMEkBXgO6zEQhfZXqTFJJfVfc/33r06VvVLYmnuWNEVYPau/CqZtb7WwAleu0nIjEcufyJbS+VHLvSlieUuk1y/F7tSdSOJ4Hq3bnRDe5HSPEHCq0I1Yxg6Py9ihMx1b/w2T04VOyqhzluFnBQO8BUHDquj7kYBA495czfPvQNQ4UC9DgrQ/Sj8/LZMKX+xUhxbVVpf9s4XiffKUIqw+2e0A2H4LC0ppMTkAzrGlYZVx/7hIPDfLjLt38PEyAC8lEX6HJCdw2pmD4S8B5e/IS/pFKPdpnAlpQsY2znWSxu+yyfDDNdXG/9U8geu7qm1DrYYfmQn/GDORuzKfZ5IC8Ut2OV0BeJuUoWkZOzwnkwotqV3R/+2u0qkn1SN6krJK18Ih0CMsrDkkgmkD0SovxpbSSRErN9u03IWckiTcNCFdJTE/BhD6cyB8NWH++ryURw829JiTMiaxhfJr3t4azGUKpgasXLnz54VDsO2SKmO5H7PF9z+mlbtWgnYHu3...
                sign_delivery: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAQAElEQVR4AeydCZxT1b3H//9zc5OZAZW6V2xr+3i+LtrS4iQz4DK5QRSX1qVaayvF5dmi4L5QUVTcsECfWpfWulDts8+9vqpUmCSDipObEfvqgopUrdTW2iIiApPc3PN//zsSmhlCZsnMZPvfzz2T5Ozn+8/9zf+ce3OvAtmEgBAQAhVCQASrQgwl3RQCQgBABEu+BUJACFQMARGsijFV8R2VGoRApRMQwap0C0r/hUANERDBqiFjy1CFQKUTEMGqdAtK/4VAPgJVGieCVaWGlWEJgWokIIJVjVaVMQmBKiUgglWlhpVhCYFqJCCClc+qEicEhEBZEhDBKkuzSKeEgBDIR0AEKx8ViRMCQqAsCYhglaVZpFPDR0BaqiQCIliVZC3pqxCocQIiWDX+BZDhC4FKIiCCVUnWkr4KgRonUKRg1Tg9Gb4QEALDSkAEa1hxS2NCQAgUQ0AEa2i4Sq1CQAgMAQERrCGAKlUKASEwNAREsIaGq9RaKwRknMNKQARrWHFLY0JACBRDQASrGHpSVggIgWElIII1rLilMSEgBIohUFrBKqbnUlYICIGaIyCClcfkY1tolBc97khqaJzY+YVgxGkORlJHBSPpH4YizuxQJH1LyHKeaAo7iwqFUMSJF0rPTetr3r7m8+ruV17LeYzzzwmG01OaLWf8+Em0q8dAghAoJwIiWDnW2K8ltU+TlT7Nbzg388H7jG9D+o+GVosV0XxFaooiGIsEisPLAPoFV7k3FgpE0FooPTetr3n7ms+ruz95AfUrRDqFCGFNdL3OOI+HrPR9zKGbiDUfQjuCbEKgRARqXrDGtqwd1RzpPLTJcq4wDJjPB3kji9Na1DBTaZpspMyvJ2L+CYmYeQy/TuPXK/j1NjsWuKwjWvf7QiEZM68plJ6bls2rGwJPA8JKBGMdAtYhqM8pMIIK1JGKjBksmvsb2ji7t6DAuFgR9CmvVxeAalak9kfC3RGwEwAdIPwSEJ2ASNdqoMfdTGaldtIvMasXg1Z6KYdbWdBme55nMJJhD9Rp9jzSr06iEcBbSwvV8cvmXV6EQPEEalqwmsPOQQEccY0GYzqjzCCq+fVknp+I+WYk4uay9ra6VcuW4XpOG9Q91EJ7BiMbm4NW6jiegp0bjDgLQlbKm5IV7dV5npUXWHj77N3lyb/ARfcqrdwfa9RnaaTTtOKA7mxCeEGj/isC7AKAO3MYDQRfQ6KTkGAealxcn3H+L2Sln92EzsImKz2Dp6cTmptX10MFb/vvT58KHdz572yrZhbnI3hcU1msz2e7Xcdjvb0pnCq4PMAMFnH+rZYIsnHeazCSXsgi76tgTEPe9ZoVrMaWj3dnr+EYYAJKZ+YlYubViVazta0NPy6W+pjJFGhupvqmiakvNVmdk/nL/aOglZrbZDmPNFnpZaicZUC++Yh4LCKM5oN9Nbe5EjXMLNary3ptWY8t+7m3117ztwYe4TpuSkYDR/qM9BQFahZ7X6uB9OcB6O+a4NZ67ZvYoP1jDdCHKsCLAOE9APw8sYfm1u12W1O48yw+cMtSvPLZK2Q5j4as9NMcXnH8zmvgqsfZVguA6HQCOAgIdgcA9oQhSUolPeEvFPL9E/Hi+B/A7UT0NySMcH2yFyCgCqRVdVJH28j3WCwmmagXtMfrlxY72MksUsEIL85b6ek7p5y7dIMTI40PEhpnsleyL5BaoxHeIg3nk85MSPI00476T7Bj/gs43GDHAhcOpVdX7Phyyz+3eLv322O+...
                sign_pickup_date: '2025-11-11T18:32:46.443Z'
                sign_delivery_date: '2025-11-11T18:32:59.471Z'
                signed_by_sender: true
                signed_by_company: false
                confirmed: false
                payment_method: carriage_paid
                truckers_sucesives: []
                deleted: false
                pallets_data: []
                etd_date: '2025-11-10T23:00:00.000Z'
                etl_date: '2025-11-10T23:00:00.000Z'
                documents: []
                etl_address: 64917511ef73c37ccae60bc4
                etd_address: 6491769cef73c37ccae60c18
                company_user: 63d7907cbe76403b35da63df
                company: 63d7907cbe76403b35da63dd
                createdAt: '2025-11-11T17:43:24.830Z'
                updatedAt: '2025-11-11T18:32:59.472Z'
                __v: 0
                temp_token: nFnPftf49q2t2r97hgpLPmh2hFFT7fp9hHB7b982GR4TMmBnTj26
                trucker_cia: 69130610d8983feafddd1b76
                trucker_user: 69130610d8983feafddd1b74
                trucker_vehicle: 691380b0c52022c07aeeaa80
                sign_image_sender_data:
                  taxid: 52930684W
                  name: Jorge
                  email: josemariapiga+87@gmail.com
                  lastname: Parada
                etl_europallets: 0
                etd_europallets: 0
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '2':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '3':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '4':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '5':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/truckers-sucesives/{service_code}:
    get:
      summary: List successive carriers
      deprecated: false
      description: '## Propósito

        Consultar la secuencia de transportistas sucesivos definida para un eCMR.


        ## Objetivo

        Consultar los transportistas sucesivos asociados a un eCMR concreto.


        ## Casos de uso

        - Revisar cadena de subcontratación del transporte.

        - Auditoría operativa de cambios de transportista.

        - Mostrar historial de carriers en detalle de eCMR.


        ## Detalles técnicos

        - Endpoint real en código: `GET /ecmr/truckers-sucesives/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: devuelve la lista de transportistas sucesivos
        asociados al eCMR.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Assignment
      parameters:
      - name: service_code
        in: path
        description: Codigo unico del servicio eCMR.
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      responses:
        '200':
          description: Lista de transportistas sucesivos
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '2':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '3':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '4':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      summary: Add Successive Carrier
      deprecated: false
      description: '## Propósito

        Agregar un transportista a la secuencia de transportistas sucesivos del eCMR.


        ## Objetivo

        Add Successive Carrier.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `POST /ecmr/truckers-sucesives/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Añade un transportista sucesivo al ECMR. **Restricciones:**
        - No se puede modificar transportistas en ECMRs con estado ''delivered'' u
        otros estados finales. **Status**: Functional with business logic restrictions.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Assignment
      parameters:
      - name: service_code
        in: path
        description: Codigo unico del servicio eCMR.
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                trucker_name:
                  type: string
                  description: Nombre o identificador del transportista sucesivo
                  example: Transportes Rápidos SL
              required:
              - trucker_name
        required: true
      responses:
        '200':
          description: Operación realizada correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '2':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '3':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '4':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    delete:
      summary: Delete Next Carrier
      deprecated: false
      description: '## Propósito

        Eliminar un transportista de la secuencia de transportistas sucesivos del
        eCMR.


        ## Objetivo

        Eliminar un transportista sucesivo previamente registrado en un eCMR.


        ## Casos de uso

        - Corregir un carrier añadido por error.

        - Ajustar secuencia de transportistas en un expediente abierto.

        - Mantener consistencia de datos de ejecución logística.


        ## Detalles técnicos

        - Endpoint real en código: `DELETE /ecmr/truckers-sucesives/{service_code}`.

        - Requiere body JSON con `trucker_name` (string no vacío).

        - Contexto funcional actual: elimina un transportista sucesivo del eCMR.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Assignment
      parameters:
      - name: service_code
        in: path
        description: Codigo unico del servicio eCMR.
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                trucker_name:
                  type: string
                  description: Nombre o identificador del transportista sucesivo a
                    eliminar
                  example: Transportes Rápidos SL
              required:
              - trucker_name
        required: true
      responses:
        '200':
          description: Operación realizada correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          headers: {}
        '400':
          description: Error de validación de datos
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                    example: 400
                  message:
                    type: string
                    example: TRUCKER_NAME_REQUIRED
              examples:
                trucker_name_required:
                  summary: Falta trucker_name en el body
                  value:
                    status: 400
                    message: TRUCKER_NAME_REQUIRED
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '2':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '3':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '4':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr:
    get:
      summary: List ECMRs with pagination
      deprecated: false
      description: '## Propósito

        Consultar la lista de eCMR disponibles para el usuario autenticado con la
        información principal de cada operación.


        ## Objetivo

        List ECMRs with pagination.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `GET /ecmr`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Devuelve una lista paginada de ECMRs asociados
        al usuario autenticado. Permite filtrar por múltiples criterios para facilitar
        la gestión de la documentación. **Filtros disponibles:** - **Estado**: Filtra
        por el estado...


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Lifecycle
      parameters:
      - name: page
        in: query
        description: Número de página para la paginación de resultados.
        required: false
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Cantidad de resultados por página.
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 10
      - name: status
        in: query
        description: 'Estado del ECMR para filtrar: - `planned`: Planificado - `accepted`:

          Aceptado por transportista - `collected`: Recogido - `locked`:

          Bloqueado/En tránsito - `delivered`: Entregado - `canceled`:

          Cancelado

          '
        required: false
        schema:
          type: string
          enum:
          - planned
          - accepted
          - collected
          - locked
          - issue
          - delivered
          - claimed
          - canceled
      - name: search
        in: query
        description: 'Texto de búsqueda. Busca coincidencias parciales en: - Código
          de

          servicio (ECMR-...) - Código personalizado - Nombres de empresas

          (origen/destino) - Direcciones'
        required: false
        schema:
          type: string
      - name: minCreate
        in: query
        description: Fecha mínima de creación (formato YYYY-MM-DD).
        required: false
        schema:
          type: string
          format: date
      - name: maxCreate
        in: query
        description: Fecha máxima de creación (formato YYYY-MM-DD).
        required: false
        schema:
          type: string
          format: date
      - name: minETL
        in: query
        description: Fecha mínima estimada de carga/recogida (ETL).
        required: false
        schema:
          type: string
          format: date
      - name: maxETL
        in: query
        description: Fecha máxima estimada de carga/recogida (ETL).
        required: false
        schema:
          type: string
          format: date
      - name: minETD
        in: query
        description: Fecha mínima estimada de entrega/descarga (ETD).
        required: false
        schema:
          type: string
          format: date
      - name: maxETD
        in: query
        description: Fecha máxima estimada de entrega/descarga (ETD).
        required: false
        schema:
          type: string
          format: date
      - name: cargo_type
        in: query
        description: 'Tipo de carga del ECMR: - `pallets`: Carga paletizada - `full`:

          Carga completa - `package`: Paquetería - `trailer`: Remolque

          completo

          '
        required: false
        schema:
          type: string
          enum:
          - pallets
          - full
          - package
          - trailer
      - name: minCreate
        in: query
        description: Fecha mínima de creación (createdAt).
        required: false
        schema:
          type: string
          format: date
      - name: maxCreate
        in: query
        description: Fecha máxima de creación (createdAt).
        required: false
        schema:
          type: string
          format: date
      - name: minETL
        in: query
        description: Fecha mínima de recogida (etl_date).
        required: false
        schema:
          type: string
          format: date
      - name: maxETL
        in: query
        description: Fecha máxima de recogida (etl_date).
        required: false
        schema:
          type: string
          format: date
      - name: minETD
        in: query
        description: Fecha mínima de entrega (etd_date).
        required: false
        schema:
          type: string
          format: date
      - name: maxETD
        in: query
        description: Fecha máxima de entrega (etd_date).
        required: false
        schema:
          type: string
          format: date
      - name: minAuction
        in: query
        description: Fecha mínima de subasta.
        required: false
        schema:
          type: string
          format: date
      - name: maxAuction
        in: query
        description: Fecha máxima de subasta.
        required: false
        schema:
          type: string
          format: date
      - name: minStart
        in: query
        description: Fecha mínima de inicio (date_start).
        required: false
        schema:
          type: string
          format: date
      - name: maxStart
        in: query
        description: Fecha máxima de inicio (date_start).
        required: false
        schema:
          type: string
          format: date
      - name: minEnd
        in: query
        description: Fecha mínima de fin (date_end).
        required: false
        schema:
          type: string
          format: date
      - name: maxEnd
        in: query
        description: Fecha máxima de fin (date_end).
        required: false
        schema:
          type: string
          format: date
      - name: minDate
        in: query
        description: Filtro combinado. Busca en fecha inicio (date_start/etl_date)
          |= fecha dada.
        required: false
        schema:
          type: string
          format: date
      - name: maxDate
        in: query
        description: Filtro combinado. Busca en fecha fin (date_end/etd_date) <= fecha
          dada.
        required: false
        schema:
          type: string
          format: date
      - name: show_canceled
        in: query
        description: Si es true, incluye los ECMRs cancelados en la lista.
        required: false
        schema:
          type: boolean
          default: false
      - name: show_claimed
        in: query
        description: Si es true, incluye los ECMRs con reclamaciones.
        required: false
        schema:
          type: boolean
          default: false
      responses:
        '200':
          description: Lista de ECMRs obtenida exitosamente. Devuelve objeto paginado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  docs:
                    type: array
                    items:
                      $ref: '#/components/schemas/ECMR'
                  totalDocs:
                    type: integer
                    description: Total de documentos encontrados con los filtros actuales.
                  limit:
                    type: integer
                    description: Límite de documentos por página aplicado.
                  totalPages:
                    type: integer
                    description: Número total de páginas disponibles.
                  page:
                    type: integer
                    description: Número de página actual.
                  pagingCounter:
                    type: integer
                    description: Índice del primer documento en esta página.
                  hasPrevPage:
                    type: boolean
                    description: Indica si existe una página anterior.
                  hasNextPage:
                    type: boolean
                    description: Indica si existe una página siguiente.
                  prevPage:
                    type: integer
                    description: Número de la página anterior (null si no hay).
                    nullable: true
                  nextPage:
                    type: integer
                    description: Número de la página siguiente (null si no hay).
                    nullable: true
                  example:
                    docs:
                    - _id: 691375bc640c7afd73ec429f
                      service_code: RIBMADqNhRP
                      status: planned
                      createdAt: '2025-01-28T10:00:00.000Z'
                      etl_address: 64917511ef73c37ccae60bc4
                      etd_address: 6491769cef73c37ccae60c18
                      etl_date: '2025-02-15T08:00:00.000Z'
                      etd_date: '2025-02-16T18:00:00.000Z'
                      cargo_type: pallets
                      pallets_num: 33
                      pallets_type: european
                      cargo_weight: 24000
                      description: Frozen Fish Transport
                    totalDocs: 1
                    limit: 10
                    totalPages: 1
                    page: 1
                    pagingCounter: 1
                    hasPrevPage: false
                    hasNextPage: false
          headers: {}
        '401':
          description: No autorizado. Token/JWT o API key ausente, invalido o sin
            permisos para la operacion.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: UNAUTHORIZED
          headers: {}
        '500':
          description: Error interno del servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      summary: Create New
      deprecated: false
      description: "## Propósito\nCrear un nuevo eCMR con los datos del envío y dejarlo\
        \ disponible para su gestión posterior.\n\n## Objetivo\nCreate New.\n\n##\
        \ Casos de uso\n- Consumir la operación desde la app web o integraciones backend.\n\
        - Ejecutar el flujo operativo correspondiente del eCMR.\n- Obtener una respuesta\
        \ consistente según el estado del recurso.\n\n## Detalles técnicos\n- Endpoint\
        \ real en código: `POST /ecmr`.\n- Mantiene parámetros, requestBody y responses\
        \ definidos en este contrato.\n- Contexto funcional actual: Crea un nuevo\
        \ ECMR en el sistema. Si el\n  usuario autenticado pertenece a una cuenta\
        \ trucker y envía\n  `force_create_cia=true`, el backend fuerza el flujo de\
        \ creación para\n  empresa (`createCompany`) y exige `company` en el body.\
        \ Si falta,\n  devuelve `400 COMPANY_ID_REQUIRED`.\n\n## Autenticación\nRequiere\
        \ JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n"
      tags:
      - ECMR - Lifecycle
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - etl_address
              - etd_address
              - etl_date
              - etd_date
              properties:
                etl_address:
                  type: string
                  description: ID de dirección de recogida
                  example: 507f1f77bcf86cd799439011
                etd_address:
                  type: string
                  description: ID de dirección de entrega
                  example: 507f1f77bcf86cd799439012
                etl_date:
                  type: string
                  format: date-time
                  description: Fecha y hora de recogida
                  example: '2024-01-15T08:00:00.000Z'
                etd_date:
                  type: string
                  format: date-time
                  description: Fecha y hora de entrega
                  example: '2024-01-16T14:00:00.000Z'
                etl_extra_time:
                  type: integer
                  description: Tiempo extra para recogida en minutos
                  example: 60
                etd_extra_time:
                  type: integer
                  description: Tiempo extra para entrega en minutos
                  example: 120
                description:
                  type: string
                  maxLength: 2000
                  description: Descripción del envío
                info_extra:
                  type: string
                  maxLength: 2000
                  description: Información adicional
                cargo_type:
                  type: string
                  enum:
                  - pallets
                  - full
                  - package
                  - trailer
                  description: Tipo de carga
                  example: pallets
                pallets_num:
                  type: integer
                  minimum: 0
                  maximum: 66
                  description: Número de palets (requerido si cargo_type es pallets)
                pallets_type:
                  type: string
                  enum:
                  - european
                  - american
                  - none
                  description: Tipo de palets
                cargo_weight:
                  type: number
                  minimum: 0
                  maximum: 24000
                  description: Peso de la carga en kg
                cargo_height:
                  type: number
                  minimum: 0
                  maximum: 2400
                  description: Altura de la carga en cm
                linear_meters:
                  type: number
                  minimum: 0
                  maximum: 1360
                  description: Metros lineales (requerido si cargo_type es package)
                plate_full_trailer:
                  type: string
                  maxLength: 15
                  description: Matrícula del remolque (requerido si cargo_type es
                    trailer)
                is_fresh:
                  type: boolean
                  description: Carga refrigerada
                fresh_cargo_temp:
                  type: number
                  minimum: -273
                  maximum: 1000
                  description: Temperatura de refrigeración
                hscode:
                  type: string
                  description: Código HS
                etl_cargo_method:
                  type: string
                  enum:
                  - back
                  - up
                  - lateral
                  description: Método de carga en origen
                etd_cargo_method:
                  type: string
                  enum:
                  - back
                  - up
                  - lateral
                  description: Método de descarga en destino
                status:
                  type: string
                  enum:
                  - planned
                  - draft
                  default: planned
                  description: Estado inicial del ECMR
                force_create_cia:
                  type: boolean
                  default: false
                  description: 'Solo para usuarios trucker. Cuando es `true`, fuerza
                    el flujo de creación como empresa y requiere `company`.

                    '
                company:
                  type: string
                  description: 'ID de compañía objetivo. Requerido cuando `force_create_cia=true`
                    para usuarios trucker.

                    '
            example:
              etl_address: 693978e10fe7ba19d690757b
              etd_address: 693978b40fe7ba19d6907572
              etl_date: '2025-02-15T08:00:00.000Z'
              etd_date: '2025-02-16T18:00:00.000Z'
              etl_extra_time: 60
              etd_extra_time: 120
              description: Frozen Fish Transport
              info_extra: Handle with care, temperature sensitive
              cargo_type: pallets
              pallets_num: 33
              pallets_type: european
              cargo_weight: 24000
              cargo_height: 220
              is_fresh: true
              fresh_cargo_temp: -18
              etl_cargo_method: back
              etd_cargo_method: back
              status: planned
              force_create_cia: false
        required: true
      responses:
        '200':
          description: ECMR creado exitosamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ECMR'
          headers: {}
        '400':
          description: Solicitud inválida o falta `company` al forzar creación para
            empresa
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalid_request:
                  value:
                    status: 400
                    message: INVALID_REQUEST
                company_id_required:
                  value:
                    status: 400
                    message: COMPANY_ID_REQUIRED
          headers: {}
        '401':
          description: No autorizado. Token/JWT o API key ausente, invalido o sin
            permisos para la operacion.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: UNAUTHORIZED
          headers: {}
        '404':
          description: Recurso o contexto de compañía no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                company_not_found:
                  value:
                    status: 404
                    message: COMPANY_NOT_FOUND
                not_found:
                  value:
                    status: 404
                    message: NOT_FOUND
          headers: {}
        '500':
          description: Error interno del servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/file/{service_code}:
    get:
      summary: Download ECMR File
      deprecated: false
      description: '## Propósito

        Recuperar el archivo generado del eCMR para su visualización o descarga.


        ## Objetivo

        Download ECMR File.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `GET /ecmr/file/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Descarga el archivo PDF oficial del ECMR. Si
        el PDF no existe, se genera al vuelo basándose en los datos actuales. Soporta
        internacionalización basada en la preferencia del usuario (es/en/fr).


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Output
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: XXXXXXhbMFp
        schema:
          type: string
      responses:
        '200':
          description: Archivo del ECMR
          content:
            application/pdf:
              schema:
                type: string
                format: binary
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '2':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '3':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '4':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/lock/{service_code}:
    put:
      summary: Lock ECMR
      deprecated: false
      description: '## Propósito

        Bloquear un eCMR para cerrar su edición y asegurar la integridad de su estado
        final.


        ## Objetivo

        Lock ECMR.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `PUT /ecmr/lock/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Bloquea un ECMR para evitar futuras modificaciones
        y genera el documento PDF final. **Efectos:** - Cambia el estado a ''locked''.
        - Genera y almacena el PDF definitivo. - Impide ediciones posteriores de datos
        críticos.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Lifecycle
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: XXXXXXhbMFp
        schema:
          type: string
      responses:
        '200':
          description: Operación realizada correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '2':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '3':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '4':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/pdf-status/{service_code}:
    get:
      summary: Obtener estado de generación del PDF
      deprecated: false
      description: "## Propósito\nConsultar el estado actual de generación del PDF\
        \ asociado a un eCMR.\n## Objetivo\nPermitir que el frontend o integraciones\
        \ verifiquen si el PDF ya está\ngenerado y listo para descarga, o si aún está\
        \ en proceso.\n## Casos de uso\n- Verificar si el PDF está disponible antes\
        \ de intentar descargarlo.\n- Mostrar indicador de progreso en la interfaz\
        \ de usuario.\n- Depurar problemas de generación de documentos.\n## Detalles\
        \ técnicos\n- Endpoint real: `GET /ecmr/pdf-status/{service_code}`.\n- No\
        \ requiere autenticación (público).\n- Consulta el estado mediante `ecmrPdfService.getPdfStatus(service_code)`.\n\
        - Devuelve el estado en el formato estándar `returnOK`:\n  `{ status, data,\
        \ 0 }`.\n## Autenticación\nNo requiere autenticación (endpoint público).\n"
      tags:
      - ECMR - Output
      parameters:
      - name: service_code
        in: path
        description: Código de servicio único del eCMR.
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      responses:
        '200':
          description: Estado del PDF consultado correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas de `returnOK`.
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta.
                    example: 200
                  data:
                    type: object
                    description: Estado actual del PDF.
                    properties:
                      generated:
                        type: boolean
                        description: Indica si el PDF ya ha sido generado.
                        example: true
                      status:
                        type: string
                        description: Estado textual del PDF (generated, pending, error).
                        example: generated
                      updatedAt:
                        type: string
                        format: date-time
                        description: Fecha de última actualización del estado.
                        example: '2024-03-15T10:30:00Z'
        '404':
          description: eCMR no encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: ECMR_NOT_FOUND
        '500':
          description: Error al consultar el estado del PDF.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR_GETTING_PDF_STATUS
      security: []
  /ecmr/render/{service_code}:
    get:
      summary: Render ECMR as HTML
      deprecated: false
      description: '## Propósito

        Generar una vista HTML del eCMR para previsualización o impresión sin

        descargar el PDF final.


        ## Objetivo

        Permitir a integraciones internas o frontends renderizar rápidamente el

        contenido del documento en pantalla.


        ## Casos de uso

        - Mostrar una previsualización en el panel antes de bloquear el eCMR.

        - Verificar estructura y contenido del documento durante soporte.

        - Integrar una vista embebida del eCMR en herramientas internas.


        ## Detalles técnicos

        Busca el eCMR por `service_code`, popula relaciones necesarias y

        responde usando el wrapper estándar de `returnOK`

        (`status`, `data`, `0`).


        ## Autenticación

        No requiere autenticación.

        '
      tags:
      - ECMR - Output
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR.
        required: true
        example: XXXXXXhbMFp
        schema:
          type: string
      responses:
        '200':
          description: HTML generado correctamente
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      html:
                        type: string
                        example: <html><body>ECMR Render</body></html>
                      service_code:
                        type: string
                        example: XXXXXXhbMFp
              example:
                '0': 0
                status: 200
                data:
                  html: <html><body>ECMR Render</body></html>
                  service_code: XXXXXXhbMFp
          headers: {}
        '400':
          description: Error de validación
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: SERVICE_CODE_REQUIRED
          headers: {}
        '404':
          description: Recurso no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
          headers: {}
        '500':
          description: Error interno del servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security: []
  /ecmr/{service_code}:
    get:
      summary: Get ECMR details
      deprecated: false
      description: '## Propósito

        Obtener el detalle completo de un eCMR específico identificado por `service_code`.


        ## Objetivo

        Get ECMR details.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `GET /ecmr/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Devuelve los detalles completos de un ECMR específico.
        Incluye objetos populados para: - Direcciones (ETL/ETD). - Información de
        la empresa y transportista. - Vehículo y conductor asignado. También calcula
        flags dinám...


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Lifecycle
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: XXXXXXhbMFp
        schema:
          type: string
      - name: temp_code
        in: query
        description: Código temporal para acceso compartido
        required: false
        schema:
          type: string
      responses:
        '200':
          description: Detalles del ECMR obtenidos exitosamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ECMR'
          headers: {}
        '401':
          description: No autorizado. Token/JWT o API key ausente, invalido o sin
            permisos para la operacion.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: UNAUTHORIZED
          headers: {}
        '404':
          description: Recurso no encontrado
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '2':
                  summary: address_not_found
                  value:
                    error: Dirección no encontrada
                '3':
                  summary: user_not_found
                  value:
                    error: Usuario no encontrado
          headers: {}
        '500':
          description: Error interno del servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    put:
      summary: Update ECMR
      deprecated: false
      description: '## Propósito

        Actualizar la información editable de un eCMR específico según su `service_code`.


        ## Objetivo

        Update ECMR.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `PUT /ecmr/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Actualiza los datos de un ECMR existente. **IMPORTANTE**:
        Solo se pueden editar ECMRs que NO estén en estados finales. Los estados finales
        son: delivered, claimed, canceled. Los campos editables incluyen: description,...


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Lifecycle
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              description: Campos a actualizar (depende del estado del ECMR)
              properties:
                description:
                  type: string
                  maxLength: 2000
                info_extra:
                  type: string
                  maxLength: 2000
                pallets_num:
                  type: integer
                  minimum: 0
                  maximum: 66
                cargo_weight:
                  type: number
                  minimum: 0
                  maximum: 24000
                cargo_height:
                  type: number
                  minimum: 0
                  maximum: 2400
                etl_cargo_method:
                  type: string
                  enum:
                  - back
                  - up
                  - lateral
                etd_cargo_method:
                  type: string
                  enum:
                  - back
                  - up
                  - lateral
            example:
              description: Frozen Fish Transport - Updated
              info_extra: Temperature check required every 4 hours
              pallets_num: 33
              cargo_weight: 24000
              cargo_height: 220
              etl_cargo_method: back
              etd_cargo_method: back
        required: true
      responses:
        '200':
          description: ECMR actualizado exitosamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ECMR'
          headers: {}
        '400':
          description: Solicitud inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: INVALID_REQUEST
          headers: {}
        '401':
          description: No autorizado. Token/JWT o API key ausente, invalido o sin
            permisos para la operacion.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: UNAUTHORIZED
          headers: {}
        '404':
          description: Recurso no encontrado
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '3':
                  summary: address_not_found
                  value:
                    error: Dirección no encontrada
                '4':
                  summary: user_not_found
                  value:
                    error: Usuario no encontrado
          headers: {}
        '500':
          description: Error interno del servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    delete:
      summary: Cancel ECMR
      deprecated: false
      description: '## Propósito

        Eliminar o cancelar un eCMR específico identificado por `service_code`.


        ## Objetivo

        Cancel ECMR.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `DELETE /ecmr/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Cancela un ECMR existente. **Restricciones:**
        - Solo se pueden cancelar ECMRs en estados cancelables (generalmente ''planned'').
        - Usuarios ''company'' pueden cancelar sus propios ECMRs. **Efectos:** - Cambia
        el estado a ...


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Lifecycle
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      responses:
        '200':
          description: ECMR cancelado exitosamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          headers: {}
        '401':
          description: No autorizado. Token/JWT o API key ausente, invalido o sin
            permisos para la operacion.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: UNAUTHORIZED
          headers: {}
        '404':
          description: Recurso no encontrado
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '2':
                  summary: address_not_found
                  value:
                    error: Dirección no encontrada
                '3':
                  summary: user_not_found
                  value:
                    error: Usuario no encontrado
          headers: {}
        '500':
          description: Error interno del servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/dashboard:
    get:
      summary: Get dashboard statistics
      deprecated: false
      description: '## Propósito

        Obtener el resumen del dashboard eCMR con métricas clave y estado general
        de las operaciones.


        ## Objetivo

        Obtener métricas y KPIs operativos de eCMR para el usuario autenticado.


        ## Casos de uso

        - Mostrar tarjetas resumen en el panel principal.

        - Analizar volumen y estado de operaciones de transporte.

        - Alimentar widgets de seguimiento en tiempo real.


        ## Detalles técnicos

        - Endpoint real en código: `GET /ecmr/dashboard`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: devuelve métricas y KPIs del usuario autenticado
        (company o trucker) sobre sus eCMR.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Operations
      parameters: []
      responses:
        '200':
          description: KPIs obtenidos correctamente para el panel de control del eCMR
          headers: {}
        '401':
          description: No autorizado. Token/JWT o API key ausente, invalido o sin
            permisos para la operacion.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: UNAUTHORIZED
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/dashboard/recent:
    get:
      summary: Get recent ECMRs
      deprecated: false
      description: '## Propósito

        Consultar las operaciones eCMR más recientes para facilitar el seguimiento
        de actividad reciente.


        ## Objetivo

        Recuperar los eCMR más recientes para renderizar actividad reciente del usuario.


        ## Casos de uso

        - Mostrar última actividad en dashboard.

        - Acceso rápido a expedientes recientes.

        - Seguimiento de operaciones recién creadas o actualizadas.


        ## Detalles técnicos

        - Endpoint real en código: `GET /ecmr/dashboard/recent`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: devuelve los eCMR más recientes del usuario autenticado.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Operations
      parameters: []
      responses:
        '200':
          description: Lista de ECMRs recientes
          headers: {}
        '401':
          description: No autorizado. Token/JWT o API key ausente, invalido o sin
            permisos para la operacion.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: UNAUTHORIZED
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/public/contact-info/{service_code}:
    get:
      summary: Public Contact Information
      deprecated: false
      description: '## Propósito

        Obtener información de contacto pública de las partes asociadas al eCMR.


        ## Objetivo

        Facilitar la comunicación operativa entre actores del envío cuando se

        accede mediante enlace compartido.


        ## Casos de uso

        - Consultar teléfono/email de contacto en un flujo público.

        - Exponer datos mínimos de contacto para coordinación logística.


        ## Detalles técnicos

        Requiere `temp_code` en query y valida su coincidencia con el token

        temporal del eCMR antes de devolver la información.


        ## Autenticación

        No requiere JWT ni API Key. El control se basa en `temp_code`.

        '
      tags:
      - ECMR - Public Access
      parameters:
      - name: service_code
        in: path
        description: Codigo unico del servicio eCMR.
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      - name: temp_code
        in: query
        description: Código temporal para acceso público al ECMR
        required: true
        example: 2f84b6f1c2a945d
        schema:
          type: string
      responses:
        '200':
          description: Información de contacto
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactInfo'
          headers: {}
        '404':
          description: Recurso no encontrado
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: address_not_found
                  value:
                    error: Dirección no encontrada
                '2':
                  summary: user_not_found
                  value:
                    error: Usuario no encontrado
          headers: {}
      security: []
  /ecmr/public/pallets/{service_code}:
    get:
      summary: ECMR Public Pallets
      deprecated: false
      description: '## Propósito

        Consultar públicamente la información de pallets del eCMR.


        ## Objetivo

        Permitir validar unidades y tipología de pallets en contextos de

        seguimiento o recepción sin autenticación de usuario.


        ## Casos de uso

        - Verificar número total de pallets antes de la descarga.

        - Consultar detalle de `pallets_data` desde un enlace compartido.


        ## Detalles técnicos

        Requiere `temp_code` en query y valida el token temporal del eCMR.

        Si es válido, responde `pallets_num`, `pallets_type` y `pallets_data`.


        ## Autenticación

        No requiere JWT ni API Key. El control se basa en `temp_code`.

        '
      tags:
      - ECMR - Public Access
      parameters:
      - name: service_code
        in: path
        description: Codigo unico del servicio eCMR.
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      - name: temp_code
        in: query
        description: Código temporal para acceso público al ECMR
        required: true
        example: 2f84b6f1c2a945d
        schema:
          type: string
      responses:
        '200':
          description: Lista de pallets
          headers: {}
        '404':
          description: Recurso no encontrado
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: address_not_found
                  value:
                    error: Dirección no encontrada
                '2':
                  summary: user_not_found
                  value:
                    error: Usuario no encontrado
          headers: {}
      security: []
  /ecmr/public/{service_code}:
    get:
      summary: ECMR Public Detail
      deprecated: false
      description: '## Propósito

        Obtener el detalle público de un eCMR compartido externamente.


        ## Objetivo

        Permitir consulta controlada de información operativa del eCMR sin login,

        validando un código temporal de acceso.


        ## Casos de uso

        - Compartir estado y datos clave del envío con terceros.

        - Consultar el detalle desde un enlace público temporal.

        - Integrar seguimiento en portales externos sin sesión autenticada.


        ## Detalles técnicos

        Requiere `service_code` (path) y `temp_code` (query). El servidor valida

        que `temp_code` coincida con `temp_token` del eCMR; en caso contrario

        devuelve `INVALID_TEMP_CODE`.


        ## Autenticación

        No requiere JWT ni API Key. El control de acceso se realiza con `temp_code`.

        '
      tags:
      - ECMR - Public Access
      parameters:
      - name: service_code
        in: path
        description: Codigo unico del servicio eCMR.
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      - name: temp_code
        in: query
        description: Código temporal para acceso público al ECMR
        required: true
        example: 2f84b6f1c2a945d
        schema:
          type: string
      responses:
        '200':
          type: object
          properties:
            _id:
              type: string
              description: ID único del ECMR en la base de datos
            service_code:
              type: string
              description: Código de servicio único generado automáticamente
              example: ECMR-2025-001
            custom_code:
              type: string
              description: Código personalizado asignado por la empresa
              example: CUSTOM-001
              maxLength: 50
            status:
              type: string
              enum:
              - planned
              - accepted
              - collected
              - locked
              - issue
              - delivered
              - claimed
              - canceled
              description: Estado actual del ECMR
              example: planned
            owner:
              type: string
              enum:
              - company
              description: Tipo de propietario del ECMR
              example: company
            price:
              type: number
              description: Precio del servicio
              example: 150.5
            company:
              $ref: '#/components/schemas/CompanyRef'
            company_user:
              $ref: '#/components/schemas/UserRef'
            trucker_cia:
              $ref: '#/components/schemas/TruckerCiaRef'
            trucker_user:
              $ref: '#/components/schemas/UserRef'
            trucker_vehicle:
              $ref: '#/components/schemas/VehicleRef'
            etl_address:
              $ref: '#/components/schemas/AddressRef'
            etd_address:
              $ref: '#/components/schemas/AddressRef'
            etl_date:
              type: string
              format: date-time
              description: Fecha y hora de recogida (ETL - Estimated Time of Loading)
              example: '2024-01-15T08:00:00.000Z'
            etd_date:
              type: string
              format: date-time
              description: Fecha y hora de entrega (ETD - Estimated Time of Delivery)
              example: '2024-01-16T14:00:00.000Z'
            etl_extra_time:
              type: number
              description: Tiempo extra permitido para recogida en minutos
              example: 60
            etd_extra_time:
              type: number
              description: Tiempo extra permitido para entrega en minutos
              example: 120
            description:
              type: string
              description: Descripción del envío
              maxLength: 2000
              example: Envío de mercancía electrónica
            info_extra:
              type: string
              description: Información adicional
              maxLength: 2000
              example: Manejar con cuidado
            plate_full_trailer:
              type: string
              description: Matrícula del remolque completo
              maxLength: 15
              example: 1234ABC
            pallets_num:
              type: integer
              description: Número de palets
              minimum: 0
              maximum: 66
              example: 12
            pallets_type:
              type: string
              enum:
              - european
              - american
              - none
              description: Tipo de palets
              example: european
            is_fresh:
              type: boolean
              description: Indica si es carga refrigerada
              example: false
            fresh_cargo_temp:
              type: number
              description: Temperatura de carga refrigerada en grados
              minimum: -273
              maximum: 1000
              example: 4
            linear_meters:
              type: number
              description: Metros lineales de carga
              minimum: 0
              maximum: 1360
              example: 8.5
            cargo_height:
              type: number
              description: Altura de la carga en cm
              minimum: 0
              maximum: 2400
              example: 180
            cargo_type:
              type: string
              enum:
              - pallets
              - full
              - package
              - trailer
              description: Tipo de carga
              example: pallets
            hscode:
              type: string
              description: Código HS (Harmonized System)
              example: '84713000'
            cargo_weight:
              type: number
              description: Peso de la carga en kg
              minimum: 0
              maximum: 24000
              example: 1500
            is_imperial_measure:
              type: boolean
              description: Indica si usa medidas imperiales
              example: false
            etl_cargo_method:
              type: string
              enum:
              - back
              - up
              - lateral
              description: Método de carga en origen
              example: back
            had_etl_cargo_method:
              type: boolean
              description: Indica si ya se especificó el método de carga en origen
              example: false
            etd_cargo_method:
              type: string
              enum:
              - back
              - up
              - lateral
            example:
              description: Updated Frozen Fish Transport
              info_extra: Updated handling instructions
              pallets_num: 33
              cargo_weight: 24000
              cargo_height: 220
              etl_cargo_method: back
              etd_cargo_method: back
              example: back
            had_etd_cargo_method:
              type: boolean
              description: Indica si ya se especificó el método de descarga en destino
              example: false
            etl_photos:
              type: array
              description: URLs de fotos de recogida
              items:
                type: string
              example:
              - /images/photo1.jpg
              - /images/photo2.jpg
            etl_comment:
              type: string
              description: Comentario de recogida
              example: Carga recogida sin incidencias
            etd_photos:
              type: array
              description: URLs de fotos de entrega
              items:
                type: string
              example:
              - /images/delivery1.jpg
              - /images/delivery2.jpg
            etd_comment:
              type: string
              description: Comentario de entrega
              example: Entrega realizada correctamente
            sign_image_trucker:
              type: string
              description: URL de imagen de firma del transportista
              example: /images/signature_trucker.png
            sign_image_cia:
              type: string
              description: URL de imagen de firma de la empresa
              example: /images/signature_company.png
            sign_pickup:
              type: string
              description: URL de imagen de firma de recogida
              example: /images/signature_pickup.png
            sign_delivery:
              type: string
              description: URL de imagen de firma de entrega
              example: /images/signature_delivery.png
            sign_pickup_date:
              type: string
              format: date-time
              description: Fecha de firma de recogida
              example: '2024-01-15T09:30:00.000Z'
            sign_delivery_date:
              type: string
              format: date-time
              description: Fecha de firma de entrega
              example: '2024-01-16T15:45:00.000Z'
            signed_by_trucker:
              type: boolean
              description: Indica si el transportista ha firmado
              example: true
            signed_by_company:
              type: boolean
              description: Indica si la empresa ha firmado
              example: true
            confirmed:
              type: boolean
              description: Indica si el ECMR está confirmado
              example: true
            temp_token:
              type: string
              description: Token temporal para compartir ECMR
            qr_token:
              type: string
              description: Token para códigos QR
            confirm_token:
              type: string
              description: Token de confirmación
            geolocationPickup:
              type: object
              description: Geolocalización de recogida
              properties:
                type:
                  type: string
                  example: Point
                coordinates:
                  type: array
                  items:
                    type: number
                  example:
                  - -3.7038
                  - 40.4168
            geolocationDelivery:
              type: object
              description: Geolocalización de entrega
              properties:
                type:
                  type: string
                  example: Point
                coordinates:
                  type: array
                  items:
                    type: number
                  example:
                  - 2.1734
                  - 41.3851
            documents:
              type: array
              description: Documentos adjuntos al ECMR
              items:
                type: object
                properties:
                  owner_trucker:
                    type: string
                    description: ID del usuario transportista propietario
                  owner_company:
                    type: string
                    description: ID del usuario empresa propietario
                  path:
                    type: string
                    description: Ruta del archivo
            payment:
              type: object
              description: Información de pago
              properties:
                paid:
                  type: boolean
                  description: Indica si está pagado
                  example: true
                payment_intent:
                  type: string
                  description: ID del intento de pago de Stripe
                pay_date:
                  type: string
                  format: date-time
                  description: Fecha de pago
            createdAt:
              type: string
              format: date-time
              description: Fecha de creación
            updatedAt:
              type: string
              format: date-time
              description: Fecha de última actualización
          description: Documento de nota de consignación electrónica completa
          headers: {}
        '404':
          description: Recurso no encontrado
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: address_not_found
                  value:
                    error: Dirección no encontrada
                '2':
                  summary: user_not_found
                  value:
                    error: Usuario no encontrado
          headers: {}
      security: []
  /ecmr/documents/{service_code}:
    get:
      operationId: getEcmrDocuments
      summary: Listar documentos de un eCMR
      deprecated: false
      description: '## Propósito

        Devuelve el listado de documentos adjuntos al eCMR para usuarios

        autenticados con acceso al servicio.


        ## Objetivo

        Permitir que empresa y transportista consulten los anexos cargados

        durante el ciclo de vida del eCMR.


        ## Casos de uso

        - Mostrar anexos en la vista privada de detalle del eCMR.

        - Verificar qué archivos están disponibles antes de una descarga.

        - Revisar documentos antes de eliminar o subir nuevas versiones.


        ## Detalles técnicos

        El backend busca el eCMR por `service_code` y pertenencia de compañía.

        Si existen documentos, transforma cada entrada a `{ _id, name }`.

        La respuesta usa `returnOK` con wrapper `{ status, data, 0 }`.

        Si no encuentra eCMR, la implementación actual no envía respuesta

        explícita (comportamiento conocido pendiente de ajuste en código).


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - ECMR - Documents
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del eCMR
        required: true
        example: ECMR-2025-001
        schema:
          type: string
      responses:
        '200':
          description: Lista de documentos obtenida correctamente
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        _id:
                          type: string
                          example: 507f1f77bcf86cd799439011
                        name:
                          type: string
                          example: factura_transportista.pdf
                      required:
                      - _id
                      - name
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                - _id: 507f1f77bcf86cd799439011
                  name: factura_transportista.pdf
                - _id: 507f1f77bcf86cd799439012
                  name: albaran_entrega.pdf
          headers: {}
        '401':
          description: Token/API Key ausente o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Usuario o tipo de cuenta no resuelto en el flujo interno
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  summary: Usuario autenticado no disponible
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                user_type_not_found:
                  summary: Tipo de cuenta no reconocido
                  value:
                    status: 404
                    message: USER_TYPE_NOT_FOUND
          headers: {}
        '500':
          description: Error interno al consultar documentos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      operationId: postEcmrDocuments
      summary: Subir documentos a un eCMR
      deprecated: false
      description: "## Propósito\nAñade nuevos ficheros al array `documents` del eCMR\
        \ indicado.\n\n## Objetivo\nPermitir que empresa o transportista adjunten\
        \ documentación operativa\ndel servicio (facturas, albaranes, justificantes,\
        \ etc.).\n\n## Casos de uso\n- Adjuntar factura o albarán tras completar una\
        \ operación.\n- Incorporar documentación de soporte para auditoría.\n- Cargar\
        \ múltiples archivos en una única petición multipart.\n\n## Flujo de validación\n\
        ```mermaid\nflowchart TD\n  A[Recibir POST /ecmr/documents/{service_code}]\
        \ --> B{¿Usuario autenticado?}\n  B -->|No| C[401 NO_TOKEN_OR_APIKEY]\n  B\
        \ -->|Sí| D{¿Usuario/tipo válidos?}\n  D -->|No| E[404 USER_NOT_FOUND o USER_TYPE_NOT_FOUND]\n\
        \  D -->|Sí| F{¿eCMR accesible por service_code?}\n  F -->|No| G[404 ECMR_NOT_FOUND]\n\
        \  F -->|Sí| H[Procesar files con límite 4]\n  H --> I[Anexar documentos y\
        \ guardar eCMR]\n  I --> J[200 OK]\n```\n\n## Detalles técnicos\nEl endpoint\
        \ recibe `multipart/form-data` con campo `files` y límite\nde 4 archivos (`multerS3.array('files',\
        \ 4)`). El código acepta envío\nsin archivos (`req.files || []`) y, en ese\
        \ caso, guarda el eCMR sin\nañadir elementos nuevos.\n\n**URL correcta**:\
        \ `/ecmr/documents/{service_code}` (sin `:` en OpenAPI).\n**Campo multipart**:\
        \ `files` (archivo único en documentación para compatibilidad con Docusaurus).\n\
        **Ejemplo de uso**:\n`curl -X POST \"$HOST/ecmr/documents/{service_code}\"\
        \ -H \"Authorization: Bearer <token>\" -F \"files=@factura.pdf\"`\nLa respuesta\
        \ usa wrapper estándar de `returnOK`.\n\n## Autenticación\nSoporta JWT Bearer\
        \ token y API Key.\n"
      tags:
      - ECMR - Documents
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del eCMR
        required: true
        example: ECMR-2025-001
        schema:
          type: string
      requestBody:
        required: false
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                files:
                  type: string
                  format: binary
                  description: Archivo a subir
      responses:
        '200':
          description: eCMR actualizado con los documentos enviados
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/ECMR'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 507f1f77bcf86cd799439011
                  service_code: ECMR-2025-001
                  status: planned
                  documents:
                  - _id: 507f1f77bcf86cd799439012
                    owner_company: 507f1f77bcf86cd799439013
                    path: ecmr-documents/ECMR-2025-001--factura.pdf
          headers: {}
        '401':
          description: Token/API Key ausente o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: eCMR no encontrado para el usuario y `service_code` indicado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                ecmr_not_found:
                  summary: El eCMR no existe o no es accesible para la compañía
                  value:
                    status: 404
                    message: ECMR_NOT_FOUND
                user_not_found:
                  summary: Usuario autenticado no disponible
                  value:
                    status: 404
                    message: USER_NOT_FOUND
          headers: {}
        '500':
          description: Error interno durante la subida o guardado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/documents/{service_code}/{document_id}:
    get:
      operationId: downloadEcmrDocument
      summary: Descargar documento de un eCMR
      deprecated: false
      description: "## Propósito\nDescarga un documento concreto asociado al eCMR\
        \ y lo entrega como\nstream binario.\n\n## Objetivo\nPermitir a usuarios autorizados\
        \ recuperar el archivo original\nalmacenado en el sistema de ficheros.\n\n\
        ## Casos de uso\n- Descargar una factura adjunta al eCMR.\n- Obtener un justificante\
        \ para revisión interna.\n- Integrar la descarga en cliente web o móvil autenticado.\n\
        \n## Flujo de validación\n```mermaid\nflowchart TD\n  A[Recibir GET /ecmr/documents/:service_code/:document_id]\
        \ --> B{¿Usuario autenticado?}\n  B -->|No| C[401 NO_TOKEN_OR_APIKEY]\n  B\
        \ -->|Sí| D[Resolver usuario, compañía y eCMR]\n  D --> E[Buscar documento\
        \ por document_id]\n  E --> F[Solicitar archivo a storage]\n  F -->|Existe|\
        \ G[200 application/octet-stream]\n  F -->|No existe| H[404 File not found]\n\
        \  E -->|Error interno| I[500 INTERNAL_ERROR]\n```\n\n## Detalles técnicos\n\
        El endpoint localiza el documento por `document_id` dentro del eCMR y\ndelega\
        \ la entrega a storage (`s3Tools.getFile`). La respuesta exitosa\nno usa wrapper\
        \ JSON, sino `application/octet-stream`.\n\n## Autenticación\nSoporta JWT\
        \ Bearer token y API Key.\n"
      tags:
      - ECMR - Documents
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del eCMR
        required: true
        example: ECMR-2025-001
        schema:
          type: string
      - name: document_id
        in: path
        description: ObjectId del documento a descargar
        required: true
        example: 507f1f77bcf86cd799439011
        schema:
          type: string
      responses:
        '200':
          description: Documento descargado correctamente
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
          headers: {}
        '401':
          description: Token/API Key ausente o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Archivo no encontrado en storage
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '500':
          description: Error interno durante la resolución del documento
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    delete:
      operationId: deleteEcmrDocument
      summary: Eliminar documento de un eCMR
      deprecated: false
      description: "## Propósito\nElimina un documento de la colección `documents`\
        \ del eCMR.\n\n## Objetivo\nPermitir depurar documentación adjunta cuando\
        \ se sube un archivo\nincorrecto o ya no debe estar asociado al servicio.\n\
        \n## Casos de uso\n- Retirar un archivo duplicado o erróneo.\n- Mantener actualizado\
        \ el conjunto de anexos del eCMR.\n- Eliminar un documento antes de subir\
        \ su versión corregida.\n\n## Flujo de validación\n```mermaid\nflowchart TD\n\
        \  A[Recibir DELETE /ecmr/documents/:service_code/:document_id] --> B{¿Usuario\
        \ autenticado?}\n  B -->|No| C[401 NO_TOKEN_OR_APIKEY]\n  B -->|Sí| D{¿Usuario/tipo\
        \ válidos?}\n  D -->|No| E[404 USER_NOT_FOUND o USER_TYPE_NOT_FOUND]\n  D\
        \ -->|Sí| F[Resolver eCMR y documento]\n  F --> G[Eliminar referencia en documents]\n\
        \  G --> H[Eliminar fichero en storage si existe path]\n  H --> I[Guardar\
        \ eCMR]\n  I --> J[200 OK]\n```\n\n## Detalles técnicos\nEl flujo elimina\
        \ la referencia del documento en el eCMR y lanza\nborrado del fichero en storage\
        \ cuando existe `path`. La respuesta\nexitosa usa wrapper `returnOK` con el\
        \ eCMR actualizado.\n\nEn la implementación actual, algunos escenarios de\
        \ no-encontrado pueden\nterminar en `500` por resolución interna del documento.\n\
        \n## Autenticación\nSoporta JWT Bearer token y API Key.\n"
      tags:
      - ECMR - Documents
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del eCMR
        required: true
        example: ECMR-2025-001
        schema:
          type: string
      - name: document_id
        in: path
        description: ObjectId del documento a eliminar
        required: true
        example: 507f1f77bcf86cd799439011
        schema:
          type: string
      responses:
        '200':
          description: eCMR actualizado tras eliminar el documento
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/ECMR'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 507f1f77bcf86cd799439011
                  service_code: ECMR-2025-001
                  status: planned
                  documents: []
          headers: {}
        '401':
          description: Token/API Key ausente o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Recurso no encontrado en flujos de resolución previos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '500':
          description: Error interno durante la eliminación
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/public/documents/{service_code}:
    get:
      operationId: getPublicEcmrDocuments
      summary: Obtener documentos públicos de un eCMR
      deprecated: false
      description: '## Propósito

        Devuelve la lista de documentos compartidos públicamente para un eCMR

        específico, validando acceso temporal por token.


        ## Objetivo

        Permitir que terceros con enlace público temporal consulten los anexos

        de un servicio sin iniciar sesión en la plataforma.


        ## Casos de uso

        - Mostrar anexos del eCMR en una vista pública de seguimiento.

        - Compartir documentos operativos con clientes o receptores externos.

        - Obtener identificadores y URL de archivos para descarga controlada.


        ## Detalles técnicos

        El endpoint exige `service_code` (path) y `temp_code` (query).

        El backend valida que `temp_code` coincida con el `temp_token` del

        eCMR. Si la validación es correcta, devuelve `data` como array de

        documentos con `{ _id, name, url }` usando el wrapper de `returnOK`.


        ## Autenticación

        No requiere JWT Bearer token ni API Key.

        '
      tags:
      - ECMR - Documents
      security: []
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del eCMR
        required: true
        example: ECMR-2025-001
        schema:
          type: string
      - name: temp_code
        in: query
        description: Código temporal que debe coincidir con `temp_token` del eCMR
        required: true
        example: 9f5d1b2c7a
        schema:
          type: string
      responses:
        '200':
          description: Listado público de documentos del eCMR
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        _id:
                          type: string
                          example: 507f1f77bcf86cd799439011
                        name:
                          type: string
                          example: factura_transportista.pdf
                        url:
                          type: string
                          format: uri
                          minLength: 1
                          example: https://api.cargoffer.com/files?file=ecmr-documents/ECMR-2025-001--factura_transportista.pdf
                      required:
                      - _id
                      - name
                      - url
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                - _id: 507f1f77bcf86cd799439011
                  name: factura_transportista.pdf
                  url: https://api.cargoffer.com/files?file=ecmr-documents/ECMR-2025-001--factura_transportista.pdf
                - _id: 507f1f77bcf86cd799439012
                  name: albaran_entrega.pdf
                  url: https://api.cargoffer.com/files?file=ecmr-documents/ECMR-2025-001--albaran_entrega.pdf
          headers: {}
        '400':
          description: Falta el parámetro temporal requerido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: TEMP_CODE_REQUIRED
          headers: {}
        '401':
          description: Código temporal inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: INVALID_TEMP_CODE
          headers: {}
        '404':
          description: No existe un eCMR con el `service_code` indicado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
          headers: {}
        '500':
          description: Error interno del servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
  /ecmr/pallets/{service_code}:
    get:
      operationId: getEcmrPallets
      summary: Listar pallets de un eCMR
      deprecated: false
      description: '## Propósito

        Obtener la colección `pallets_data` asociada a un eCMR concreto.


        ## Objetivo

        Permitir consultar el detalle de bultos/unidades logísticas registradas

        para una carta de porte electrónica identificada por `service_code`.


        ## Casos de uso

        - Mostrar el detalle de mercancía en la vista de un eCMR.

        - Sincronizar desde una integración externa el estado actual de pallets.

        - Verificar la composición de carga antes de editar o limpiar pallets.


        ## Detalles técnicos

        Busca el eCMR por `service_code` y devuelve `pallets_data` tal como está

        persistido en MongoDB, envuelto en el formato estándar de `returnOK`

        (`status`, `data`, `0`).


        Si el eCMR no existe, devuelve `ECMR_NOT_FOUND`.


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - ECMR - Pallets
      parameters:
      - name: service_code
        in: path
        description: Código de servicio único del eCMR a consultar.
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      responses:
        '200':
          description: Lista actual de pallets del eCMR solicitada correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas de `returnOK`.
                    example: 0
                  status:
                    type: integer
                    description: Código HTTP de la respuesta.
                    example: 200
                  data:
                    type: array
                    description: Colección `pallets_data` del eCMR.
                    items:
                      type: object
                      properties:
                        _id:
                          type: string
                          description: Identificador interno del pallet (ObjectId).
                          example: 691a8dd8d31e3e26fd2ab7bc
                        marks_and_numbers:
                          type: string
                          description: Marcas y números identificativos del pallet.
                          example: PAL-A01
                        pieces:
                          type: integer
                          minimum: 0
                          maximum: 1000
                          description: Número de piezas/bultos del pallet.
                          example: 20
                        weight:
                          type: number
                          minimum: 0
                          maximum: 24000
                          description: Peso en kg.
                          example: 1200
                        volume_m3:
                          type: number
                          minimum: 0
                          maximum: 1000
                          description: Volumen en metros cúbicos.
                          example: 3.2
                        cargoNature:
                          type: string
                          description: Naturaleza de la mercancía.
                          example: Alimentación refrigerada
                        packagingType:
                          type: string
                          enum:
                          - pallets
                          - full
                          - package
                          - trailer
                          description: Tipo de embalaje/carga.
                          example: pallets
                        hs_code:
                          type: string
                          description: Código HS opcional asociado al pallet.
                          example: '020714'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                - _id: 691a8dd8d31e3e26fd2ab7bc
                  marks_and_numbers: PAL-A01
                  pieces: 20
                  weight: 1200
                  volume_m3: 3.2
                  cargoNature: Alimentación refrigerada
                  packagingType: pallets
                  hs_code: '020714'
                - _id: 691a8dd8d31e3e26fd2ab7bd
                  marks_and_numbers: PAL-A02
                  pieces: 10
                  weight: 700
                  volume_m3: 1.4
                  cargoNature: Congelado
                  packagingType: package
                  hs_code: '020714'
          headers: {}
        '400':
          description: Parámetro de ruta inválido o ausente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: SERVICE_CODE_REQUIRED
          headers: {}
        '401':
          description: Token JWT ausente, expirado, inválido o cuenta bloqueada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se envió ni token ni API key
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token inválido o expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: eCMR no encontrado o contexto de autenticación no resoluble.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                ecmr_not_found:
                  summary: No existe un eCMR con ese service_code
                  value:
                    status: 404
                    message: ECMR_NOT_FOUND
                user_not_found:
                  summary: Usuario no encontrado durante validación de sesión
                  value:
                    status: 404
                    message: USER_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado al recuperar los pallets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      operationId: replaceEcmrPallets
      summary: Reemplazar pallets de un eCMR
      deprecated: false
      description: "## Propósito\nSustituir el contenido completo de `pallets_data`\
        \ por una nueva lista de\npallets enviada en el body.\n\n## Objetivo\nPermitir\
        \ una actualización masiva y atómica del detalle de pallets del\neCMR, sin\
        \ editar uno a uno.\n\n## Casos de uso\n- Volver a cargar el detalle completo\
        \ de pallets desde un ERP.\n- Corregir toda la composición de carga tras una\
        \ modificación logística.\n- Normalizar el listado cuando hubo cambios de\
        \ bultos/peso/volumen.\n\n## Detalles técnicos\nEste endpoint **reemplaza**\
        \ la colección actual (`pallets_data`) por la\nnueva lista válida recibida.\
        \ No hace append incremental.\n\nValidaciones en runtime:\n- El body debe\
        \ ser un array no vacío (`PALLET_DATA_REQUIRED` si falla).\n- Cada pallet\
        \ válido admite rangos:\n  - `pieces`: 0..1000\n  - `weight`: 0..24000\n \
        \ - `volume_m3`: 0..1000\n- `packagingType` debe ajustarse al enum del modelo:\n\
        \  `pallets`, `full`, `package`, `trailer`.\n\n## Permisos\nEn operaciones\
        \ de escritura, el backend valida ownership del eCMR:\n- usuario company solo\
        \ puede editar eCMR con owner `company`\n- usuario trucker solo puede editar\
        \ eCMR con owner `trucker_cia`\nEn caso contrario devuelve `ACCESS_DENIED`.\n\
        \n## Autenticación\nSoporta JWT Bearer token y API Key.\n"
      tags:
      - ECMR - Pallets
      parameters:
      - name: service_code
        in: path
        description: Código de servicio único del eCMR a actualizar.
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              minItems: 1
              description: Lista completa de pallets que reemplazará el estado actual.
              items:
                type: object
                properties:
                  marks_and_numbers:
                    type: string
                    description: Marcas y números identificativos.
                    example: PAL-A01
                  pieces:
                    type: integer
                    minimum: 0
                    maximum: 1000
                    description: Número de piezas/bultos.
                    example: 30
                  weight:
                    type: number
                    minimum: 0
                    maximum: 24000
                    description: Peso en kg.
                    example: 1850
                  volume_m3:
                    type: number
                    minimum: 0
                    maximum: 1000
                    description: Volumen en metros cúbicos.
                    example: 4.6
                  cargoNature:
                    type: string
                    description: Naturaleza de la mercancía.
                    example: Congelado
                  packagingType:
                    type: string
                    enum:
                    - pallets
                    - full
                    - package
                    - trailer
                    description: Tipo de embalaje/carga.
                    example: pallets
                  hs_code:
                    type: string
                    description: Código HS opcional.
                    example: '020714'
            examples:
              replace_two_pallets:
                summary: Reemplazo completo con dos pallets
                value:
                - marks_and_numbers: PAL-A01
                  pieces: 30
                  weight: 1850
                  volume_m3: 4.6
                  cargoNature: Congelado
                  packagingType: pallets
                  hs_code: '020714'
                - marks_and_numbers: PAL-A02
                  pieces: 12
                  weight: 600
                  volume_m3: 1.8
                  cargoNature: Seco
                  packagingType: package
                  hs_code: 040299
      responses:
        '200':
          description: Colección de pallets reemplazada correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    description: Estado final de `pallets_data` tras el reemplazo.
                    items:
                      type: object
                      properties:
                        _id:
                          type: string
                          example: 691a8dd8d31e3e26fd2ab7bc
                        marks_and_numbers:
                          type: string
                          example: PAL-A01
                        pieces:
                          type: integer
                          minimum: 0
                          maximum: 1000
                          example: 30
                        weight:
                          type: number
                          minimum: 0
                          maximum: 24000
                          example: 1850
                        volume_m3:
                          type: number
                          minimum: 0
                          maximum: 1000
                          example: 4.6
                        cargoNature:
                          type: string
                          example: Congelado
                        packagingType:
                          type: string
                          enum:
                          - pallets
                          - full
                          - package
                          - trailer
                          example: pallets
                        hs_code:
                          type: string
                          example: '020714'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                - _id: 691a8dd8d31e3e26fd2ab7bc
                  marks_and_numbers: PAL-A01
                  pieces: 30
                  weight: 1850
                  volume_m3: 4.6
                  cargoNature: Congelado
                  packagingType: pallets
                  hs_code: '020714'
          headers: {}
        '400':
          description: Body inválido o faltan datos obligatorios del endpoint.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                service_code_required:
                  summary: Falta service_code
                  value:
                    status: 400
                    message: SERVICE_CODE_REQUIRED
                pallet_data_required:
                  summary: Body vacío o no es array
                  value:
                    status: 400
                    message: PALLET_DATA_REQUIRED
          headers: {}
        '401':
          description: Token JWT ausente, expirado, inválido o cuenta bloqueada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '403':
          description: El usuario autenticado no tiene permisos sobre ese eCMR.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: ACCESS_DENIED
          headers: {}
        '404':
          description: No existe un eCMR con el `service_code` indicado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: ECMR_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante el reemplazo de pallets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                update_failed:
                  summary: Falló la actualización del modelo
                  value:
                    status: 500
                    message: UPDATE_FAILED
                internal_error:
                  summary: Error interno no controlado
                  value:
                    status: 500
                    message: INTERNAL_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    delete:
      operationId: clearEcmrPallets
      summary: Vaciar todos los pallets de un eCMR
      deprecated: false
      description: '## Propósito

        Eliminar todos los elementos de `pallets_data` para un eCMR.


        ## Objetivo

        Restablecer la sección de pallets a estado vacío cuando sea necesario

        rehacer o limpiar por completo la composición de carga.


        ## Casos de uso

        - Reset previo a una carga completa de nuevos pallets.

        - Limpieza administrativa tras detectar datos obsoletos.

        - Correcciones operativas en fase de planificación del envío.


        ## Detalles técnicos

        El backend establece `pallets_data = []` y sincroniza `pallets_num = 0`.

        Devuelve el payload en formato estándar de `returnOK`.


        ## Permisos

        Se aplican reglas de ownership (`ACCESS_DENIED`) idénticas a POST/PUT/DELETE.


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - ECMR - Pallets
      parameters:
      - name: service_code
        in: path
        description: Código de servicio único del eCMR a vaciar.
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      responses:
        '200':
          description: Se eliminaron todos los pallets del eCMR.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      pallets:
                        type: array
                        description: Lista de pallets final (vacía tras la operación).
                        items:
                          type: object
                    required:
                    - pallets
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  pallets: []
          headers: {}
        '400':
          description: Parámetro requerido ausente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: SERVICE_CODE_REQUIRED
          headers: {}
        '401':
          description: Token JWT ausente, expirado, inválido o cuenta bloqueada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '403':
          description: El usuario autenticado no tiene permisos sobre ese eCMR.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: ACCESS_DENIED
          headers: {}
        '404':
          description: eCMR no encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: ECMR_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante el vaciado de pallets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                clear_failed:
                  summary: Falló la limpieza interna
                  value:
                    status: 500
                    message: CLEAR_FAILED
                internal_error:
                  summary: Error interno no controlado
                  value:
                    status: 500
                    message: INTERNAL_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/pallets/{service_code}/{pallet_id}:
    put:
      operationId: updateEcmrPallet
      summary: Actualizar un pallet específico de un eCMR
      deprecated: false
      description: '## Propósito

        Actualizar de forma parcial los campos de un pallet ya existente dentro

        de `pallets_data`.


        ## Objetivo

        Permitir correcciones puntuales (peso, piezas, volumen o identificación)

        sin tener que reenviar toda la lista de pallets.


        ## Casos de uso

        - Corregir el peso de un pallet tras una nueva pesada.

        - Ajustar `pieces` por recuento físico.

        - Modificar `packagingType` o descripción de naturaleza de carga.


        ## Detalles técnicos

        - Requiere `service_code` y `pallet_id`.

        - El body debe contener al menos un campo (`PALLET_DATA_REQUIRED` si va vacío).

        - Campos numéricos fuera de rango no generan error directo; se ignoran.

        - Si el `pallet_id` no existe en `pallets_data`, devuelve `PALLET_NOT_FOUND`.


        ## Permisos

        Se aplican reglas de ownership (`ACCESS_DENIED`) idénticas a POST/DELETE.


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - ECMR - Pallets
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del eCMR.
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      - name: pallet_id
        in: path
        description: ObjectId del pallet a modificar.
        required: true
        example: 691a8dd8d31e3e26fd2ab7bc
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              minProperties: 1
              description: Campos editables del pallet (actualización parcial).
              properties:
                marks_and_numbers:
                  type: string
                  description: Marcas y números identificativos.
                  example: PAL-A01-REV2
                pieces:
                  type: integer
                  minimum: 0
                  maximum: 1000
                  description: Número de piezas/bultos.
                  example: 28
                weight:
                  type: number
                  minimum: 0
                  maximum: 24000
                  description: Peso en kg.
                  example: 1760
                volume_m3:
                  type: number
                  minimum: 0
                  maximum: 1000
                  description: Volumen en metros cúbicos.
                  example: 4.2
                cargoNature:
                  type: string
                  description: Naturaleza de la mercancía.
                  example: Refrigerado
                packagingType:
                  type: string
                  enum:
                  - pallets
                  - full
                  - package
                  - trailer
                  description: Tipo de embalaje/carga.
                  example: pallets
            examples:
              update_weight_and_pieces:
                summary: Actualizar peso y piezas
                value:
                  weight: 1760
                  pieces: 28
              update_packaging:
                summary: Actualizar tipo de embalaje
                value:
                  packagingType: package
                  cargoNature: Refrigerado
      responses:
        '200':
          description: Pallet actualizado correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      pallet:
                        type: object
                        properties:
                          _id:
                            type: string
                            example: 691a8dd8d31e3e26fd2ab7bc
                          marks_and_numbers:
                            type: string
                            example: PAL-A01-REV2
                          pieces:
                            type: integer
                            minimum: 0
                            maximum: 1000
                            example: 28
                          weight:
                            type: number
                            minimum: 0
                            maximum: 24000
                            example: 1760
                          volume_m3:
                            type: number
                            minimum: 0
                            maximum: 1000
                            example: 4.2
                          cargoNature:
                            type: string
                            example: Refrigerado
                          packagingType:
                            type: string
                            enum:
                            - pallets
                            - full
                            - package
                            - trailer
                            example: package
                          hs_code:
                            type: string
                            example: '020714'
                    required:
                    - pallet
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  pallet:
                    _id: 691a8dd8d31e3e26fd2ab7bc
                    marks_and_numbers: PAL-A01-REV2
                    pieces: 28
                    weight: 1760
                    volume_m3: 4.2
                    cargoNature: Refrigerado
                    packagingType: package
                    hs_code: '020714'
          headers: {}
        '400':
          description: Parámetros o body obligatorios ausentes.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                service_code_required:
                  summary: Falta service_code
                  value:
                    status: 400
                    message: SERVICE_CODE_REQUIRED
                pallet_id_required:
                  summary: Falta pallet_id
                  value:
                    status: 400
                    message: PALLET_ID_REQUIRED
                pallet_data_required:
                  summary: Body vacío
                  value:
                    status: 400
                    message: PALLET_DATA_REQUIRED
          headers: {}
        '401':
          description: Token JWT ausente, expirado, inválido o cuenta bloqueada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '403':
          description: El usuario autenticado no tiene permisos sobre ese eCMR.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: ACCESS_DENIED
          headers: {}
        '404':
          description: eCMR o pallet no encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                ecmr_not_found:
                  summary: No existe un eCMR con ese service_code
                  value:
                    status: 404
                    message: ECMR_NOT_FOUND
                pallet_not_found:
                  summary: El pallet indicado no existe en la lista
                  value:
                    status: 404
                    message: PALLET_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la actualización del pallet.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    delete:
      operationId: removeEcmrPallet
      summary: Eliminar un pallet específico de un eCMR
      deprecated: false
      description: '## Propósito

        Quitar un pallet concreto de `pallets_data` usando su `pallet_id`.


        ## Objetivo

        Permitir eliminar entradas puntuales de pallets sin modificar el resto

        de la colección del eCMR.


        ## Casos de uso

        - Eliminar un pallet cargado por error.

        - Retirar un bulto cancelado en la operativa.

        - Limpiar duplicados detectados por validaciones externas.


        ## Detalles técnicos

        La implementación actual intenta eliminar por ObjectId.

        Si el `pallet_id` no es válido o no existe, la operación no falla por

        ese motivo y devuelve `200` con la lista resultante (sin cambios).


        ## Permisos

        Se aplican reglas de ownership (`ACCESS_DENIED`) idénticas a POST/PUT.


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - ECMR - Pallets
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del eCMR.
        required: true
        example: RIBMADqNhRP
        schema:
          type: string
      - name: pallet_id
        in: path
        description: Identificador del pallet a eliminar (ObjectId esperado).
        required: true
        example: 691a8dd8d31e3e26fd2ab7bc
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
      responses:
        '200':
          description: Operación ejecutada correctamente y lista de pallets resultante.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      pallets:
                        type: array
                        items:
                          type: object
                          properties:
                            _id:
                              type: string
                              example: 691a8dd8d31e3e26fd2ab7bd
                            marks_and_numbers:
                              type: string
                              example: PAL-A02
                            pieces:
                              type: integer
                              minimum: 0
                              maximum: 1000
                              example: 12
                            weight:
                              type: number
                              minimum: 0
                              maximum: 24000
                              example: 600
                            volume_m3:
                              type: number
                              minimum: 0
                              maximum: 1000
                              example: 1.8
                            cargoNature:
                              type: string
                              example: Seco
                            packagingType:
                              type: string
                              enum:
                              - pallets
                              - full
                              - package
                              - trailer
                              example: package
                            hs_code:
                              type: string
                              example: 040299
                    required:
                    - pallets
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  pallets:
                  - _id: 691a8dd8d31e3e26fd2ab7bd
                    marks_and_numbers: PAL-A02
                    pieces: 12
                    weight: 600
                    volume_m3: 1.8
                    cargoNature: Seco
                    packagingType: package
                    hs_code: 040299
          headers: {}
        '400':
          description: Parámetros obligatorios ausentes.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                service_code_required:
                  summary: Falta service_code
                  value:
                    status: 400
                    message: SERVICE_CODE_REQUIRED
                pallet_identifier_required:
                  summary: Falta pallet_id
                  value:
                    status: 400
                    message: PALLET_IDENTIFIER_REQUIRED
          headers: {}
        '401':
          description: Token JWT ausente, expirado, inválido o cuenta bloqueada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '403':
          description: El usuario autenticado no tiene permisos sobre ese eCMR.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: ACCESS_DENIED
          headers: {}
        '404':
          description: eCMR no encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: ECMR_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la eliminación del pallet.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/qr/check/{service_code}:
    get:
      summary: Check QR Code Status
      deprecated: false
      description: '## Propósito

        Verificar el estado del flujo QR y su disponibilidad para el eCMR indicado.


        ## Objetivo

        Check QR Code Status.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `GET /ecmr/qr/check/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Verifica el estado de uso de un código QR para
        firmas. **Restricciones:** - No funciona para ECMRs ya entregados (status:
        delivered). - Verifica si una fase específica ya ha sido firmada. **Parámetros
        de consulta:** -...


        ## Autenticación

        No requiere autenticación (`security: []`).

        '
      tags:
      - ECMR - Qr
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: XXXXXXQgmdd
        schema:
          type: string
      - name: phase
        in: query
        description: Fase a verificar (planned, accepted, collected)
        required: false
        example: planned
        schema:
          type: string
          enum:
          - planned
          - accepted
          - collected
      responses:
        '200':
          description: Estado del QR consultado correctamente
          content:
            application/json:
              schema:
                type: object
                properties:
                  used:
                    type: boolean
                    description: Indica si la fase consultada ya se utilizó.
          headers: {}
        '403':
          description: Entrega completada o fase no permitida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '404':
          description: QR no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
      security: []
  /ecmr/qr/delivery/{service_code}:
    put:
      summary: Delivery signature via QR
      deprecated: false
      description: '## Propósito

        Registrar y validar la entrega del envío dentro del flujo QR del eCMR.


        ## Objetivo

        Delivery signature via QR.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `PUT /ecmr/qr/delivery/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Firma de entrega mediante flujo QR. Esencial
        para la confirmación de entrega "sin contacto" o verificada. El destinatario
        escanea el QR o el conductor escanea el QR del destinatario para validar la
        entrega.


        ## Autenticación

        No requiere autenticación (`security: []`).

        '
      tags:
      - ECMR - Qr
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: ''
        schema:
          type: string
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                image:
                  type: string
                  format: binary
                  description: Imagen de la firma
                  example: ''
                moreImages:
                  type: array
                  items:
                    type: string
                    format: binary
                  description: Imágenes adicionales (máximo 4)
                  example: ''
        required: true
      responses:
        '200':
          description: Operación realizada correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '2':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '3':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '4':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security: []
  /ecmr/qr/details/{token}:
    get:
      summary: Get ECMR details from QR token
      deprecated: false
      description: '## Propósito

        Consultar el detalle de un eCMR a partir de un token de acceso QR.


        ## Objetivo

        Get ECMR details from QR token.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `GET /ecmr/qr/details/{token}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Obtiene el detalle del ECMR a partir de su token
        QR.


        ## Autenticación

        No requiere autenticación (`security: []`).

        '
      tags:
      - ECMR - Qr
      parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Detalle del ECMR obtenido correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ECMR'
          headers: {}
        '404':
          description: ECMR no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '500':
          description: Error interno del servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
      security: []
  /ecmr/qr/download/{token}:
    get:
      summary: Download ECMR PDF from QR token
      deprecated: false
      description: '## Propósito

        Descargar el documento eCMR utilizando un token válido del flujo QR.


        ## Objetivo

        Download ECMR PDF from QR token.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `GET /ecmr/qr/download/{token}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Descarga el PDF del ECMR usando un token QR válido.


        ## Autenticación

        No requiere autenticación (`security: []`).

        '
      tags:
      - ECMR - Qr
      parameters:
      - name: token
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: PDF generado correctamente
          content:
            application/pdf:
              schema:
                type: string
                format: binary
          headers: {}
        '404':
          description: ECMR no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '500':
          description: Error interno del servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '503':
          description: Error temporal al generar o recuperar el PDF
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
      security: []
  /ecmr/qr/pickup/{service_code}:
    put:
      summary: Pickup signature via QR
      deprecated: false
      description: '## Propósito

        Registrar y validar la recogida del envío dentro del flujo QR del eCMR.


        ## Objetivo

        Pickup signature via QR.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `PUT /ecmr/qr/pickup/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Firma de recogida mediante flujo QR. Permite
        al personal de almacén/carga firmar la recogida en el dispositivo del conductor
        o viceversa, validando la operación mediante el escaneo del código QR único
        del servicio.


        ## Autenticación

        No requiere autenticación (`security: []`).

        '
      tags:
      - ECMR - Qr
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: ''
        schema:
          type: string
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                image:
                  type: string
                  format: binary
                  description: Imagen de la firma
                  example: ''
                moreImages:
                  type: array
                  items:
                    type: string
                    format: binary
                  description: Imágenes adicionales (máximo 4)
                  example: ''
        required: true
      responses:
        '200':
          description: Operación realizada correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '2':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '3':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '4':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security: []
  /ecmr/qr/sender/{service_code}:
    put:
      summary: Signature of issuer via QR
      deprecated: false
      description: '## Propósito

        Registrar y validar la acción del remitente dentro del flujo QR del eCMR.


        ## Objetivo

        Signature of issuer via QR.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `PUT /ecmr/qr/sender/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Firma del emisor mediante flujo QR (sin login
        de app). Permite firmar escaneando el QR del ECMR, usado cuando el cargador
        no tiene la app instalada pero debe firmar en el dispositivo del conductor.


        ## Autenticación

        No requiere autenticación (`security: []`).

        '
      tags:
      - ECMR - Qr
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del ECMR
        required: true
        example: ''
        schema:
          type: string
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                image:
                  type: string
                  format: binary
                  description: Imagen de la firma
                  example: ''
              required:
              - image
            examples: {}
        required: true
      responses:
        '200':
          description: Operación realizada correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
          headers: {}
        '403':
          description: Error de autorización, validación o permisos insuficientes
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensaje descriptivo del error
              examples:
                '1':
                  summary: unauthorized
                  value:
                    error: No tienes permisos para acceder a este recurso
                '2':
                  summary: duplicate_name
                  value:
                    error: El nombre de la dirección ya existe
                '3':
                  summary: invalid_data
                  value:
                    error: Datos inválidos proporcionados
                '4':
                  summary: not_owner
                  value:
                    error: No eres el propietario de esta dirección
          headers: {}
      security: []
  /ecmr/qr/{service_code}:
    get:
      summary: Generate QR for ECMR
      deprecated: false
      description: '## Propósito

        Obtener los datos del flujo QR de un eCMR identificado por `service_code`.


        ## Objetivo

        Generate QR for ECMR.


        ## Casos de uso

        - Consumir la operación desde la app web o integraciones backend.

        - Ejecutar el flujo operativo correspondiente del eCMR.

        - Obtener una respuesta consistente según el estado del recurso.


        ## Detalles técnicos

        - Endpoint real en código: `GET /ecmr/qr/{service_code}`.

        - Mantiene parámetros, requestBody y responses definidos en este contrato.

        - Contexto funcional actual: Genera un código QR para un ECMR de la compañía
        o transportista autenticado.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - ECMR - Qr
      parameters:
      - name: service_code
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: QR generado correctamente
          content:
            application/json:
              schema:
                type: string
                description: Data URL base64 del QR.
          headers: {}
        '401':
          description: No autorizado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '404':
          description: Recurso no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '500':
          description: Error interno del servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr/sign:
    get:
      operationId: listPendingEcmrSignatures
      summary: Listar eCMR pendientes de firma
      deprecated: false
      description: '## Propósito

        Recupera los eCMR pendientes de firma para el usuario autenticado en el módulo
        de firma.


        ## Objetivo

        Exponer una vista de pendientes de firma en una sola llamada para alimentar

        la UI de firma rápida.


        ## Casos de uso

        - Abrir la bandeja de documentos pendientes desde el flujo de firma.

        - Aplicar paginación con `page` y `limit` para listados largos.


        ## Detalles técnicos

        En el estado actual del código (`sign.controller.getPending`) la

        operación está deshabilitada y devuelve de forma explícita

        `500 NOT_IMPLEMENTED_OR_BROKEN_IN_SOURCE`.


        ## Autenticación

        Requiere `queryAuth` (query params admitidos: `token`, `access_token`,

        `accessToken`, `x-access-token`, `x-access_token`) o `apiKeyAuth`.

        '
      tags:
      - ECMR - Sign
      parameters:
      - name: page
        in: query
        description: Número de página (1-indexed).
        required: false
        example: 1
        schema:
          type: integer
          minimum: 1
          maximum: 100000
          default: 1
      - name: limit
        in: query
        description: Tamaño de página. Si es menor o igual a 0, el backend usa `ITEMS_PAGE`.
        required: false
        example: 20
        schema:
          type: integer
          minimum: 1
          maximum: 100
      responses:
        '401':
          description: Token en query y API Key ausentes o inválidos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '500':
          description: Endpoint temporalmente no implementado o roto en origen
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: NOT_IMPLEMENTED_OR_BROKEN_IN_SOURCE
          headers: {}
      security:
      - queryAuth: []
      - apiKeyAuth: []
    post:
      operationId: signEcmrByCompanyProfile
      summary: Firmar eCMR con firma de la compañía autenticada
      deprecated: false
      description: '## Propósito

        Aplica al eCMR la firma corporativa almacenada en la compañía del usuario
        autenticado.


        ## Objetivo

        Permitir firma operativa sin subir imagen de firma en cada solicitud, reutilizando

        el sello/firma ya registrada en la compañía.


        ## Casos de uso

        - Firma administrativa de un eCMR por parte de una empresa con firma ya configurada.

        - Automatizar la firma corporativa de documentos en el flujo interno.


        ## Detalles técnicos

        El backend exige `service_code` en JSON. Si la compañía no tiene firma cargada,

        devuelve `403 SIGN_NOT_FOUND`. La respuesta de éxito usa wrapper `returnOK`.


        ## Autenticación

        Requiere `queryAuth` (query params admitidos: `token`, `access_token`,

        `accessToken`, `x-access-token`, `x-access_token`) o `apiKeyAuth`.

        '
      tags:
      - ECMR - Sign
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                service_code:
                  type: string
                  description: Código del servicio/eCMR a firmar.
                  minLength: 1
                  maxLength: 64
                  example: MADBCN8F3A2
              required:
              - service_code
            example:
              service_code: MADBCN8F3A2
      responses:
        '200':
          description: eCMR firmado correctamente con wrapper `returnOK`
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/ECMR'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 66b4af3d0d7d6b2ec15a1123
                  service_code: MADBCN8F3A2
                  status: accepted
                  signed_by_company: true
          headers: {}
        '401':
          description: Token en query y API Key ausentes o inválidos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '403':
          description: La compañía autenticada no tiene firma configurada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: SIGN_NOT_FOUND
          headers: {}
        '404':
          description: Error de datos de usuario o eCMR no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  summary: Usuario no encontrado
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                service_code_not_found:
                  summary: Falta service_code en body
                  value:
                    status: 404
                    message: SERVICE_CODE_NOT_FOUND
                ecmr_not_found:
                  summary: No existe eCMR para ese código/compañía
                  value:
                    status: 404
                    message: ECMR_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la firma
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: CANT_SIGN
          headers: {}
      security:
      - queryAuth: []
      - apiKeyAuth: []
  /ecmr/sign/delivery/{service_code}:
    put:
      operationId: signEcmrDelivery
      summary: Firmar entrega del eCMR
      deprecated: false
      description: '## Propósito

        Registra la firma y evidencias de entrega para un eCMR.


        ## Objetivo

        Marcar el cierre operativo de entrega, guardar POD y actualizar estado a `delivered`.


        ## Casos de uso

        - Firma de entrega por transportista con imágenes de prueba.

        - Firma operativa de empresa con comentario ETD y geoposición.


        ## Detalles técnicos

        El backend acepta `multipart/form-data` con `image` o `sign` (al menos uno).

        Puede incluir `moreImages` (máximo 4), `comment`, `etd_comment`, `europallets`
        y

        `geolocation`. En éxito responde con wrapper `returnOK`.


        ## Autenticación

        Requiere `queryAuth` (token en query, por ejemplo `?token=`) o `apiKeyAuth`.

        '
      tags:
      - ECMR - Sign
      parameters:
      - name: service_code
        in: path
        description: Código del eCMR en fase de entrega.
        required: true
        example: MADBCN8F3A2
        schema:
          type: string
          minLength: 1
          maxLength: 64
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                image:
                  type: string
                  format: binary
                  description: Imagen principal de firma.
                sign:
                  type: string
                  description: Firma en texto/base64.
                  minLength: 1
                  maxLength: 5000000
                  example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
                moreImages:
                  type: array
                  description: Fotos adicionales de entrega (POD).
                  minItems: 0
                  maxItems: 4
                  items:
                    type: string
                    format: binary
                comment:
                  type: string
                  description: Comentario de entrega (alias alternativo de `etd_comment`).
                  minLength: 0
                  maxLength: 2000
                  example: Entrega completada sin incidencias.
                etd_comment:
                  type: string
                  description: Comentario ETD de entrega.
                  minLength: 0
                  maxLength: 2000
                  example: Cliente receptor conforme.
                europallets:
                  type: integer
                  description: Número de europalets informado en entrega.
                  minimum: 0
                  maximum: 10000
                  example: 12
                geolocation:
                  type: string
                  description: JSON serializado con coordenadas (se parsea con `JSON.parse`).
                  minLength: 2
                  maxLength: 1024
                  example: '{"coords":{"latitude":41.3851,"longitude":2.1734}}'
              anyOf:
              - required:
                - image
              - required:
                - sign
      responses:
        '200':
          description: Firma de entrega registrada con wrapper `returnOK`
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/ECMR'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 66b4af3d0d7d6b2ec15a1123
                  service_code: MADBCN8F3A2
                  status: delivered
                  sign_delivery_date: '2026-03-03T13:25:00.000Z'
          headers: {}
        '401':
          description: Token en query y API Key ausentes o inválidos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Usuario o eCMR no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  summary: Usuario no encontrado
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                service_code_not_found:
                  summary: service_code ausente
                  value:
                    status: 404
                    message: SERVICE_CODE_NOT_FOUND
                ecmr_not_found:
                  summary: eCMR no encontrado
                  value:
                    status: 404
                    message: ECMR_NOT_FOUND
          headers: {}
        '406':
          description: Firma ausente o no utilizable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 406
                message: SIGNATURE_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la firma de entrega
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: CANT_SIGN
          headers: {}
      security:
      - queryAuth: []
      - apiKeyAuth: []
  /ecmr/sign/pickup/{service_code}:
    put:
      operationId: signEcmrPickup
      summary: Firmar recogida del eCMR
      deprecated: false
      description: '## Propósito

        Registra la firma y evidencias de recogida para un eCMR.


        ## Objetivo

        Marcar el hito de recogida, persistir evidencias y actualizar el estado a
        `collected`.


        ## Casos de uso

        - El transportista firma la recogida desde móvil adjuntando fotos del punto
        de carga.

        - Una compañía firma en nombre operativo y añade comentario/geo.


        ## Detalles técnicos

        El backend acepta `multipart/form-data` con `image` o `sign` (al menos uno).

        Puede incluir `moreImages` (máximo 4), `comment`, `etl_comment`, `europallets`
        y

        `geolocation`. En éxito responde con wrapper `returnOK`.


        ## Autenticación

        Requiere `queryAuth` (token en query, por ejemplo `?token=`) o `apiKeyAuth`.

        '
      tags:
      - ECMR - Sign
      parameters:
      - name: service_code
        in: path
        description: Código del eCMR en fase de recogida.
        required: true
        example: MADBCN8F3A2
        schema:
          type: string
          minLength: 1
          maxLength: 64
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                image:
                  type: string
                  format: binary
                  description: Imagen principal de firma.
                sign:
                  type: string
                  description: Firma en texto/base64.
                  minLength: 1
                  maxLength: 5000000
                  example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
                moreImages:
                  type: array
                  description: Fotos adicionales de recogida.
                  minItems: 0
                  maxItems: 4
                  items:
                    type: string
                    format: binary
                comment:
                  type: string
                  description: Comentario de recogida (alias alternativo de `etl_comment`).
                  minLength: 0
                  maxLength: 2000
                  example: Mercancía recibida en buen estado.
                etl_comment:
                  type: string
                  description: Comentario ETL de recogida.
                  minLength: 0
                  maxLength: 2000
                  example: Carga con flejes y precinto.
                europallets:
                  type: integer
                  description: Número de europalets informado en recogida.
                  minimum: 0
                  maximum: 10000
                  example: 12
                geolocation:
                  type: string
                  description: JSON serializado con coordenadas (se parsea con `JSON.parse`).
                  minLength: 2
                  maxLength: 1024
                  example: '{"coords":{"latitude":42.5668,"longitude":-8.9952}}'
              anyOf:
              - required:
                - image
              - required:
                - sign
      responses:
        '200':
          description: Firma de recogida registrada con wrapper `returnOK`
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/ECMR'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 66b4af3d0d7d6b2ec15a1123
                  service_code: MADBCN8F3A2
                  status: collected
                  sign_pickup_date: '2026-03-03T10:45:00.000Z'
          headers: {}
        '401':
          description: Token en query y API Key ausentes o inválidos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Usuario o eCMR no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  summary: Usuario no encontrado
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                service_code_not_found:
                  summary: service_code ausente
                  value:
                    status: 404
                    message: SERVICE_CODE_NOT_FOUND
                ecmr_not_found:
                  summary: eCMR no encontrado
                  value:
                    status: 404
                    message: ECMR_NOT_FOUND
          headers: {}
        '406':
          description: Firma ausente o no utilizable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 406
                message: SIGNATURE_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la firma de recogida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: CANT_SIGN
          headers: {}
      security:
      - queryAuth: []
      - apiKeyAuth: []
  /ecmr/sign/qr/{service_code}/check:
    get:
      operationId: checkSignQrUsage
      summary: Verificar uso del QR de firma
      deprecated: false
      description: "## Propósito\nComprueba si un QR de firma asociado a un eCMR ya\
        \ fue utilizado para una fase.\n\n## Objetivo\nEvitar reuso indebido de enlaces\
        \ QR y permitir a la UI decidir si debe bloquear\nuna firma duplicada.\n\n\
        ## Casos de uso\n- Validar en cliente si la fase `planned`, `accepted` o `collected`\
        \ ya tiene firma.\n- Mostrar mensaje preventivo antes de abrir el formulario\
        \ de firma.\n\n## Flujo de validación\n```mermaid\nflowchart TD\n  A[Recibir\
        \ GET /ecmr/sign/qr/{service_code}/check] --> B{Autenticado por queryAuth\
        \ o apiKeyAuth}\n  B -->|No| C[401 NO_TOKEN_OR_APIKEY]\n  B -->|Sí| D{Existe\
        \ eCMR por service_code}\n  D -->|No| E[404 QR_NOT_FOUND]\n  D -->|Sí| F{status\
        \ === delivered}\n  F -->|Sí| G[403 DELIVERY_COMPLETED]\n  F -->|No| H[Calcular\
        \ used según phase]\n  H --> I[200 returnOK { used: boolean }]\n```\n\n##\
        \ Detalles técnicos\nLa lógica del servicio usa el query param `phase` para\
        \ decidir qué campo inspeccionar:\n`planned -> sign_image_sender`, `accepted\
        \ -> sign_pickup`, `collected -> sign_delivery`.\nSi `phase` no llega, `used`\
        \ se evalúa en `false`.\n\n## Autenticación\nRequiere `queryAuth` (token en\
        \ query, por ejemplo `?token=`) o `apiKeyAuth`.\n"
      tags:
      - ECMR - Sign
      parameters:
      - name: service_code
        in: path
        description: Código de servicio del eCMR.
        required: true
        example: MADBCN8F3A2
        schema:
          type: string
          minLength: 1
          maxLength: 64
      - name: phase
        in: query
        description: Fase a comprobar para uso de QR.
        required: false
        example: accepted
        schema:
          type: string
          enum:
          - planned
          - accepted
          - collected
      responses:
        '200':
          description: Resultado de comprobación con wrapper `returnOK`
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      used:
                        type: boolean
                        description: Indica si la fase consultada ya tiene firma registrada.
                        example: true
                    required:
                    - used
                required:
                - status
                - data
                - '0'
              examples:
                used_true:
                  summary: QR ya utilizado para la fase consultada
                  value:
                    '0': 0
                    status: 200
                    data:
                      used: true
                used_false:
                  summary: QR aún no utilizado para la fase consultada
                  value:
                    '0': 0
                    status: 200
                    data:
                      used: false
          headers: {}
        '401':
          description: Token en query y API Key ausentes o inválidos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '403':
          description: El eCMR ya está entregado y no admite validación de QR de firma
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: DELIVERY_COMPLETED
          headers: {}
        '404':
          description: QR/eCMR no encontrado para el `service_code` enviado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: QR_NOT_FOUND
          headers: {}
      security:
      - queryAuth: []
      - apiKeyAuth: []
  /ecmr/sign/sender/{service_code}:
    put:
      operationId: signEcmrSender
      summary: Firmar remitente del eCMR
      deprecated: false
      description: '## Propósito

        Registra la firma del remitente (`signed_by_sender`) sobre un eCMR específico.


        ## Objetivo

        Completar la firma de origen del documento para avanzar en el flujo operacional.


        ## Casos de uso

        - Un usuario de empresa firma el remitente desde formulario web con imagen.

        - El cliente envía firma en texto/base64 mediante el campo `sign`.


        ## Detalles técnicos

        Esta ruta aplica `userMiddleware.isCompany` además de `isLogedQuery`; por
        tanto,

        solo perfiles de compañía pueden ejecutarla. El body es `multipart/form-data`
        y

        acepta `image` o `sign` (al menos uno de los dos). También admite `geolocation`.

        La respuesta de éxito usa wrapper `returnOK`.


        ## Autenticación

        Requiere `queryAuth` (token en query, por ejemplo `?token=`) o `apiKeyAuth`.

        '
      tags:
      - ECMR - Sign
      parameters:
      - name: service_code
        in: path
        description: Código del eCMR a firmar por el remitente.
        required: true
        example: MADBCN8F3A2
        schema:
          type: string
          minLength: 1
          maxLength: 64
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                image:
                  type: string
                  format: binary
                  description: Archivo de imagen de firma.
                sign:
                  type: string
                  description: Firma en texto/base64.
                  minLength: 1
                  maxLength: 5000000
                  example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
                geolocation:
                  type: string
                  description: JSON serializado con coordenadas (se parsea con `JSON.parse`).
                  minLength: 2
                  maxLength: 1024
                  example: '{"coords":{"latitude":42.5668,"longitude":-8.9952}}'
              anyOf:
              - required:
                - image
              - required:
                - sign
      responses:
        '200':
          description: Firma de remitente registrada con wrapper `returnOK`
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/ECMR'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 66b4af3d0d7d6b2ec15a1123
                  service_code: MADBCN8F3A2
                  signed_by_sender: true
                  status: accepted
          headers: {}
        '401':
          description: Token en query y API Key ausentes o inválidos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Usuario no permitido/no encontrado o eCMR inexistente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_allowed:
                  summary: Restricción de rol (solo company)
                  value:
                    status: 404
                    message: USER_NOT_ALLOWED
                user_not_found:
                  summary: Usuario no encontrado
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                service_code_not_found:
                  summary: service_code ausente
                  value:
                    status: 404
                    message: SERVICE_CODE_NOT_FOUND
                ecmr_not_found:
                  summary: eCMR no encontrado
                  value:
                    status: 404
                    message: ECMR_NOT_FOUND
          headers: {}
        '406':
          description: No se pudo obtener una firma válida en la operación
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 406
                message: SIGNATURE_NOT_FOUND
          headers: {}
        '500':
          description: Error inesperado durante la firma
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: CANT_SIGN
          headers: {}
      security:
      - queryAuth: []
      - apiKeyAuth: []
  /ecmr-templates:
    get:
      summary: List ECMR Templates
      deprecated: false
      description: '## Propósito

        Obtener una lista paginada de plantillas eCMR pertenecientes a la empresa

        autenticada.


        ## Objetivo

        Permitir la consulta rápida de plantillas reutilizables para crear nuevos

        eCMR con menor carga operativa.


        ## Casos de uso

        - Listar plantillas en una tabla con paginación.

        - Buscar plantillas por `name` o `description` usando `search`.

        - Ordenar resultados por campo y dirección con `sort`.


        ## Detalles técnicos

        - Endpoint real: `GET /ecmr-templates`.

        - Usa `ecmrTemplateService.getTemplates(companyId, req.query)`.

        - Soporta query params: `page`, `limit`, `sort`, `search`.

        - Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.


        ## Autenticación

        Requiere `userMiddleware.isLoged` (JWT Bearer o API Key).

        '
      tags:
      - ecmr-templates
      parameters:
      - name: page
        in: query
        description: Número de página (1-based). Por defecto `1`.
        required: false
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Tamaño de página. Por defecto `10`.
        required: false
        schema:
          type: integer
          minimum: 1
          default: 10
      - name: sort
        in: query
        description: Ordenación Mongo/Mongoose (ej. `-createdAt`, `name`).
        required: false
        schema:
          type: string
          default: -createdAt
      - name: search
        in: query
        description: Texto libre para buscar por nombre o descripción.
        required: false
        schema:
          type: string
          default: ''
      responses:
        '200':
          description: Lista paginada de plantillas.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      docs:
                        type: array
                        items:
                          $ref: '#/components/schemas/ECMR'
                      totalDocs:
                        type: integer
                      limit:
                        type: integer
                      page:
                        type: integer
                      totalPages:
                        type: integer
                      pagingCounter:
                        type: integer
                      hasPrevPage:
                        type: boolean
                      hasNextPage:
                        type: boolean
                      prevPage:
                        type: integer
                        nullable: true
                      nextPage:
                        type: integer
                        nullable: true
                required:
                - status
                - data
                - '0'
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Empresa/usuario no encontrado en contexto autenticado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: COMPANY_NOT_FOUND
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      summary: Create ECMR Template
      deprecated: false
      description: '## Propósito

        Crear una nueva plantilla eCMR para reutilizar estructura de envíos.


        ## Objetivo

        Reducir tiempo de creación de eCMR guardando datos frecuentes

        (origen/destino, tipo de carga, observaciones y campos operativos).


        ## Casos de uso

        - Crear una plantilla base para rutas recurrentes.

        - Guardar configuraciones por cliente/mercancía.

        - Centralizar plantillas por compañía.


        ## Detalles técnicos

        - Endpoint real: `POST /ecmr-templates`.

        - Requiere `name` no vacío (`TEMPLATE_NAME_REQUIRED`).

        - Valida duplicado de nombre (`TEMPLATE_NAME_DUPLICATE`, `409`).

        - Valida existencia de `etl_address` y `etd_address` cuando se envían.

        - Respuesta de éxito real: `201` con `tools.returnOK`.


        ## Autenticación

        Requiere `userMiddleware.isLoged` (JWT Bearer o API Key).

        '
      tags:
      - ecmr-templates
      requestBody:
        content:
          application/json:
            schema:
              type: object
              description: Datos de plantilla eCMR a crear
              additionalProperties: true
              properties:
                name:
                  type: string
                description:
                  type: string
                etl_address:
                  type: string
                etd_address:
                  type: string
                cargo_type:
                  type: string
                is_pallet:
                  type: boolean
                pallets_type:
                  type: string
                pallets_num:
                  type: number
                linear_meters:
                  type: number
                is_fresh:
                  type: boolean
                fresh_cargo_temp:
                  type: number
                etl_cargo_method:
                  type: string
                had_etl_cargo_method:
                  type: boolean
                etd_cargo_method:
                  type: string
                had_etd_cargo_method:
                  type: boolean
                info_extra:
                  type: string
                payment_method:
                  type: string
                price:
                  type: number
                refund:
                  type: number
            example:
              name: Plantilla Ruta Madrid-Barcelona
              description: Carga refrigerada con entrega temprana
              etl_address: 66b4af3d0d7d6b2ec15a2201
              etd_address: 66b4af3d0d7d6b2ec15a2202
              cargo_type: pallets
              is_pallet: true
              pallets_type: european
              pallets_num: 12
              linear_meters: 6.5
              is_fresh: true
              fresh_cargo_temp: 4
              etl_cargo_method: back
              had_etl_cargo_method: true
              etd_cargo_method: lateral
              had_etd_cargo_method: true
              info_extra: Mercancía frágil. Mantener cadena de frío.
              payment_method: transfer
              price: 1250
              refund: 0
        required: true
      responses:
        '201':
          description: Plantilla creada correctamente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: Validación de entrada fallida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: TEMPLATE_NAME_REQUIRED
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Empresa o direcciones asociadas no encontradas.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                company_not_found:
                  value:
                    status: 404
                    message: COMPANY_NOT_FOUND
                etl_not_found:
                  value:
                    status: 404
                    message: ETL_ADDRESS_NOT_FOUND
                etd_not_found:
                  value:
                    status: 404
                    message: ETD_ADDRESS_NOT_FOUND
        '409':
          description: Conflicto por nombre de plantilla duplicado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 409
                message: TEMPLATE_NAME_DUPLICATE
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr-templates/all:
    get:
      summary: List all templates
      deprecated: false
      description: '## Propósito

        Obtener la colección completa de plantillas de la compañía sin paginación.


        ## Objetivo

        Exponer un listado compacto para selectores, asistentes y carga inicial

        donde se necesita el conjunto completo.


        ## Casos de uso

        - Poblar `dropdowns` de plantillas.

        - Resolver selección rápida previa a crear eCMR.

        - Cachear plantillas en cliente para flujos cortos.


        ## Detalles técnicos

        - Endpoint real: `GET /ecmr-templates/all`.

        - Retorna una lista simplificada (campos esenciales y direcciones pobladas).

        - Ordena por nombre ascendente en base de datos.

        - Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.


        ## Autenticación

        Requiere `userMiddleware.isLoged` (JWT Bearer o API Key).

        '
      tags:
      - ecmr-templates
      responses:
        '200':
          description: Lista completa de plantillas (sin paginación).
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ECMR'
                required:
                - status
                - data
                - '0'
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Empresa no encontrada en contexto autenticado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: COMPANY_NOT_FOUND
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr-templates/duplicate:
    post:
      summary: Duplicate Template
      deprecated: false
      description: '## Propósito

        Duplicar una plantilla existente para crear una variante editable.


        ## Objetivo

        Acelerar creación de plantillas similares sin rehacer todos los campos.


        ## Casos de uso

        - Crear variantes por cliente o tipo de carga.

        - Generar versiones por temporada/ruta.

        - Clonar una plantilla base y ajustar solo nombre/descripción.


        ## Detalles técnicos

        - Endpoint real: `POST /ecmr-templates/duplicate`.

        - Body requerido: `templateId`, `name`.

        - `description` es opcional y sobreescribe la descripción original si se envía.

        - Valida existencia de plantilla origen y nombre único.

        - Respuesta de éxito real: `201` con `tools.returnOK`.


        ## Autenticación

        Requiere `userMiddleware.isLoged` (JWT Bearer o API Key).

        '
      tags:
      - ecmr-templates
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                templateId:
                  type: string
                  description: ID de la plantilla origen.
                  example: 60d5ecb8b5c9c62c3cc6c5b9
                name:
                  type: string
                  description: Nombre de la nueva plantilla duplicada.
                  example: Copia Ruta Norte
                description:
                  type: string
                  description: Nueva descripción opcional.
              required:
              - templateId
              - name
        required: true
      responses:
        '201':
          description: Plantilla duplicada correctamente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: Datos requeridos ausentes.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing_template_id:
                  value:
                    status: 400
                    message: TEMPLATE_ID_REQUIRED
                missing_name:
                  value:
                    status: 400
                    message: TEMPLATE_NAME_REQUIRED
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Empresa o plantilla origen no encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                company_not_found:
                  value:
                    status: 404
                    message: COMPANY_NOT_FOUND
                template_not_found:
                  value:
                    status: 404
                    message: TEMPLATE_NOT_FOUND
        '409':
          description: Nombre de plantilla duplicado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 409
                message: TEMPLATE_NAME_DUPLICATE
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /ecmr-templates/{id}:
    get:
      summary: Get Template By ID
      deprecated: false
      description: '## Propósito

        Recuperar el detalle de una plantilla concreta por su identificador.


        ## Objetivo

        Permitir inspección/edición de una plantilla existente desde frontend u

        otros clientes API.


        ## Casos de uso

        - Abrir pantalla de edición de plantilla.

        - Mostrar vista detallada antes de crear eCMR.

        - Verificar contenido de plantilla tras creación o duplicado.


        ## Detalles técnicos

        - Endpoint real: `GET /ecmr-templates/{id}`.

        - Requiere `id` de path.

        - Si no existe `id`: `TEMPLATE_ID_REQUIRED` (400).

        - Si no existe plantilla para esa compañía: `TEMPLATE_NOT_FOUND` (404).

        - Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.


        ## Autenticación

        Requiere `userMiddleware.isLoged` (JWT Bearer o API Key).

        '
      tags:
      - ecmr-templates
      parameters:
      - name: id
        in: path
        description: Identificador de plantilla eCMR.
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Detalle de plantilla.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/ECMR'
                required:
                - status
                - data
                - '0'
        '400':
          description: Falta el identificador de plantilla.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: TEMPLATE_ID_REQUIRED
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Empresa o plantilla no encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                company_not_found:
                  value:
                    status: 404
                    message: COMPANY_NOT_FOUND
                template_not_found:
                  value:
                    status: 404
                    message: TEMPLATE_NOT_FOUND
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    put:
      summary: Update template
      deprecated: false
      description: '## Propósito

        Actualizar una plantilla eCMR existente de la empresa autenticada.


        ## Objetivo

        Mantener plantillas sincronizadas con cambios operativos sin crear una

        nueva plantilla desde cero.


        ## Casos de uso

        - Cambiar direcciones de origen/destino de una plantilla.

        - Ajustar nombre, descripción o campos de carga.

        - Corregir una plantilla antes de su reutilización masiva.


        ## Detalles técnicos

        - Endpoint real: `PUT /ecmr-templates/{id}`.

        - Requiere `id` de path.

        - El controlador elimina `company` del body para evitar cambio de dueño.

        - Valida duplicado de nombre y existencia de direcciones cuando aplica.

        - Respuesta de éxito: `200` con `tools.returnOK`.


        ## Autenticación

        Requiere `userMiddleware.isLoged` (JWT Bearer o API Key).

        '
      tags:
      - ecmr-templates
      parameters:
      - name: id
        in: path
        description: Identificador de plantilla eCMR.
        required: true
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              description: Datos de plantilla eCMR a actualizar
              additionalProperties: true
              properties:
                name:
                  type: string
                description:
                  type: string
                etl_address:
                  type: string
                etd_address:
                  type: string
                cargo_type:
                  type: string
                is_pallet:
                  type: boolean
                pallets_type:
                  type: string
                pallets_num:
                  type: number
                linear_meters:
                  type: number
                is_fresh:
                  type: boolean
                fresh_cargo_temp:
                  type: number
                etl_cargo_method:
                  type: string
                had_etl_cargo_method:
                  type: boolean
                etd_cargo_method:
                  type: string
                had_etd_cargo_method:
                  type: boolean
                info_extra:
                  type: string
                payment_method:
                  type: string
                price:
                  type: number
                refund:
                  type: number
            example:
              name: Plantilla Ruta Madrid-Barcelona
              description: Carga refrigerada con entrega temprana
              etl_address: 66b4af3d0d7d6b2ec15a2201
              etd_address: 66b4af3d0d7d6b2ec15a2202
              cargo_type: pallets
              is_pallet: true
              pallets_type: european
              pallets_num: 12
              linear_meters: 6.5
              is_fresh: true
              fresh_cargo_temp: 4
              etl_cargo_method: back
              had_etl_cargo_method: true
              etd_cargo_method: lateral
              had_etd_cargo_method: true
              info_extra: Mercancía frágil. Mantener cadena de frío.
              payment_method: transfer
              price: 1250
              refund: 0
        required: true
      responses:
        '200':
          description: Plantilla actualizada correctamente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: Falta el identificador de plantilla.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: TEMPLATE_ID_REQUIRED
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Empresa, plantilla o direcciones no encontradas.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                company_not_found:
                  value:
                    status: 404
                    message: COMPANY_NOT_FOUND
                template_not_found:
                  value:
                    status: 404
                    message: TEMPLATE_NOT_FOUND
                etl_not_found:
                  value:
                    status: 404
                    message: ETL_ADDRESS_NOT_FOUND
                etd_not_found:
                  value:
                    status: 404
                    message: ETD_ADDRESS_NOT_FOUND
        '409':
          description: Conflicto por nombre de plantilla duplicado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 409
                message: TEMPLATE_NAME_DUPLICATE
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    delete:
      summary: Delete Template
      deprecated: false
      description: '## Propósito

        Eliminar una plantilla eCMR existente de la compañía autenticada.


        ## Objetivo

        Mantener limpio el catálogo de plantillas, retirando configuraciones

        obsoletas o redundantes.


        ## Casos de uso

        - Limpiar plantillas en desuso.

        - Evitar selección accidental de plantillas antiguas.

        - Gestionar catálogo de plantillas por ciclo operativo.


        ## Detalles técnicos

        - Endpoint real: `DELETE /ecmr-templates/{id}`.

        - Requiere `id` de path.

        - Si la plantilla no existe para la compañía: `TEMPLATE_NOT_FOUND` (404).

        - Respuesta de éxito actual del controlador: `200` con objeto vacío en `data`.


        ## Autenticación

        Requiere `userMiddleware.isLoged` (JWT Bearer o API Key).

        '
      tags:
      - ecmr-templates
      parameters:
      - name: id
        in: path
        description: Identificador de plantilla eCMR.
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Plantilla eliminada correctamente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
              example:
                '0': 0
                status: 200
                data: {}
        '400':
          description: Falta el identificador de plantilla.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: TEMPLATE_ID_REQUIRED
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Empresa o plantilla no encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                company_not_found:
                  value:
                    status: 404
                    message: COMPANY_NOT_FOUND
                template_not_found:
                  value:
                    status: 404
                    message: TEMPLATE_NOT_FOUND
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /files:
    get:
      operationId: getFile
      summary: Descargar archivo desde S3
      deprecated: false
      description: "## Propósito\nRecuperar y descargar un archivo almacenado en S3\
        \ mediante su\nidentificador único.\n## Objetivo\nPermitir la descarga de\
        \ archivos subidos al sistema (documentos,\nimágenes, evidencias) desde integraciones\
        \ externas.\n## Casos de uso\n- Obtener archivos adjuntos a un eCMR o incidencia.\n\
        - Descargar documentación subida por el transportista.\n- Acceder a evidencias\
        \ documentales desde sistemas externos.\n## Detalles técnicos\n- Endpoint\
        \ real: `GET /files?file=...`.\n- Requiere el query parameter `file` con el\
        \ nombre/identificador\n  del archivo en S3.\n- Si el nombre contiene `--`,\
        \ la parte posterior se usa como\n  nombre de descarga.\n- Si falta `file`:\
        \ `400 FILE_PARAM_REQUIRED`.\n- Responde con el archivo binario directamente.\n\
        ## Autenticación\nNo requiere autenticación (endpoint público para acceso\
        \ directo).\n"
      tags:
      - Files
      parameters:
      - name: file
        in: query
        description: Identificador del archivo en S3 (incluye prefijo y nombre).
        required: true
        example: ecmr/RIBMADqNhRP/document.pdf
        schema:
          type: string
      responses:
        '200':
          description: Archivo solicitado.
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '400':
          description: Falta el parámetro file.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: FILE_PARAM_REQUIRED
        '404':
          description: Archivo no encontrado en S3.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: FILE_NOT_FOUND
      security: []
  /images:
    get:
      summary: Get image by param query
      deprecated: false
      description: '## Propósito

        Servir un recurso binario almacenado en storage usando la clave enviada por
        query string.


        ## Objetivo

        Facilitar la carga de imágenes o adjuntos en clientes que no construyen rutas
        con parámetro `path`.


        ## Casos de uso

        - Renderizar imágenes desde una URL tipo `/images?file=...`.

        - Reutilizar la misma clave de archivo en distintos módulos sin cambiar el
        endpoint.

        - Integraciones legacy que envían el identificador por query param.


        ## Detalles técnicos

        - Endpoint real en código: `GET /images` con query `file`.

        - El controlador combina `body`, `query` y `params`; para esta ruta se espera
        `query.file`.

        - Si `file` no llega, el backend intenta resolver `-` en storage y responde
        error.

        - El contenido se transmite por streaming y expone `Content-Type`, `Content-Length`
        y `Content-disposition`.


        ## Autenticación

        No requiere autenticación (`security: []`).

        '
      tags:
      - images
      parameters:
      - name: file
        in: query
        description: Nombre o clave del archivo en storage.
        required: true
        example: ecmr-proof-98765.jpg
        schema:
          type: string
          minLength: 1
      responses:
        '200':
          description: Recurso servido exitosamente
          content:
            '*/*':
              schema:
                type: string
                format: binary
          headers:
            Content-Type:
              required: false
              description: Tipo MIME real del archivo servido desde storage
              schema:
                type: string
                example: image/jpeg
            Content-Disposition:
              required: false
              description: Header de descarga con el nombre generado por backend
              schema:
                type: string
                example: attachment; filename=file.jpeg
            Content-Length:
              required: false
              description: Tamaño del archivo en bytes
              schema:
                type: integer
                example: 128500
        '404':
          description: Archivo no encontrado en storage
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                '1':
                  summary: file_not_found_primary_storage
                  value:
                    status: 404
                    message: 'File not found: NotFound'
                '2':
                  summary: file_not_found_s3_fallback
                  value:
                    status: 404
                    message: 'File not found in S3: NotFound'
          headers: {}
        '500':
          description: Error interno del servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security: []
  /images/{file}:
    get:
      summary: Get specific image by filename
      deprecated: false
      description: '## Propósito

        Servir un recurso binario almacenado en el storage configurado (MinIO o AWS
        S3) a partir del nombre de archivo enviado en `path`.


        ## Objetivo

        Permitir recuperar imágenes y adjuntos referenciados desde la plataforma mediante
        una URL directa y estable.


        ## Casos de uso

        - Visualización de firmas eCMR almacenadas como archivo.

        - Descarga de evidencias gráficas asociadas a incidencias.

        - Reutilización de una clave de storage en enlaces internos.


        ## Detalles técnicos

        - Endpoint real en código: `GET /images/{file}`.

        - El controlador combina `body`, `query` y `params`; en esta ruta prevalece
        `params.file`.

        - El contenido se transmite por streaming y expone `Content-Type`, `Content-Length`
        y `Content-disposition`.

        - Los errores se devuelven con `tools.returnKO` en formato `{ status, message
        }`, usando el código HTTP reportado por el storage cuando está disponible.


        ## Autenticación

        No requiere autenticación (`security: []`).

        '
      tags:
      - images
      parameters:
      - name: file
        in: path
        description: Nombre o clave del archivo en storage.
        required: true
        example: ecmr-signature-12345.png
        schema:
          type: string
          minLength: 1
      responses:
        '200':
          description: Recurso servido exitosamente
          content:
            '*/*':
              schema:
                type: string
                format: binary
          headers:
            Content-Type:
              required: false
              description: Tipo MIME real del archivo servido desde storage
              schema:
                type: string
                example: image/png
            Content-Disposition:
              required: false
              description: Header de descarga con el nombre generado por backend
              schema:
                type: string
                example: attachment; filename=file.png
            Content-Length:
              required: false
              description: Tamaño del archivo en bytes
              schema:
                type: integer
                example: 102400
        '404':
          description: Archivo no encontrado en storage
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                '1':
                  summary: file_not_found_primary_storage
                  value:
                    status: 404
                    message: 'File not found: NotFound'
                '2':
                  summary: file_not_found_s3_fallback
                  value:
                    status: 404
                    message: 'File not found in S3: NotFound'
          headers: {}
        '500':
          description: Error interno del servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security: []
  /invoices:
    get:
      operationId: listInvoices
      summary: Listar facturas
      deprecated: false
      description: "## Propósito\nObtener la lista de facturas asociadas a la empresa\
        \ o transportista\nautenticado.\n## Objetivo\nPermitir la consulta de facturación\
        \ desde el panel de administración\no integraciones contables.\n## Casos de\
        \ uso\n- Consultar el histórico de facturas emitidas.\n- Listar facturas pendientes\
        \ de pago.\n- Exportar datos de facturación para reconciliación.\n## Detalles\
        \ técnicos\n- Endpoint real: `GET /invoices/`.\n- Dependiendo del rol, usa\
        \ `invoicesService.getCompany` o\n  `invoicesService.getTrucker`.\n- Respuesta\
        \ envuelta con `tools.returnOK`: `{ status, data, 0 }`.\n## Autenticación\n\
        Requiere JWT Bearer token o API Key (middleware `isLoged`).\n"
      tags:
      - invoices
      parameters: []
      responses:
        '200':
          description: Lista de facturas.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      type: object
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Empresa o usuario no encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /invoices/{id}:
    get:
      operationId: getInvoice
      summary: Obtener detalle de factura
      deprecated: false
      description: "## Propósito\nObtener el detalle completo de una factura específica.\n\
        ## Objetivo\nConsultar información detallada de una factura, incluyendo\n\
        líneas, impuestos y estado de pago.\n## Casos de uso\n- Ver detalle de una\
        \ factura antes del pago.\n- Descargar PDF de la factura (si aplica).\n- Verificar\
        \ el estado de una factura concreta.\n## Detalles técnicos\n- Endpoint real:\
        \ `GET /invoices/{id}`.\n- El `id` corresponde al identificador de la factura.\n\
        - Dependiendo del rol, usa `invoicesService.getInvoiceCompany` o\n  `invoicesService.getInvoiceTrucker`.\n\
        - Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.\n## Autenticación\n\
        Requiere JWT Bearer token o API Key (middleware `isLoged`).\n"
      tags:
      - invoices
      parameters:
      - name: id
        in: path
        description: Identificador de la factura.
        required: true
        example: 6450d58755656096b9a92355
        schema:
          type: string
      responses:
        '200':
          description: Detalle de la factura solicitada.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Factura no encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /issues/:
    get:
      operationId: listMyIssues
      summary: Listar incidencias del usuario con paginación y filtros
      deprecated: false
      description: '## Propósito

        Devuelve la lista paginada de incidencias creadas por el usuario

        autenticado.


        ## Objetivo

        Permitir seguimiento del estado de tickets y consulta histórica de

        incidencias con búsqueda y filtrado.


        ## Casos de uso

        - Mostrar bandeja de incidencias del usuario.

        - Filtrar tickets activos o cerrados por estado agregado.

        - Buscar por código, autor, estado, categoría o texto del mensaje.


        ## Detalles técnicos

        - Solo devuelve incidencias donde `idAuthor` coincide con el usuario.

        - `filter=open` incluye solo `open`.

        - `filter=resolved` incluye solo `resolved`.

        - `filter=closed` incluye `closed` y `cancelByClient`.

        - El parámetro de búsqueda es `search` y aplica regex en varios campos.


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - issues
      parameters:
      - name: page
        in: query
        description: Número de página (1-indexed)
        required: false
        example: 1
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Resultados por página; si es menor o igual a 0 se usa `ITEMS_PAGE`
        required: false
        example: 10
        schema:
          type: integer
          minimum: 1
          default: 10
      - name: filter
        in: query
        description: Filtro agregado por estado
        required: false
        example: open
        schema:
          type: string
          enum:
          - all
          - open
          - resolved
          - closed
          default: all
      - name: search
        in: query
        description: Texto libre para buscar en `code`, `status`, `nameAuthor`, `author`,
          `relatedTo`, `message` y `file`
        required: false
        example: 2025-4F9rg
        schema:
          type: string
      responses:
        '200':
          description: Lista paginada de incidencias del usuario autenticado
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/PaginatedIssueList'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  docs:
                  - _id: 68ef43c97655a2921563627e
                    service_code: ''
                    internal_code: 2025-4F9rg
                    status: closed
                    idAuthor: 507f1f77bcf86cd799439012
                    nameAuthor: Usuario Anónimo
                    author: anonymous
                    relatedTo: delivery
                    message: El vehículo sufrió una avería en la A-6 km 42
                    files: []
                    messages: []
                    createdAt: '2025-10-15T06:48:41.511Z'
                    updatedAt: '2025-10-18T00:00:00.120Z'
                  totalDocs: 15
                  limit: 10
                  totalPages: 2
                  page: 1
                  pagingCounter: 1
                  hasPrevPage: false
                  hasNextPage: true
                  prevPage: null
                  nextPage: 2
          headers: {}
        '401':
          description: Token JWT ausente, expirado o API Key inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: No se encontró la compañía asociada al usuario autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: CIA_NOT_FOUND
          headers: {}
        '405':
          description: El usuario no tiene rol autorizado (`admin`, `gestor` o `driver`)
            para listar incidencias
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 405
                message: USER_NOT_ALLOWED
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      operationId: createIssue
      summary: Crear una nueva incidencia
      deprecated: false
      description: "## Propósito\nCrea una incidencia nueva asociada al usuario autenticado\
        \ y a su\ncompañía, con mensaje inicial y adjuntos opcionales.\n\n## Objetivo\n\
        Habilitar el registro estructurado de problemas operativos o técnicos\ndesde\
        \ el área privada del usuario.\n\n## Casos de uso\n- Reportar un problema\
        \ de entrega (`delivery`).\n- Abrir una incidencia relacionada con documentación\
        \ (`document`).\n- Adjuntar evidencias iniciales (capturas, fotos, PDFs).\n\
        \n## Detalles técnicos\n- URL OpenAPI: `/issues/` (creación de incidencia).\n\
        - Recibe `multipart/form-data`.\n- `title` y `message` son obligatorios.\n\
        - `files` acepta hasta 6 adjuntos.\n- Para cargar varios adjuntos en Swagger/UI,\
        \ enviar el campo `files`\n  repetido en el form-data.\n- El backend asigna\
        \ `internal_code`, `status=pending` y metadatos del autor.\n\n## Autenticación\n\
        Soporta JWT Bearer token y API Key.\n"
      tags:
      - issues
      parameters: []
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                title:
                  type: string
                  description: Título descriptivo de la incidencia
                  example: Problema con entrega CMR
                message:
                  type: string
                  description: Descripción detallada del problema
                  example: El CMR
                relatedTo:
                  type: string
                  enum:
                  - config
                  - delivery
                  - auction
                  - messages
                  - others
                  - user
                  - trucker
                  - address
                  - document
                  - bid
                  - contract
                  - userRegister
                  description: Categoría de la incidencia
                  example: delivery
                service_code:
                  type: string
                  description: Código de servicio relacionado (opcional)
                  example: SRV-001
                files:
                  type: array
                  items:
                    type: string
                    format: binary
                  description: Archivos adjuntos (máximo 6)
                  maxItems: 6
              required:
              - title
              - message
            encoding:
              files:
                style: form
                explode: true
        required: true
      responses:
        '200':
          description: Incidencia creada correctamente
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/Issue'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 68ef43c97655a2921563627e
                  service_code: ''
                  internal_code: 2025-4F9rg
                  status: pending
                  idAuthor: 507f1f77bcf86cd799439012
                  nameAuthor: Usuario Anónimo
                  author: anonymous
                  relatedTo: delivery
                  message: El vehículo sufrió una avería en la A-6 km 42
                  files: []
                  messages: []
                  createdAt: '2025-10-15T06:48:41.511Z'
                  updatedAt: '2025-10-18T00:00:00.120Z'
          headers: {}
        '401':
          description: Token JWT ausente, expirado o API Key inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: No se encontró la compañía asociada al usuario autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: CIA_NOT_FOUND
          headers: {}
        '405':
          description: El usuario no tiene rol autorizado (`admin`, `gestor` o `driver`)
            para crear incidencias
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 405
                message: USER_NOT_ALLOWED
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /issues/codes:
    get:
      operationId: listIssueServiceCodes
      summary: Obtener códigos de servicio para incidencias
      deprecated: false
      description: '## Propósito

        Devuelve los `service_code` de eCMRs que el usuario autenticado puede

        usar como referencia al crear una incidencia.


        ## Objetivo

        Facilitar autocompletado y vinculación de tickets con operaciones

        reales de transporte.


        ## Casos de uso

        - Autocompletar el campo `service_code` al crear un ticket.

        - Filtrar códigos por prefijo mediante `autocomplete`.

        - Paginar resultados en interfaces con muchos eCMRs.


        ## Detalles técnicos

        - Requiere rol `admin` dentro de la compañía.

        - Soporta paginación por `page` y `limit`.

        - Filtra por regex case-insensitive sobre `service_code`.

        - Retorna una lista sin duplicados.


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - issues
      parameters:
      - name: page
        in: query
        description: Número de página (1-indexed)
        required: false
        example: 1
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Número de códigos por página
        required: false
        example: 10
        schema:
          type: integer
          minimum: 1
          default: 10
      - name: autocomplete
        in: query
        description: Texto para filtrar códigos de servicio por coincidencia parcial
        required: false
        example: MAD
        schema:
          type: string
      responses:
        '200':
          description: Lista de códigos de servicio disponibles
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      type: string
                    example:
                    - RIBMADqNhRP
                    - MADBARxKjLM
                    - VALSEVpQoZN
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                - RIBMADqNhRP
                - MADBARxKjLM
                - VALSEVpQoZN
          headers: {}
        '401':
          description: Token JWT ausente, expirado o API Key inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: No se encontró la compañía asociada al usuario autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: CIA_NOT_FOUND
          headers: {}
        '405':
          description: El usuario no tiene rol `admin` para consultar códigos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 405
                message: USER_NOT_ALLOWED
          headers: {}
        '500':
          description: Error inesperado durante la consulta de códigos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /issues/reasons:
    get:
      operationId: listIssueReasons
      summary: Obtener motivos predefinidos de incidencias
      deprecated: false
      description: '## Propósito

        Devuelve el catálogo estático de motivos que puede seleccionar el

        usuario al crear una incidencia.


        ## Objetivo

        Estandarizar la clasificación de tickets para facilitar su análisis y

        priorización.


        ## Casos de uso

        - Rellenar un selector de categorías en el formulario de incidencias.

        - Evitar valores libres no controlados en `relatedTo`.

        - Sincronizar catálogos entre frontend y backend.


        ## Detalles técnicos

        - Lee un archivo JSON local (`motives.json`).

        - Cada elemento incluye `title` y `value`.

        - No requiere parámetros de entrada.


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - issues
      parameters: []
      responses:
        '200':
          description: Lista de motivos disponibles
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        title:
                          type: string
                          example: config
                        value:
                          type: string
                          example: config
                      required:
                      - title
                      - value
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                - title: config
                  value: config
                - title: address
                  value: address
                - title: document
                  value: document
                - title: ECMR
                  value: ECMR
                - title: messages
                  value: messages
                - title: user
                  value: user
                - title: others
                  value: others
          headers: {}
        '401':
          description: Token JWT ausente, expirado o API Key inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /issues/{id}:
    get:
      operationId: getIssueDetails
      summary: Obtener detalle completo de una incidencia
      deprecated: false
      description: '## Propósito

        Devuelve el detalle completo de una incidencia concreta, incluyendo su

        hilo de mensajes.


        ## Objetivo

        Permitir al usuario revisar el contexto y evolución de un ticket.


        ## Casos de uso

        - Abrir el detalle de una incidencia desde el listado.

        - Consultar adjuntos y mensajes intercambiados.

        - Validar estado y metadatos antes de responder o cerrar.


        ## Detalles técnicos

        - Busca por `_id` y restringe por `idAuthor` del usuario autenticado.

        - Si no existe o no pertenece al usuario, retorna `NOT_FOUND`.


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - issues
      parameters:
      - name: id
        in: path
        description: ID de la incidencia
        required: true
        example: 507f1f77bcf86cd799439011
        schema:
          type: string
      responses:
        '200':
          description: Detalle de la incidencia
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/Issue'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 68ef43c97655a2921563627e
                  service_code: ''
                  internal_code: 2025-4F9rg
                  status: closed
                  idAuthor: 507f1f77bcf86cd799439012
                  nameAuthor: Usuario Anónimo
                  author: anonymous
                  relatedTo: delivery
                  message: El vehículo sufrió una avería en la A-6 km 42
                  files: []
                  messages: []
                  createdAt: '2025-10-15T06:48:41.511Z'
                  updatedAt: '2025-10-18T00:00:00.120Z'
          headers: {}
        '401':
          description: Token JWT ausente, expirado o API Key inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Incidencia no encontrada o fuera del alcance del usuario autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
          headers: {}
        '405':
          description: El usuario no tiene rol autorizado (`admin`, `gestor` o `driver`)
            para consultar incidencias
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 405
                message: USER_NOT_ALLOWED
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      operationId: addIssueMessage
      summary: Añadir mensaje a una incidencia
      deprecated: false
      description: "## Propósito\nAgrega un mensaje nuevo al hilo de conversación\
        \ de una incidencia\nexistente.\n\n## Objetivo\nRegistrar seguimiento, aclaraciones\
        \ o evidencias adicionales en un\nticket ya creado.\n\n## Casos de uso\n-\
        \ Enviar actualización de estado al equipo de soporte.\n- Adjuntar capturas\
        \ o documentos complementarios.\n- Solicitar información adicional sobre una\
        \ incidencia abierta.\n\n## Detalles técnicos\n- URL OpenAPI: `/issues/{id}`\
        \ (no `/issues/:id`).\n- Recibe `multipart/form-data`.\n- `message` es obligatorio.\n\
        - `files` acepta hasta 2 adjuntos por mensaje.\n- Para cargar varios adjuntos\
        \ en Swagger/UI, enviar el campo `files`\n  repetido en el form-data.\n- Valida\
        \ pertenencia de la incidencia al usuario por `idAuthor`.\n\n## Autenticación\n\
        Soporta JWT Bearer token y API Key.\n"
      tags:
      - issues
      parameters:
      - name: id
        in: path
        description: ID de la incidencia
        required: true
        example: 507f1f77bcf86cd799439011
        schema:
          type: string
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                message:
                  type: string
                  description: Mensaje a añadir
                  example: Adjunto captura de pantalla del error.
                files:
                  type: array
                  items:
                    type: string
                    format: binary
                  description: Archivos adjuntos al mensaje (máximo 2)
                  maxItems: 2
              required:
              - message
            encoding:
              files:
                style: form
                explode: true
        required: true
      responses:
        '200':
          description: Mensaje agregado correctamente y ticket actualizado
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/Issue'
                required:
                - status
                - data
                - '0'
          headers: {}
        '401':
          description: Token JWT ausente, expirado o API Key inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Usuario o incidencia no encontrados
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  summary: Usuario autenticado no encontrado
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                issue_not_found:
                  summary: Incidencia no encontrada o fuera del alcance del usuario
                  value:
                    status: 404
                    message: NOT_FOUND
          headers: {}
        '405':
          description: El usuario no tiene rol autorizado (`admin`, `gestor` o `driver`)
            para responder incidencias
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 405
                message: USER_NOT_ALLOWED
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /issues/{id}/resolve:
    post:
      operationId: closeIssueByClient
      summary: Cerrar incidencia por parte del cliente
      deprecated: false
      description: '## Propósito

        Cambia el estado de una incidencia a `cancelByClient` para indicar que

        el cliente la da por cerrada.


        ## Objetivo

        Permitir cierre explícito de tickets cuando el usuario ya no requiere

        seguimiento.


        ## Casos de uso

        - El usuario confirma que su problema quedó resuelto.

        - Se cancela un ticket abierto por cambio de contexto.

        - Se limpia la bandeja de incidencias activas.


        ## Detalles técnicos

        - Busca por `_id` e `idAuthor`.

        - Si existe, actualiza estado a `cancelByClient`.

        - La normalización a `closed` puede aplicarse en listados.


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - issues
      parameters:
      - name: id
        in: path
        description: ID de la incidencia a cerrar
        required: true
        example: 507f1f77bcf86cd799439011
        schema:
          type: string
      responses:
        '200':
          description: Incidencia cerrada correctamente
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/Issue'
                required:
                - status
                - data
                - '0'
          headers: {}
        '401':
          description: Token JWT ausente, expirado o API Key inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Incidencia no encontrada o fuera del alcance del usuario autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
          headers: {}
        '405':
          description: El usuario no tiene rol autorizado (`admin`, `gestor` o `driver`)
            para cerrar incidencias
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 405
                message: USER_NOT_ALLOWED
          headers: {}
        '503':
          description: Error al persistir el cierre de la incidencia
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 503
                message: SERVICE_UNAVAILABLE
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /notifications:
    get:
      summary: Listar notificaciones del usuario autenticado
      deprecated: false
      description: '## Propósito

        Recuperar la bandeja de notificaciones del usuario autenticado con paginación
        y filtros.


        ## Objetivo

        Permitir al frontend renderizar el centro de notificaciones y su estado de
        lectura sin cálculos adicionales.


        ## Casos de uso

        - Cargar listado inicial de notificaciones.

        - Aplicar filtros por tipo, prioridad o solo no leídas.

        - Sincronizar contador de no leídas junto al listado.


        ## Detalles técnicos

        - Ruta protegida por `userMiddleware.isLoged`.

        - La query se valida con `validateNotificationQuery`.

        - `page` default real: `1`.

        - `limit` default real: `20` (máximo `100`).

        - Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - notifications
      parameters:
      - name: page
        in: query
        description: Número de página (base 1).
        required: false
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Tamaño de página. El backend limita el valor máximo a 100.
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 20
      - name: unreadOnly
        in: query
        description: Filtro de no leídas; el backend espera string `true` o `false`.
        required: false
        schema:
          type: string
          enum:
          - 'true'
          - 'false'
          default: 'false'
      - name: type
        in: query
        description: Tipo de notificación.
        required: false
        schema:
          type: string
          enum:
          - ecmr_created
          - ecmr_accepted
          - ecmr_collected
          - ecmr_delivered
          - ecmr_canceled
          - ecmr_claimed
          - ecmr_locked
          - ecmr_issue
          - payment_received
          - payment_pending
          - payment_failed
          - user_invited
          - user_verified
          - system_maintenance
          - general
      - name: priority
        in: query
        description: Prioridad de la notificación.
        required: false
        schema:
          type: string
          enum:
          - low
          - medium
          - high
          - urgent
      responses:
        '200':
          description: Listado paginado obtenido correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/Notification'
                      unreadCount:
                        type: integer
                        example: 3
                      pagination:
                        type: object
                        properties:
                          totalDocs:
                            type: integer
                            example: 32
                          limit:
                            type: integer
                            example: 20
                          page:
                            type: integer
                            example: 1
                          totalPages:
                            type: integer
                            example: 2
                          hasNextPage:
                            type: boolean
                            example: true
                          hasPrevPage:
                            type: boolean
                            example: false
                          nextPage:
                            type: integer
                            nullable: true
                            example: 2
                          prevPage:
                            type: integer
                            nullable: true
                            example: null
                        required:
                        - totalDocs
                        - limit
                        - page
                        - totalPages
                        - hasNextPage
                        - hasPrevPage
                    required:
                    - data
                    - unreadCount
                    - pagination
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  data:
                  - _id: 65e732302241abb3849272bc
                    type: ecmr_delivered
                    priority: high
                    title: Entrega completada
                    message: La eCMR ECM-12345 se ha entregado.
                    read: false
                    company_user: 507f1f77bcf86cd799439012
                    createdAt: '2026-03-02T08:10:00.000Z'
                    updatedAt: '2026-03-02T08:10:00.000Z'
                  unreadCount: 1
                  pagination:
                    totalDocs: 1
                    limit: 20
                    page: 1
                    totalPages: 1
                    hasNextPage: false
                    hasPrevPage: false
                    nextPage: null
                    prevPage: null
        '400':
          description: Parámetros de consulta inválidos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalidPage:
                  value:
                    status: 400
                    message: INVALID_PAGE_NUMBER
                invalidLimit:
                  value:
                    status: 400
                    message: INVALID_LIMIT_VALUE
                invalidPriority:
                  value:
                    status: 400
                    message: INVALID_PRIORITY_FILTER
        '401':
          description: No autenticado o sesión inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '500':
          description: Error interno durante la consulta.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INVALID_USER_TYPE
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      summary: Crear notificación manual (solo admin)
      deprecated: false
      description: "## Propósito\nCrear una notificación manual dirigida a un usuario\
        \ desde contexto administrativo.\n\n## Objetivo\nPermitir alertas manuales\
        \ del sistema cuando no provienen de flujos automáticos (ECMR, pagos, etc.).\n\
        \n## Casos de uso\n- Comunicar incidencias operativas.\n- Enviar avisos administrativos\
        \ puntuales.\n- Notificar ventanas de mantenimiento.\n\n## Detalles técnicos\n\
        - Ruta protegida por `userMiddleware.isLoged`.\n- Requiere rol admin en `req.company.role`;\
        \ si no, devuelve `403 ADMIN_ACCESS_REQUIRED`.\n- Middleware `validateNotificationCreation`\
        \ valida `type`, `title`, `message`, `recipientType`, `recipientId`.\n- El\
        \ middleware normaliza el destinatario: `recipientType=company_user|trucker_cia`\
        \ + `recipientId`\n  se mapea automáticamente a `company_user` o `trucker_cia`.\n\
        - Middleware `rateLimitNotifications` aplica máximo 10 creaciones/minuto por\
        \ usuario (`429 RATE_LIMIT_EXCEEDED`).\n- El controlador exige además `company_user`\
        \ o `trucker_cia` (uno solo).\n- Respuesta de éxito en envelope `tools.returnOK`:\
        \ `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT (`bearerAuth`)\
        \ o API key (`apiKeyAuth`) con contexto admin válido.\n"
      tags:
      - notifications
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - type
              - title
              - message
              properties:
                type:
                  type: string
                  enum:
                  - ecmr_created
                  - ecmr_accepted
                  - ecmr_collected
                  - ecmr_delivered
                  - ecmr_canceled
                  - ecmr_claimed
                  - ecmr_locked
                  - ecmr_issue
                  - payment_received
                  - payment_pending
                  - payment_failed
                  - user_invited
                  - user_verified
                  - system_maintenance
                  - general
                title:
                  type: string
                  maxLength: 255
                message:
                  type: string
                  maxLength: 1000
                recipientType:
                  type: string
                  description: 'Opcional si se envía `company_user` o `trucker_cia`
                    de forma explícita. Si se usa, debe acompañarse de `recipientId`.

                    '
                recipientId:
                  type: string
                  description: 'Opcional si se envía `company_user` o `trucker_cia`
                    de forma explícita. Si se usa, debe acompañarse de `recipientType`.

                    '
                company_user:
                  type: string
                  description: ObjectId del usuario destino. Opcional si se envía
                    `recipientType=company_user` + `recipientId`.
                trucker_cia:
                  type: string
                  description: ObjectId alternativo de transportista. Opcional si
                    se envía `recipientType=trucker_cia` + `recipientId`.
                priority:
                  type: string
                  enum:
                  - low
                  - medium
                  - high
                  - urgent
                  default: medium
                data:
                  type: object
                  additionalProperties: true
                relatedEntityType:
                  type: string
                  enum:
                  - ECMR
                  - Payment
                  - CompanyUser
                  - Company
                relatedEntityId:
                  type: string
                scheduledFor:
                  type: string
                  format: date-time
                expiresAt:
                  type: string
                  format: date-time
                sendEmail:
                  type: boolean
                  default: true
            example:
              type: system_maintenance
              title: Mantenimiento programado
              message: La plataforma tendrá mantenimiento entre 03:00 y 05:00.
              recipientType: company_user
              recipientId: 507f1f77bcf86cd799439012
              company_user: 507f1f77bcf86cd799439012
              priority: high
              sendEmail: true
      responses:
        '201':
          description: Notificación creada correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 201
                  data:
                    type: object
                    properties:
                      notification:
                        $ref: '#/components/schemas/Notification'
                    required:
                    - notification
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 201
                data:
                  notification:
                    _id: 65e732302241abb3849272bc
                    type: system_maintenance
                    priority: high
                    title: Mantenimiento programado
                    message: La plataforma tendrá mantenimiento entre 03:00 y 05:00.
                    read: false
                    company_user: 507f1f77bcf86cd799439012
                    createdAt: '2026-03-03T09:00:00.000Z'
                    updatedAt: '2026-03-03T09:00:00.000Z'
        '400':
          description: Validación de payload fallida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingType:
                  value:
                    status: 400
                    message: NOTIFICATION_TYPE_REQUIRED
                missingRecipient:
                  value:
                    status: 400
                    message: RECIPIENT_ID_REQUIRED
                missingControllerFields:
                  value:
                    status: 400
                    message: REQUIRED_FIELDS_MISSING
        '401':
          description: No autenticado o usuario no encontrado para rate-limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: USER_NOT_FOUND
        '403':
          description: Requiere permisos de administrador.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: ADMIN_ACCESS_REQUIRED
        '429':
          description: Límite de creación de notificaciones excedido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 429
                message: RATE_LIMIT_EXCEEDED
        '500':
          description: Error interno durante la creación.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                serviceUserRequired:
                  value:
                    status: 500
                    message: USER_ID_REQUIRED
                validationRuntime:
                  value:
                    status: 500
                    message: Cannot read properties of undefined (reading 'includes')
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /notifications/count/unread:
    get:
      summary: Obtener contador de no leídas
      deprecated: false
      description: '## Propósito

        Devolver el total de notificaciones no leídas del usuario autenticado.


        ## Objetivo

        Alimentar badges y contadores de UI sin descargar el listado completo.


        ## Casos de uso

        - Badge en menú superior.

        - Refresco periódico de contador.

        - Actualización tras marcar notificaciones como leídas.


        ## Detalles técnicos

        - Ruta protegida por `userMiddleware.isLoged`.

        - No usa parámetros de paginación ni filtros.

        - La capa de servicio cuenta documentos `deleted: false` y `read: false`.

        - Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - notifications
      parameters: []
      responses:
        '200':
          description: Contador obtenido correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      count:
                        type: integer
                        example: 4
                    required:
                    - count
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  count: 4
        '401':
          description: No autenticado o sesión inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '500':
          description: Error interno durante el conteo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INVALID_USER_TYPE
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /notifications/read-all:
    put:
      summary: Marcar todas las notificaciones como leídas
      deprecated: false
      description: '## Propósito

        Marcar como leídas todas las notificaciones pendientes del usuario autenticado.


        ## Objetivo

        Permitir limpieza masiva del centro de notificaciones con una única acción.


        ## Casos de uso

        - Botón “Marcar todo como leído”.

        - Reseteo de pendientes tras revisar bandeja.

        - Sincronización de estado al entrar en el módulo.


        ## Detalles técnicos

        - Ruta protegida por `userMiddleware.isLoged`.

        - El servicio ejecuta `updateMany` sobre notificaciones `deleted: false` y
        `read: false` del usuario.

        - Retorna `modifiedCount` con número de registros afectados.

        - Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - notifications
      parameters: []
      responses:
        '200':
          description: Operación masiva completada correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        example: All notifications marked as read
                      modifiedCount:
                        type: integer
                        example: 8
                    required:
                    - message
                    - modifiedCount
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  message: All notifications marked as read
                  modifiedCount: 8
        '401':
          description: No autenticado o sesión inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '500':
          description: Error interno durante la actualización masiva.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INVALID_USER_TYPE
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /notifications/types:
    get:
      summary: Obtener tipos y prioridades válidas
      deprecated: false
      description: '## Propósito

        Exponer los tipos de notificación y prioridades soportadas por el modelo.


        ## Objetivo

        Evitar hardcode en frontend para filtros y formularios de notificaciones.


        ## Casos de uso

        - Poblar selectores de filtros.

        - Validar payloads en cliente.

        - Mostrar catálogo de eventos notificados.


        ## Detalles técnicos

        - Ruta protegida por `userMiddleware.isLoged`.

        - El controlador devuelve un objeto con dos arrays: `validTypes` y `validPriorities`.

        - Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - notifications
      parameters: []
      responses:
        '200':
          description: Catálogo de tipos y prioridades obtenido correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      validTypes:
                        type: array
                        items:
                          type: string
                      validPriorities:
                        type: array
                        items:
                          type: string
                          enum:
                          - low
                          - medium
                          - high
                          - urgent
                    required:
                    - validTypes
                    - validPriorities
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  validTypes:
                  - ecmr_created
                  - ecmr_accepted
                  - ecmr_collected
                  - ecmr_delivered
                  - ecmr_canceled
                  - ecmr_claimed
                  - ecmr_locked
                  - ecmr_issue
                  - payment_received
                  - payment_pending
                  - payment_failed
                  - user_invited
                  - user_verified
                  - system_maintenance
                  - general
                  validPriorities:
                  - low
                  - medium
                  - high
                  - urgent
        '401':
          description: No autenticado o sesión inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '500':
          description: Error interno al recuperar catálogos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: ERROR
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /notifications/unread:
    get:
      summary: Listar notificaciones no leídas
      deprecated: false
      description: '## Propósito

        Obtener solo notificaciones pendientes de lectura del usuario autenticado.


        ## Objetivo

        Reducir el payload del centro de notificaciones cuando la UI solo necesita
        pendientes.


        ## Casos de uso

        - Panel “No leídas”.

        - Bandeja compacta en header.

        - Carga incremental de pendientes.


        ## Detalles técnicos

        - Ruta protegida por `userMiddleware.isLoged`.

        - Query validada por `validateNotificationQuery`.

        - `page` default real: `1`.

        - `limit` default real: `20` (máximo `100`).

        - No tiene middleware de rate-limit en esta ruta, por lo que no aplica `429`.

        - Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - notifications
      parameters:
      - name: page
        in: query
        description: Número de página (base 1).
        required: false
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Tamaño de página. El backend limita el valor máximo a 100.
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 20
      - name: priority
        in: query
        description: Filtra por prioridad de notificación.
        required: false
        schema:
          type: string
          enum:
          - low
          - medium
          - high
          - urgent
      responses:
        '200':
          description: Notificaciones no leídas obtenidas correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/Notification'
                      unreadCount:
                        type: integer
                        example: 5
                      pagination:
                        type: object
                        properties:
                          totalDocs:
                            type: integer
                            example: 5
                          limit:
                            type: integer
                            example: 20
                          page:
                            type: integer
                            example: 1
                          totalPages:
                            type: integer
                            example: 1
                          hasNextPage:
                            type: boolean
                            example: false
                          hasPrevPage:
                            type: boolean
                            example: false
                          nextPage:
                            type: integer
                            nullable: true
                            example: null
                          prevPage:
                            type: integer
                            nullable: true
                            example: null
                        required:
                        - totalDocs
                        - limit
                        - page
                        - totalPages
                        - hasNextPage
                        - hasPrevPage
                    required:
                    - data
                    - unreadCount
                    - pagination
                required:
                - status
                - data
                - '0'
        '400':
          description: Parámetros de consulta inválidos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalidPage:
                  value:
                    status: 400
                    message: INVALID_PAGE_NUMBER
                invalidLimit:
                  value:
                    status: 400
                    message: INVALID_LIMIT_VALUE
        '401':
          description: No autenticado o sesión inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '500':
          description: Error interno durante la consulta.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INVALID_USER_TYPE
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /notifications/{notificationId}:
    delete:
      summary: Eliminar una notificación específica
      deprecated: false
      description: '## Propósito

        Eliminar una notificación concreta del usuario autenticado.


        ## Objetivo

        Permitir que el usuario gestione manualmente su bandeja y descarte avisos
        irrelevantes.


        ## Casos de uso

        - Ocultar avisos ya no útiles.

        - Limpiar la bandeja de notificaciones.

        - Gestionar historial visible por usuario.


        ## Detalles técnicos

        - Ruta protegida por `userMiddleware.isLoged`.

        - `notificationId` se valida con formato ObjectId en `validateNotificationId`.

        - Se registra acción vía `midAction.saveAction(''notifications'')`.

        - El servicio ejecuta soft delete mediante plugin `mongoose-delete`.

        - Si no existe o no pertenece al usuario, actualmente responde `500 NOTIFICATION_NOT_FOUND`.

        - Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - notifications
      parameters:
      - name: notificationId
        in: path
        description: ID de notificación (ObjectId de MongoDB).
        required: true
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
        example: 507f1f77bcf86cd799439011
      responses:
        '200':
          description: Notificación eliminada correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      deleted:
                        type: boolean
                        example: true
                    required:
                    - deleted
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  deleted: true
        '400':
          description: ID de notificación inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingId:
                  value:
                    status: 400
                    message: NOTIFICATION_ID_REQUIRED
                invalidFormat:
                  value:
                    status: 400
                    message: INVALID_NOTIFICATION_ID_FORMAT
        '401':
          description: No autenticado o sesión inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '500':
          description: Error interno (incluye notificación no encontrada en implementación
            actual).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                notFoundAs500:
                  value:
                    status: 500
                    message: NOTIFICATION_NOT_FOUND
                invalidUserType:
                  value:
                    status: 500
                    message: INVALID_USER_TYPE
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /notifications/{notificationId}/read:
    put:
      summary: Marcar notificación específica como leída
      deprecated: false
      description: '## Propósito

        Cambiar el estado de lectura de una notificación puntual del usuario autenticado.


        ## Objetivo

        Permitir interacción individual desde el listado de notificaciones.


        ## Casos de uso

        - Marcar una notificación al abrir su detalle.

        - Reducir contador de no leídas de forma selectiva.

        - Sincronizar estado leído entre vistas.


        ## Detalles técnicos

        - Ruta protegida por `userMiddleware.isLoged`.

        - `notificationId` se valida con formato ObjectId en `validateNotificationId`.

        - Si la notificación no existe o no pertenece al usuario, el servicio lanza
        `NOTIFICATION_NOT_FOUND` y actualmente se expone como `500`.

        - Se registra acción vía `midAction.saveAction(''notifications'')`.

        - Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).

        '
      tags:
      - notifications
      parameters:
      - name: notificationId
        in: path
        description: ID de notificación (ObjectId de MongoDB).
        required: true
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
        example: 507f1f77bcf86cd799439011
      responses:
        '200':
          description: Notificación marcada como leída correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      notification:
                        $ref: '#/components/schemas/Notification'
                    required:
                    - notification
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  notification:
                    _id: 65e732302241abb3849272bc
                    type: ecmr_issue
                    priority: high
                    title: Incidencia detectada
                    message: Se detectó una incidencia en la entrega.
                    read: true
                    readAt: '2026-03-03T09:30:00.000Z'
                    company_user: 507f1f77bcf86cd799439012
                    createdAt: '2026-03-03T08:00:00.000Z'
                    updatedAt: '2026-03-03T09:30:00.000Z'
        '400':
          description: ID de notificación inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missingId:
                  value:
                    status: 400
                    message: NOTIFICATION_ID_REQUIRED
                invalidFormat:
                  value:
                    status: 400
                    message: INVALID_NOTIFICATION_ID_FORMAT
        '401':
          description: No autenticado o sesión inválida.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '500':
          description: Error interno (incluye notificación no encontrada en implementación
            actual).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                notFoundAs500:
                  value:
                    status: 500
                    message: NOTIFICATION_NOT_FOUND
                invalidUserType:
                  value:
                    status: 500
                    message: INVALID_USER_TYPE
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /poi/city:
    get:
      operationId: searchCity
      summary: Buscar ciudades por nombre
      deprecated: false
      description: '## Propósito

        Buscar ciudades mediante un texto de búsqueda, utilizando

        el servicio externo de puntos de interés (POI).

        ## Objetivo

        Permitir autocompletado y búsqueda de ciudades para direcciones

        y puntos de interés en la interfaz de usuario.

        ## Casos de uso

        - Autocompletar campo de ciudad en formularios.

        - Buscar ubicaciones para planificación de rutas.

        - Obtener coordenadas de ciudades para búsquedas POI.

        ## Detalles técnicos

        - Endpoint real: `GET /poi/city?search=...`.

        - Acepta `search` como query parameter.

        - No requiere autenticación.

        - Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.

        ## Autenticación

        No requiere autenticación.

        '
      tags:
      - POI
      parameters:
      - name: search
        in: query
        description: Texto de búsqueda para filtrar ciudades.
        required: true
        example: Madrid
        schema:
          type: string
      responses:
        '200':
          description: Lista de ciudades encontradas.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        city:
                          type: string
                          description: Nombre de la ciudad.
                        lat:
                          type: number
                          description: Latitud de la ciudad.
                        lng:
                          type: number
                          description: Longitud de la ciudad.
                        country:
                          type: string
                          description: Código del país.
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security: []
  /poi/nearby:
    get:
      operationId: getNearbyLocations
      summary: Obtener lugares cercanos por coordenadas
      deprecated: false
      description: '## Propósito

        Obtener una lista de puntos de interés cercanos a una ubicación

        geográfica dada.

        ## Objetivo

        Permitir que los conductores y planificadores encuentren servicios

        cercanos durante la ruta.

        ## Casos de uso

        - Encontrar puntos de servicio cerca de una ubicación.

        - Obtener información genérica de lugares cercanos.

        ## Detalles técnicos

        - Endpoint real: `GET /poi/nearby?lat=...&lng=...`.

        - Acepta `lat` y `lng` como query parameters.

        - No requiere autenticación.

        - Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.

        ## Autenticación

        No requiere autenticación.

        '
      tags:
      - POI
      parameters:
      - name: lat
        in: query
        description: Latitud de la ubicación de referencia.
        required: true
        example: 40.4168
        schema:
          type: number
          format: float
      - name: lng
        in: query
        description: Longitud de la ubicación de referencia.
        required: true
        example: -3.7038
        schema:
          type: number
          format: float
      responses:
        '200':
          description: Lista de lugares cercanos.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      type: object
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security: []
  /poi/parking/nearby:
    get:
      operationId: getNearbyParking
      summary: Obtener aparcamientos cercanos
      deprecated: false
      description: '## Propósito

        Buscar aparcamientos para camiones cercanos a una ubicación.

        ## Objetivo

        Facilitar la localización de zonas de estacionamiento seguro

        para conductores durante sus rutas.

        ## Casos de uso

        - Encontrar aparcamiento cercano para descansos reglamentarios.

        - Localizar zonas de estacionamiento seguro cerca de destino.

        ## Detalles técnicos

        - Endpoint real: `GET /poi/parking/nearby?lat=...&lng=...&radius=...`.

        - Acepta `lat`, `lng` y opcionalmente `radius` como query parameters.

        - No requiere autenticación.

        - Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.

        ## Autenticación

        No requiere autenticación.

        '
      tags:
      - POI
      parameters:
      - name: lat
        in: query
        description: Latitud de la ubicación de referencia.
        required: true
        example: 40.4168
        schema:
          type: number
          format: float
      - name: lng
        in: query
        description: Longitud de la ubicación de referencia.
        required: true
        example: -3.7038
        schema:
          type: number
          format: float
      - name: radius
        in: query
        description: Radio de búsqueda en metros (por defecto 5000).
        required: false
        example: 5000
        schema:
          type: integer
          default: 5000
      responses:
        '200':
          description: Lista de aparcamientos cercanos.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      type: object
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security: []
  /poi/restaurant/nearby:
    get:
      operationId: getNearbyRestaurants
      summary: Obtener restaurantes cercanos
      deprecated: false
      description: '## Propósito

        Buscar restaurantes cercanos a una ubicación geográfica.

        ## Objetivo

        Ayudar a conductores a encontrar opciones de comida durante sus rutas.

        ## Casos de uso

        - Planificar paradas para comidas en rutas largas.

        - Encontrar restaurantes cerca de puntos de carga/descarga.

        ## Detalles técnicos

        - Endpoint real: `GET /poi/restaurant/nearby?lat=...&lng=...&radius=...`.

        - Acepta `lat`, `lng` y opcionalmente `radius` como query parameters.

        - No requiere autenticación.

        - Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.

        ## Autenticación

        No requiere autenticación.

        '
      tags:
      - POI
      parameters:
      - name: lat
        in: query
        description: Latitud de la ubicación de referencia.
        required: true
        example: 40.4168
        schema:
          type: number
          format: float
      - name: lng
        in: query
        description: Longitud de la ubicación de referencia.
        required: true
        example: -3.7038
        schema:
          type: number
          format: float
      - name: radius
        in: query
        description: Radio de búsqueda en metros (por defecto 5000).
        required: false
        example: 5000
        schema:
          type: integer
          default: 5000
      responses:
        '200':
          description: Lista de restaurantes cercanos.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      type: object
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security: []
  /poi/shower/nearby:
    get:
      operationId: getNearbyShowers
      summary: Obtener duchas cercanas
      deprecated: false
      description: '## Propósito

        Buscar duchas disponibles para conductores cerca de una ubicación.

        ## Objetivo

        Ayudar a conductores a encontrar servicios de higiene durante

        sus paradas en ruta.

        ## Casos de uso

        - Localizar duchas en áreas de servicio cercanas.

        - Planificar paradas con servicios completos.

        ## Detalles técnicos

        - Endpoint real: `GET /poi/shower/nearby?lat=...&lng=...&radius=...`.

        - Acepta `lat`, `lng` y opcionalmente `radius` como query parameters.

        - No requiere autenticación.

        - Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.

        ## Autenticación

        No requiere autenticación.

        '
      tags:
      - POI
      parameters:
      - name: lat
        in: query
        description: Latitud de la ubicación de referencia.
        required: true
        example: 40.4168
        schema:
          type: number
          format: float
      - name: lng
        in: query
        description: Longitud de la ubicación de referencia.
        required: true
        example: -3.7038
        schema:
          type: number
          format: float
      - name: radius
        in: query
        description: Radio de búsqueda en metros (por defecto 5000).
        required: false
        example: 5000
        schema:
          type: integer
          default: 5000
      responses:
        '200':
          description: Lista de duchas cercanas.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      type: object
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security: []
  /poi/workshop/nearby:
    get:
      operationId: getNearbyWorkshops
      summary: Obtener talleres cercanos
      deprecated: false
      description: '## Propósito

        Buscar talleres mecánicos cercanos a una ubicación geográfica.

        ## Objetivo

        Ayudar a conductores a encontrar asistencia mecánica durante sus rutas.

        ## Casos de uso

        - Localizar talleres ante una avería en ruta.

        - Planificar mantenimiento preventivo en puntos estratégicos.

        ## Detalles técnicos

        - Endpoint real: `GET /poi/workshop/nearby?lat=...&lng=...&radius=...`.

        - Acepta `lat`, `lng` y opcionalmente `radius` como query parameters.

        - No requiere autenticación.

        - Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.

        ## Autenticación

        No requiere autenticación.

        '
      tags:
      - POI
      parameters:
      - name: lat
        in: query
        description: Latitud de la ubicación de referencia.
        required: true
        example: 40.4168
        schema:
          type: number
          format: float
      - name: lng
        in: query
        description: Longitud de la ubicación de referencia.
        required: true
        example: -3.7038
        schema:
          type: number
          format: float
      - name: radius
        in: query
        description: Radio de búsqueda en metros (por defecto 5000).
        required: false
        example: 5000
        schema:
          type: integer
          default: 5000
      responses:
        '200':
          description: Lista de talleres cercanos.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      type: object
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security: []
  /profile/:
    get:
      operationId: getProfile
      summary: Obtener perfil autenticado
      deprecated: false
      description: '## Propósito

        Recupera el perfil del usuario autenticado para mostrar su información

        actual de cuenta.


        ## Objetivo

        Permitir que el frontend cargue los datos del usuario logueado sin

        realizar consultas adicionales.


        ## Casos de uso

        - Cargar pantalla de perfil al iniciar sesión.

        - Leer datos base para formularios de edición.

        - Confirmar estado de cuenta, rol e idioma configurado.


        ## Detalles técnicos

        - El endpoint está protegido por `userMiddleware.isLoged`.

        - Devuelve el documento de usuario sin parseo adicional.

        - Si no hay contexto de autenticación válido en el controlador, retorna `NOT_FOUND`.


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - profile
      parameters: []
      responses:
        '200':
          description: Perfil obtenido correctamente
          content:
            application/json:
              schema:
                type: object
                required:
                - status
                - data
                - '0'
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    description: Documento de usuario crudo persistido en base de
                      datos
                    additionalProperties: true
              example:
                '0': 0
                status: 200
                data:
                  _id: 63d7907cbe76403b35da63df
                  role: admin
                  status: true
                  name: Usuario
                  lastname: Demo
                  email: cia@testing.com
                  password: $2a$10$EXAMPLEHASH
                  recovery_token: ''
          headers: {}
        '401':
          description: Token JWT ausente, expirado o API Key inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: Sin token ni apikey
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token inválido
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
                account_blocked:
                  summary: Cuenta bloqueada
                  value:
                    status: 401
                    message: ACCOUNT_BLOCKED
          headers: {}
        '404':
          description: Perfil o contexto de autenticación no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                not_found:
                  summary: Fallback del controlador
                  value:
                    status: 404
                    message: NOT_FOUND
                user_not_found:
                  summary: Usuario no encontrado
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                apikey_not_found:
                  summary: API key no encontrada
                  value:
                    status: 404
                    message: APIKEY_NOT_FOUND
                cia_not_found:
                  summary: Compañía no encontrada
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '500':
          description: Error interno al recuperar perfil
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                database_error:
                  summary: Error de base de datos
                  value:
                    status: 500
                    message: DATABASE_ERROR
                cant_send:
                  summary: Error genérico de envío
                  value:
                    status: 500
                    message: CANT_SEND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      operationId: updateProfile
      summary: Actualizar perfil autenticado
      deprecated: false
      description: "## Propósito\nActualiza los campos editables del perfil del usuario\
        \ autenticado.\n\n## Objetivo\nPermitir mantener datos de usuario al día (nombre,\
        \ teléfono, idioma,\nzona horaria y preferencias adicionales).\n\n## Casos\
        \ de uso\n- Editar nombre y apellidos del usuario.\n- Cambiar país, idioma\
        \ o timezone de trabajo.\n- Guardar preferencias auxiliares permitidas por\
        \ runtime.\n\n## Detalles técnicos\n- El endpoint está protegido por `userMiddleware.isLoged`.\n\
        - El servicio aplica `updateData` del modelo y bloquea claves sensibles\n\
        \  (`password`, `status`, `recovery_token`, etc.).\n- Runtime permite `additionalProperties`\
        \ en el body.\n- Devuelve el documento actualizado en formato crudo.\n\n##\
        \ Autenticación\nSoporta JWT Bearer token y API Key.\n"
      tags:
      - profile
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              allOf:
              - $ref: '#/components/schemas/ProfileUpdate'
              additionalProperties: true
            examples:
              profile_fields:
                summary: Campos típicos de perfil
                value:
                  name: Usuario
                  lastname: Demo
                  phone: '976487663'
                  timezone: Europe/Madrid
                  i18n: es
              extra_runtime_field:
                summary: Campo extra permitido por runtime
                value:
                  customPreference: true
                  color: blue
      responses:
        '200':
          description: Perfil actualizado correctamente
          content:
            application/json:
              schema:
                type: object
                required:
                - status
                - data
                - '0'
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    description: Documento de usuario crudo actualizado
                    additionalProperties: true
              example:
                '0': 0
                status: 200
                data:
                  _id: 63d7907cbe76403b35da63df
                  role: admin
                  name: Usuario actualizado
                  password: $2a$10$EXAMPLEHASH
                  recovery_token: ''
          headers: {}
        '401':
          description: Token JWT ausente, expirado o API Key inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: Sin token ni apikey
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token inválido
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
                account_blocked:
                  summary: Cuenta bloqueada
                  value:
                    status: 401
                    message: ACCOUNT_BLOCKED
          headers: {}
        '404':
          description: Perfil o contexto de autenticación no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                not_found:
                  summary: Fallback del controlador
                  value:
                    status: 404
                    message: NOT_FOUND
                user_not_found:
                  summary: Usuario no encontrado
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                apikey_not_found:
                  summary: API key no encontrada
                  value:
                    status: 404
                    message: APIKEY_NOT_FOUND
                cia_not_found:
                  summary: Compañía no encontrada
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '500':
          description: Error interno al actualizar perfil
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                database_error:
                  summary: Error de base de datos
                  value:
                    status: 500
                    message: DATABASE_ERROR
                cant_send:
                  summary: Error genérico de envío
                  value:
                    status: 500
                    message: CANT_SEND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /profile/change-password:
    post:
      operationId: changePassword
      summary: Cambiar contraseña del usuario autenticado
      deprecated: false
      description: '## Propósito

        Cambia la contraseña del usuario autenticado validando credencial

        actual y confirmación de la nueva clave.


        ## Objetivo

        Permitir rotación de credenciales desde el perfil sin intervención

        administrativa.


        ## Casos de uso

        - Cambiar contraseña de forma preventiva.

        - Actualizar contraseña tras sospecha de acceso no autorizado.

        - Normalizar claves tras políticas internas de seguridad.


        ## Detalles técnicos

        - Requiere `current`, `new_pass` y `confirm_pass`.

        - Si `current` es inválida retorna `INVALID_PASSWORD` (403).

        - Si `new_pass` y `confirm_pass` no coinciden retorna `PASSWORD_NOT_MATCH`
        (405).

        - En éxito, guarda hash bcrypt y limpia `recovery_token`.

        - Devuelve `model.parse(user)` (sin `password` ni `recovery_token`).


        ## Autenticación

        Soporta JWT Bearer token y API Key.

        '
      tags:
      - profile
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PasswordChange'
            example:
              current: oldPassword123
              new_pass: newSecurePassword123
              confirm_pass: newSecurePassword123
      responses:
        '200':
          description: Contraseña cambiada correctamente
          content:
            application/json:
              schema:
                type: object
                required:
                - status
                - data
                - '0'
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/Profile'
              example:
                '0': 0
                status: 200
                data:
                  _id: 63d7907cbe76403b35da63df
                  role: admin
                  status: true
                  name: Usuario
                  lastname: Demo
                  email: cia@testing.com
                  timezone: Europe/Madrid
                  i18n: es
          headers: {}
        '400':
          description: Error al persistir la nueva contraseña
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: DATABASE_ERROR
          headers: {}
        '401':
          description: Token JWT ausente, expirado o API Key inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: Sin token ni apikey
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token inválido
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
                account_blocked:
                  summary: Cuenta bloqueada
                  value:
                    status: 401
                    message: ACCOUNT_BLOCKED
          headers: {}
        '403':
          description: Contraseña actual inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: INVALID_PASSWORD
          headers: {}
        '404':
          description: Perfil o contexto de autenticación no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                not_found:
                  summary: Fallback del controlador
                  value:
                    status: 404
                    message: NOT_FOUND
                user_not_found:
                  summary: Usuario no encontrado
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                apikey_not_found:
                  summary: API key no encontrada
                  value:
                    status: 404
                    message: APIKEY_NOT_FOUND
                cia_not_found:
                  summary: Compañía no encontrada
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '405':
          description: La nueva contraseña y su confirmación no coinciden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 405
                message: PASSWORD_NOT_MATCH
          headers: {}
        '500':
          description: Error interno durante el cambio de contraseña
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                database_error:
                  summary: Error de base de datos
                  value:
                    status: 500
                    message: DATABASE_ERROR
                cant_update:
                  summary: Error genérico de actualización
                  value:
                    status: 500
                    message: CANT_UPDATE
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /settings/legal:
    get:
      operationId: getLegalContent
      summary: Obtener contenido legal (términos y privacidad)
      deprecated: false
      description: "## Propósito\nRecuperar el contenido de términos y condiciones\
        \ / política de\nprivacidad en formato Markdown.\n## Objetivo\nProporcionar\
        \ a la aplicación el texto legal actualizado para\nmostrar al usuario en pantallas\
        \ de registro, configuración o\ncumplimiento normativo.\n## Casos de uso\n\
        - Mostrar términos y condiciones en el registro de usuario.\n- Consultar la\
        \ política de privacidad desde la configuración.\n- Obtener contenido legal\
        \ con soporte de idioma (i18n).\n## Detalles técnicos\n- Endpoint real: `GET\
        \ /settings/legal`.\n- Lee el contenido de `terms_privacy.md` del servidor.\n\
        - Soporta `lang` query parameter para internacionalización.\n- Respuesta con\
        \ `tools.returnOK`: `{ status, data, 0 }`,\n  donde `data` contiene el texto\
        \ Markdown.\n## Autenticación\nNo requiere autenticación (endpoint público).\n"
      tags:
      - Settings
      parameters:
      - name: lang
        in: query
        description: Código de idioma para el contenido legal (por defecto 'es').
        required: false
        example: es
        schema:
          type: string
          default: es
      responses:
        '200':
          description: Contenido legal en formato Markdown.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: string
                    description: Texto Markdown con términos y condiciones.
        '500':
          description: Error al leer el archivo legal.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_ERROR
      security: []
  /trailers:
    get:
      operationId: listMyTrailers
      summary: Listar remolques de la flota
      deprecated: false
      description: '## Propósito

        Obtener una lista paginada de los remolques registrados para la

        empresa o transportista autenticado.

        ## Objetivo

        Permitir la gestión y consulta de remolques asociados a la flota

        del usuario.

        ## Casos de uso

        - Listar remolques disponibles para asignar a vehículos.

        - Buscar remolques por matrícula o tipo.

        - Consultar capacidad y características de remolques.

        ## Detalles técnicos

        - Endpoint real: `GET /trailers/`.

        - Soporta paginación con `page` y `limit` (default: page=1, limit=10).

        - Soporta búsqueda por `search` (matrícula o tipo).

        - Soporta `autocomplete` para completado rápido.

        - Respuesta paginada con `tools.customPagination`.

        ## Autenticación

        Requiere JWT Bearer token o API Key (middleware `isLoged`).

        '
      tags:
      - trailers
      parameters:
      - name: page
        in: query
        description: Número de página (1-based). Por defecto `1`.
        required: false
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Tamaño de página. Por defecto `10`.
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 10
      - name: search
        in: query
        description: Texto de búsqueda por matrícula o tipo.
        required: false
        schema:
          type: string
          default: ''
      - name: autocomplete
        in: query
        description: Texto para autocompletado (sobrescribe search si se envía).
        required: false
        schema:
          type: string
          default: ''
      responses:
        '200':
          description: Lista paginada de remolques.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      docs:
                        type: array
                        items:
                          $ref: '#/components/schemas/Trailer'
                      totalDocs:
                        type: integer
                      limit:
                        type: integer
                      page:
                        type: integer
                      totalPages:
                        type: integer
                      hasPrevPage:
                        type: boolean
                      hasNextPage:
                        type: boolean
                      prevPage:
                        type: integer
                        nullable: true
                      nextPage:
                        type: integer
                        nullable: true
        '400':
          description: Error al obtener la lista de remolques.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: CANT_GET
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Empresa no encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: COMPANY_NOT_FOUND
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      operationId: createTrailer
      summary: Crear un nuevo remolque
      deprecated: false
      description: '## Propósito

        Registrar un nuevo remolque en la flota de la empresa autenticada.

        ## Objetivo

        Añadir remolques al inventario de la empresa para su posterior

        asignación a vehículos y uso en operaciones de transporte.

        ## Casos de uso

        - Añadir un remolque nuevo a la flota.

        - Registrar remolque con datos de capacidad y tipo.

        - Preparar el remolque para asignación a un vehículo.

        ## Detalles técnicos

        - Endpoint real: `POST /trailers/`.

        - Requiere `plate` (matrícula) no vacía.

        - Valida duplicado de matrícula dentro de la misma empresa (`409`).

        - Normaliza la matrícula a mayúsculas sin espacios.

        - Acepta `capacity_value` (numérico), `type`, y demás campos del modelo.

        - Respuesta con `tools.returnOK`: `{ status, data, 0 }`.

        ## Autenticación

        Requiere JWT Bearer token o API Key (middleware `isLoged`).

        '
      tags:
      - trailers
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - plate
              properties:
                plate:
                  type: string
                  description: Matrícula del remolque.
                  example: 1234ABC
                type:
                  type: string
                  description: Tipo de remolque (ej. lona, frigorífico, cisterna).
                  example: lona
                capacity_value:
                  type: number
                  description: Capacidad de carga en kg.
                  example: 24000
      responses:
        '200':
          description: Remolque creado correctamente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: Error de validación (plate requerido).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                plate_required:
                  summary: Matrícula requerida
                  value:
                    status: 400
                    message: PLATE_REQUIRED
                cant_create:
                  summary: Error al crear
                  value:
                    status: 400
                    message: CANT_CREATE
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Empresa no encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: COMPANY_NOT_FOUND
        '409':
          description: Ya existe un remolque con esa matrícula.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 409
                message: TRAILER_PLATE_ALREADY_EXISTS
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /trailers/{id}:
    put:
      operationId: updateTrailer
      summary: Actualizar un remolque
      deprecated: false
      description: '## Propósito

        Actualizar los datos de un remolque existente.

        ## Objetivo

        Permitir modificar atributos del remolque (matrícula, tipo,

        capacidad) sin tener que eliminarlo y crearlo de nuevo.

        ## Casos de uso

        - Corregir la matrícula de un remolque.

        - Actualizar el tipo o capacidad tras una modificación.

        - Cambiar características operativas del remolque.

        ## Detalles técnicos

        - Endpoint real: `PUT /trailers/{id}`.

        - Verifica que el remolque pertenezca a la empresa autenticada.

        - Si se cambia la matrícula, valida que no exista duplicado (`409`).

        - Normaliza matrícula a mayúsculas.

        - Respuesta con `tools.returnOK`: `{ status, data, 0 }`.

        ## Autenticación

        Requiere JWT Bearer token o API Key (middleware `isLoged`).

        '
      tags:
      - trailers
      parameters:
      - name: id
        in: path
        description: ObjectId del remolque a actualizar.
        required: true
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                plate:
                  type: string
                  description: Nueva matrícula del remolque.
                  example: 5678DEF
                type:
                  type: string
                  description: Nuevo tipo de remolque.
                  example: frigorifico
                capacity_value:
                  type: number
                  description: Nueva capacidad de carga en kg.
                  example: 26000
      responses:
        '200':
          description: Remolque actualizado correctamente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: Error de validación o actualización.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: CANT_UPDATE
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Remolque o empresa no encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
        '409':
          description: Matrícula duplicada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 409
                message: TRAILER_PLATE_ALREADY_EXISTS
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    delete:
      operationId: deleteTrailer
      summary: Eliminar un remolque
      deprecated: false
      description: '## Propósito

        Eliminar un remolque de la flota de la empresa autenticada.

        ## Objetivo

        Dar de baja remolques que ya no están en servicio o fueron

        registrados por error.

        ## Casos de uso

        - Un remolque se vende y se elimina de la flota.

        - Se registró por error y se necesita eliminar.

        - Limpiar remolques obsoletos del inventario.

        ## Detalles técnicos

        - Endpoint real: `DELETE /trailers/{id}`.

        - Verifica que el remolque pertenezca a la empresa autenticada.

        - Valida que el remolque no esté asignado a un vehículo activo (`409`).

        - Valida que el remolque no esté asignado a un eCMR activo (`409`).

        - Elimina físicamente el documento.

        - Respuesta con `tools.returnOK`: `{ status, data, 0 }`.

        ## Autenticación

        Requiere JWT Bearer token o API Key (middleware `isLoged`).

        '
      tags:
      - trailers
      parameters:
      - name: id
        in: path
        description: ObjectId del remolque a eliminar.
        required: true
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
      responses:
        '200':
          description: Remolque eliminado correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      _id:
                        type: string
                        description: ObjectId del remolque eliminado.
                required:
                - status
                - data
                - '0'
        '400':
          description: Error al eliminar el remolque.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: CANT_DELETE
        '401':
          description: Token/API key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Remolque o empresa no encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
        '409':
          description: Remolque asignado a un vehículo o eCMR activo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                assigned_to_vehicle:
                  value:
                    status: 409
                    message: TRAILER_ASSIGNED_TO_VEHICLE
                assigned_to_ecmr:
                  value:
                    status: 409
                    message: TRAILER_ASSIGNED_TO_ECMR
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /users/:
    get:
      operationId: listUsers
      summary: Listar usuarios de la compañía
      deprecated: false
      description: '## Propósito

        Lista usuarios asociados a la compañía del usuario autenticado.


        ## Objetivo

        Facilitar gestión de cuentas internas (admin, gestor, driver) con paginación
        y búsqueda.


        ## Casos de uso

        - Mostrar tabla de usuarios en el panel de administración.

        - Buscar usuarios por nombre, email, teléfono o taxid.


        ## Detalles técnicos

        Internamente usa `paginate` y aplica filtro de búsqueda por regex sobre varios
        campos.

        La respuesta de éxito utiliza `tools.returnOK`, por lo que el resultado se
        envuelve en `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters:
      - name: page
        in: query
        description: Número de página (1-indexed).
        required: false
        example: 1
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Elementos por página.
        required: false
        example: 10
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 10
      - name: search
        in: query
        description: Texto de búsqueda libre sobre role, name, lastname, email, phone,
          country, taxid.
        required: false
        example: juan
        schema:
          type: string
          minLength: 0
          maxLength: 100
      - name: filter
        in: query
        description: Filtro funcional recibido por controlador.
        required: false
        example: all
        schema:
          type: string
          minLength: 1
          maxLength: 50
          default: all
      - name: order
        in: query
        description: Orden serializado en JSON (campo y dirección).
        required: false
        example: '[{"field":"createdAt","orderBy":"DESC"}]'
        schema:
          type: string
          minLength: 2
          maxLength: 500
      responses:
        '200':
          description: Lista paginada de usuarios.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      docs:
                        type: array
                        items:
                          $ref: '#/components/schemas/User'
                      totalDocs:
                        type: integer
                        example: 2
                      limit:
                        type: integer
                        example: 10
                      totalPages:
                        type: integer
                        example: 1
                      page:
                        type: integer
                        example: 1
                      pagingCounter:
                        type: integer
                        example: 1
                      hasPrevPage:
                        type: boolean
                        example: false
                      hasNextPage:
                        type: boolean
                        example: false
                      prevPage:
                        nullable: true
                        type: integer
                        example: null
                      nextPage:
                        nullable: true
                        type: integer
                        example: null
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  docs:
                  - _id: 507f1f77bcf86cd799439011
                    name: Juan
                    lastname: Pérez García
                    email: juan.perez@empresa.com
                    role: gestor
                    status: true
                  - _id: 507f1f77bcf86cd799439012
                    name: María
                    lastname: López Sánchez
                    email: maria.lopez@empresa.com
                    role: driver
                    status: true
                  totalDocs: 2
                  limit: 10
                  totalPages: 1
                  page: 1
                  pagingCounter: 1
                  hasPrevPage: false
                  hasNextPage: false
                  prevPage: null
                  nextPage: null
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Usuario o compañía no encontrados por el contexto autenticado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '405':
          description: Error controlado por excepción en la operación de listado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 405
                message: reject is not defined
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    put:
      operationId: updateUserByBodyId
      summary: Actualizar usuario por ID en body
      deprecated: false
      description: '## Propósito

        Actualiza un usuario de la misma compañía usando `id` en el body.


        ## Objetivo

        Mantener compatibilidad con clientes que no envían el ID en la URL.


        ## Casos de uso

        - Panel legacy que actualiza usuarios mediante payload.


        ## Detalles técnicos

        Requiere rol `admin`. Si no hay permisos, el controlador responde con `USER_NOT_FOUND`
        (404)

        en lugar de un 403/405. Respuesta de éxito envuelta en `{ status, data, 0
        }`.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
              - $ref: '#/components/schemas/UserUpdate'
              - type: object
                properties:
                  id:
                    type: string
                    pattern: ^[0-9a-fA-F]{24}$
                    minLength: 24
                    maxLength: 24
                    example: 693981f60fe7ba19d6907664
                required:
                - id
            example:
              id: 693981f60fe7ba19d6907664
              name: Ana María
              lastname: Martínez Ruiz
      responses:
        '200':
          description: Usuario actualizado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/User'
              example:
                '0': 0
                status: 200
                data:
                  _id: 693981f60fe7ba19d6907664
                  name: Ana María
                  lastname: Martínez Ruiz
                  email: ana.martinez@empresa.com
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Usuario objetivo o compañía no encontrados, o sin permisos
            admin (comportamiento actual).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '406':
          description: Email ya existe en otro usuario.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 406
                message: USER_ALREADY_EXIST
          headers: {}
        '500':
          description: Error interno en resolución de modelos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: INTERNAL_SERVER_ERROR
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      operationId: createUser
      summary: Crear nuevo usuario
      deprecated: false
      description: '## Propósito

        Crea un usuario nuevo dentro de la compañía del usuario autenticado.


        ## Objetivo

        Permitir alta de cuentas internas para operación y administración.


        ## Casos de uso

        - Alta de un gestor para un equipo de oficina.

        - Alta de un conductor para operación diaria.


        ## Detalles técnicos

        Solo `admin` puede crear usuarios. El backend genera contraseña temporal (`generatePass`),

        guarda hash y envía email con la nueva clave. Respuesta de éxito envuelta
        en `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreate'
            examples:
              company_user:
                summary: Alta de usuario de empresa
                value:
                  name: Ana Maria
                  lastname: Martínez Ruiz
                  email: ana.martinez@empresa.com
                  phone: '+34612345678'
                  accountType: company
                  role: gestor
              trucker_user:
                summary: Alta de usuario de transportista
                value:
                  name: Carlos
                  lastname: Rodríguez Díaz
                  email: carlos.rodriguez@transportes.com
                  phone: '+34687654321'
                  taxid: 98765432B
                  accountType: trucker
                  role: driver
      responses:
        '200':
          description: Usuario creado correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/User'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 507f1f77bcf86cd799439015
                  name: Ana Maria
                  lastname: Martínez Ruiz
                  email: ana.martinez@empresa.com
                  role: gestor
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '403':
          description: Datos inválidos o error al crear en base de datos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                data_not_valid:
                  value:
                    status: 403
                    message: DATA_NOT_VALID
          headers: {}
        '404':
          description: Sin permisos admin (comportamiento actual) o compañía no encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '500':
          description: Error interno en resolución de contexto.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: CIA_NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /users/access:
    get:
      operationId: getCurrentUserAccessHistory
      summary: Obtener historial de accesos del usuario autenticado
      deprecated: false
      description: '## Propósito

        Devuelve el historial paginado de accesos (login) del usuario autenticado.


        ## Objetivo

        Permitir revisión de seguridad de sesiones recientes del propio usuario.


        ## Casos de uso

        - Auditar accesos desde dispositivos distintos.

        - Revisar actividad reciente de login.


        ## Detalles técnicos

        Usa el mismo controlador que `/users/access/{id}`. Cuando no se envía `id`,

        se usa automáticamente el `_id` del usuario autenticado. Consume `page` y
        `limit` por query.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters:
      - name: page
        in: query
        description: Número de página.
        required: false
        example: 1
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Elementos por página.
        required: false
        example: 10
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 10
      responses:
        '200':
          description: Historial paginado de accesos del usuario autenticado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      docs:
                        type: array
                        items:
                          $ref: '#/components/schemas/Access'
                      totalDocs:
                        type: integer
                        example: 2
                      limit:
                        type: integer
                        example: 10
                      totalPages:
                        type: integer
                        example: 1
                      page:
                        type: integer
                        example: 1
              example:
                '0': 0
                status: 200
                data:
                  docs:
                  - _id: 507f1f77bcf86cd799439011
                    ip: 192.168.1.100
                    createdAt: '2024-01-15T09:00:00.000Z'
                  - _id: 507f1f77bcf86cd799439012
                    ip: 192.168.1.101
                    createdAt: '2024-01-14T16:30:00.000Z'
                  totalDocs: 2
                  limit: 10
                  totalPages: 1
                  page: 1
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '500':
          description: Error interno (incluye errores de resolución de contexto).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: CIA_NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /users/access/{id}:
    get:
      operationId: getUserAccessHistoryById
      summary: Obtener historial de accesos de un usuario
      deprecated: false
      description: '## Propósito

        Devuelve el historial de accesos de un usuario específico.


        ## Objetivo

        Permitir auditoría administrativa de seguridad por usuario.


        ## Casos de uso

        - Revisar sesiones de un miembro del equipo.

        - Investigar accesos sospechosos de una cuenta.


        ## Detalles técnicos

        Si el ID no corresponde al usuario autenticado, solo `admin` puede consultar.

        Errores de permisos se encapsulan como 500 por el controlador actual.

        Éxito envuelto en `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters:
      - name: id
        in: path
        description: ObjectId del usuario objetivo.
        required: true
        example: 693981f60fe7ba19d6907664
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
      - name: page
        in: query
        description: Número de página.
        required: false
        example: 1
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Elementos por página.
        required: false
        example: 10
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 10
      responses:
        '200':
          description: Historial paginado de accesos.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      docs:
                        type: array
                        items:
                          $ref: '#/components/schemas/Access'
                      totalDocs:
                        type: integer
                        example: 2
                      limit:
                        type: integer
                        example: 10
                      totalPages:
                        type: integer
                        example: 1
                      page:
                        type: integer
                        example: 1
              example:
                '0': 0
                status: 200
                data:
                  docs:
                  - _id: 507f1f77bcf86cd799439011
                    ip: 192.168.1.100
                    browser:
                      name: chrome
                      version: 91.0.4472.124
                    os:
                      name: windows
                      version: '10'
                    device:
                      name: desktop
                      version: ''
                  - _id: 507f1f77bcf86cd799439012
                    ip: 192.168.1.100
                    browser:
                      name: safari
                      version: '14.1'
                    os:
                      name: macos
                      version: 11.2.3
                    device:
                      name: mobile
                      version: iPhone
                  totalDocs: 2
                  limit: 10
                  totalPages: 1
                  page: 1
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '500':
          description: Error interno (incluye errores de permisos encapsulados).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_allowed_wrapped:
                  value:
                    status: 500
                    message: USER_NOT_ALLOWED
                user_not_found_wrapped:
                  value:
                    status: 500
                    message: USER_NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /users/actions/{id}:
    get:
      operationId: getUserActionsHistory
      summary: Obtener historial de acciones de usuario
      deprecated: false
      description: '## Propósito

        Devuelve el histórico paginado de acciones de un usuario.


        ## Objetivo

        Facilitar auditoría operativa de acciones realizadas en el sistema.


        ## Casos de uso

        - Revisar actividad de un usuario específico.

        - Trazabilidad de operaciones sobre módulos críticos.


        ## Detalles técnicos

        Si un usuario no admin intenta consultar otro ID, internamente se genera `USER_NOT_ALLOWED`,

        pero el controlador actual encapsula ese error y responde con 500.

        Éxito envuelto en `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters:
      - name: id
        in: path
        description: ObjectId del usuario.
        required: true
        example: 693981f60fe7ba19d6907664
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
      - name: page
        in: query
        description: Número de página.
        required: false
        example: 1
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Elementos por página.
        required: false
        example: 10
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 10
      responses:
        '200':
          description: Historial paginado de acciones.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      docs:
                        type: array
                        items:
                          $ref: '#/components/schemas/Action'
                      totalDocs:
                        type: integer
                        example: 2
                      limit:
                        type: integer
                        example: 10
                      totalPages:
                        type: integer
                        example: 1
                      page:
                        type: integer
                        example: 1
              example:
                '0': 0
                status: 200
                data:
                  docs:
                  - _id: 507f1f77bcf86cd799439011
                    action: update
                    type: company_user
                    relatedTo: users
                    ref: 507f1f77bcf86cd799439012
                  - _id: 507f1f77bcf86cd799439013
                    action: create
                    type: company_user
                    relatedTo: users
                    ref: 507f1f77bcf86cd799439014
                  totalDocs: 2
                  limit: 10
                  totalPages: 1
                  page: 1
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '500':
          description: Error interno (incluye errores de permisos encapsulados por
            el controlador).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_allowed_wrapped:
                  value:
                    status: 500
                    message: USER_NOT_ALLOWED
                cia_not_found_wrapped:
                  value:
                    status: 500
                    message: CIA_NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /users/activate/{id}:
    post:
      operationId: resendActivationEmail
      summary: Reenviar activación de usuario
      deprecated: false
      description: '## Propósito

        Genera un nuevo token de activación para un usuario y reenvía el email.


        ## Objetivo

        Recuperar activaciones pendientes cuando el usuario no recibió el primer correo.


        ## Casos de uso

        - Usuario sin correo inicial de activación.

        - Token anterior expirado o invalidado.


        ## Detalles técnicos

        Requiere rol `admin` y usuario objetivo dentro de la misma compañía.

        Guarda `recovery_token` y envía correo mediante `mail.sendActive`.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters:
      - name: id
        in: path
        description: ObjectId del usuario objetivo.
        required: true
        example: 693981f60fe7ba19d6907664
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
      responses:
        '200':
          description: Reenvío solicitado correctamente.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: 693981f60fe7ba19d6907664
                    required:
                    - id
              example:
                '0': 0
                status: 200
                data:
                  id: 693981f60fe7ba19d6907664
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '403':
          description: Error al enviar correo de activación.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '404':
          description: Usuario o compañía no encontrados.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '405':
          description: Usuario autenticado sin permisos admin.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 405
                message: USER_NOT_ALLOWED
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /users/activate/{token}:
    get:
      operationId: activateUserAccountByToken
      summary: Activar cuenta de usuario por token
      deprecated: false
      description: '## Propósito

        Intenta activar una cuenta usando el token recibido por correo.


        ## Objetivo

        Completar la validación inicial de email para habilitar el acceso del usuario.


        ## Casos de uso

        - El usuario hace clic en el enlace de activación enviado por email.

        - Reintento de activación cuando el token sigue siendo válido.


        ## Detalles técnicos

        Ruta pública que responde con HTML renderizado (`email_account_activate_ok`
        o `email_account_activate_ko`).

        El comportamiento actual observable no es completamente estable: la implementación
        usa símbolos no definidos

        en algunas ramas (`TruckerUser`, variable `error`), por lo que también pueden
        aparecer errores 500 no controlados.


        ## Autenticación

        No requiere autenticación.

        '
      tags:
      - users
      parameters:
      - name: token
        in: path
        description: Token de activación enviado por email.
        required: true
        example: 7f4a8e6bc0d341f7b4d37a1d
        schema:
          type: string
          minLength: 8
          maxLength: 256
      responses:
        '200':
          description: Plantilla HTML de activación (éxito o fallo funcional de token).
          content:
            text/html:
              schema:
                type: string
                example: <html><body>Account activated successfully</body></html>
          headers: {}
        '500':
          description: Error interno no controlado durante la activación (comportamiento
            observable actual).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: TruckerUser is not defined
          headers: {}
        '503':
          description: Fallo al renderizar la plantilla HTML.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 503
                message: renderTemplate
          headers: {}
      security: []
  /users/me:
    get:
      operationId: getCurrentUserProfile
      summary: Obtener perfil del usuario autenticado
      deprecated: false
      description: '## Propósito

        Devuelve los datos del usuario autenticado.


        ## Objetivo

        Permitir a la interfaz cargar el perfil actual sin conocer el ID explícitamente.


        ## Casos de uso

        - Cargar la pantalla "Mi perfil" al iniciar sesión.

        - Consultar rol y tipo de cuenta para habilitar funcionalidades.


        ## Detalles técnicos

        Usa `CompanyUser.parseMe` y responde con `tools.returnOK`, por lo que el contrato
        real de éxito está envuelto

        en `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters: []
      responses:
        '200':
          description: Perfil del usuario autenticado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/User'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 507f1f77bcf86cd799439011
                  name: Juan
                  lastname: Pérez García
                  email: juan.perez@empresa.com
                  role: gestor
                  status: true
                  accountType: company
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: Usuario no encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: USER_NOT_FOUND
          headers: {}
        '500':
          description: Error interno durante la consulta.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: CIA_NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    put:
      operationId: updateCurrentUserProfile
      summary: Actualizar perfil del usuario autenticado
      deprecated: false
      description: '## Propósito

        Actualiza los campos editables del perfil del usuario autenticado.


        ## Objetivo

        Permitir mantenimiento de datos de contacto y preferencias del perfil actual.


        ## Casos de uso

        - Corregir nombre o apellidos del usuario.

        - Cambiar teléfono o datos de perfil permitidos.


        ## Detalles técnicos

        Ejecuta validación con `validateData` y responde con `tools.returnOK`/`tools.returnKO`.

        El contrato de éxito usa wrapper `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserUpdate'
            example:
              name: Juan Carlos
              lastname: Pérez García López
              phone: '+34612345678'
      responses:
        '200':
          description: Perfil actualizado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/User'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 507f1f77bcf86cd799439011
                  name: Juan Carlos
                  lastname: Pérez García López
                  email: juan.perez@empresa.com
          headers: {}
        '400':
          description: Datos inválidos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: DATA_NOT_VALID
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Usuario no encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: USER_NOT_FOUND
          headers: {}
        '406':
          description: Email ya existe en otro usuario.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 406
                message: USER_ALREADY_EXIST
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /users/password/{id}:
    post:
      operationId: resetUserPassword
      summary: Resetear contraseña de usuario
      deprecated: false
      description: '## Propósito

        Genera una contraseña temporal nueva para el usuario objetivo.


        ## Objetivo

        Permitir recuperación administrativa de credenciales.


        ## Casos de uso

        - Usuario no puede acceder y requiere reseteo.

        - Política interna de renovación manual de credenciales.


        ## Detalles técnicos

        Solo `admin`. Se genera contraseña con `generatePass`, se actualiza `password`
        y se envía email.

        El éxito devuelve `{ status, data, 0 }` con `data.id`.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters:
      - name: id
        in: path
        description: ObjectId del usuario objetivo.
        required: true
        example: 693981f60fe7ba19d6907664
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
      responses:
        '200':
          description: Contraseña restablecida.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: 693981f60fe7ba19d6907664
              example:
                '0': 0
                status: 200
                data:
                  id: 693981f60fe7ba19d6907664
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '403':
          description: Error al actualizar contraseña.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '404':
          description: Usuario o compañía no encontrados.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '405':
          description: Usuario autenticado sin permisos admin.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 405
                message: USER_NOT_ALLOWED
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /users/status/{id}:
    post:
      operationId: changeUserStatus
      summary: Cambiar estado de usuario
      deprecated: false
      description: '## Propósito

        Actualiza el estado (`status`) de un usuario de la compañía.


        ## Objetivo

        Habilitar o bloquear cuentas de forma administrativa.


        ## Casos de uso

        - Bloquear temporalmente un usuario.

        - Reactivar una cuenta tras revisión.


        ## Detalles técnicos

        Solo `admin`. Aplica `updateOne` con el body recibido.

        El éxito devuelve `{ status, data, 0 }` con el `id` modificado.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters:
      - name: id
        in: path
        description: ObjectId del usuario objetivo.
        required: true
        example: 693981f60fe7ba19d6907664
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: boolean
                  description: Estado final deseado para el usuario.
                  example: true
              required:
              - status
            examples:
              activate:
                summary: Activar usuario
                value:
                  status: true
              deactivate:
                summary: Desactivar usuario
                value:
                  status: false
      responses:
        '200':
          description: Estado actualizado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: 693981f60fe7ba19d6907664
              example:
                '0': 0
                status: 200
                data:
                  id: 693981f60fe7ba19d6907664
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '403':
          description: Error al aplicar el cambio de estado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers: {}
        '404':
          description: Usuario o compañía no encontrados.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '405':
          description: Usuario autenticado sin permisos admin.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 405
                message: USER_NOT_ALLOWED
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /users/{id}:
    get:
      operationId: getUserDetailsById
      summary: Obtener detalle de usuario por ID
      deprecated: false
      description: '## Propósito

        Devuelve el detalle de un usuario concreto dentro de la compañía.


        ## Objetivo

        Permitir a perfiles administrativos consultar información completa de otro
        usuario.


        ## Casos de uso

        - Abrir ficha de un usuario desde el listado.

        - Verificar rol/estado antes de realizar acciones administrativas.


        ## Detalles técnicos

        Requiere rol `admin` y que el usuario objetivo pertenezca a la misma compañía.

        Si no se cumple, el comportamiento actual devuelve `USER_NOT_FOUND` (404).

        Éxito envuelto en `{ status, data, 0 }`.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters:
      - name: id
        in: path
        description: ObjectId del usuario objetivo.
        required: true
        example: 693981f60fe7ba19d6907664
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
      responses:
        '200':
          description: Detalle de usuario.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/User'
              example:
                '0': 0
                status: 200
                data:
                  _id: 693981f60fe7ba19d6907664
                  name: Ana
                  lastname: Martínez
                  email: ana@empresa.com
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Usuario no encontrado, compañía no encontrada o sin permisos
            admin (comportamiento actual).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '500':
          description: Error interno del servidor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: CIA_NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    put:
      operationId: updateUserById
      summary: Actualizar usuario por ID
      deprecated: false
      description: '## Propósito

        Actualiza un usuario usando el ID de la URL.


        ## Objetivo

        Permitir edición administrativa de usuarios de la misma compañía.


        ## Casos de uso

        - Cambiar nombre, apellidos o teléfono de un usuario interno.


        ## Detalles técnicos

        Misma lógica funcional que `PUT /users/` pero con ID en path.

        Requiere rol `admin`; faltas de permisos se materializan como `USER_NOT_FOUND`
        (404) en la implementación actual.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters:
      - name: id
        in: path
        description: ObjectId del usuario a actualizar.
        required: true
        example: 693981f60fe7ba19d6907664
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserUpdate'
            example:
              name: Ana María
              lastname: Martínez Ruiz
              phone: '+34612345678'
      responses:
        '200':
          description: Usuario actualizado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/User'
              example:
                '0': 0
                status: 200
                data:
                  _id: 693981f60fe7ba19d6907664
                  name: Ana María
                  lastname: Martínez Ruiz
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '404':
          description: Usuario no encontrado, compañía no encontrada o falta de permisos
            admin.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '406':
          description: Email ya existe en otro usuario.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 406
                message: USER_ALREADY_EXIST
          headers: {}
        '500':
          description: Error interno en resolución de modelos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 500
                message: CIA_NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    delete:
      operationId: deleteUserById
      summary: Eliminar usuario
      deprecated: false
      description: '## Propósito

        Elimina un usuario de la compañía y retira su referencia del array `users`.


        ## Objetivo

        Permitir limpieza de cuentas internas no vigentes.


        ## Casos de uso

        - Baja de un empleado.

        - Retirada de una cuenta creada por error.


        ## Detalles técnicos

        Solo `admin`. La validación de permisos la hace `checkHasPermisions`.

        Éxito en wrapper `{ status, data, 0 }` con `data.id`.


        ## Autenticación

        Requiere JWT Bearer o API Key válidos.

        '
      tags:
      - users
      parameters:
      - name: id
        in: path
        description: ObjectId del usuario a eliminar.
        required: true
        example: 693982450fe7ba19d69076b0
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
      responses:
        '200':
          description: Usuario eliminado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: 693982450fe7ba19d69076b0
                    required:
                    - id
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  id: 693982450fe7ba19d69076b0
          headers: {}
        '401':
          description: Token/API Key ausente o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
          headers: {}
        '403':
          description: Error al ejecutar la eliminación.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 403
                message: Cast to ObjectId failed
          headers: {}
        '404':
          description: Usuario o compañía no encontrados.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                user_not_found:
                  value:
                    status: 404
                    message: USER_NOT_FOUND
                cia_not_found:
                  value:
                    status: 404
                    message: CIA_NOT_FOUND
          headers: {}
        '405':
          description: Usuario autenticado sin permisos de administrador.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 405
                message: USER_NOT_ALLOWED
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /vehicles/:
    get:
      operationId: listMyFleet
      summary: Listar vehículos de la flota
      deprecated: false
      description: "## Propósito\nDevuelve el listado paginado de vehículos de la\
        \ flota del\ntransportista autenticado.\n\n## Objetivo\nPermitir que los transportistas\
        \ consulten y gestionen su flota\nde vehículos para asignarlos a eCMRs de\
        \ transporte.\n\n## Casos de uso\n- Listar todos los vehículos de la flota\
        \ en el panel de gestión.\n- Obtener el vehículo por defecto del conductor\
        \ (`isSign=true`).\n- Paginar la flota cuando hay muchos vehículos registrados.\n\
        \n## Detalles técnicos\n- Busca la empresa que contiene al transportista autenticado\n\
        \  en su array `truckers`.\n- Pagina manualmente sobre `company.vehicles`\
        \ con populate.\n- Si `isSign=true` y el usuario tiene `default_vehicle`,\
        \ incluye\n  el campo `defaultVehicle` en la respuesta con los datos completos.\n\
        - Los parámetros `search`, `autocomplete` y `extra` se aceptan por\n  compatibilidad\
        \ pero **no filtran** los resultados de vehículos.\n\n## Autenticación\nRequiere\
        \ JWT Bearer token o API Key.\n"
      tags:
      - vehicles
      parameters:
      - name: page
        in: query
        description: Número de página (empieza en 1)
        required: false
        example: 1
        schema:
          type: integer
          minimum: 1
          default: 1
      - name: limit
        in: query
        description: Número de vehículos por página
        required: false
        example: 10
        schema:
          type: integer
          minimum: 1
          default: 10
      - name: isSign
        in: query
        description: 'Si es `true`, incluye `defaultVehicle` en la respuesta cuando
          el usuario tiene un vehículo por defecto configurado.

          '
        required: false
        example: 'false'
        schema:
          type: string
          enum:
          - 'true'
          - 'false'
      - name: search
        in: query
        description: Parámetro legacy — sin efecto en vehículos
        required: false
        schema:
          type: string
          deprecated: true
      - name: autocomplete
        in: query
        description: Parámetro legacy — sin efecto en vehículos
        required: false
        schema:
          type: string
          deprecated: true
      - name: extra
        in: query
        description: Parámetro legacy — sin efecto en vehículos
        required: false
        schema:
          type: string
          deprecated: true
      responses:
        '200':
          description: Lista paginada de vehículos. Si `isSign=true`, puede incluir
            `defaultVehicle`
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas `returnOK`
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/PaginatedVehicleList'
                required:
                - status
                - data
                - '0'
              examples:
                fleet_list:
                  summary: Lista estándar de vehículos
                  value:
                    '0': 0
                    status: 200
                    data:
                      docs:
                      - _id: 6450d58755656096b9a92355
                        cargo_type:
                        - back
                        vehicle_type: ft
                        plate: 1234ABC
                        image: 6450d4b6.../image/20230530/1685428904859--truck.jpg
                        itv: 643700268.../itv/20251124/1763983649992--itv.pdf
                        shipping_type: dry
                        createdAt: '2023-05-02T09:19:03.864Z'
                        updatedAt: '2025-11-24T11:27:31.045Z'
                      totalDocs: 1
                      limit: 10
                      totalPages: 1
                      page: 1
                      pagingCounter: 1
                      hasPrevPage: false
                      hasNextPage: false
                      prevPage: null
                      nextPage: null
                with_default_vehicle:
                  summary: Incluye vehículo por defecto (isSign=true)
                  value:
                    '0': 0
                    status: 200
                    data:
                      docs: []
                      totalDocs: 0
                      limit: 10
                      totalPages: 0
                      page: 1
                      pagingCounter: 1
                      hasPrevPage: false
                      hasNextPage: false
                      prevPage: null
                      nextPage: null
                      defaultVehicle:
                        _id: 6450d58755656096b9a92355
                        cargo_type:
                        - back
                        vehicle_type: ft
                        plate: 1234ABC
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido, o error al
            cargar la flota
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token JWT inválido o expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
                cant_send:
                  summary: Error interno al procesar la flota (populate falló)
                  value:
                    status: 401
                    message: CANT_SEND
          headers: {}
        '404':
          description: No se encontró la empresa asociada al transportista autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: COMPANY_NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    post:
      operationId: createVehicle
      summary: Crear vehículo
      deprecated: false
      description: "## Propósito\nRegistra un nuevo vehículo en la flota del transportista\
        \ autenticado.\n\n## Objetivo\nPermitir que los transportistas añadan vehículos\
        \ a su flota para\npoder asignarlos a eCMRs de transporte.\n\n## Casos de\
        \ uso\n- Dar de alta un nuevo vehículo con su matrícula y tipo.\n- Subir la\
        \ foto del vehículo y el documento ITV al registrarlo.\n- Registrar un vehículo\
        \ refrigerado con temperatura de carga.\n\n## Flujo de creación\n```mermaid\n\
        flowchart TD\n  A[Recibir POST /vehicles/] --> B{¿Empresa encontrada?}\n \
        \ B -->|No| C[404 COMPANY_NOT_FOUND]\n  B -->|Sí| D[Parsear cargo_type como\
        \ JSON]\n  D --> E[Crear vehículo con Vehicle.createData]\n  E --> F{¿Se enviaron\
        \ archivos?}\n  F -->|Sí| G[Asignar keys S3 a image/itv]\n  F -->|No| H[Sin\
        \ archivos]\n  G --> I[Guardar vehículo en BD]\n  H --> I\n  I --> J[Añadir\
        \ a company.vehicles]\n  J --> K[200 OK - Vehículo creado]\n```\n\n## Detalles\
        \ técnicos\n- Acepta `multipart/form-data` con archivos opcionales `image`\
        \ e `itv`.\n- Los archivos se suben a **S3** mediante `multerS3.any()`.\n\
        - El campo `cargo_type` se envía como JSON string en el form-data,\n  debe\
        \ ser un array JSON válido (ejemplo: `[\"back\"]`).\n- El vehículo se añade\
        \ al array `company.vehicles` de la empresa.\n- URL OpenAPI: `/vehicles/`\
        \ (alta de vehículo).\n\n## Autenticación\nRequiere JWT Bearer token o API\
        \ Key.\n"
      tags:
      - vehicles
      parameters: []
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                cargo_type:
                  type: string
                  description: 'Tipos de carga como JSON string array. Valores posibles:
                    `back`, `up`, `lateral`.

                    '
                  example: '["back"]'
                fresh_cargo_temp:
                  type: number
                  description: Temperatura para carga refrigerada (solo si `shipping_type`
                    es `fresh`)
                  example: 4
                vehicle_type:
                  type: string
                  description: Código del tipo de vehículo (ver GET /vehicles/types)
                  enum:
                  - NONE
                  - r3c
                  - tir
                  - rt
                  - r2c
                  - r2d
                  - van
                  - frc
                  - f2c
                  - adr
                  - ft
                  - pt
                  - cc
                  - hdcc
                  - dump
                  - live
                  - cocar
                  example: tir
                plate:
                  type: string
                  description: Matrícula del vehículo
                  example: 1234abc
                shipping_type:
                  type: string
                  description: Tipo de envío (seco o refrigerado)
                  enum:
                  - fresh
                  - dry
                  example: dry
                image:
                  type: string
                  format: binary
                  description: Foto del vehículo (se sube a S3)
                itv:
                  type: string
                  format: binary
                  description: Documento ITV del vehículo (se sube a S3)
            example:
              cargo_type: '["back"]'
              vehicle_type: ft
              plate: 1234abc
              shipping_type: dry
      responses:
        '200':
          description: Vehículo creado y añadido a la flota de la empresa
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/Vehicle'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 6450d58755656096b9a92355
                  cargo_type:
                  - back
                  vehicle_type: ft
                  plate: 1234abc
                  image: /images?file=6450d4b6.../image/20230530/1685428904859--truck.jpg
                  itv: 643700268.../itv/20251124/1763983649992--itv.pdf
                  shipping_type: dry
                  fresh_cargo_temp: null
          headers: {}
        '400':
          description: Error de validación de payload o error al guardar el vehículo
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                cargo_type_invalid:
                  summary: '`cargo_type` no es un JSON array válido'
                  value:
                    status: 400
                    message: CARGO_TYPE_INVALID
                cant_create:
                  summary: Error al guardar en base de datos
                  value:
                    status: 400
                    message: CANT_CREATE
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token JWT inválido o expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: No se encontró la compañía asociada al usuario autenticado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: COMPANY_NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /vehicles/types:
    get:
      operationId: getVehicleTypes
      summary: Obtener tipos de vehículo
      deprecated: false
      description: "## Propósito\nDevuelve el catálogo maestro de tipos de vehículo\
        \ disponibles.\n\n## Objetivo\nProporcionar la lista de tipos de vehículo\
        \ habilitados para usar\nen selectores al crear o editar un vehículo de la\
        \ flota.\n\n## Casos de uso\n- Rellenar un selector de tipo de vehículo en\
        \ el formulario de alta.\n- Consultar los tipos disponibles con sus traducciones.\n\
        \n## Detalles técnicos\n- Filtra por `visible: true` en la colección `VehicleType`.\n\
        - Ordena alfabéticamente por `name`.\n- Excluye campos internos: `_id`, `langs._id`,\
        \ `createdAt`,\n  `updatedAt`, `deleted`, `__v`.\n- Cada tipo incluye `code`\
        \ y array `langs` con traducciones\n  por idioma (`es`, `en`).\n\n## Autenticación\n\
        Requiere JWT Bearer token o API Key.\n"
      tags:
      - vehicles
      parameters: []
      responses:
        '200':
          description: Lista de tipos de vehículo visibles con traducciones
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    description: Campo legacy presente en respuestas `returnOK`
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/VehicleType'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                - visible: true
                  code: tir
                  langs:
                  - lang: es
                    name: articulado de carga general
                  - lang: en
                    name: general cargo articulated
                - visible: true
                  code: van
                  langs:
                  - lang: es
                    name: furgoneta
                  - lang: en
                    name: van
          headers: {}
        '400':
          description: Error inesperado al consultar los tipos de vehículo
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: CANT_GET
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token JWT inválido o expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /vehicles/{id}:
    get:
      operationId: getVehicleDetails
      summary: Obtener detalle de vehículo
      deprecated: false
      description: "## Propósito\nObtiene el detalle completo de un vehículo específico\
        \ de la flota.\n\n## Objetivo\nMostrar toda la información de un vehículo\
        \ para su visualización\no edición en la interfaz.\n\n## Casos de uso\n- Ver\
        \ los detalles completos de un vehículo seleccionado.\n- Precargar el formulario\
        \ de edición con los datos actuales.\n\n## Detalles técnicos\n- Verifica que\
        \ el vehículo (`id`) pertenezca al array\n  `company.vehicles` de la empresa\
        \ del transportista.\n- Si el vehículo no pertenece o no existe, devuelve\
        \ `404 NOT_FOUND`.\n- Devuelve los datos parseados con `Vehicle.parseDetails()`.\n\
        \n## Autenticación\nRequiere JWT Bearer token o API Key.\n"
      tags:
      - vehicles
      parameters:
      - name: id
        in: path
        description: ObjectId del vehículo
        required: true
        example: 6450d58755656096b9a92355
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
      responses:
        '200':
          description: Detalle completo del vehículo
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/Vehicle'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 6450d58755656096b9a92355
                  cargo_type:
                  - back
                  vehicle_type: ft
                  plate: 1234ABC
                  image: 6450d4b6.../image/20230530/1685428904859--truck.jpg
                  itv: 643700268.../itv/20251124/1763983649992--itv.pdf
                  shipping_type: dry
                  fresh_cargo_temp: null
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token JWT inválido o expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: 'El vehículo no existe o no pertenece a la flota del transportista
            autenticado.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    put:
      operationId: updateVehicle
      summary: Actualizar vehículo
      deprecated: false
      description: "## Propósito\nActualiza los datos de un vehículo existente de\
        \ la flota.\n\n## Objetivo\nPermitir modificar los datos de un vehículo (tipo,\
        \ matrícula,\ntipo de envío, carga, temperatura) y actualizar sus archivos.\n\
        \n## Casos de uso\n- Cambiar la matrícula o el tipo de vehículo.\n- Actualizar\
        \ el tipo de envío de seco a refrigerado.\n- Subir una nueva foto o documento\
        \ ITV.\n\n## Flujo de actualización\n```mermaid\nflowchart TD\n  A[Recibir\
        \ PUT /vehicles/{id}] --> B{¿Empresa encontrada?}\n  B -->|No| C[404 NOT_FOUND]\n\
        \  B -->|Sí| D{¿Vehículo en company.vehicles?}\n  D -->|No| E[404 NOT_FOUND]\n\
        \  D -->|Sí| F{¿Vehículo existe en BD?}\n  F -->|No| G[404 NOT_FOUND]\n  F\
        \ -->|Sí| H[Parsear cargo_type como JSON]\n  H --> I[Actualizar con Vehicle.updateData]\n\
        \  I --> J[Guardar vehículo]\n  J --> K[200 OK - Vehículo actualizado]\n```\n\
        \n## Detalles técnicos\n- Verifica pertenencia del vehículo al array `company.vehicles`.\n\
        - Acepta `multipart/form-data` con archivos opcionales vía `multerS3.any()`.\n\
        - El campo `cargo_type` se parsea desde JSON string y debe\n  ser un array\
        \ JSON válido (ejemplo: `[\"back\"]`).\n- Actualiza campos con `Vehicle.updateData()`.\n\
        - URL OpenAPI: `/vehicles/{id}` (no `/vehicles/:id`).\n\n## Autenticación\n\
        Requiere JWT Bearer token o API Key.\n"
      tags:
      - vehicles
      parameters:
      - name: id
        in: path
        description: ObjectId del vehículo a actualizar
        required: true
        example: 6450d58755656096b9a92355
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                cargo_type:
                  type: string
                  description: Tipos de carga como JSON string array
                  example: '["back"]'
                fresh_cargo_temp:
                  type: number
                  description: Temperatura para carga refrigerada
                  example: 2
                vehicle_type:
                  type: string
                  description: Código del tipo de vehículo
                  enum:
                  - NONE
                  - r3c
                  - tir
                  - rt
                  - r2c
                  - r2d
                  - van
                  - frc
                  - f2c
                  - adr
                  - ft
                  - pt
                  - cc
                  - hdcc
                  - dump
                  - live
                  - cocar
                  example: ft
                plate:
                  type: string
                  description: Matrícula del vehículo
                  example: 1234abc
                shipping_type:
                  type: string
                  description: Tipo de envío
                  enum:
                  - fresh
                  - dry
                  example: dry
                image:
                  type: string
                  format: binary
                  description: Nueva foto del vehículo (se sube a S3)
                itv:
                  type: string
                  format: binary
                  description: Nuevo documento ITV (se sube a S3)
            example:
              cargo_type: '["back"]'
              vehicle_type: ft
              plate: 1234abc
              shipping_type: dry
      responses:
        '200':
          description: Vehículo actualizado exitosamente
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    $ref: '#/components/schemas/Vehicle'
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 6450d58755656096b9a92355
                  cargo_type:
                  - back
                  vehicle_type: ft
                  plate: 1234abc
                  image: /images?file=6450d4b6.../image/20230530/1685428904859--truck.jpg
                  itv: 643700268.../itv/20251124/1763983649992--itv.pdf
                  shipping_type: dry
                  fresh_cargo_temp: null
          headers: {}
        '400':
          description: Error de validación de payload o error al guardar cambios del
            vehículo
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                cargo_type_invalid:
                  summary: '`cargo_type` no es un JSON array válido'
                  value:
                    status: 400
                    message: CARGO_TYPE_INVALID
                cant_update:
                  summary: Error al guardar en base de datos
                  value:
                    status: 400
                    message: CANT_UPDATE
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token JWT inválido o expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: 'El vehículo no existe o no pertenece a la flota del transportista
            autenticado.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
    delete:
      operationId: deleteVehicle
      summary: Eliminar vehículo
      deprecated: false
      description: "## Propósito\nElimina (soft-delete) un vehículo de la flota y\
        \ lo desasocia\nde la empresa.\n\n## Objetivo\nPermitir que los transportistas\
        \ den de baja vehículos que ya\nno están en servicio.\n\n## Casos de uso\n\
        - Un vehículo se vende y se elimina de la flota.\n- Se registró por error\
        \ y se necesita eliminar.\n\n## Flujo de eliminación\n```mermaid\nflowchart\
        \ TD\n  A[Recibir DELETE /vehicles/:id] --> B{¿Empresa encontrada?}\n  B -->|No|\
        \ C[404 NOT_FOUND]\n  B -->|Sí| D{¿Vehículo en company.vehicles?}\n  D -->|No|\
        \ E[404 NOT_FOUND]\n  D -->|Sí| F{¿Vehículo existe en BD?}\n  F -->|No| G[404\
        \ NOT_FOUND]\n  F -->|Sí| H[Soft-delete del vehículo]\n  H --> I[Eliminar\
        \ ID del array company.vehicles]\n  I --> J[200 OK - ID del vehículo eliminado]\n\
        ```\n\n## Detalles técnicos\n- Verifica existencia y pertenencia a `company.vehicles`.\n\
        - Utiliza **soft-delete** (`mongoose-delete`).\n- Elimina el ID del array\
        \ `company.vehicles` y guarda la empresa.\n- Devuelve solo el `_id` del vehículo\
        \ eliminado.\n\n## Autenticación\nRequiere JWT Bearer token o API Key.\n"
      tags:
      - vehicles
      parameters:
      - name: id
        in: path
        description: ObjectId del vehículo a eliminar
        required: true
        example: 6450d58755656096b9a92355
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
      responses:
        '200':
          description: Vehículo eliminado (soft-delete) y desasociado de la empresa
          content:
            application/json:
              schema:
                type: object
                properties:
                  '0':
                    type: integer
                    example: 0
                  status:
                    type: integer
                    example: 200
                  data:
                    type: object
                    properties:
                      _id:
                        type: string
                        description: ObjectId del vehículo eliminado
                        example: 6450d58755656096b9a92355
                    required:
                    - _id
                required:
                - status
                - data
                - '0'
              example:
                '0': 0
                status: 200
                data:
                  _id: 6450d58755656096b9a92355
          headers: {}
        '400':
          description: Error al eliminar el vehículo de la base de datos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: CANT_DELETE
          headers: {}
        '401':
          description: Token JWT o API Key ausente, expirado o inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                no_token_or_apikey:
                  summary: No se proporcionó token JWT ni API Key
                  value:
                    status: 401
                    message: NO_TOKEN_OR_APIKEY
                token_not_valid:
                  summary: Token JWT inválido o expirado
                  value:
                    status: 401
                    message: TOKEN_NOT_VALID
          headers: {}
        '404':
          description: 'El vehículo no existe o no pertenece a la flota del transportista
            autenticado.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
          headers: {}
      security:
      - bearerAuth: []
      - apiKeyAuth: []
  /vehicles/{id}/trailer:
    put:
      operationId: assignTrailerToVehicle
      summary: Asignar remolque a un vehículo
      deprecated: false
      description: '## Propósito

        Asignar un remolque existente a un vehículo de la flota.

        ## Objetivo

        Vincular un remolque (`trailer._id`) a un vehículo para completar

        la configuración del conjunto tractor-remolque en un eCMR.

        ## Casos de uso

        - Asignar el remolque correcto para una ruta específica.

        - Actualizar la configuración del vehículo antes de crear un eCMR.

        - Cambiar el remolque asignado por mantenimiento o disponibilidad.

        ## Detalles técnicos

        - Endpoint real: `PUT /vehicles/{id}/trailer`.

        - Recibe `{ "trailer": "<trailer_id>" }` en el body.

        - Requiere que el vehículo pertenezca a la empresa autenticada.

        - Requiere que el remolque exista y pertenezca a la misma empresa.

        - Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.

        ## Autenticación

        Requiere JWT Bearer token o API Key.

        '
      tags:
      - vehicles
      parameters:
      - name: id
        in: path
        description: ObjectId del vehículo
        required: true
        example: 6450d58755656096b9a92355
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - trailer
              properties:
                trailer:
                  type: string
                  description: ObjectId del remolque a asignar.
                  example: 6450d58755656096b9a92399
      responses:
        '200':
          description: Remolque asignado correctamente al vehículo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: Error de validación o asignación.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: CANT_UPDATE
        '401':
          description: Token JWT o API Key ausente, expirado o inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 401
                message: NO_TOKEN_OR_APIKEY
        '404':
          description: Vehículo o remolque no encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: NOT_FOUND
        '409':
          description: Conflicto (remolque ya asignado a otro vehículo).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 409
                message: TRAILER_ALREADY_ASSIGNED
      security:
      - bearerAuth: []
      - apiKeyAuth: []
components:
  schemas:
    Address:
      type: object
      description: 'Representa una dirección física asociada a un usuario en el sistema
        ECMR.

        Incluye datos de geolocalización en formato GeoJSON y metadatos de uso.

        '
      properties:
        _id:
          type: string
          description: Identificador único de la dirección (ObjectId de MongoDB)
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
          example: 507f1f77bcf86cd799439011
        user:
          type: string
          description: Usuario propietario de la dirección (ObjectId)
          pattern: ^[0-9a-fA-F]{24}$
          minLength: 24
          maxLength: 24
          example: 507f1f77bcf86cd799439012
        name:
          type: string
          minLength: 1
          description: 'Nombre descriptivo de la dirección (ej. ''Oficina Central'',

            ''Almacén''). Debe ser único para el usuario.'
          example: Oficina Central
        company_name:
          type: string
          minLength: 1
          description: Nombre de la empresa asociada a la dirección
          example: Cargoffer SL
        phone:
          type: string
          description: Número de teléfono de contacto para la dirección (opcional)
          example: +34 912 345 678
        city:
          type: string
          description: Ciudad de la dirección
          example: Madrid
        state:
          type: string
          description: Estado o provincia de la dirección
          example: Madrid
        country:
          type: string
          description: País de la dirección (en formato texto completo)
          example: España
        zipcode:
          type: string
          description: Código postal de la dirección
          example: '28013'
        street_number:
          type: string
          description: Número de la calle
          example: '10'
        street_address:
          type: string
          description: Nombre de calle/ruta de la dirección
          example: Calle Mayor
        neighborhood:
          type: string
          description: Barrio o sublocalidad (si aplica)
          example: Centro
        province:
          type: string
          description: Provincia o división administrativa secundaria
          example: Madrid
        location:
          type: object
          description: Coordenadas geográficas en formato GeoJSON
          properties:
            type:
              type: string
              description: Tipo GeoJSON
              enum:
              - Point
              example: Point
            coordinates:
              type: array
              description: Coordenadas GeoJSON en formato [lng, lat]
              items:
                type: number
              minItems: 2
              maxItems: 2
              example:
              - -3.7038
              - 40.4168
          required:
          - type
          - coordinates
        placeId:
          type: string
          description: Identificador de lugar devuelto por Google Maps
          example: ChIJgTwKgJcpQg0RaSKMYcHeNsQ
        name_address:
          type: string
          description: Dirección formateada completa
          example: Calle Mayor 10, 28013 Madrid, España
        destinations:
          type: array
          description: Destinos auxiliares asociados a la dirección
          items:
            type: object
            properties:
              address:
                type: string
                description: ID de dirección destino
                example: 507f1f77bcf86cd799439011
              minimalRoute:
                type: object
                properties:
                  distance:
                    type: number
                    example: 120.5
                  timeCost:
                    type: number
                    example: 95
        can_be_deleted:
          type: boolean
          description: 'Indica si la dirección puede ser eliminada (false si está
            en uso en

            ECMRs activos)'
          example: true
        isDefault:
          type: boolean
          description: Indica si esta es la dirección por defecto del usuario
          example: false
        createdAt:
          type: string
          format: date-time
          description: Fecha de creación en formato ISO 8601
          example: '2025-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          description: Fecha de última actualización en formato ISO 8601
          example: '2025-01-15T10:30:00.000Z'
        deleted:
          type: boolean
          description: Estado de borrado lógico (plugin mongoose-delete)
          example: false
      required:
      - _id
      - name
      - company_name
    PaginatedAddressList:
      type: object
      description: Respuesta paginada con lista de direcciones
      properties:
        docs:
          type: array
          items:
            $ref: '#/components/schemas/Address'
          description: Array de direcciones en la página actual
        totalDocs:
          type: integer
          description: Total de direcciones disponibles
          example: 25
        limit:
          type: integer
          description: Número de direcciones por página
          example: 10
        totalPages:
          type: integer
          description: Total de páginas disponibles
          example: 3
        page:
          type: integer
          description: Página actual
          example: 1
        pagingCounter:
          type: integer
          description: Contador de paginación
          example: 1
        hasPrevPage:
          type: boolean
          description: Indica si hay página anterior
          example: false
        hasNextPage:
          type: boolean
          description: Indica si hay página siguiente
          example: true
        prevPage:
          type: integer
          description: Número de página anterior o null
          nullable: true
          example: null
        nextPage:
          type: integer
          description: Número de página siguiente o null
          nullable: true
          example: 2
    AddressCreate:
      type: object
      description: 'Datos requeridos para crear una nueva dirección. Soporta geocodificación

        automática vía Google Maps.'
      properties:
        name:
          type: string
          minLength: 1
          description: Nombre único de la dirección
          example: Oficina Central
        company_name:
          type: string
          minLength: 1
          description: Nombre de la empresa
          example: Cargoffer SL
        phone:
          type: string
          description: Teléfono de contacto (opcional)
          example: +34 912 345 678
        addressGoogleMaps:
          type: object
          description: 'Objeto de Google Places utilizado para parsear ubicación y
            campos de

            dirección.'
          properties:
            place_id:
              type: string
              description: Identificador de lugar de Google
              example: ChIJLdx3TsE5Lw0Rmh7qsvQUqII
            formatted_address:
              type: string
              description: Dirección completa formateada por Google
              example: Calle de la Logística, 123, 28045 Madrid, España
            address_components:
              type: array
              description: Componentes de dirección devueltos por Google
              items:
                type: object
                properties:
                  long_name:
                    type: string
                    example: Madrid
                  short_name:
                    type: string
                    example: MD
                  types:
                    type: array
                    items:
                      type: string
                    example:
                    - locality
                    - political
                required:
                - long_name
                - types
            geometry:
              type: object
              properties:
                location:
                  type: object
                  properties:
                    lat:
                      type: number
                      example: 40.39242
                    lng:
                      type: number
                      example: -3.69462
                  required:
                  - lat
                  - lng
              required:
              - location
            plus_code:
              type: object
              properties:
                global_code:
                  type: string
                  example: 8CGRXQRC+X5
                compound_code:
                  type: string
                  example: XQRC+X5 Madrid, Spain
            types:
              type: array
              items:
                type: string
              example:
              - street_address
          required:
          - place_id
          - address_components
          - formatted_address
          - geometry
        isDefault:
          type: boolean
          description: Establecer como dirección por defecto (opcional)
          example: true
      required:
      - name
      - company_name
    AddressUpdate:
      type: object
      description: 'Datos para actualizar una dirección existente. Similar a creación,

        permite geocodificación.'
      properties:
        name:
          type: string
          minLength: 1
          description: Nuevo nombre único (opcional)
          example: Oficina Central Modificada
        company_name:
          type: string
          minLength: 1
          description: Nuevo nombre de empresa (opcional)
          example: Cargoffer SL
        phone:
          type: string
          description: Nuevo teléfono (opcional)
          example: +34 912 345 679
        addressGoogleMaps:
          type: object
          description: Datos de Google Maps para actualizar ubicación (opcional)
          properties:
            place_id:
              type: string
              description: Identificador de lugar de Google
              example: ChIJLdx3TsE5Lw0Rmh7qsvQUqII
            formatted_address:
              type: string
              description: Dirección completa formateada por Google
              example: Calle Mayor 10, 28014 Madrid, España
            address_components:
              type: array
              description: Componentes de dirección devueltos por Google
              items:
                type: object
                properties:
                  long_name:
                    type: string
                    example: Madrid
                  short_name:
                    type: string
                    example: MD
                  types:
                    type: array
                    items:
                      type: string
                    example:
                    - locality
                    - political
                required:
                - long_name
                - types
            geometry:
              type: object
              properties:
                location:
                  type: object
                  properties:
                    lat:
                      type: number
                      example: 40.4168
                    lng:
                      type: number
                      example: -3.7038
                  required:
                  - lat
                  - lng
              required:
              - location
            plus_code:
              type: object
              properties:
                global_code:
                  type: string
                  example: 8CGRXQRC+X5
                compound_code:
                  type: string
                  example: XQRC+X5 Madrid, Spain
            types:
              type: array
              items:
                type: string
              example:
              - street_address
        isDefault:
          type: boolean
          description: Cambiar estado de dirección por defecto (opcional)
          example: false
    ApiKey:
      type: object
      description: Información de clave API (con datos enmascarados para seguridad)
      properties:
        apikey:
          type: string
          description: Clave API enmascarada (solo muestra los últimos 4 caracteres)
          example: '************abcd'
        temp_code:
          type: string
          description: Código temporal único para identificar la clave API
          example: aB3dE5fG7hI9jK
        role:
          type: string
          description: Rol de la clave API
          enum:
          - dev
          - gestor
          - admin
          example: admin
        createdAt:
          type: string
          format: date-time
          description: Fecha de creación de la clave API
          example: '2024-01-15T10:30:00.000Z'
    ApiKeyList:
      type: object
      description: Lista de claves API del usuario
      properties:
        apikeys:
          type: array
          items:
            $ref: '#/components/schemas/ApiKey'
      example:
        apikeys:
        - apikey: '************abcd'
          temp_code: aB3dE5fG7hI9jK
          role: admin
          createdAt: '2024-01-15T10:30:00.000Z'
        - apikey: '************efgh'
          temp_code: mN8oP1qR3sT5uV
          role: dev
          createdAt: '2024-01-20T14:45:00.000Z'
    ApiKeyCreate:
      type: object
      description: Datos para crear una nueva clave API
      properties:
        type:
          type: string
          description: Tipo/rol de la clave API (opcional)
          enum:
          - dev
          - gestor
          - admin
          default: admin
          example: admin
    ApiKeyCreated:
      type: object
      description: Respuesta al crear una nueva clave API
      properties:
        apikey:
          type: string
          description: Clave API completa (solo se muestra una vez al crearla)
          example: C4dE6fG8hI0jK2l
    ApiKeyListResponse:
      type: object
      description: Respuesta de éxito para el listado de claves API
      properties:
        '0':
          type: integer
          example: 0
        status:
          type: integer
          example: 200
        data:
          $ref: '#/components/schemas/ApiKeyList'
      required:
      - status
      - data
      - '0'
    ApiKeyCreatedResponse:
      type: object
      description: Respuesta de éxito para la creación de clave API
      properties:
        '0':
          type: integer
          example: 0
        status:
          type: integer
          example: 200
        data:
          $ref: '#/components/schemas/ApiKeyCreated'
      required:
      - status
      - data
      - '0'
    ApiKeyDeleteResponse:
      type: object
      description: Respuesta de éxito para la eliminación de clave API
      properties:
        '0':
          type: integer
          example: 0
        status:
          type: integer
          example: 200
        data:
          type: object
          properties: {}
          example: {}
      required:
      - status
      - data
      - '0'
    SuccessResponse:
      type: object
      description: Respuesta de éxito genérica
      properties:
        '0':
          type: integer
          example: 0
        status:
          type: integer
          example: 200
        data:
          description: Carga útil devuelta por el endpoint
          example: {}
      required:
      - status
      - data
      - '0'
    Error:
      type: object
      description: Respuesta de error estándar
      properties:
        status:
          type: integer
          description: Código de estado HTTP
          example: 404
        message:
          type: string
          description: Mensaje descriptivo del error
          example: USER_NOT_FOUND
      required:
      - status
      - message
    LoginRequest:
      type: object
      description: Credenciales para iniciar sesión
      properties:
        email:
          type: string
          format: email
          description: Email del usuario
          example: user@company.com
        password:
          type: string
          description: Contraseña del usuario
          example: SecurePass123!
        accountType:
          type: string
          enum:
          - company
          - trucker
          description: Tipo de cuenta del usuario
          example: company
      required:
      - email
      - password
    RegisterRequest:
      type: object
      description: 'Datos completos para registrar una nueva cuenta.

        Incluye información de facturación, usuario y dirección.

        '
      properties:
        accountType:
          type: string
          enum:
          - company
          - trucker
          description: Tipo de cuenta a crear
          example: company
        socialName:
          type: string
          description: Nombre social de la empresa
          example: Mi Empresa SL
        invoice_data:
          type: object
          description: Datos de facturación obligatorios
          properties:
            taxid:
              type: string
              description: NIF/CIF de la empresa (obligatorio)
              example: B12345678
            phone:
              type: string
              description: Teléfono de facturación
              example: +34 912 345 678
          required:
          - taxid
        user:
          type: object
          description: Datos del usuario administrador
          properties:
            name:
              type: string
              description: Nombre del usuario
              example: Juan
            lastname:
              type: string
              description: Apellidos del usuario
              example: Pérez García
            email:
              type: string
              format: email
              description: Email del usuario (único en el sistema)
              example: juan.perez@empresa.com
            password:
              type: string
              description: Contraseña del usuario
              example: SecurePass123!
          required:
          - name
          - lastname
          - email
          - password
        address:
          type: object
          description: Dirección principal de la empresa
          properties:
            street:
              type: string
              description: Calle y número
              example: Calle Mayor 10
            city:
              type: string
              description: Ciudad
              example: Madrid
            state:
              type: string
              description: Provincia
              example: Madrid
            country:
              type: string
              description: País
              example: España
            postalCode:
              type: string
              description: Código postal
              example: '28013'
            phone:
              type: string
              description: Teléfono de contacto
              example: +34 912 345 678
            email:
              type: string
              format: email
              description: Email de contacto
              example: contacto@empresa.com
        lang:
          type: string
          description: Idioma preferido (opcional)
          example: es
        ref_code:
          type: string
          description: Código de referencia (opcional)
          example: REF123
      required:
      - accountType
      - socialName
      - invoice_data
      - user
      - address
    RecoveryRequest:
      type: object
      description: Solicitud de recuperación de contraseña
      properties:
        email:
          type: string
          format: email
          description: Email del usuario que solicita recuperación
          example: user@company.com
      required:
      - email
    RecoveryPasswordRequest:
      type: object
      description: Cambio de contraseña usando token de recuperación
      properties:
        token:
          type: string
          description: Token de recuperación recibido por email
          example: abc123def456ghi789
        password:
          type: string
          description: Nueva contraseña
          example: NewSecurePass123!
      required:
      - token
      - password
    TokenResponse:
      type: object
      description: Respuesta exitosa de login con token JWT
      properties:
        token:
          type: string
          description: Token JWT para autenticación en futuras requests
          example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
        expiresIn:
          type: integer
          format: int64
          description: Timestamp de expiración del token (milisegundos desde epoch)
          example: 1638360000000
        hasPaymentMethod:
          type: boolean
          description: Indica si la cuenta tiene método de pago configurado
          example: true
        hasVehicles:
          type: boolean
          description: Para transportistas - indica si tiene vehículos registrados
          example: false
        hasDrivers:
          type: boolean
          description: Para transportistas - indica si tiene conductores registrados
          example: true
        accountType:
          type: string
          enum:
          - company
          - trucker
          description: Tipo de cuenta del usuario
          example: company
        hasSign:
          type: boolean
          description: Indica si la cuenta tiene firma digital configurada
          example: false
    Category:
      type: object
      description: Categoría de carga encontrada (heading o subheading)
      properties:
        title:
          type: string
          description: Título de la categoría (limpio, sin prefijo 'heading')
          example: live animals
        label:
          type: string
          description: Etiquetas de la categoría unidas y limpiadas
          example: animales vivos
        url:
          type: string
          description: URL de referencia (solo para headings, vacío para subheadings)
          example: https://example.com/live-animals
    CategoryList:
      type: array
      description: Lista de categorías encontradas
      items:
        $ref: '#/components/schemas/Category'
      example:
      - title: live animals
        label: animales vivos
        url: https://example.com/live-animals
      - title: meat and edible meat offal
        label: carne y despojos comestibles
        url: https://example.com/meat
      - title: fish and crustaceans
        label: pescado y crustáceos
        url: ''
    CompanyData:
      type: object
      description: Datos completos de la empresa (company o trucker)
      properties:
        _id:
          type: string
          description: ID único de la empresa
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          description: Nombre de la empresa
          example: Transportes Pérez SL
        socialName:
          type: string
          description: Nombre social/legal de la empresa
          example: Transportes Pérez Sociedad Limitada
        ref_code:
          type: string
          description: Código de referencia
          example: TP2025
        address:
          type: object
          description: Dirección completa de la empresa
          properties:
            email:
              type: string
              format: email
              description: Email de contacto
              example: contacto@transportes-perez.com
            phone:
              type: string
              description: Teléfono de contacto
              example: '+34612345678'
            street_address:
              type: string
              description: Dirección de la calle
              example: Calle Mayor, 123
            street_number:
              type: string
              description: Número de la calle
              example: '123'
            city:
              type: string
              description: Ciudad
              example: Madrid
            state:
              type: string
              description: Provincia/Estado
              example: Madrid
            country:
              type: string
              description: Código de país (3 letras)
              example: esp
            zipcode:
              type: string
              description: Código postal
              example: '28001'
            neighborhood:
              type: string
              description: Barrio
              example: Centro
            province:
              type: string
              description: Provincia
              example: Madrid
            location:
              type: object
              description: Coordenadas geográficas
              properties:
                type:
                  type: string
                  enum:
                  - Point
                  example: Point
                coordinates:
                  type: array
                  items:
                    type: number
                  description:
                  - longitud
                  - latitud
                  example:
                  - -3.70379
                  - 40.416775
            place_id:
              type: string
              description: ID de lugar de Google Maps
              example: ChIJgTwKgJcpQg0RaSKMYcHeNs
            name_address:
              type: string
              description: Nombre de la dirección
              example: Sede Central
            timezone:
              type: string
              description: Zona horaria
              example: Europe/Madrid
        addresses:
          type: array
          description: Lista de IDs de direcciones adicionales
          items:
            type: string
          example:
          - 507f1f77bcf86cd799439012
        address_default:
          type: string
          description: ID de la dirección por defecto
          example: 507f1f77bcf86cd799439012
        invoice_data:
          type: object
          description: Datos de facturación
          properties:
            taxid:
              type: string
              description: Número de identificación fiscal
              example: B12345678
            email:
              type: string
              format: email
              description: Email para facturación
              example: facturacion@transportes-perez.com
            phone:
              type: string
              description: Teléfono para facturación
              example: '+34612345678'
            name_address:
              type: string
              description: Nombre de dirección de facturación
              example: Dirección Fiscal
            bank_account:
              type: string
              description: Número de cuenta bancaria (IBAN)
              example: ES9121000418450200051332
            account_holder:
              type: string
              description: Titular de la cuenta bancaria
              example: Transportes Pérez SL
        contact_person:
          type: object
          description: Persona de contacto
          properties:
            name:
              type: string
              description: Nombre de la persona de contacto
              example: Juan
            lastname:
              type: string
              description: Apellidos de la persona de contacto
              example: Pérez García
            email:
              type: string
              format: email
              description: Email de la persona de contacto
              example: juan.perez@transportes-perez.com
            phone:
              type: string
              description: Teléfono de la persona de contacto
              example: '+34612345678'
        status:
          type: boolean
          description: Estado activo de la empresa
          example: true
        reason:
          type: string
          description: Razón del estado actual
          enum:
          - BAD_USER
          - NONE
          - PENDING
          - ACTIVE
          - BLOCKED
          example: ACTIVE
        hasSign:
          type: boolean
          description: Indica si la empresa tiene firma digital
          example: true
        sign:
          type: string
          description: Firma digital en formato base64
          example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
        payment_settings:
          type: object
          description: Configuración de pagos (Stripe)
          properties:
            stripe_customer:
              type: string
              description: ID del cliente en Stripe
              example: cus_1234567890
        createdAt:
          type: string
          format: date-time
          description: Fecha de creación
          example: '2023-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          description: Fecha de última actualización
          example: '2024-01-15T14:20:00.000Z'
    CompanyDataUpdate:
      type: object
      description: Datos para actualizar la información de la empresa
      properties:
        name:
          type: string
          description: Nuevo nombre de la empresa
          example: Transportes Pérez y Asociados SL
        socialName:
          type: string
          description: Nuevo nombre social
          example: Transportes Pérez y Asociados Sociedad Limitada
        address:
          type: object
          description: Nueva dirección (incluye Google Maps data)
          properties:
            email:
              type: string
              format: email
              example: nuevo@empresa.com
            phone:
              type: string
              example: '+34687654321'
            street_address:
              type: string
              example: Avenida Principal, 456
            city:
              type: string
              example: Barcelona
            state:
              type: string
              example: Barcelona
            country:
              type: string
              example: esp
            zipcode:
              type: string
              example: 08001
            place_id:
              type: string
              example: ChIJ5TCOcRaYpBIRCmZHTz37sEQ
            name_address:
              type: string
              example: Nueva Sede
        invoice_data:
          type: object
          description: Nuevos datos de facturación
          properties:
            taxid:
              type: string
              example: B87654321
            email:
              type: string
              format: email
              example: facturacion@nuevaempresa.com
            phone:
              type: string
              example: '+34687654321'
            bank_account:
              type: string
              example: ES9121000418450200051333
            account_holder:
              type: string
              example: Nueva Empresa SL
        contact_person:
          type: object
          description: Nueva persona de contacto
          properties:
            name:
              type: string
              example: María
            lastname:
              type: string
              example: López Sánchez
            email:
              type: string
              format: email
              example: maria.lopez@nuevaempresa.com
            phone:
              type: string
              example: '+34687654321'
      additionalProperties: false
    ProfileComplete:
      type: object
      description: Estado de completitud del perfil de empresa
      properties:
        isCompleted:
          type: boolean
          description: Indica si el perfil está completo (status=true y reason='NONE')
          example: true
        isPaymentMethodSet:
          type: boolean
          description: 'Indica si el método de pago está configurado (Stripe customer

            existe)'
          example: true
    Country:
      type: object
      description: Información completa de un país según estándar internacional
      properties:
        code:
          type: string
          description: Código ISO 3166-1 alpha-2 del país
          example: ES
        enabled:
          type: boolean
          description: Indica si el país está habilitado en el sistema
          example: true
        altSpellings:
          type: array
          items:
            type: string
          description: Nombres alternativos del país
          example:
          - ES
          - Spain
          - España
        area:
          type: number
          description: Área del país en kilómetros cuadrados
          example: 505992
        borders:
          type: array
          items:
            type: string
          description: Códigos de países fronterizos
          example:
          - AD
          - FR
          - GI
          - PT
          - MA
        callingCode:
          type: array
          items:
            type: string
          description: Códigos de llamada telefónica
          example:
          - '34'
        capital:
          type: string
          description: Capital del país
          example: Madrid
        cca2:
          type: string
          description: Código ISO 3166-1 alpha-2
          example: ES
        cca3:
          type: string
          description: Código ISO 3166-1 alpha-3
          example: ESP
        ccn3:
          type: string
          description: Código numérico ISO 3166-1
          example: '724'
        cioc:
          type: string
          description: Código del Comité Olímpico Internacional
          example: ESP
        currency:
          type: array
          items:
            type: string
          description: Monedas oficiales
          example:
          - EUR
        demonym:
          type: string
          description: Gentilicio
          example: Spanish
        landlocked:
          type: boolean
          description: Indica si el país no tiene costa
          example: false
        languages:
          type: object
          description: Idiomas oficiales con códigos ISO 639-1
          properties: {}
          example:
            spa: Spanish
        latlng:
          type: array
          items:
            type: number
          description: Coordenadas geográficas [latitud, longitud]
          example:
          - 40
          - -4
        name:
          type: object
          description: Nombres del país en diferentes formatos
          properties:
            common:
              type: string
              description: Nombre común
              example: Spain
            official:
              type: string
              description: Nombre oficial
              example: Kingdom of Spain
            native:
              type: object
              description: Nombres nativos por idioma
              properties: {}
          example:
            common: Spain
            official: Kingdom of Spain
        region:
          type: string
          description: Región continental
          example: Europe
        subregion:
          type: string
          description: Subregión
          example: Southern Europe
        tld:
          type: array
          items:
            type: string
          description: Dominios de nivel superior
          example:
          - .es
        translations:
          type: object
          description: Traducciones del nombre del país
          properties: {}
          example:
            deu:
              common: Spanien
              official: Königreich Spanien
        createdAt:
          type: string
          format: date-time
          description: Fecha de creación del registro
        updatedAt:
          type: string
          format: date-time
          description: Fecha de última actualización
    PaginatedCountryList:
      type: object
      description: Respuesta paginada con lista de países
      properties:
        docs:
          type: array
          items:
            $ref: '#/components/schemas/Country'
          description: Array de países en la página actual
        totalDocs:
          type: integer
          description: Total de países disponibles
          example: 195
        limit:
          type: integer
          description: Número de países por página
          example: 10
        totalPages:
          type: integer
          description: Total de páginas disponibles
          example: 20
        page:
          type: integer
          description: Página actual
          example: 1
        pagingCounter:
          type: integer
          description: Contador de paginación
          example: 1
        hasPrevPage:
          type: boolean
          description: Indica si hay página anterior
          example: false
        hasNextPage:
          type: boolean
          description: Indica si hay página siguiente
          example: true
        prevPage:
          type: integer
          description: Número de página anterior o null
          nullable: true
          example: null
        nextPage:
          type: integer
          description: Número de página siguiente o null
          nullable: true
          example: 2
    Driver:
      type: object
      description: Perfil de conductor devuelto por `parseDriver`
      properties:
        _id:
          type: string
          description: ID del conductor
          example: 643700268a290ac6df9237cf
        name:
          type: string
          description: Nombre
          example: Aureliano
        lastname:
          type: string
          description: Apellidos
          example: Dariel
        email:
          type: string
          format: email
          description: Email del conductor
          example: trucker@testing.com
        phone:
          type: string
          description: Telefono del conductor
          example: '644333555'
        taxid:
          type: string
          description: Identificacion fiscal
          example: 33222444K
        address:
          type: string
          description: Direccion libre (si existe en el documento)
          example: rua nova de abaixo, 7
        accountType:
          type: string
          description: Campo legacy si existe en el documento
          example: default
        default_vehicle:
          type: string
          description: ID de vehiculo por defecto
          example: 69241a6793ebf2871d670bbe
        emailVerified:
          type: boolean
          description: Estado de verificacion del email
          example: false
        birthDate:
          type: string
          format: date-time
          description: Fecha de nacimiento
          example: '1995-08-11T00:00:00.000Z'
        country:
          type: string
          description: Codigo de pais
          example: esp
        timezone:
          type: string
          description: Zona horaria
          example: europe/madrid
        i18n:
          type: string
          description: Idioma preferido
          example: es
        refresh_time:
          type: integer
          enum:
          - 1
          - 3
          - 5
          - 10
          description: Frecuencia de refresco configurada
          example: 3
        createdAt:
          type: string
          format: date-time
          description: Fecha de creacion
          example: '2024-01-16T11:56:26.895Z'
        image:
          type: string
          nullable: true
          description: Ruta de imagen o null
          example: /images?file=643700268a290ac6df9237cf/image/20240116/profile.jpg
      required:
      - _id
      - email
    DriverList:
      type: object
      description: Resultado de `customPagination` para conductores
      properties:
        docs:
          type: array
          items:
            $ref: '#/components/schemas/Driver'
        totalDocs:
          type: integer
          example: 1
        limit:
          type: integer
          example: 10
        totalPages:
          type: integer
          example: 1
        page:
          type: integer
          example: 1
        pagingCounter:
          type: integer
          example: 1
        hasPrevPage:
          type: boolean
          example: false
        hasNextPage:
          type: boolean
          example: false
        prevPage:
          type: integer
          nullable: true
          example: null
        nextPage:
          type: integer
          nullable: true
          example: null
      required:
      - docs
      - totalDocs
      - limit
      - totalPages
      - page
      - pagingCounter
      - hasPrevPage
      - hasNextPage
      - prevPage
      - nextPage
    DriverCreate:
      type: object
      description: Payload de creacion de conductor (runtime requiere `email`)
      properties:
        email:
          type: string
          format: email
          description: Campo obligatorio para crear el conductor
          example: trucker@testing.com
        name:
          type: string
          example: Aureliano
        lastname:
          type: string
          example: Dariel
        phone:
          type: string
          example: '644333555'
        birthDate:
          type: string
          format: date-time
          description: Debe cumplir validacion de mayoria de edad
          example: '1995-08-11T00:00:00.000Z'
        taxid:
          type: string
          example: 33222444K
        country:
          type: string
          example: esp
        timezone:
          type: string
          example: europe/madrid
        i18n:
          type: string
          description: Aceptado, pero el runtime lo sobreescribe con i18n del usuario
            autenticado
          example: es
        default_vehicle:
          type: string
          example: 69241a6793ebf2871d670bbe
        role:
          type: string
          enum:
          - admin
          - dev
          - gestor
          - driver
          example: driver
        accountType:
          type: string
          description: Campo opcional legacy
          example: default
        address:
          type: string
          description: Direccion libre opcional
          example: rua nova de abaixo, 7
      required:
      - email
    DriverUpdate:
      type: object
      description: Payload de actualizacion de conductor
      properties:
        email:
          type: string
          format: email
          description: Si cambia, valida colision y puede devolver USER_ALREADY_EXIST
          example: trucker@testing.com
        name:
          type: string
          example: Aureliano
        lastname:
          type: string
          example: Dariel
        phone:
          type: string
          example: '644333555'
        birthDate:
          type: string
          format: date-time
          description: Debe cumplir validacion de mayoria de edad
          example: '1995-08-11T00:00:00.000Z'
        taxid:
          type: string
          example: 33222444K
        country:
          type: string
          example: esp
        timezone:
          type: string
          example: europe/madrid
        i18n:
          type: string
          example: es
        default_vehicle:
          type: string
          example: 69241a6793ebf2871d670bbe
        role:
          type: string
          enum:
          - admin
          - dev
          - gestor
          - driver
          example: driver
        accountType:
          type: string
          description: Campo opcional legacy
          example: default
        address:
          type: string
          description: Direccion libre opcional
          example: rua nova de abaixo, 7
    ECMR:
      type: object
      description: Documento de nota de consignación electrónica completa
      properties:
        _id:
          type: string
          description: ID único del ECMR en la base de datos
        service_code:
          type: string
          description: Código de servicio único generado automáticamente
          example: ECMR-2025-001
        custom_code:
          type: string
          description: Código personalizado asignado por la empresa
          maxLength: 50
          example: CUSTOM-001
        status:
          type: string
          enum:
          - planned
          - accepted
          - collected
          - locked
          - issue
          - delivered
          - claimed
          - canceled
          description: Estado actual del ECMR
          example: planned
        owner:
          type: string
          enum:
          - company
          description: Tipo de propietario del ECMR
          example: company
        price:
          type: number
          description: Precio del servicio
          example: 150.5
        company:
          $ref: '#/components/schemas/CompanyRef'
        company_user:
          $ref: '#/components/schemas/UserRef'
        trucker_cia:
          $ref: '#/components/schemas/TruckerCiaRef'
        trucker_user:
          $ref: '#/components/schemas/UserRef'
        trucker_vehicle:
          $ref: '#/components/schemas/VehicleRef'
        etl_address:
          $ref: '#/components/schemas/AddressRef'
        etd_address:
          $ref: '#/components/schemas/AddressRef'
        etl_date:
          type: string
          format: date-time
          description: Fecha y hora de recogida (ETL - Estimated Time of Loading)
          example: '2024-01-15T08:00:00.000Z'
        etd_date:
          type: string
          format: date-time
          description: Fecha y hora de entrega (ETD - Estimated Time of Delivery)
          example: '2024-01-16T14:00:00.000Z'
        etl_extra_time:
          type: number
          description: Tiempo extra permitido para recogida en minutos
          example: 60
        etd_extra_time:
          type: number
          description: Tiempo extra permitido para entrega en minutos
          example: 120
        description:
          type: string
          description: Descripción del envío
          maxLength: 2000
          example: Envío de mercancía electrónica
        info_extra:
          type: string
          description: Información adicional
          maxLength: 2000
          example: Manejar con cuidado
        plate_full_trailer:
          type: string
          description: Matrícula del remolque completo
          maxLength: 15
          example: 1234ABC
        pallets_num:
          type: integer
          description: Número de palets
          minimum: 0
          maximum: 66
          example: 12
        pallets_type:
          type: string
          enum:
          - european
          - american
          - none
          description: Tipo de palets
          example: european
        is_fresh:
          type: boolean
          description: Indica si es carga refrigerada
          example: false
        fresh_cargo_temp:
          type: number
          description: Temperatura de carga refrigerada en grados
          minimum: -273
          maximum: 1000
          example: 4
        linear_meters:
          type: number
          description: Metros lineales de carga
          minimum: 0
          maximum: 1360
          example: 8.5
        cargo_height:
          type: number
          description: Altura de la carga en cm
          minimum: 0
          maximum: 2400
          example: 180
        cargo_type:
          type: string
          enum:
          - pallets
          - full
          - package
          - trailer
          description: Tipo de carga
          example: pallets
        hscode:
          type: string
          description: Código HS (Harmonized System)
          example: '84713000'
        cargo_weight:
          type: number
          description: Peso de la carga en kg
          minimum: 0
          maximum: 24000
          example: 1500
        is_imperial_measure:
          type: boolean
          description: Indica si usa medidas imperiales
          example: false
        etl_cargo_method:
          type: string
          enum:
          - back
          - up
          - lateral
          description: Método de carga en origen
          example: back
        had_etl_cargo_method:
          type: boolean
          description: Indica si ya se especificó el método de carga en origen
          example: false
        etd_cargo_method:
          type: string
          enum:
          - back
          - up
          - lateral
        example:
          description: Updated Frozen Fish Transport
          info_extra: Updated handling instructions
          pallets_num: 33
          cargo_weight: 24000
          cargo_height: 220
          etl_cargo_method: back
          etd_cargo_method: back
          example: back
        had_etd_cargo_method:
          type: boolean
          description: Indica si ya se especificó el método de descarga en destino
          example: false
        etl_photos:
          type: array
          description: URLs de fotos de recogida
          items:
            type: string
          example:
          - /images/photo1.jpg
          - /images/photo2.jpg
        etl_comment:
          type: string
          description: Comentario de recogida
          example: Carga recogida sin incidencias
        etd_photos:
          type: array
          description: URLs de fotos de entrega
          items:
            type: string
          example:
          - /images/delivery1.jpg
          - /images/delivery2.jpg
        etd_comment:
          type: string
          description: Comentario de entrega
          example: Entrega realizada correctamente
        sign_image_trucker:
          type: string
          description: URL de imagen de firma del transportista
          example: /images/signature_trucker.png
        sign_image_cia:
          type: string
          description: URL de imagen de firma de la empresa
          example: /images/signature_company.png
        sign_pickup:
          type: string
          description: URL de imagen de firma de recogida
          example: /images/signature_pickup.png
        sign_delivery:
          type: string
          description: URL de imagen de firma de entrega
          example: /images/signature_delivery.png
        sign_pickup_date:
          type: string
          format: date-time
          description: Fecha de firma de recogida
          example: '2024-01-15T09:30:00.000Z'
        sign_delivery_date:
          type: string
          format: date-time
          description: Fecha de firma de entrega
          example: '2024-01-16T15:45:00.000Z'
        signed_by_trucker:
          type: boolean
          description: Indica si el transportista ha firmado
          example: true
        signed_by_company:
          type: boolean
          description: Indica si la empresa ha firmado
          example: true
        confirmed:
          type: boolean
          description: Indica si el ECMR está confirmado
          example: true
        temp_token:
          type: string
          description: Token temporal para compartir ECMR
        qr_token:
          type: string
          description: Token para códigos QR
        confirm_token:
          type: string
          description: Token de confirmación
        geolocationPickup:
          type: object
          description: Geolocalización de recogida
          properties:
            type:
              type: string
              example: Point
            coordinates:
              type: array
              items:
                type: number
              example:
              - -3.7038
              - 40.4168
        geolocationDelivery:
          type: object
          description: Geolocalización de entrega
          properties:
            type:
              type: string
              example: Point
            coordinates:
              type: array
              items:
                type: number
              example:
              - 2.1734
              - 41.3851
        documents:
          type: array
          description: Documentos adjuntos al ECMR
          items:
            type: object
            properties:
              owner_trucker:
                type: string
                description: ID del usuario transportista propietario
              owner_company:
                type: string
                description: ID del usuario empresa propietario
              path:
                type: string
                description: Ruta del archivo
        payment:
          type: object
          description: Información de pago
          properties:
            paid:
              type: boolean
              description: Indica si está pagado
              example: true
            payment_intent:
              type: string
              description: ID del intento de pago de Stripe
            pay_date:
              type: string
              format: date-time
              description: Fecha de pago
        createdAt:
          type: string
          format: date-time
          description: Fecha de creación
        updatedAt:
          type: string
          format: date-time
          description: Fecha de última actualización
    CompanyRef:
      type: object
      description: Referencia a empresa
      properties:
        _id:
          type: string
        name:
          type: string
        contact_person:
          type: object
          properties:
            phone:
              type: string
            email:
              type: string
    UserRef:
      type: object
      description: Referencia a usuario
      properties:
        _id:
          type: string
        name:
          type: string
        lastname:
          type: string
        email:
          type: string
    TruckerCiaRef:
      type: object
      description: Referencia a compañía transportista
      properties:
        _id:
          type: string
        name:
          type: string
        contact_person:
          type: object
          properties:
            phone:
              type: string
            email:
              type: string
    VehicleRef:
      type: object
      description: Referencia a vehículo
      properties:
        _id:
          type: string
        plate:
          type: string
    AddressRef:
      type: object
      description: Referencia a dirección
      properties:
        _id:
          type: string
        city:
          type: string
        zipcode:
          type: string
        country:
          type: string
        name:
          type: string
    ContactInfo:
      type: object
      description: Información de contacto del ECMR
      properties:
        name:
          type: string
          description: Nombre de la empresa o transportista
          example: Transportes García S.L.
        phone:
          type: string
          description: Teléfono de contacto
          example: '+34600123456'
        email:
          type: string
          description: Email de contacto
          example: contacto@transportesgarcia.com
    ECMRDetails:
      type: object
      description: Detalles del ECMR desde token QR
      properties:
        ecmr:
          $ref: '#/components/schemas/ECMR'
        token:
          type: string
    Document:
      type: object
      description: Documento adjunto al ECMR
      properties:
        _id:
          type: string
          description: ID único del documento
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          description: Nombre del archivo (extraído del path S3)
          example: factura_transportista.pdf
        owner_trucker:
          type: string
          description: 'ID del usuario transportista que subió el documento (solo
            presente

            si fue subido por transportista)'
          example: 507f1f77bcf86cd799439012
        owner_company:
          type: string
          description: 'ID del usuario empresa que subió el documento (solo presente
            si fue

            subido por empresa)'
          example: 507f1f77bcf86cd799439013
        path:
          type: string
          description: Ruta del archivo en S3
          example: ecmr-documents/ECMR-2025-001--factura_transportista.pdf
    DocumentList:
      type: array
      description: Lista de documentos adjuntos al ECMR
      items:
        $ref: '#/components/schemas/Document'
      example:
      - _id: 507f1f77bcf86cd799439011
        name: factura_transportista.pdf
        owner_trucker: 507f1f77bcf86cd799439012
      - _id: 507f1f77bcf86cd799439013
        name: certificado_seguro.pdf
        owner_company: 507f1f77bcf86cd799439014
    LinkCompanyRequest:
      type: object
      properties:
        company_id:
          type: string
          description: ID de la empresa a vincular
      required:
      - company_id
    CustomCodeRequest:
      type: object
      properties:
        custom_code:
          type: string
          description: Código personalizado
      required:
      - custom_code
    Issue:
      type: object
      description: Incidencia/ticket de soporte completo
      properties:
        _id:
          type: string
          description: ID único de la incidencia
          example: 507f1f77bcf86cd799439011
        internal_code:
          type: string
          description: Código interno de la incidencia (año-código)
          example: 2025-ABC123
        service_code:
          type: string
          description: Código de servicio relacionado (opcional)
          example: SRV-001
        status:
          type: string
          enum:
          - pending
          - open
          - resolved
          - cancelByClient
          - closed
          description: Estado actual de la incidencia
          example: open
        idAuthor:
          type: string
          description: ID del usuario que creó la incidencia
          example: 507f1f77bcf86cd799439012
        nameAuthor:
          type: string
          description: Nombre completo del autor
          example: Juan Pérez García
        author:
          type: string
          enum:
          - anonymous
          - company
          - trucker
          description: Tipo de autor
          example: company
        relatedTo:
          type: string
          enum:
          - config
          - delivery
          - auction
          - messages
          - others
          - user
          - trucker
          - address
          - document
          - bid
          - contract
          - userRegister
          description: Aspecto del sistema relacionado con la incidencia
          example: delivery
        message:
          type: string
          description: Mensaje inicial de la incidencia
          example: 'Problema con la entrega del CMR #12345'
        files:
          type: array
          items:
            type: string
          description: URLs de archivos adjuntos iniciales
          example:
          - /images?file=issue_001_doc.pdf
        messages:
          type: array
          items:
            $ref: '#/components/schemas/IssueMessage'
          description: Conversación/mensajes adicionales
        resolved:
          type: string
          format: date-time
          description: Fecha de resolución (si aplica)
          example: '2025-01-20T14:30:00.000Z'
        createdAt:
          type: string
          format: date-time
          description: Fecha de creación
          example: '2025-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          description: Fecha de última actualización
          example: '2025-01-15T12:45:00.000Z'
    IssueMessage:
      type: object
      description: Mensaje individual en una conversación de incidencia
      properties:
        authorId:
          type: string
          description: ID del autor del mensaje
          example: 507f1f77bcf86cd799439012
        authorName:
          type: string
          description: Nombre del autor del mensaje
          example: Juan Pérez García
        message:
          type: string
          description: Contenido del mensaje
          example: Hemos revisado el problema y está solucionado.
        createdAt:
          type: string
          format: date-time
          description: Fecha del mensaje
          example: '2025-01-15T11:00:00.000Z'
        files:
          type: array
          items:
            type: string
          description: URLs de archivos adjuntos al mensaje
          example:
          - /images?file=response_screenshot.png
    ServiceCode:
      type: string
      description: Código de servicio único
      example: SRV-001
    ServiceCodeList:
      type: array
      description: Lista de códigos de servicio disponibles
      items:
        $ref: '#/components/schemas/ServiceCode'
      example:
      - SRV-001
      - SRV-002
      - DEL-100
    Reason:
      type: object
      description: Motivo predefinido para incidencias
      properties:
        code:
          type: string
          description: Código del motivo
          example: DELIVERY_DELAY
        description:
          type: string
          description: Descripción del motivo
          example: Retraso en la entrega
        category:
          type: string
          description: Categoría del motivo
          example: delivery
    ReasonList:
      type: array
      description: Lista de motivos disponibles
      items:
        $ref: '#/components/schemas/Reason'
    PaginatedIssueList:
      type: object
      description: Respuesta paginada con lista de incidencias
      properties:
        docs:
          type: array
          items:
            $ref: '#/components/schemas/Issue'
          description: Array de incidencias en la página actual
        totalDocs:
          type: integer
          description: Total de incidencias disponibles
          example: 25
        limit:
          type: integer
          description: Número de incidencias por página
          example: 10
        totalPages:
          type: integer
          description: Total de páginas disponibles
          example: 3
        page:
          type: integer
          description: Página actual
          example: 1
        pagingCounter:
          type: integer
          description: Contador de paginación
          example: 1
        hasPrevPage:
          type: boolean
          description: Indica si hay página anterior
          example: false
        hasNextPage:
          type: boolean
          description: Indica si hay página siguiente
          example: true
        prevPage:
          type: integer
          description: Número de página anterior o null
          nullable: true
          example: null
        nextPage:
          type: integer
          description: Número de página siguiente o null
          nullable: true
          example: 2
    Notification:
      type: object
      description: 'Representa una notificación en el sistema ECMR.


        Las notificaciones se asocian a usuarios específicos (company_user o

        trucker_cia) y contienen


        información sobre eventos importantes como cambios de estado de eCMRs,

        pagos, etc.

        '
      properties:
        _id:
          type: string
          description: Identificador único de la notificación (ObjectId de MongoDB)
          example: 507f1f77bcf86cd799439011
        type:
          type: string
          enum:
          - ecmr_created
          - ecmr_accepted
          - ecmr_collected
          - ecmr_delivered
          - ecmr_canceled
          - ecmr_claimed
          - ecmr_locked
          - ecmr_issue
          - payment_received
          - payment_pending
          - payment_failed
          - user_invited
          - user_verified
          - system_maintenance
          - general
          description: Tipo de notificación que determina el contenido y comportamiento
          example: ecmr_delivered
        priority:
          type: string
          enum:
          - low
          - medium
          - high
          - urgent
          description: Nivel de prioridad de la notificación
          default: medium
          example: high
        title:
          type: string
          maxLength: 255
          description: Título descriptivo de la notificación
          example: Carga Entregada
        message:
          type: string
          maxLength: 1000
          description: Mensaje detallado de la notificación
          example: La carga de la eCMR ECM-12345 ha sido entregada correctamente.
        read:
          type: boolean
          description: Indica si la notificación ha sido leída por el usuario
          default: false
          example: false
        readAt:
          type: string
          format: date-time
          description: Fecha y hora cuando se marcó como leída (ISO 8601)
          nullable: true
          example: '2025-01-15T14:30:00.000Z'
        company_user:
          type: string
          description: 'ID del usuario de empresa destinatario (ObjectId). Solo se
            usa si es

            destinatario company_user.'
          nullable: true
          example: 507f1f77bcf86cd799439012
        trucker_cia:
          type: string
          description: 'ID de la empresa transportista destinataria (ObjectId). Solo
            se usa

            si es destinatario trucker_cia.'
          nullable: true
          example: 507f1f77bcf86cd799439013
        relatedEntityType:
          type: string
          enum:
          - ECMR
          - Payment
          - CompanyUser
          - Company
          description: Tipo de entidad relacionada con la notificación
          nullable: true
          example: ECMR
        relatedEntityId:
          type: string
          description: ID de la entidad relacionada (como service_code para eCMRs)
          nullable: true
          example: ECM-12345
        relatedEntityModel:
          type: string
          enum:
          - ECMR
          - Payment
          - CompanyUser
          - Company
          description: Modelo de la entidad relacionada (para documentación)
          nullable: true
          example: ECMR
        data:
          type: object
          description: Datos adicionales específicos del tipo de notificación
          properties: {}
          example:
            ecmrNumber: ECM-12345
            deliveryDate: '2025-01-15T12:00:00.000Z'
            driver:
              name: Juan
              lastname: Pérez
        emailSent:
          type: boolean
          description: Indica si se ha enviado email de notificación
          default: false
          example: true
        emailSentAt:
          type: string
          format: date-time
          description: Fecha y hora del envío del email (ISO 8601)
          nullable: true
          example: '2025-01-15T10:35:00.000Z'
        emailError:
          type: string
          description: Error ocurrido durante el envío del email (si aplica)
          nullable: true
          example: null
        pushSent:
          type: boolean
          description: Indica si se ha enviado push notification
          default: false
          example: false
        pushSentAt:
          type: string
          format: date-time
          description: Fecha y hora del envío de push notification (ISO 8601)
          nullable: true
          example: null
        pushError:
          type: string
          description: Error ocurrido durante el envío de push notification (si aplica)
          nullable: true
          example: null
        scheduledFor:
          type: string
          format: date-time
          description: Fecha y hora programada para envío (ISO 8601)
          nullable: true
          example: null
        sentAt:
          type: string
          format: date-time
          description: Fecha y hora real de envío (ISO 8601)
          nullable: true
          example: '2025-01-15T10:30:00.000Z'
        expiresAt:
          type: string
          format: date-time
          description: Fecha y hora de expiración automática (ISO 8601)
          nullable: true
          example: '2025-02-15T10:30:00.000Z'
        createdAt:
          type: string
          format: date-time
          description: Fecha de creación de la notificación (ISO 8601)
          example: '2025-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          description: Fecha de última actualización (ISO 8601)
          example: '2025-01-15T10:30:00.000Z'
      required:
      - type
      - title
      - message
    UnreadCountResponse:
      type: object
      description: Respuesta con el contador de notificaciones no leídas
      properties:
        count:
          type: integer
          description: Número de notificaciones no leídas
          example: 5
      required:
      - count
    NotificationTypesResponse:
      type: array
      description: Lista de tipos de notificación disponibles
      items:
        type: object
        properties:
          type:
            type: string
            description: Código del tipo de notificación
            example: ecmr_delivered
          category:
            type: string
            description: Categoría del tipo de notificación
            example: ecmr
          description:
            type: string
            description: Descripción del tipo de notificación
            example: Notificación enviada cuando una carga es entregada
    MarkAllReadResponse:
      type: object
      description: Respuesta al marcar todas las notificaciones como leídas
      properties:
        message:
          type: string
          description: Mensaje de confirmación
          example: All notifications marked as read
        modifiedCount:
          type: integer
          description: Número de notificaciones marcadas como leídas
          example: 8
      required:
      - message
      - modifiedCount
    DeleteNotificationResponse:
      type: object
      description: Respuesta al eliminar una notificación
      properties:
        _id:
          type: string
          description: ID de la notificación eliminada
          example: 507f1f77bcf86cd799439011
        message:
          type: string
          description: Mensaje de confirmación
          example: Notification deleted successfully
      required:
      - _id
      - message
    Profile:
      type: object
      description: Información completa del perfil de usuario (company o trucker)
      properties:
        _id:
          type: string
          description: ID único del usuario
          example: 507f1f77bcf86cd799439011
        name:
          type: string
          description: Nombre del usuario
          example: Juan
        lastname:
          type: string
          description: Apellidos del usuario
          example: Pérez García
        email:
          type: string
          format: email
          description: Correo electrónico del usuario
          example: juan.perez@empresa.com
        phone:
          type: string
          description: Número de teléfono
          example: '+34612345678'
        role:
          type: string
          description: Rol del usuario en el sistema
          enum:
          - admin
          - gestor
          - driver
          - dev
          example: gestor
        status:
          type: boolean
          description: Estado activo del usuario
          example: true
        reason:
          type: string
          description: Razón del estado actual
          enum:
          - BAD_USER
          - NONE
          - PENDING
          - ACTIVE
          - BLOCKED
          example: ACTIVE
        emailVerified:
          type: boolean
          description: Si el email está verificado
          example: true
        country:
          type: string
          description: Código de país (3 letras)
          example: esp
        timezone:
          type: string
          description: Zona horaria del usuario
          example: Europe/Madrid
        i18n:
          type: string
          description: Idioma preferido
          example: es
        taxid:
          type: string
          description: Número de identificación fiscal (solo para truckers)
          example: 12345678A
        image:
          type: string
          description: URL de la imagen de perfil (solo para truckers)
          example: /images?file=profile_123.jpg
        birthDate:
          type: string
          format: date-time
          description: Fecha de nacimiento
          example: '1985-06-15T00:00:00.000Z'
        refresh_time:
          type: integer
          description: Tiempo de refresco del token en minutos
          enum:
          - 1
          - 3
          - 5
          - 10
          example: 3
        createdAt:
          type: string
          format: date-time
          description: Fecha de creación del usuario
          example: '2023-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          description: Fecha de última actualización
          example: '2024-01-15T14:20:00.000Z'
    ProfileUpdate:
      type: object
      description: Datos para actualizar el perfil del usuario
      properties:
        name:
          type: string
          description: Nuevo nombre
          example: Juan Carlos
        lastname:
          type: string
          description: Nuevos apellidos
          example: Pérez López
        phone:
          type: string
          description: Nuevo número de teléfono
          example: '+34687654321'
        country:
          type: string
          description: Nuevo código de país
          example: esp
        timezone:
          type: string
          description: Nueva zona horaria
          example: Europe/Madrid
        i18n:
          type: string
          description: Nuevo idioma preferido
          example: en
        birthDate:
          type: string
          format: date
          description: Nueva fecha de nacimiento
          example: '1985-06-15T00:00:00.000Z'
      additionalProperties: true
    PasswordChange:
      type: object
      description: Datos para cambiar la contraseña
      properties:
        current:
          type: string
          description: Contraseña actual
          example: oldPassword123
        new_pass:
          type: string
          description: Nueva contraseña
          minLength: 8
          example: newSecurePassword123
        confirm_pass:
          type: string
          description: Confirmación de la nueva contraseña
          example: newSecurePassword123
      required:
      - current
      - new_pass
      - confirm_pass
    User:
      type: object
      description: Información completa de usuario (Company o Trucker)
      properties:
        _id:
          type: string
          description: ID único del usuario en la base de datos
          example: 507f1f77bcf86cd799439011
        role:
          type: string
          enum:
          - admin
          - dev
          - gestor
          - driver
          description: Rol del usuario en el sistema
          example: gestor
        status:
          type: boolean
          description: Estado activo/inactivo del usuario
          example: true
        reason:
          type: string
          enum:
          - BAD_USER
          - NONE
          - PENDING
          - ACTIVE
          - BLOCKED
          description: Razón del estado actual
          example: NONE
        reasonDate:
          type: string
          format: date-time
          description: Fecha de la razón del estado
        reasonMessage:
          type: string
          description: Mensaje adicional sobre la razón del estado
        name:
          type: string
          description: Nombre del usuario
          example: Juan
        lastname:
          type: string
          description: Apellidos del usuario
          example: Pérez García
        birthDate:
          type: string
          format: date
          description: Fecha de nacimiento (debe ser mayor de 18 años)
          example: '1990-05-15T00:00:00.000Z'
        emailVerified:
          type: boolean
          description: Indica si el email está verificado
          example: true
        emailVerifiedDate:
          type: string
          format: date-time
          description: Fecha de verificación del email
        email:
          type: string
          format: email
          description: Email del usuario (único en el sistema)
          example: juan.perez@empresa.com
        phone:
          type: string
          description: Teléfono del usuario
          minLength: 9
          maxLength: 15
          example: '+34612345678'
        refresh_time:
          type: integer
          enum:
          - 1
          - 3
          - 5
          - 10
          description: Tiempo de refresco del token en horas
          example: 3
        lastSignInAt:
          type: string
          format: date-time
          description: Última fecha de inicio de sesión
        lastSignInIp:
          type: string
          description: Última IP de inicio de sesión
        resetPasswordSentAt:
          type: string
          format: date-time
          description: Fecha de envío del último reset de contraseña
        country:
          type: string
          description: Código de país (3 letras)
          minLength: 3
          maxLength: 3
          example: esp
        timezone:
          type: string
          description: Zona horaria del usuario
          example: Europe/Madrid
        taxid:
          type: string
          description: NIF/CIF del usuario
          example: 12345678A
        img:
          type: string
          description: URL de la imagen de perfil
          example: /images?file=user123.jpg
        i18n:
          type: string
          description: Idioma preferido
          example: es
        accountType:
          type: string
          enum:
          - company
          - trucker
          description: Tipo de cuenta del usuario
          example: company
        createdAt:
          type: string
          format: date-time
          description: Fecha de creación
        updatedAt:
          type: string
          format: date-time
          description: Fecha de última actualización
    UserCreate:
      type: object
      description: Datos para crear un nuevo usuario
      required:
      - name
      - lastname
      - email
      - password
      - accountType
      properties:
        name:
          type: string
          description: Nombre del usuario
          example: Juan
        lastname:
          type: string
          description: Apellidos del usuario
          example: Pérez García
        email:
          type: string
          format: email
          description: Email del usuario (debe ser único)
          example: juan.perez@empresa.com
        password:
          type: string
          description: Contraseña del usuario (mínimo 8 caracteres)
          minLength: 8
          example: SecurePass123!
        phone:
          type: string
          description: Teléfono del usuario
          minLength: 9
          maxLength: 15
          example: '+34612345678'
        birthDate:
          type: string
          format: date
          description: Fecha de nacimiento (debe ser mayor de 18 años)
          example: '1990-05-15T00:00:00.000Z'
        taxid:
          type: string
          description: NIF/CIF del usuario
          example: 12345678A
        role:
          type: string
          enum:
          - admin
          - dev
          - gestor
          - driver
          description: 'Rol del usuario (por defecto ''gestor'' para company, ''driver''
            para

            trucker)'
          example: gestor
        country:
          type: string
          description: Código de país (3 letras)
          minLength: 3
          maxLength: 3
          example: esp
        timezone:
          type: string
          description: Zona horaria
          example: Europe/Madrid
        i18n:
          type: string
          description: Idioma preferido
          example: es
        accountType:
          type: string
          enum:
          - company
          - trucker
          description: Tipo de cuenta del usuario
          example: company
    UserUpdate:
      type: object
      description: Datos para actualizar un usuario existente
      properties:
        name:
          type: string
          description: Nombre del usuario
          example: Juan Carlos
        lastname:
          type: string
          description: Apellidos del usuario
          example: Pérez García López
        email:
          type: string
          format: email
          description: Email del usuario
          example: juancarlos.perez@empresa.com
        phone:
          type: string
          description: Teléfono del usuario
          minLength: 9
          maxLength: 15
          example: '+34612345678'
        birthDate:
          type: string
          format: date
          description: Fecha de nacimiento
          example: '1990-05-15T00:00:00.000Z'
        taxid:
          type: string
          description: NIF/CIF del usuario
          example: 12345678A
        country:
          type: string
          description: Código de país (3 letras)
          minLength: 3
          maxLength: 3
          example: esp
        timezone:
          type: string
          description: Zona horaria
          example: Europe/Madrid
        i18n:
          type: string
          description: Idioma preferido
          example: es
    StatusChange:
      type: object
      description: Datos para cambiar el estado de un usuario
      required:
      - status
      properties:
        status:
          type: string
          enum:
          - active
          - inactive
          description: Nuevo estado del usuario
          example: active
    Action:
      type: object
      description: Registro de acción histórica del usuario
      properties:
        _id:
          type: string
          description: ID único de la acción
        user:
          type: string
          description: ID del usuario que realizó la acción
        code:
          type: string
          description: Código de referencia de la acción
        relatedTo:
          type: string
          enum:
          - config
          - ecmr
          - auction
          - messages
          - others
          - users
          - issues
          - docs
          - address
          - vehicles
          - drivers
          - profile
          description: Entidad relacionada con la acción
          example: ecmr
        action:
          type: string
          enum:
          - others
          - read
          - create
          - update
          - delete
          - assign
          - unassign
          - close
          - reopen
          - cancel
          - resolve
          - edit
          - comment
          - revise
          - approbed
          description: Tipo de acción realizada
          example: update
        type:
          type: string
          enum:
          - vehicle
          - trucker_user
          - delivery
          - trucker_cia
          - settings
          - issue
          - company
          - ecmr
          - company_user
          - address
          description: Tipo de entidad afectada
          example: ecmr
        ref:
          type: string
          description: ID de la entidad referenciada
        resolved:
          type: string
          format: date-time
          description: Fecha de resolución (si aplica)
        createdAt:
          type: string
          format: date-time
          description: Fecha de creación de la acción
        updatedAt:
          type: string
          format: date-time
          description: Fecha de última actualización
    Access:
      type: object
      description: Registro de acceso del usuario
      properties:
        _id:
          type: string
          description: ID único del registro de acceso
        user:
          type: string
          description: ID del usuario que accedió
        ip:
          type: string
          description: Dirección IP del acceso
          example: 192.168.1.100
        browser:
          type: object
          description: Información del navegador
          properties:
            name:
              type: string
              description: Nombre del navegador
              example: chrome
            version:
              type: string
              description: Versión del navegador
              example: 91.0.4472.124
        os:
          type: object
          description: Información del sistema operativo
          properties:
            name:
              type: string
              description: Nombre del SO
              example: windows
            version:
              type: string
              description: Versión del SO
              example: '10'
        device:
          type: object
          description: Información del dispositivo
          properties:
            name:
              type: string
              description: Nombre del dispositivo
              example: desktop
            version:
              type: string
              description: Versión del dispositivo
        createdAt:
          type: string
          format: date-time
          description: Fecha y hora del acceso
    Vehicle:
      type: object
      description: Información completa de un vehículo
      properties:
        _id:
          type: string
          description: ID único del vehículo
          example: 507f1f77bcf86cd799439011
        cargo_type:
          type: array
          items:
            type: string
            enum:
            - NONE
            - up
            - lateral
            - back
          description: Tipos de carga que puede transportar
          example:
          - up
          - lateral
        fresh_cargo_temp:
          type: number
          description: Temperatura para carga refrigerada (en grados)
          example: 4
        vehicle_type:
          type: string
          description: Tipo de vehículo
          enum:
          - NONE
          - r3c
          - tir
          - rt
          - r2c
          - r2d
          - van
          - frc
          - f2c
          - adr
          - ft
          - pt
          - cc
          - hdcc
          - dump
          - live
          - cocar
          example: tir
        plate:
          type: string
          description: Matrícula del vehículo
          example: 1234ABC
        image:
          type: string
          description: URL de la imagen del vehículo
          example: /images?file=vehicle_123.jpg
        itv:
          type: string
          description: Documento ITV (clave S3)
          example: itv_document_123.pdf
        shipping_type:
          type: string
          description: Tipo de envío
          enum:
          - fresh
          - dry
          example: dry
    PaginatedVehicleList:
      type: object
      description: Respuesta paginada con lista de vehículos
      properties:
        docs:
          type: array
          items:
            $ref: '#/components/schemas/Vehicle'
          description: Array de vehículos en la página actual
        totalDocs:
          type: integer
          description: Total de vehículos disponibles
          example: 15
        limit:
          type: integer
          description: Número de vehículos por página
          example: 10
        totalPages:
          type: integer
          description: Total de páginas disponibles
          example: 2
        page:
          type: integer
          description: Página actual
          example: 1
        pagingCounter:
          type: integer
          description: Contador de paginación
          example: 1
        hasPrevPage:
          type: boolean
          description: Indica si hay página anterior
          example: false
        hasNextPage:
          type: boolean
          description: Indica si hay página siguiente
          example: true
        prevPage:
          type: integer
          description: Número de página anterior o null
          nullable: true
          example: null
        nextPage:
          type: integer
          description: Número de página siguiente o null
          nullable: true
          example: 2
        defaultVehicle:
          $ref: '#/components/schemas/Vehicle'
          description: Vehículo por defecto del usuario (opcional)
    VehicleType:
      type: object
      description: Tipo de vehículo disponible
      properties:
        visible:
          type: boolean
          description: Indica si el tipo está visible
          example: true
        code:
          type: string
          description: Código del tipo de vehículo
          example: tir
        langs:
          type: array
          description: Traducciones del nombre por idioma
          items:
            type: object
            properties:
              lang:
                type: string
                description: Código de idioma
                example: es
              name:
                type: string
                description: Nombre del tipo de vehículo
                example: Camión TIR
    BillingPricingTier:
      type: object
      description: Tier de pricing configurable para suscripciones ECMR
      properties:
        _id:
          type: string
          description: ObjectId del tier en MongoDB
          example: 65a6f0aa3c1234567890abcd
        code:
          type: string
          description: Código interno del tier
          example: BASIC
        name:
          type: string
          description: Nombre comercial del plan
          example: Basic Plan
        stripePriceId:
          type: string
          description: Identificador de precio en Stripe
          example: price_1QwErTy123
        priceCents:
          type: integer
          description: Precio del plan en céntimos
          example: 9900
        includedPetitions:
          type: integer
          description: Número de peticiones incluidas en el ciclo
          example: 500
        createdAt:
          type: string
          format: date-time
          description: Fecha de creación
          example: '2026-03-04T10:15:30.000Z'
        updatedAt:
          type: string
          format: date-time
          description: Fecha de última actualización
          example: '2026-03-04T10:15:30.000Z'
      required:
      - code
      - name
      - stripePriceId
      - priceCents
      - includedPetitions
    BillingPricingTiersResponse:
      type: object
      description: Respuesta de éxito del catálogo de tiers de billing
      properties:
        '0':
          type: integer
          example: 0
        status:
          type: integer
          example: 200
        data:
          type: array
          items:
            $ref: '#/components/schemas/BillingPricingTier'
      required:
      - status
      - data
      - '0'
    BillingCycle:
      type: object
      description: Estado de ciclo de facturación del usuario en ECMR
      properties:
        _id:
          type: string
          description: ObjectId del ciclo de facturación
          example: 65a6f0aa3c1234567890abce
        userProfile:
          type: string
          description: ObjectId del perfil de usuario asociado
          example: 65a6f0aa3c1234567890abcf
        pricingTier:
          oneOf:
          - type: string
            description: ObjectId del tier
          - $ref: '#/components/schemas/BillingPricingTier'
          description: Tier asociado al ciclo (id u objeto poblado)
        stripeSubscriptionId:
          type: string
          description: Identificador de suscripción Stripe
          example: sub_1QwErTy123
        status:
          type: string
          enum:
          - active
          - cancelled
          - suspended
          description: Estado interno normalizado del ciclo
          example: active
        currency:
          type: string
          description: Moneda ISO del ciclo
          example: eur
        startDate:
          type: string
          format: date-time
          description: Inicio del periodo actual
          example: '2026-03-01T00:00:00.000Z'
        endDate:
          type: string
          format: date-time
          description: Fin del periodo actual
          example: '2026-04-01T00:00:00.000Z'
        usageCount:
          type: integer
          description: Consumo acumulado registrado
          example: 12
        cancelAtPeriodEnd:
          type: boolean
          description: Si la suscripción cancelará al final del periodo
          example: false
        cancelAt:
          type: string
          format: date-time
          nullable: true
          description: Fecha prevista de cancelación si aplica
          example: null
        createdAt:
          type: string
          format: date-time
          description: Fecha de creación del ciclo
          example: '2026-03-01T00:00:00.000Z'
        updatedAt:
          type: string
          format: date-time
          description: Fecha de última actualización del ciclo
          example: '2026-03-04T09:00:00.000Z'
    BillingCheckStripeResponse:
      type: object
      description: Respuesta de estado de suscripción del usuario autenticado
      properties:
        '0':
          type: integer
          example: 0
        status:
          type: integer
          example: 200
        data:
          allOf:
          - $ref: '#/components/schemas/BillingCycle'
          nullable: true
          description: '''null'' cuando no existe perfil/ciclo para el usuario'
      required:
      - status
      - data
      - '0'
    BillingCheckoutSessionRequest:
      type: object
      description: Datos necesarios para crear una sesión de Stripe Checkout
      properties:
        priceId:
          type: string
          description: Stripe Price ID o ObjectId de tier local
          example: price_1QwErTy123
        successUrl:
          type: string
          format: uri
          description: URL de redirección en caso de pago exitoso
          example: https://app.example.com/billing/success
        cancelUrl:
          type: string
          format: uri
          description: URL de redirección en caso de cancelación
          example: https://app.example.com/billing/cancel
      required:
      - priceId
      - successUrl
      - cancelUrl
    BillingPortalSessionRequest:
      type: object
      description: Datos necesarios para crear una sesión de Stripe Billing Portal
      properties:
        returnUrl:
          type: string
          format: uri
          description: URL de retorno al salir del portal
          example: https://app.example.com/billing
      required:
      - returnUrl
    BillingSessionUrlResponse:
      type: object
      description: Respuesta de creación de sesión Stripe con URL de redirección
      properties:
        '0':
          type: integer
          example: 0
        status:
          type: integer
          example: 200
        data:
          type: object
          properties:
            url:
              type: string
              format: uri
              description: URL generada por Stripe para completar el flujo
              example: https://checkout.stripe.com/c/pay/cs_test_123
          required:
          - url
      required:
      - status
      - data
      - '0'
    BillingInvoiceLine:
      type: object
      description: Línea simplificada de una factura Stripe
      properties:
        description:
          type: string
          nullable: true
          description: Descripción de la línea
          example: Suscripción mensual Basic Plan
        amount:
          type: integer
          description: Importe de la línea en céntimos
          example: 9900
        quantity:
          type: integer
          nullable: true
          description: Cantidad facturada en la línea
          example: 1
    BillingInvoice:
      type: object
      description: Factura Stripe simplificada devuelta por el endpoint de billing
      properties:
        id:
          type: string
          description: Identificador de factura Stripe
          example: in_1QwErTy123
        status:
          type: string
          description: Estado de factura en Stripe
          example: paid
        amountDue:
          type: integer
          description: Importe total adeudado en céntimos
          example: 9900
        amountPaid:
          type: integer
          description: Importe pagado en céntimos
          example: 9900
        currency:
          type: string
          description: Moneda ISO
          example: eur
        created:
          type: integer
          description: Timestamp UNIX de creación
          example: 1709635200
        pdfUrl:
          type: string
          format: uri
          nullable: true
          description: URL directa al PDF de factura
          example: https://pay.stripe.com/invoice/abc/pdf
        hostedUrl:
          type: string
          format: uri
          nullable: true
          description: URL hosted de factura en Stripe
          example: https://invoice.stripe.com/i/acct_123/invst_123
        subtotal:
          type: integer
          description: Subtotal en céntimos
          example: 9900
        total:
          type: integer
          description: Total en céntimos
          example: 9900
        lines:
          type: array
          items:
            $ref: '#/components/schemas/BillingInvoiceLine'
    BillingInvoicesResponse:
      type: object
      description: Respuesta paginada por cursor para facturas de billing
      properties:
        '0':
          type: integer
          example: 0
        status:
          type: integer
          example: 200
        data:
          type: object
          properties:
            data:
              type: array
              items:
                $ref: '#/components/schemas/BillingInvoice'
            hasMore:
              type: boolean
              description: Indica si hay más facturas en Stripe
              example: false
            lastId:
              type: string
              nullable: true
              description: Último id de factura para usar como cursor
              example: in_1QwErTy123
          required:
          - data
          - hasMore
          - lastId
      required:
      - status
      - data
      - '0'
    BillingWebhookReceived:
      type: object
      description: Confirmación de recepción de webhook Stripe
      properties:
        received:
          type: boolean
          example: true
      required:
      - received
    Trailer:
      type: object
      description: Información de un remolque (trailer) de la flota
      properties:
        _id:
          type: string
          description: ID único del remolque
          example: 507f1f77bcf86cd799439011
        plate:
          type: string
          description: Matrícula del remolque
          maxLength: 20
          example: REMO1234
        type:
          type: string
          description: Tipo de remolque
          enum:
          - NONE
          - box
          - platform
          - refrigerated
          - tanker
          - other
          default: NONE
          example: box
        capacity_value:
          type: number
          description: Capacidad numérica
          minimum: 0
          default: 0
          example: 20000
        capacity_unit:
          type: string
          description: Unidad de capacidad
          enum:
          - kg
          - t
          - m3
          default: kg
          example: kg
        company:
          type: string
          description: ObjectId de la empresa propietaria
          example: 507f1f77bcf86cd799439012
        active:
          type: boolean
          description: Si el remolque está activo
          default: true
          example: true
      required:
      - plate
      - company
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT Bearer authentication
    apiKeyAuth:
      type: apiKey
      in: query
      name: apikey
      description: 'API Key authentication for programmatic access.

        - Company API keys start with ''C'' (e.g., C123456789)

        - Trucker API keys start with ''T'' (e.g., T123456789)

        '
    queryAuth:
      type: apiKey
      in: query
      name: token
      description: 'JWT token authentication via query for endpoints that require
        it.

        The middleware also accepts aliases: `access_token`, `accessToken`,

        `x-access-token`, `x-access_token`.

        '
  parameters: {}
  responses: {}
