{
  "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\nregistradas, 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`.\n",
            "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`).\n",
            "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.\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 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\nflowchart 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\nSoporta 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.\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\ncampos 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.\n",
            "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\nflowchart 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.\n",
            "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\nDevuelve la lista de claves API activas asociadas a la empresa del\nusuario autenticado (empresa o transportista).\n\n## Objetivo\nPermitir a los usuarios visualizar sus claves API existentes para\ngestionar integraciones, identificar claves por su `temp_code` y\nverificar los roles asignados.\n\n## Casos de uso\n- Consultar las claves API activas en el panel de configuración de la empresa.\n- Identificar una clave específica por su `temp_code` antes de revocarla.\n- Verificar el rol (`dev`, `gestor`, `admin`) asignado a cada clave.\n\n## Detalles técnicos\nLas claves se devuelven **enmascaradas**: todos los caracteres excepto\nlos últimos 4 se reemplazan por `*` (ej. `******a1b2`). La clave\ncompleta solo se muestra en la respuesta de creación (`POST /apikeys`).\n\nEn el estado actual, el middleware de autenticación unificado puebla\n`req.company` (también para cuentas trucker). El controlador conserva un\nfallback legacy sobre `req.trucker`, pero la rama principal operativa es\n`req.company`.\n\nCada clave incluye:\n- `apikey`: Versión enmascarada para identificación visual.\n- `temp_code`: Código temporal de 15 caracteres usado para eliminar la clave.\n- `role`: Nivel de permisos (`dev`, `gestor`, `admin`).\n- `createdAt`: Fecha de generación en formato ISO 8601.\n\n## Autenticación\nRequiere JWT Bearer token (`isLoged` middleware). También acepta API Key.\n",
        "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.\n",
            "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.\n",
            "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.\n",
            "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`.\n",
            "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.\n",
            "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\nDevuelve los tipos de cuenta disponibles en el sistema ECMR.\n\n## Objetivo\nPermitir que el formulario de registro consulte dinámicamente qué\ntipos de cuenta están activos (empresa o transportista) para\nmostrar las opciones correspondientes al usuario.\n\n## Casos de uso\n- Cargar las opciones del selector de tipo de cuenta en el formulario de registro.\n- Verificar qué tipos de cuenta están habilitados en el sistema.\n\n## Detalles técnicos\nConsulta la colección `AccountType` y devuelve todos los registros.\nCada tipo tiene un `id`, un `value` (`company` o `trucker`) y un\nflag `active` que indica si acepta nuevos registros.\n\n## Troubleshooting\nSi en entorno demo \"no devuelve nada\", suele significar que la\ncolección `account_types` está vacía. En ese caso la respuesta\nsigue siendo `200 OK` con `data: []`.\n\n## Autenticación\nNo requiere autenticación.\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.\n",
            "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`.\n",
            "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.\n",
            "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).\n",
            "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\nrestablecerla 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\nflowchart 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).\n",
            "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\nuna 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\nNo 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.\n",
            "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\nCompletar 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.\n",
            "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.\n",
            "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\nObtener el estado de suscripción actual del usuario autenticado en\nbilling ECMR.\n\n## Objetivo\nPermitir que la app determine si un usuario tiene ciclo activo,\nsuspendido o cancelado y adaptar funcionalidades premium.\n\n## Casos de uso\n- Mostrar estado del plan en dashboard de billing.\n- Bloquear/desbloquear funcionalidades según suscripción.\n- Forzar sincronización ligera desde Stripe si no hay ciclo local activo.\n\n## Detalles técnicos\nUsa `tools.getUserContext(req)` para resolver `userId`.\nSi hay email disponible, asegura `UserProfile` antes de consultar.\nDevuelve `BillingCycle` poblado con `pricingTier` o `null` si no existe\nperfil/ciclo.\nEn `catch`, usa `returnKO` sin código explícito (`418`).\n\n## Autenticación\nRequiere JWT Bearer token o API key (`isLoged` middleware).\n",
        "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\nCrear una sesión de Stripe Checkout para que el usuario inicie o cambie\nsu suscripción ECMR.\n\n## Objetivo\nDevolver una URL segura de Stripe para completar el flujo de pago desde\nfrontend sin exponer lógica sensible de servidor.\n\n## Casos de uso\n- Contratar un nuevo plan ECMR.\n- Migrar de tier al seleccionar otro `priceId`.\n- Reintentar alta de suscripción tras fallo previo.\n\n## Detalles técnicos\nRequiere `priceId`, `successUrl` y `cancelUrl`.\nSi `priceId` es ObjectId de Mongo, el backend intenta resolver su\n`stripePriceId`.\nEl backend asegura `UserProfile` antes de crear checkout y responde\n`data.url` con el link de Stripe.\nEn `catch`, usa `returnKO` sin código explícito (`418`).\n\n## Autenticación\nRequiere JWT Bearer token o API key (`isLoged` middleware).\n",
        "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\nGenerar una sesión de Stripe Billing Portal para que el usuario\nautenticado gestione su suscripción.\n\n## Objetivo\nRedirigir al usuario a un portal seguro de Stripe para actualizar método\nde pago, revisar plan o cancelar suscripción.\n\n## Casos de uso\n- Abrir área de gestión de suscripción desde la app.\n- Actualizar tarjeta o método de pago.\n- Gestionar cancelación/reactivación según reglas de Stripe.\n\n## Detalles técnicos\nRequiere `returnUrl` para volver a la app tras salir del portal.\nEl backend resuelve `customerId` desde `UserProfile` y retorna\n`data.url` de la sesión de portal.\nEn `catch`, usa `returnKO` sin código explícito (`418`).\n\n## Autenticación\nRequiere JWT Bearer token o API key (`isLoged` middleware).\n",
        "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\nDevolver facturas Stripe simplificadas del usuario autenticado con\nsoporte de paginación por cursor.\n\n## Objetivo\nPermitir construir histórico de facturación en frontend sin exponer\ntoda la estructura nativa de Stripe.\n\n## Casos de uso\n- Renderizar listado de facturas en panel de billing.\n- Cargar más resultados con cursor (`starting_after`).\n- Acceder a URL PDF y hosted invoice para descarga/visualización.\n\n## Detalles técnicos\n`limit` se parsea como entero y por defecto toma `10`.\n`starting_after` se usa como cursor Stripe.\nLa respuesta devuelve `data[]` de facturas normalizadas, `hasMore` y\n`lastId`.\nEn `catch`, usa `returnKO` sin código explícito (`418`).\n\n## Autenticación\nRequiere JWT Bearer token o API key (`isLoged` middleware).\n",
        "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\nExponer el catálogo de planes de facturación ECMR disponibles para\niniciar una suscripción.\n\n## Objetivo\nPermitir al frontend cargar los tiers de precio y mostrar al usuario\nlos límites/capacidades asociados antes de contratar.\n\n## Casos de uso\n- Pintar página de pricing pública.\n- Refrescar catálogo de planes antes de checkout.\n- Validar que un `priceId` pertenece a un plan conocido.\n\n## Detalles técnicos\nConsulta `PricingTiersECMR` ordenado por `priceCents` ascendente y\nresponde usando el envelope estándar `returnOK`.\nEn caso de error, el controlador usa `returnKO` sin código explícito,\npor lo que el status por defecto es `418`.\n\n## Autenticación\nNo requiere autenticación.\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\nRecibir y validar eventos enviados por Stripe para mantener sincronizado\nel estado de suscripciones y facturas en ECMR.\n\n## Objetivo\nProcesar eventos como `checkout.session.completed`,\n`customer.subscription.updated` o eventos de invoice para actualizar\nciclos de facturación y estado interno del usuario.\n\n## Casos de uso\n- Confirmar una suscripción completada tras checkout.\n- Sincronizar cambios de estado de suscripción.\n- Registrar pagos o fallos de pago de facturas.\n\n## Detalles técnicos\nRequiere cabecera `stripe-signature` para verificar firma con\n`STRIPE_WEBHOOK_SECRET`.\nEl endpoint usa `req.rawBody` (o fallback `req.body`) para construir\nel evento con Stripe y no usa `returnOK/returnKO`; responde JSON simple\nen éxito y `text/plain` en errores de firma/handler.\n\n## Autenticación\nNo requiere JWT ni API key. La autenticación es criptográfica por\nfirma Stripe (`stripe-signature`).\n",
        "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\ncombinados 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.\n",
            "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\n",
            "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.\n",
            "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.\n",
            "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\nRequiere 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.\n",
            "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.\n",
            "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\nPermitir 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\nRequiere 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.\n",
            "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\nNo 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\nPermitir 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.\n",
            "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.\n",
            "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\nPermitir 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\nflowchart 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\nActualizar los datos de empresa vinculados al eCMR indicado.\n\n## Objetivo\nUpdate Company Information.\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: `PUT /ecmr/company-info/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nConsultar la información de contacto asociada a un eCMR específico.\n\n## Objetivo\nObtener información de contacto.\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: `GET /ecmr/contact-info/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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...\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nDefinir o modificar el código personalizado asociado a un eCMR.\n\n## Objetivo\nSave custom code.\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: `PUT /ecmr/customcode/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nVincular una empresa al eCMR para habilitar su colaboración en la operación.\n\n## Objetivo\nLink company to ECMR.\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/link-company/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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...\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nConfigurar o actualizar el método de pago asociado a un eCMR.\n\n## Objetivo\nUpdate ECMR Payment Method.\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: `PUT /ecmr/payment-method/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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...\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nEnviar por correo electrónico el PDF del eCMR a una lista de destinatarios.\n\n## Objetivo\nFacilitar el reenvío de documentación de transporte a clientes,\noperadores o contactos administrativos desde la plataforma.\n\n## Casos de uso\n- Reenviar un eCMR a nuevos destinatarios tras una actualización.\n- Compartir el PDF con equipos de operaciones o facturación.\n- Notificar a múltiples contactos en un mismo envío.\n\n## Detalles técnicos\nRequiere `service_code` y `emails[]` (1 a 10 elementos). El backend\nvalida formato de email, permisos de acceso al eCMR y genera/obtiene\nel PDF antes del envío.\n\n## Autenticación\nSoporta JWT Bearer token y API Key.\n",
        "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\nAsignar o reasignar un transportista a un eCMR específico.\n\n## Objetivo\nUpdate Carrier Restricted Assignment.\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: `PUT /ecmr/trucker-assign/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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...\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nActualizar la información del transportista asociada al eCMR indicado.\n\n## Objetivo\nUpdate Carrier Information.\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: `PUT /ecmr/trucker-info/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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...\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nConsultar la secuencia de transportistas sucesivos definida para un eCMR.\n\n## Objetivo\nConsultar los transportistas sucesivos asociados a un eCMR concreto.\n\n## Casos de uso\n- Revisar cadena de subcontratación del transporte.\n- Auditoría operativa de cambios de transportista.\n- Mostrar historial de carriers en detalle de eCMR.\n\n## Detalles técnicos\n- Endpoint real en código: `GET /ecmr/truckers-sucesives/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- Contexto funcional actual: devuelve la lista de transportistas sucesivos asociados al eCMR.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nAgregar un transportista a la secuencia de transportistas sucesivos del eCMR.\n\n## Objetivo\nAdd Successive Carrier.\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/truckers-sucesives/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nEliminar un transportista de la secuencia de transportistas sucesivos del eCMR.\n\n## Objetivo\nEliminar un transportista sucesivo previamente registrado en un eCMR.\n\n## Casos de uso\n- Corregir un carrier añadido por error.\n- Ajustar secuencia de transportistas en un expediente abierto.\n- Mantener consistencia de datos de ejecución logística.\n\n## Detalles técnicos\n- Endpoint real en código: `DELETE /ecmr/truckers-sucesives/{service_code}`.\n- Requiere body JSON con `trucker_name` (string no vacío).\n- Contexto funcional actual: elimina un transportista sucesivo del eCMR.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nConsultar la lista de eCMR disponibles para el usuario autenticado con la información principal de cada operación.\n\n## Objetivo\nList ECMRs with pagination.\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: `GET /ecmr`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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...\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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`:\nAceptado por transportista - `collected`: Recogido - `locked`:\nBloqueado/En tránsito - `delivered`: Entregado - `canceled`:\nCancelado\n",
            "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\nservicio (ECMR-...) - Código personalizado - Nombres de empresas\n(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`:\nCarga completa - `package`: Paquetería - `trailer`: Remolque\ncompleto\n",
            "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`.\n"
                  },
                  "company": {
                    "type": "string",
                    "description": "ID de compañía objetivo. Requerido cuando `force_create_cia=true` para usuarios trucker.\n"
                  }
                }
              },
              "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\nRecuperar el archivo generado del eCMR para su visualización o descarga.\n\n## Objetivo\nDownload ECMR File.\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: `GET /ecmr/file/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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).\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\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": "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\nBloquear un eCMR para cerrar su edición y asegurar la integridad de su estado final.\n\n## Objetivo\nLock ECMR.\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: `PUT /ecmr/lock/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nGenerar una vista HTML del eCMR para previsualización o impresión sin\ndescargar el PDF final.\n\n## Objetivo\nPermitir a integraciones internas o frontends renderizar rápidamente el\ncontenido del documento en pantalla.\n\n## Casos de uso\n- Mostrar una previsualización en el panel antes de bloquear el eCMR.\n- Verificar estructura y contenido del documento durante soporte.\n- Integrar una vista embebida del eCMR en herramientas internas.\n\n## Detalles técnicos\nBusca el eCMR por `service_code`, popula relaciones necesarias y\nresponde usando el wrapper estándar de `returnOK`\n(`status`, `data`, `0`).\n\n## Autenticación\nNo requiere autenticación.\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\nObtener el detalle completo de un eCMR específico identificado por `service_code`.\n\n## Objetivo\nGet ECMR details.\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: `GET /ecmr/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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...\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nActualizar la información editable de un eCMR específico según su `service_code`.\n\n## Objetivo\nUpdate ECMR.\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: `PUT /ecmr/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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,...\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nEliminar o cancelar un eCMR específico identificado por `service_code`.\n\n## Objetivo\nCancel ECMR.\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: `DELETE /ecmr/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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 ...\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nObtener el resumen del dashboard eCMR con métricas clave y estado general de las operaciones.\n\n## Objetivo\nObtener métricas y KPIs operativos de eCMR para el usuario autenticado.\n\n## Casos de uso\n- Mostrar tarjetas resumen en el panel principal.\n- Analizar volumen y estado de operaciones de transporte.\n- Alimentar widgets de seguimiento en tiempo real.\n\n## Detalles técnicos\n- Endpoint real en código: `GET /ecmr/dashboard`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- Contexto funcional actual: devuelve métricas y KPIs del usuario autenticado (company o trucker) sobre sus eCMR.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nConsultar las operaciones eCMR más recientes para facilitar el seguimiento de actividad reciente.\n\n## Objetivo\nRecuperar los eCMR más recientes para renderizar actividad reciente del usuario.\n\n## Casos de uso\n- Mostrar última actividad en dashboard.\n- Acceso rápido a expedientes recientes.\n- Seguimiento de operaciones recién creadas o actualizadas.\n\n## Detalles técnicos\n- Endpoint real en código: `GET /ecmr/dashboard/recent`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- Contexto funcional actual: devuelve los eCMR más recientes del usuario autenticado.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nObtener información de contacto pública de las partes asociadas al eCMR.\n\n## Objetivo\nFacilitar la comunicación operativa entre actores del envío cuando se\naccede mediante enlace compartido.\n\n## Casos de uso\n- Consultar teléfono/email de contacto en un flujo público.\n- Exponer datos mínimos de contacto para coordinación logística.\n\n## Detalles técnicos\nRequiere `temp_code` en query y valida su coincidencia con el token\ntemporal del eCMR antes de devolver la información.\n\n## Autenticación\nNo requiere JWT ni API Key. El control se basa en `temp_code`.\n",
        "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\nConsultar públicamente la información de pallets del eCMR.\n\n## Objetivo\nPermitir validar unidades y tipología de pallets en contextos de\nseguimiento o recepción sin autenticación de usuario.\n\n## Casos de uso\n- Verificar número total de pallets antes de la descarga.\n- Consultar detalle de `pallets_data` desde un enlace compartido.\n\n## Detalles técnicos\nRequiere `temp_code` en query y valida el token temporal del eCMR.\nSi es válido, responde `pallets_num`, `pallets_type` y `pallets_data`.\n\n## Autenticación\nNo requiere JWT ni API Key. El control se basa en `temp_code`.\n",
        "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\nObtener el detalle público de un eCMR compartido externamente.\n\n## Objetivo\nPermitir consulta controlada de información operativa del eCMR sin login,\nvalidando un código temporal de acceso.\n\n## Casos de uso\n- Compartir estado y datos clave del envío con terceros.\n- Consultar el detalle desde un enlace público temporal.\n- Integrar seguimiento en portales externos sin sesión autenticada.\n\n## Detalles técnicos\nRequiere `service_code` (path) y `temp_code` (query). El servidor valida\nque `temp_code` coincida con `temp_token` del eCMR; en caso contrario\ndevuelve `INVALID_TEMP_CODE`.\n\n## Autenticación\nNo requiere JWT ni API Key. El control de acceso se realiza con `temp_code`.\n",
        "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\nDevuelve el listado de documentos adjuntos al eCMR para usuarios\nautenticados con acceso al servicio.\n\n## Objetivo\nPermitir que empresa y transportista consulten los anexos cargados\ndurante el ciclo de vida del eCMR.\n\n## Casos de uso\n- Mostrar anexos en la vista privada de detalle del eCMR.\n- Verificar qué archivos están disponibles antes de una descarga.\n- Revisar documentos antes de eliminar o subir nuevas versiones.\n\n## Detalles técnicos\nEl backend busca el eCMR por `service_code` y pertenencia de compañía.\nSi existen documentos, transforma cada entrada a `{ _id, name }`.\nLa respuesta usa `returnOK` con wrapper `{ status, data, 0 }`.\nSi no encuentra eCMR, la implementación actual no envía respuesta\nexplícita (comportamiento conocido pendiente de ajuste en código).\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"
            }
          }
        ],
        "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\nEl 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\nDevuelve la lista de documentos compartidos públicamente para un eCMR\nespecífico, validando acceso temporal por token.\n\n## Objetivo\nPermitir que terceros con enlace público temporal consulten los anexos\nde un servicio sin iniciar sesión en la plataforma.\n\n## Casos de uso\n- Mostrar anexos del eCMR en una vista pública de seguimiento.\n- Compartir documentos operativos con clientes o receptores externos.\n- Obtener identificadores y URL de archivos para descarga controlada.\n\n## Detalles técnicos\nEl endpoint exige `service_code` (path) y `temp_code` (query).\nEl backend valida que `temp_code` coincida con el `temp_token` del\neCMR. Si la validación es correcta, devuelve `data` como array de\ndocumentos con `{ _id, name, url }` usando el wrapper de `returnOK`.\n\n## Autenticación\nNo requiere JWT Bearer token ni API Key.\n",
        "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\nObtener la colección `pallets_data` asociada a un eCMR concreto.\n\n## Objetivo\nPermitir consultar el detalle de bultos/unidades logísticas registradas\npara una carta de porte electrónica identificada por `service_code`.\n\n## Casos de uso\n- Mostrar el detalle de mercancía en la vista de un eCMR.\n- Sincronizar desde una integración externa el estado actual de pallets.\n- Verificar la composición de carga antes de editar o limpiar pallets.\n\n## Detalles técnicos\nBusca el eCMR por `service_code` y devuelve `pallets_data` tal como está\npersistido en MongoDB, envuelto en el formato estándar de `returnOK`\n(`status`, `data`, `0`).\n\nSi el eCMR no existe, devuelve `ECMR_NOT_FOUND`.\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 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\nEliminar todos los elementos de `pallets_data` para un eCMR.\n\n## Objetivo\nRestablecer la sección de pallets a estado vacío cuando sea necesario\nrehacer o limpiar por completo la composición de carga.\n\n## Casos de uso\n- Reset previo a una carga completa de nuevos pallets.\n- Limpieza administrativa tras detectar datos obsoletos.\n- Correcciones operativas en fase de planificación del envío.\n\n## Detalles técnicos\nEl backend establece `pallets_data = []` y sincroniza `pallets_num = 0`.\nDevuelve el payload en formato estándar de `returnOK`.\n\n## Permisos\nSe aplican reglas de ownership (`ACCESS_DENIED`) idénticas a POST/PUT/DELETE.\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 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\nActualizar de forma parcial los campos de un pallet ya existente dentro\nde `pallets_data`.\n\n## Objetivo\nPermitir correcciones puntuales (peso, piezas, volumen o identificación)\nsin tener que reenviar toda la lista de pallets.\n\n## Casos de uso\n- Corregir el peso de un pallet tras una nueva pesada.\n- Ajustar `pieces` por recuento físico.\n- Modificar `packagingType` o descripción de naturaleza de carga.\n\n## Detalles técnicos\n- Requiere `service_code` y `pallet_id`.\n- El body debe contener al menos un campo (`PALLET_DATA_REQUIRED` si va vacío).\n- Campos numéricos fuera de rango no generan error directo; se ignoran.\n- Si el `pallet_id` no existe en `pallets_data`, devuelve `PALLET_NOT_FOUND`.\n\n## Permisos\nSe aplican reglas de ownership (`ACCESS_DENIED`) idénticas a POST/DELETE.\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 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\nQuitar un pallet concreto de `pallets_data` usando su `pallet_id`.\n\n## Objetivo\nPermitir eliminar entradas puntuales de pallets sin modificar el resto\nde la colección del eCMR.\n\n## Casos de uso\n- Eliminar un pallet cargado por error.\n- Retirar un bulto cancelado en la operativa.\n- Limpiar duplicados detectados por validaciones externas.\n\n## Detalles técnicos\nLa implementación actual intenta eliminar por ObjectId.\nSi el `pallet_id` no es válido o no existe, la operación no falla por\nese motivo y devuelve `200` con la lista resultante (sin cambios).\n\n## Permisos\nSe aplican reglas de ownership (`ACCESS_DENIED`) idénticas a POST/PUT.\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 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\nVerificar el estado del flujo QR y su disponibilidad para el eCMR indicado.\n\n## Objetivo\nCheck QR Code Status.\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: `GET /ecmr/qr/check/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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:** -...\n\n## Autenticación\nNo requiere autenticación (`security: []`).\n",
        "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\nRegistrar y validar la entrega del envío dentro del flujo QR del eCMR.\n\n## Objetivo\nDelivery signature via QR.\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: `PUT /ecmr/qr/delivery/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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.\n\n## Autenticación\nNo requiere autenticación (`security: []`).\n",
        "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\nConsultar el detalle de un eCMR a partir de un token de acceso QR.\n\n## Objetivo\nGet ECMR details from QR token.\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: `GET /ecmr/qr/details/{token}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- Contexto funcional actual: Obtiene el detalle del ECMR a partir de su token QR.\n\n## Autenticación\nNo requiere autenticación (`security: []`).\n",
        "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\nDescargar el documento eCMR utilizando un token válido del flujo QR.\n\n## Objetivo\nDownload ECMR PDF from QR token.\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: `GET /ecmr/qr/download/{token}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- Contexto funcional actual: Descarga el PDF del ECMR usando un token QR válido.\n\n## Autenticación\nNo requiere autenticación (`security: []`).\n",
        "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\nRegistrar y validar la recogida del envío dentro del flujo QR del eCMR.\n\n## Objetivo\nPickup signature via QR.\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: `PUT /ecmr/qr/pickup/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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.\n\n## Autenticación\nNo requiere autenticación (`security: []`).\n",
        "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\nRegistrar y validar la acción del remitente dentro del flujo QR del eCMR.\n\n## Objetivo\nSignature of issuer via QR.\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: `PUT /ecmr/qr/sender/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- 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.\n\n## Autenticación\nNo requiere autenticación (`security: []`).\n",
        "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\nObtener los datos del flujo QR de un eCMR identificado por `service_code`.\n\n## Objetivo\nGenerate QR for ECMR.\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: `GET /ecmr/qr/{service_code}`.\n- Mantiene parámetros, requestBody y responses definidos en este contrato.\n- Contexto funcional actual: Genera un código QR para un ECMR de la compañía o transportista autenticado.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nRecupera los eCMR pendientes de firma para el usuario autenticado en el módulo de firma.\n\n## Objetivo\nExponer una vista de pendientes de firma en una sola llamada para alimentar\nla UI de firma rápida.\n\n## Casos de uso\n- Abrir la bandeja de documentos pendientes desde el flujo de firma.\n- Aplicar paginación con `page` y `limit` para listados largos.\n\n## Detalles técnicos\nEn el estado actual del código (`sign.controller.getPending`) la\noperación está deshabilitada y devuelve de forma explícita\n`500 NOT_IMPLEMENTED_OR_BROKEN_IN_SOURCE`.\n\n## Autenticación\nRequiere `queryAuth` (query params admitidos: `token`, `access_token`,\n`accessToken`, `x-access-token`, `x-access_token`) o `apiKeyAuth`.\n",
        "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\nAplica al eCMR la firma corporativa almacenada en la compañía del usuario autenticado.\n\n## Objetivo\nPermitir firma operativa sin subir imagen de firma en cada solicitud, reutilizando\nel sello/firma ya registrada en la compañía.\n\n## Casos de uso\n- Firma administrativa de un eCMR por parte de una empresa con firma ya configurada.\n- Automatizar la firma corporativa de documentos en el flujo interno.\n\n## Detalles técnicos\nEl backend exige `service_code` en JSON. Si la compañía no tiene firma cargada,\ndevuelve `403 SIGN_NOT_FOUND`. La respuesta de éxito usa wrapper `returnOK`.\n\n## Autenticación\nRequiere `queryAuth` (query params admitidos: `token`, `access_token`,\n`accessToken`, `x-access-token`, `x-access_token`) o `apiKeyAuth`.\n",
        "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\nRegistra la firma y evidencias de entrega para un eCMR.\n\n## Objetivo\nMarcar el cierre operativo de entrega, guardar POD y actualizar estado a `delivered`.\n\n## Casos de uso\n- Firma de entrega por transportista con imágenes de prueba.\n- Firma operativa de empresa con comentario ETD y geoposición.\n\n## Detalles técnicos\nEl backend acepta `multipart/form-data` con `image` o `sign` (al menos uno).\nPuede incluir `moreImages` (máximo 4), `comment`, `etd_comment`, `europallets` y\n`geolocation`. En éxito responde con wrapper `returnOK`.\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 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\nRegistra la firma y evidencias de recogida para un eCMR.\n\n## Objetivo\nMarcar el hito de recogida, persistir evidencias y actualizar el estado a `collected`.\n\n## Casos de uso\n- El transportista firma la recogida desde móvil adjuntando fotos del punto de carga.\n- Una compañía firma en nombre operativo y añade comentario/geo.\n\n## Detalles técnicos\nEl backend acepta `multipart/form-data` con `image` o `sign` (al menos uno).\nPuede incluir `moreImages` (máximo 4), `comment`, `etl_comment`, `europallets` y\n`geolocation`. En éxito responde con wrapper `returnOK`.\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 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\nRegistra la firma del remitente (`signed_by_sender`) sobre un eCMR específico.\n\n## Objetivo\nCompletar la firma de origen del documento para avanzar en el flujo operacional.\n\n## Casos de uso\n- Un usuario de empresa firma el remitente desde formulario web con imagen.\n- El cliente envía firma en texto/base64 mediante el campo `sign`.\n\n## Detalles técnicos\nEsta ruta aplica `userMiddleware.isCompany` además de `isLogedQuery`; por tanto,\nsolo perfiles de compañía pueden ejecutarla. El body es `multipart/form-data` y\nacepta `image` o `sign` (al menos uno de los dos). También admite `geolocation`.\nLa respuesta de éxito usa wrapper `returnOK`.\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 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\nObtener una lista paginada de plantillas eCMR pertenecientes a la empresa\nautenticada.\n\n## Objetivo\nPermitir la consulta rápida de plantillas reutilizables para crear nuevos\neCMR con menor carga operativa.\n\n## Casos de uso\n- Listar plantillas en una tabla con paginación.\n- Buscar plantillas por `name` o `description` usando `search`.\n- Ordenar resultados por campo y dirección con `sort`.\n\n## Detalles técnicos\n- Endpoint real: `GET /ecmr-templates`.\n- Usa `ecmrTemplateService.getTemplates(companyId, req.query)`.\n- Soporta query params: `page`, `limit`, `sort`, `search`.\n- Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.\n\n## Autenticación\nRequiere `userMiddleware.isLoged` (JWT Bearer o API Key).\n",
        "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\nCrear una nueva plantilla eCMR para reutilizar estructura de envíos.\n\n## Objetivo\nReducir tiempo de creación de eCMR guardando datos frecuentes\n(origen/destino, tipo de carga, observaciones y campos operativos).\n\n## Casos de uso\n- Crear una plantilla base para rutas recurrentes.\n- Guardar configuraciones por cliente/mercancía.\n- Centralizar plantillas por compañía.\n\n## Detalles técnicos\n- Endpoint real: `POST /ecmr-templates`.\n- Requiere `name` no vacío (`TEMPLATE_NAME_REQUIRED`).\n- Valida duplicado de nombre (`TEMPLATE_NAME_DUPLICATE`, `409`).\n- Valida existencia de `etl_address` y `etd_address` cuando se envían.\n- Respuesta de éxito real: `201` con `tools.returnOK`.\n\n## Autenticación\nRequiere `userMiddleware.isLoged` (JWT Bearer o API Key).\n",
        "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\nObtener la colección completa de plantillas de la compañía sin paginación.\n\n## Objetivo\nExponer un listado compacto para selectores, asistentes y carga inicial\ndonde se necesita el conjunto completo.\n\n## Casos de uso\n- Poblar `dropdowns` de plantillas.\n- Resolver selección rápida previa a crear eCMR.\n- Cachear plantillas en cliente para flujos cortos.\n\n## Detalles técnicos\n- Endpoint real: `GET /ecmr-templates/all`.\n- Retorna una lista simplificada (campos esenciales y direcciones pobladas).\n- Ordena por nombre ascendente en base de datos.\n- Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.\n\n## Autenticación\nRequiere `userMiddleware.isLoged` (JWT Bearer o API Key).\n",
        "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\nDuplicar una plantilla existente para crear una variante editable.\n\n## Objetivo\nAcelerar creación de plantillas similares sin rehacer todos los campos.\n\n## Casos de uso\n- Crear variantes por cliente o tipo de carga.\n- Generar versiones por temporada/ruta.\n- Clonar una plantilla base y ajustar solo nombre/descripción.\n\n## Detalles técnicos\n- Endpoint real: `POST /ecmr-templates/duplicate`.\n- Body requerido: `templateId`, `name`.\n- `description` es opcional y sobreescribe la descripción original si se envía.\n- Valida existencia de plantilla origen y nombre único.\n- Respuesta de éxito real: `201` con `tools.returnOK`.\n\n## Autenticación\nRequiere `userMiddleware.isLoged` (JWT Bearer o API Key).\n",
        "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\nRecuperar el detalle de una plantilla concreta por su identificador.\n\n## Objetivo\nPermitir inspección/edición de una plantilla existente desde frontend u\notros clientes API.\n\n## Casos de uso\n- Abrir pantalla de edición de plantilla.\n- Mostrar vista detallada antes de crear eCMR.\n- Verificar contenido de plantilla tras creación o duplicado.\n\n## Detalles técnicos\n- Endpoint real: `GET /ecmr-templates/{id}`.\n- Requiere `id` de path.\n- Si no existe `id`: `TEMPLATE_ID_REQUIRED` (400).\n- Si no existe plantilla para esa compañía: `TEMPLATE_NOT_FOUND` (404).\n- Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.\n\n## Autenticación\nRequiere `userMiddleware.isLoged` (JWT Bearer o API Key).\n",
        "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\nActualizar una plantilla eCMR existente de la empresa autenticada.\n\n## Objetivo\nMantener plantillas sincronizadas con cambios operativos sin crear una\nnueva plantilla desde cero.\n\n## Casos de uso\n- Cambiar direcciones de origen/destino de una plantilla.\n- Ajustar nombre, descripción o campos de carga.\n- Corregir una plantilla antes de su reutilización masiva.\n\n## Detalles técnicos\n- Endpoint real: `PUT /ecmr-templates/{id}`.\n- Requiere `id` de path.\n- El controlador elimina `company` del body para evitar cambio de dueño.\n- Valida duplicado de nombre y existencia de direcciones cuando aplica.\n- Respuesta de éxito: `200` con `tools.returnOK`.\n\n## Autenticación\nRequiere `userMiddleware.isLoged` (JWT Bearer o API Key).\n",
        "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\nEliminar una plantilla eCMR existente de la compañía autenticada.\n\n## Objetivo\nMantener limpio el catálogo de plantillas, retirando configuraciones\nobsoletas o redundantes.\n\n## Casos de uso\n- Limpiar plantillas en desuso.\n- Evitar selección accidental de plantillas antiguas.\n- Gestionar catálogo de plantillas por ciclo operativo.\n\n## Detalles técnicos\n- Endpoint real: `DELETE /ecmr-templates/{id}`.\n- Requiere `id` de path.\n- Si la plantilla no existe para la compañía: `TEMPLATE_NOT_FOUND` (404).\n- Respuesta de éxito actual del controlador: `200` con objeto vacío en `data`.\n\n## Autenticación\nRequiere `userMiddleware.isLoged` (JWT Bearer o API Key).\n",
        "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\nServir un recurso binario almacenado en storage usando la clave enviada por query string.\n\n## Objetivo\nFacilitar la carga de imágenes o adjuntos en clientes que no construyen rutas con parámetro `path`.\n\n## Casos de uso\n- Renderizar imágenes desde una URL tipo `/images?file=...`.\n- Reutilizar la misma clave de archivo en distintos módulos sin cambiar el endpoint.\n- Integraciones legacy que envían el identificador por query param.\n\n## Detalles técnicos\n- Endpoint real en código: `GET /images` con query `file`.\n- El controlador combina `body`, `query` y `params`; para esta ruta se espera `query.file`.\n- Si `file` no llega, el backend intenta resolver `-` en storage y responde error.\n- El contenido se transmite por streaming y expone `Content-Type`, `Content-Length` y `Content-disposition`.\n\n## Autenticación\nNo requiere autenticación (`security: []`).\n",
        "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\nServir un recurso binario almacenado en el storage configurado (MinIO o AWS S3) a partir del nombre de archivo enviado en `path`.\n\n## Objetivo\nPermitir recuperar imágenes y adjuntos referenciados desde la plataforma mediante una URL directa y estable.\n\n## Casos de uso\n- Visualización de firmas eCMR almacenadas como archivo.\n- Descarga de evidencias gráficas asociadas a incidencias.\n- Reutilización de una clave de storage en enlaces internos.\n\n## Detalles técnicos\n- Endpoint real en código: `GET /images/{file}`.\n- El controlador combina `body`, `query` y `params`; en esta ruta prevalece `params.file`.\n- El contenido se transmite por streaming y expone `Content-Type`, `Content-Length` y `Content-disposition`.\n- Los errores se devuelven con `tools.returnKO` en formato `{ status, message }`, usando el código HTTP reportado por el storage cuando está disponible.\n\n## Autenticación\nNo requiere autenticación (`security: []`).\n",
        "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\nRequiere 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\nlí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\nRequiere 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\nDevuelve la lista paginada de incidencias creadas por el usuario\nautenticado.\n\n## Objetivo\nPermitir seguimiento del estado de tickets y consulta histórica de\nincidencias con búsqueda y filtrado.\n\n## Casos de uso\n- Mostrar bandeja de incidencias del usuario.\n- Filtrar tickets activos o cerrados por estado agregado.\n- Buscar por código, autor, estado, categoría o texto del mensaje.\n\n## Detalles técnicos\n- Solo devuelve incidencias donde `idAuthor` coincide con el usuario.\n- `filter=open` incluye solo `open`.\n- `filter=resolved` incluye solo `resolved`.\n- `filter=closed` incluye `closed` y `cancelByClient`.\n- El parámetro de búsqueda es `search` y aplica regex en varios campos.\n\n## Autenticación\nSoporta JWT Bearer token y API Key.\n",
        "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\nHabilitar 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\nSoporta 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\nDevuelve los `service_code` de eCMRs que el usuario autenticado puede\nusar como referencia al crear una incidencia.\n\n## Objetivo\nFacilitar autocompletado y vinculación de tickets con operaciones\nreales de transporte.\n\n## Casos de uso\n- Autocompletar el campo `service_code` al crear un ticket.\n- Filtrar códigos por prefijo mediante `autocomplete`.\n- Paginar resultados en interfaces con muchos eCMRs.\n\n## Detalles técnicos\n- Requiere rol `admin` dentro de la compañía.\n- Soporta paginación por `page` y `limit`.\n- Filtra por regex case-insensitive sobre `service_code`.\n- Retorna una lista sin duplicados.\n\n## Autenticación\nSoporta JWT Bearer token y API Key.\n",
        "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\nDevuelve el catálogo estático de motivos que puede seleccionar el\nusuario al crear una incidencia.\n\n## Objetivo\nEstandarizar la clasificación de tickets para facilitar su análisis y\npriorización.\n\n## Casos de uso\n- Rellenar un selector de categorías en el formulario de incidencias.\n- Evitar valores libres no controlados en `relatedTo`.\n- Sincronizar catálogos entre frontend y backend.\n\n## Detalles técnicos\n- Lee un archivo JSON local (`motives.json`).\n- Cada elemento incluye `title` y `value`.\n- No requiere parámetros de entrada.\n\n## Autenticación\nSoporta JWT Bearer token y API Key.\n",
        "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\nDevuelve el detalle completo de una incidencia concreta, incluyendo su\nhilo de mensajes.\n\n## Objetivo\nPermitir al usuario revisar el contexto y evolución de un ticket.\n\n## Casos de uso\n- Abrir el detalle de una incidencia desde el listado.\n- Consultar adjuntos y mensajes intercambiados.\n- Validar estado y metadatos antes de responder o cerrar.\n\n## Detalles técnicos\n- Busca por `_id` y restringe por `idAuthor` del usuario autenticado.\n- Si no existe o no pertenece al usuario, retorna `NOT_FOUND`.\n\n## Autenticación\nSoporta 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"
            }
          }
        ],
        "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\nSoporta 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\nCambia el estado de una incidencia a `cancelByClient` para indicar que\nel cliente la da por cerrada.\n\n## Objetivo\nPermitir cierre explícito de tickets cuando el usuario ya no requiere\nseguimiento.\n\n## Casos de uso\n- El usuario confirma que su problema quedó resuelto.\n- Se cancela un ticket abierto por cambio de contexto.\n- Se limpia la bandeja de incidencias activas.\n\n## Detalles técnicos\n- Busca por `_id` e `idAuthor`.\n- Si existe, actualiza estado a `cancelByClient`.\n- La normalización a `closed` puede aplicarse en listados.\n\n## Autenticación\nSoporta JWT Bearer token y API Key.\n",
        "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\nRecuperar la bandeja de notificaciones del usuario autenticado con paginación y filtros.\n\n## Objetivo\nPermitir al frontend renderizar el centro de notificaciones y su estado de lectura sin cálculos adicionales.\n\n## Casos de uso\n- Cargar listado inicial de notificaciones.\n- Aplicar filtros por tipo, prioridad o solo no leídas.\n- Sincronizar contador de no leídas junto al listado.\n\n## Detalles técnicos\n- Ruta protegida por `userMiddleware.isLoged`.\n- La query se valida con `validateNotificationQuery`.\n- `page` default real: `1`.\n- `limit` default real: `20` (máximo `100`).\n- Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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`.\n"
                  },
                  "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`.\n"
                  },
                  "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\nDevolver el total de notificaciones no leídas del usuario autenticado.\n\n## Objetivo\nAlimentar badges y contadores de UI sin descargar el listado completo.\n\n## Casos de uso\n- Badge en menú superior.\n- Refresco periódico de contador.\n- Actualización tras marcar notificaciones como leídas.\n\n## Detalles técnicos\n- Ruta protegida por `userMiddleware.isLoged`.\n- No usa parámetros de paginación ni filtros.\n- La capa de servicio cuenta documentos `deleted: false` y `read: false`.\n- Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nMarcar como leídas todas las notificaciones pendientes del usuario autenticado.\n\n## Objetivo\nPermitir limpieza masiva del centro de notificaciones con una única acción.\n\n## Casos de uso\n- Botón “Marcar todo como leído”.\n- Reseteo de pendientes tras revisar bandeja.\n- Sincronización de estado al entrar en el módulo.\n\n## Detalles técnicos\n- Ruta protegida por `userMiddleware.isLoged`.\n- El servicio ejecuta `updateMany` sobre notificaciones `deleted: false` y `read: false` del usuario.\n- Retorna `modifiedCount` con número de registros afectados.\n- Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nExponer los tipos de notificación y prioridades soportadas por el modelo.\n\n## Objetivo\nEvitar hardcode en frontend para filtros y formularios de notificaciones.\n\n## Casos de uso\n- Poblar selectores de filtros.\n- Validar payloads en cliente.\n- Mostrar catálogo de eventos notificados.\n\n## Detalles técnicos\n- Ruta protegida por `userMiddleware.isLoged`.\n- El controlador devuelve un objeto con dos arrays: `validTypes` y `validPriorities`.\n- Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nObtener solo notificaciones pendientes de lectura del usuario autenticado.\n\n## Objetivo\nReducir el payload del centro de notificaciones cuando la UI solo necesita pendientes.\n\n## Casos de uso\n- Panel “No leídas”.\n- Bandeja compacta en header.\n- Carga incremental de pendientes.\n\n## Detalles técnicos\n- Ruta protegida por `userMiddleware.isLoged`.\n- Query validada por `validateNotificationQuery`.\n- `page` default real: `1`.\n- `limit` default real: `20` (máximo `100`).\n- No tiene middleware de rate-limit en esta ruta, por lo que no aplica `429`.\n- Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nEliminar una notificación concreta del usuario autenticado.\n\n## Objetivo\nPermitir que el usuario gestione manualmente su bandeja y descarte avisos irrelevantes.\n\n## Casos de uso\n- Ocultar avisos ya no útiles.\n- Limpiar la bandeja de notificaciones.\n- Gestionar historial visible por usuario.\n\n## Detalles técnicos\n- Ruta protegida por `userMiddleware.isLoged`.\n- `notificationId` se valida con formato ObjectId en `validateNotificationId`.\n- Se registra acción vía `midAction.saveAction('notifications')`.\n- El servicio ejecuta soft delete mediante plugin `mongoose-delete`.\n- Si no existe o no pertenece al usuario, actualmente responde `500 NOTIFICATION_NOT_FOUND`.\n- Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nCambiar el estado de lectura de una notificación puntual del usuario autenticado.\n\n## Objetivo\nPermitir interacción individual desde el listado de notificaciones.\n\n## Casos de uso\n- Marcar una notificación al abrir su detalle.\n- Reducir contador de no leídas de forma selectiva.\n- Sincronizar estado leído entre vistas.\n\n## Detalles técnicos\n- Ruta protegida por `userMiddleware.isLoged`.\n- `notificationId` se valida con formato ObjectId en `validateNotificationId`.\n- Si la notificación no existe o no pertenece al usuario, el servicio lanza `NOTIFICATION_NOT_FOUND` y actualmente se expone como `500`.\n- Se registra acción vía `midAction.saveAction('notifications')`.\n- Respuesta de éxito en envelope `tools.returnOK`: `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT (`bearerAuth`) o API key (`apiKeyAuth`).\n",
        "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\nBuscar ciudades mediante un texto de búsqueda, utilizando\nel servicio externo de puntos de interés (POI).\n## Objetivo\nPermitir autocompletado y búsqueda de ciudades para direcciones\ny puntos de interés en la interfaz de usuario.\n## Casos de uso\n- Autocompletar campo de ciudad en formularios.\n- Buscar ubicaciones para planificación de rutas.\n- Obtener coordenadas de ciudades para búsquedas POI.\n## Detalles técnicos\n- Endpoint real: `GET /poi/city?search=...`.\n- Acepta `search` como query parameter.\n- No requiere autenticación.\n- Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.\n## Autenticación\nNo requiere autenticación.\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\nObtener una lista de puntos de interés cercanos a una ubicación\ngeográfica dada.\n## Objetivo\nPermitir que los conductores y planificadores encuentren servicios\ncercanos durante la ruta.\n## Casos de uso\n- Encontrar puntos de servicio cerca de una ubicación.\n- Obtener información genérica de lugares cercanos.\n## Detalles técnicos\n- Endpoint real: `GET /poi/nearby?lat=...&lng=...`.\n- Acepta `lat` y `lng` como query parameters.\n- No requiere autenticación.\n- Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.\n## Autenticación\nNo requiere autenticación.\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\nBuscar aparcamientos para camiones cercanos a una ubicación.\n## Objetivo\nFacilitar la localización de zonas de estacionamiento seguro\npara conductores durante sus rutas.\n## Casos de uso\n- Encontrar aparcamiento cercano para descansos reglamentarios.\n- Localizar zonas de estacionamiento seguro cerca de destino.\n## Detalles técnicos\n- Endpoint real: `GET /poi/parking/nearby?lat=...&lng=...&radius=...`.\n- Acepta `lat`, `lng` y opcionalmente `radius` como query parameters.\n- No requiere autenticación.\n- Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.\n## Autenticación\nNo requiere autenticación.\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\nBuscar restaurantes cercanos a una ubicación geográfica.\n## Objetivo\nAyudar a conductores a encontrar opciones de comida durante sus rutas.\n## Casos de uso\n- Planificar paradas para comidas en rutas largas.\n- Encontrar restaurantes cerca de puntos de carga/descarga.\n## Detalles técnicos\n- Endpoint real: `GET /poi/restaurant/nearby?lat=...&lng=...&radius=...`.\n- Acepta `lat`, `lng` y opcionalmente `radius` como query parameters.\n- No requiere autenticación.\n- Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.\n## Autenticación\nNo requiere autenticación.\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\nBuscar duchas disponibles para conductores cerca de una ubicación.\n## Objetivo\nAyudar a conductores a encontrar servicios de higiene durante\nsus paradas en ruta.\n## Casos de uso\n- Localizar duchas en áreas de servicio cercanas.\n- Planificar paradas con servicios completos.\n## Detalles técnicos\n- Endpoint real: `GET /poi/shower/nearby?lat=...&lng=...&radius=...`.\n- Acepta `lat`, `lng` y opcionalmente `radius` como query parameters.\n- No requiere autenticación.\n- Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.\n## Autenticación\nNo requiere autenticación.\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\nBuscar talleres mecánicos cercanos a una ubicación geográfica.\n## Objetivo\nAyudar a conductores a encontrar asistencia mecánica durante sus rutas.\n## Casos de uso\n- Localizar talleres ante una avería en ruta.\n- Planificar mantenimiento preventivo en puntos estratégicos.\n## Detalles técnicos\n- Endpoint real: `GET /poi/workshop/nearby?lat=...&lng=...&radius=...`.\n- Acepta `lat`, `lng` y opcionalmente `radius` como query parameters.\n- No requiere autenticación.\n- Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.\n## Autenticación\nNo requiere autenticación.\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\nRecupera el perfil del usuario autenticado para mostrar su información\nactual de cuenta.\n\n## Objetivo\nPermitir que el frontend cargue los datos del usuario logueado sin\nrealizar consultas adicionales.\n\n## Casos de uso\n- Cargar pantalla de perfil al iniciar sesión.\n- Leer datos base para formularios de edición.\n- Confirmar estado de cuenta, rol e idioma configurado.\n\n## Detalles técnicos\n- El endpoint está protegido por `userMiddleware.isLoged`.\n- Devuelve el documento de usuario sin parseo adicional.\n- Si no hay contexto de autenticación válido en el controlador, retorna `NOT_FOUND`.\n\n## Autenticación\nSoporta JWT Bearer token y API Key.\n",
        "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\nCambia la contraseña del usuario autenticado validando credencial\nactual y confirmación de la nueva clave.\n\n## Objetivo\nPermitir rotación de credenciales desde el perfil sin intervención\nadministrativa.\n\n## Casos de uso\n- Cambiar contraseña de forma preventiva.\n- Actualizar contraseña tras sospecha de acceso no autorizado.\n- Normalizar claves tras políticas internas de seguridad.\n\n## Detalles técnicos\n- Requiere `current`, `new_pass` y `confirm_pass`.\n- Si `current` es inválida retorna `INVALID_PASSWORD` (403).\n- Si `new_pass` y `confirm_pass` no coinciden retorna `PASSWORD_NOT_MATCH` (405).\n- En éxito, guarda hash bcrypt y limpia `recovery_token`.\n- Devuelve `model.parse(user)` (sin `password` ni `recovery_token`).\n\n## Autenticación\nSoporta JWT Bearer token y API Key.\n",
        "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\nObtener una lista paginada de los remolques registrados para la\nempresa o transportista autenticado.\n## Objetivo\nPermitir la gestión y consulta de remolques asociados a la flota\ndel usuario.\n## Casos de uso\n- Listar remolques disponibles para asignar a vehículos.\n- Buscar remolques por matrícula o tipo.\n- Consultar capacidad y características de remolques.\n## Detalles técnicos\n- Endpoint real: `GET /trailers/`.\n- Soporta paginación con `page` y `limit` (default: page=1, limit=10).\n- Soporta búsqueda por `search` (matrícula o tipo).\n- Soporta `autocomplete` para completado rápido.\n- Respuesta paginada con `tools.customPagination`.\n## Autenticación\nRequiere JWT Bearer token o API Key (middleware `isLoged`).\n",
        "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\nRegistrar un nuevo remolque en la flota de la empresa autenticada.\n## Objetivo\nAñadir remolques al inventario de la empresa para su posterior\nasignación a vehículos y uso en operaciones de transporte.\n## Casos de uso\n- Añadir un remolque nuevo a la flota.\n- Registrar remolque con datos de capacidad y tipo.\n- Preparar el remolque para asignación a un vehículo.\n## Detalles técnicos\n- Endpoint real: `POST /trailers/`.\n- Requiere `plate` (matrícula) no vacía.\n- Valida duplicado de matrícula dentro de la misma empresa (`409`).\n- Normaliza la matrícula a mayúsculas sin espacios.\n- Acepta `capacity_value` (numérico), `type`, y demás campos del modelo.\n- Respuesta con `tools.returnOK`: `{ status, data, 0 }`.\n## Autenticación\nRequiere JWT Bearer token o API Key (middleware `isLoged`).\n",
        "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\nActualizar los datos de un remolque existente.\n## Objetivo\nPermitir modificar atributos del remolque (matrícula, tipo,\ncapacidad) sin tener que eliminarlo y crearlo de nuevo.\n## Casos de uso\n- Corregir la matrícula de un remolque.\n- Actualizar el tipo o capacidad tras una modificación.\n- Cambiar características operativas del remolque.\n## Detalles técnicos\n- Endpoint real: `PUT /trailers/{id}`.\n- Verifica que el remolque pertenezca a la empresa autenticada.\n- Si se cambia la matrícula, valida que no exista duplicado (`409`).\n- Normaliza matrícula a mayúsculas.\n- Respuesta con `tools.returnOK`: `{ status, data, 0 }`.\n## Autenticación\nRequiere JWT Bearer token o API Key (middleware `isLoged`).\n",
        "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\nEliminar un remolque de la flota de la empresa autenticada.\n## Objetivo\nDar de baja remolques que ya no están en servicio o fueron\nregistrados por error.\n## Casos de uso\n- Un remolque se vende y se elimina de la flota.\n- Se registró por error y se necesita eliminar.\n- Limpiar remolques obsoletos del inventario.\n## Detalles técnicos\n- Endpoint real: `DELETE /trailers/{id}`.\n- Verifica que el remolque pertenezca a la empresa autenticada.\n- Valida que el remolque no esté asignado a un vehículo activo (`409`).\n- Valida que el remolque no esté asignado a un eCMR activo (`409`).\n- Elimina físicamente el documento.\n- Respuesta con `tools.returnOK`: `{ status, data, 0 }`.\n## Autenticación\nRequiere JWT Bearer token o API Key (middleware `isLoged`).\n",
        "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\nLista usuarios asociados a la compañía del usuario autenticado.\n\n## Objetivo\nFacilitar gestión de cuentas internas (admin, gestor, driver) con paginación y búsqueda.\n\n## Casos de uso\n- Mostrar tabla de usuarios en el panel de administración.\n- Buscar usuarios por nombre, email, teléfono o taxid.\n\n## Detalles técnicos\nInternamente usa `paginate` y aplica filtro de búsqueda por regex sobre varios campos.\nLa respuesta de éxito utiliza `tools.returnOK`, por lo que el resultado se envuelve en `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nActualiza un usuario de la misma compañía usando `id` en el body.\n\n## Objetivo\nMantener compatibilidad con clientes que no envían el ID en la URL.\n\n## Casos de uso\n- Panel legacy que actualiza usuarios mediante payload.\n\n## Detalles técnicos\nRequiere rol `admin`. Si no hay permisos, el controlador responde con `USER_NOT_FOUND` (404)\nen lugar de un 403/405. Respuesta de éxito envuelta en `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nCrea un usuario nuevo dentro de la compañía del usuario autenticado.\n\n## Objetivo\nPermitir alta de cuentas internas para operación y administración.\n\n## Casos de uso\n- Alta de un gestor para un equipo de oficina.\n- Alta de un conductor para operación diaria.\n\n## Detalles técnicos\nSolo `admin` puede crear usuarios. El backend genera contraseña temporal (`generatePass`),\nguarda hash y envía email con la nueva clave. Respuesta de éxito envuelta en `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nDevuelve el historial paginado de accesos (login) del usuario autenticado.\n\n## Objetivo\nPermitir revisión de seguridad de sesiones recientes del propio usuario.\n\n## Casos de uso\n- Auditar accesos desde dispositivos distintos.\n- Revisar actividad reciente de login.\n\n## Detalles técnicos\nUsa el mismo controlador que `/users/access/{id}`. Cuando no se envía `id`,\nse usa automáticamente el `_id` del usuario autenticado. Consume `page` y `limit` por query.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nDevuelve el historial de accesos de un usuario específico.\n\n## Objetivo\nPermitir auditoría administrativa de seguridad por usuario.\n\n## Casos de uso\n- Revisar sesiones de un miembro del equipo.\n- Investigar accesos sospechosos de una cuenta.\n\n## Detalles técnicos\nSi el ID no corresponde al usuario autenticado, solo `admin` puede consultar.\nErrores de permisos se encapsulan como 500 por el controlador actual.\nÉxito envuelto en `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nDevuelve el histórico paginado de acciones de un usuario.\n\n## Objetivo\nFacilitar auditoría operativa de acciones realizadas en el sistema.\n\n## Casos de uso\n- Revisar actividad de un usuario específico.\n- Trazabilidad de operaciones sobre módulos críticos.\n\n## Detalles técnicos\nSi un usuario no admin intenta consultar otro ID, internamente se genera `USER_NOT_ALLOWED`,\npero el controlador actual encapsula ese error y responde con 500.\nÉxito envuelto en `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nGenera un nuevo token de activación para un usuario y reenvía el email.\n\n## Objetivo\nRecuperar activaciones pendientes cuando el usuario no recibió el primer correo.\n\n## Casos de uso\n- Usuario sin correo inicial de activación.\n- Token anterior expirado o invalidado.\n\n## Detalles técnicos\nRequiere rol `admin` y usuario objetivo dentro de la misma compañía.\nGuarda `recovery_token` y envía correo mediante `mail.sendActive`.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nIntenta activar una cuenta usando el token recibido por correo.\n\n## Objetivo\nCompletar la validación inicial de email para habilitar el acceso del usuario.\n\n## Casos de uso\n- El usuario hace clic en el enlace de activación enviado por email.\n- Reintento de activación cuando el token sigue siendo válido.\n\n## Detalles técnicos\nRuta pública que responde con HTML renderizado (`email_account_activate_ok` o `email_account_activate_ko`).\nEl comportamiento actual observable no es completamente estable: la implementación usa símbolos no definidos\nen algunas ramas (`TruckerUser`, variable `error`), por lo que también pueden aparecer errores 500 no controlados.\n\n## Autenticación\nNo requiere autenticación.\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\nDevuelve los datos del usuario autenticado.\n\n## Objetivo\nPermitir a la interfaz cargar el perfil actual sin conocer el ID explícitamente.\n\n## Casos de uso\n- Cargar la pantalla \"Mi perfil\" al iniciar sesión.\n- Consultar rol y tipo de cuenta para habilitar funcionalidades.\n\n## Detalles técnicos\nUsa `CompanyUser.parseMe` y responde con `tools.returnOK`, por lo que el contrato real de éxito está envuelto\nen `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nActualiza los campos editables del perfil del usuario autenticado.\n\n## Objetivo\nPermitir mantenimiento de datos de contacto y preferencias del perfil actual.\n\n## Casos de uso\n- Corregir nombre o apellidos del usuario.\n- Cambiar teléfono o datos de perfil permitidos.\n\n## Detalles técnicos\nEjecuta validación con `validateData` y responde con `tools.returnOK`/`tools.returnKO`.\nEl contrato de éxito usa wrapper `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nGenera una contraseña temporal nueva para el usuario objetivo.\n\n## Objetivo\nPermitir recuperación administrativa de credenciales.\n\n## Casos de uso\n- Usuario no puede acceder y requiere reseteo.\n- Política interna de renovación manual de credenciales.\n\n## Detalles técnicos\nSolo `admin`. Se genera contraseña con `generatePass`, se actualiza `password` y se envía email.\nEl éxito devuelve `{ status, data, 0 }` con `data.id`.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nActualiza el estado (`status`) de un usuario de la compañía.\n\n## Objetivo\nHabilitar o bloquear cuentas de forma administrativa.\n\n## Casos de uso\n- Bloquear temporalmente un usuario.\n- Reactivar una cuenta tras revisión.\n\n## Detalles técnicos\nSolo `admin`. Aplica `updateOne` con el body recibido.\nEl éxito devuelve `{ status, data, 0 }` con el `id` modificado.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nDevuelve el detalle de un usuario concreto dentro de la compañía.\n\n## Objetivo\nPermitir a perfiles administrativos consultar información completa de otro usuario.\n\n## Casos de uso\n- Abrir ficha de un usuario desde el listado.\n- Verificar rol/estado antes de realizar acciones administrativas.\n\n## Detalles técnicos\nRequiere rol `admin` y que el usuario objetivo pertenezca a la misma compañía.\nSi no se cumple, el comportamiento actual devuelve `USER_NOT_FOUND` (404).\nÉxito envuelto en `{ status, data, 0 }`.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nActualiza un usuario usando el ID de la URL.\n\n## Objetivo\nPermitir edición administrativa de usuarios de la misma compañía.\n\n## Casos de uso\n- Cambiar nombre, apellidos o teléfono de un usuario interno.\n\n## Detalles técnicos\nMisma lógica funcional que `PUT /users/` pero con ID en path.\nRequiere rol `admin`; faltas de permisos se materializan como `USER_NOT_FOUND` (404) en la implementación actual.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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\nElimina un usuario de la compañía y retira su referencia del array `users`.\n\n## Objetivo\nPermitir limpieza de cuentas internas no vigentes.\n\n## Casos de uso\n- Baja de un empleado.\n- Retirada de una cuenta creada por error.\n\n## Detalles técnicos\nSolo `admin`. La validación de permisos la hace `checkHasPermisions`.\nÉxito en wrapper `{ status, data, 0 }` con `data.id`.\n\n## Autenticación\nRequiere JWT Bearer o API Key válidos.\n",
        "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.\n",
            "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\nflowchart 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`.\n",
                    "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\nRequiere 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.\n",
            "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\nRequiere 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.\n",
            "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.\n",
            "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\nAsignar un remolque existente a un vehículo de la flota.\n## Objetivo\nVincular un remolque (`trailer._id`) a un vehículo para completar\nla configuración del conjunto tractor-remolque en un eCMR.\n## Casos de uso\n- Asignar el remolque correcto para una ruta específica.\n- Actualizar la configuración del vehículo antes de crear un eCMR.\n- Cambiar el remolque asignado por mantenimiento o disponibilidad.\n## Detalles técnicos\n- Endpoint real: `PUT /vehicles/{id}/trailer`.\n- Recibe `{ \"trailer\": \"<trailer_id>\" }` en el body.\n- Requiere que el vehículo pertenezca a la empresa autenticada.\n- Requiere que el remolque exista y pertenezca a la misma empresa.\n- Respuesta envuelta con `tools.returnOK`: `{ status, data, 0 }`.\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}$"
            }
          }
        ],
        "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.\nIncluye datos de geolocalización en formato GeoJSON y metadatos de uso.\n",
        "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',\n'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\nECMRs 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\nautomá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\ndirecció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,\npermite 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.\nIncluye información de facturación, usuario y dirección.\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\nexiste)",
            "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\nsi fue subido por transportista)",
            "example": "507f1f77bcf86cd799439012"
          },
          "owner_company": {
            "type": "string",
            "description": "ID del usuario empresa que subió el documento (solo presente si fue\nsubido 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.\n\nLas notificaciones se asocian a usuarios específicos (company_user o\ntrucker_cia) y contienen\n\ninformación sobre eventos importantes como cambios de estado de eCMRs,\npagos, etc.\n",
        "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\ndestinatario company_user.",
            "nullable": true,
            "example": "507f1f77bcf86cd799439012"
          },
          "trucker_cia": {
            "type": "string",
            "description": "ID de la empresa transportista destinataria (ObjectId). Solo se usa\nsi 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\ntrucker)",
            "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.\n- Company API keys start with 'C' (e.g., C123456789)\n- Trucker API keys start with 'T' (e.g., T123456789)\n"
      },
      "queryAuth": {
        "type": "apiKey",
        "in": "query",
        "name": "token",
        "description": "JWT token authentication via query for endpoints that require it.\nThe middleware also accepts aliases: `access_token`, `accessToken`,\n`x-access-token`, `x-access_token`.\n"
      }
    },
    "parameters": {},
    "responses": {}
  }
}