{
  "components": {
    "schemas": {
      "ActivityResponse": {
        "properties": {
          "docs": {
            "description": "Array de actividades encontradas",
            "items": {
              "$ref": "#/components/schemas/UserActivity"
            },
            "type": "array"
          },
          "limit": {
            "description": "N\u00famero de actividades por p\u00e1gina",
            "example": 25,
            "type": "integer"
          },
          "page": {
            "description": "P\u00e1gina actual",
            "example": 1,
            "type": "integer"
          },
          "pages": {
            "description": "N\u00famero total de p\u00e1ginas disponibles",
            "example": 6,
            "type": "integer"
          },
          "total": {
            "description": "N\u00famero total de actividades encontradas (sin paginaci\u00f3n)",
            "example": 150,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "BillingModeUpdate": {
        "properties": {
          "code": {
            "description": "C\u00f3digo del nuevo plan de precios a aplicar",
            "example": "premium",
            "type": "string"
          }
        },
        "required": [
          "code"
        ],
        "type": "object"
      },
      "BlackSpot": {
        "description": "Punto negro (zona de alta peligrosidad)",
        "properties": {
          "coordinates": {
            "description": "Coordenadas [latitud, longitud]",
            "items": {
              "type": "number"
            },
            "maxItems": 2,
            "minItems": 2,
            "type": "array"
          },
          "dangerLevel": {
            "description": "Nivel de peligrosidad (0-100)",
            "example": 75,
            "maximum": 100,
            "minimum": 0,
            "type": "integer"
          },
          "description": {
            "description": "Descripci\u00f3n del punto negro",
            "type": "string"
          },
          "id": {
            "description": "ID \u00fanico del punto negro",
            "type": "string"
          }
        },
        "type": "object"
      },
      "BlackSpotsResponse": {
        "properties": {
          "blackSpots": {
            "items": {
              "$ref": "#/components/schemas/BlackSpot"
            },
            "type": "array"
          },
          "metadata": {
            "properties": {
              "averageDangerLevel": {
                "description": "Nivel de peligrosidad promedio",
                "type": "integer"
              },
              "maxDangerLevel": {
                "description": "Nivel de peligrosidad m\u00e1ximo",
                "type": "integer"
              },
              "total": {
                "description": "Total de puntos negros encontrados",
                "type": "integer"
              }
            },
            "type": "object"
          },
          "request": {
            "properties": {
              "areaCovered": {
                "description": "\u00c1rea cubierta en km\u00b2",
                "type": "number"
              },
              "minDangerLevel": {
                "description": "Nivel m\u00ednimo solicitado",
                "type": "integer"
              },
              "pointsCount": {
                "description": "N\u00famero de puntos en la solicitud",
                "type": "integer"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "ChapterResult": {
        "description": "## \ud83d\udccb Cap\u00edtulo HS (Nivel de agrupaci\u00f3n)\nRepresenta un cap\u00edtulo del c\u00f3digo HS, que agrupa partidas relacionadas por tipo de producto. Cada cap\u00edtulo pertenece exactamente a una secci\u00f3n.\n\n### \ud83d\udcdd Campos Principales\n- `_id`: Identificador \u00fanico en la base de datos\n- `title`: T\u00edtulo completo del cap\u00edtulo (incluye n\u00famero)\n- `label`: Array de etiquetas descriptivas\n- `headings`: Lista de partidas hijas (puede estar vac\u00eda)\n\n### \ud83c\udfaf Cu\u00e1ndo usar este nivel\n- Para navegaci\u00f3n por categor\u00edas de productos\n- Para an\u00e1lisis por sector econ\u00f3mico\n- Para estad\u00edsticas de comercio por cap\u00edtulo\n\n### \ud83d\udcca Ejemplo Real\n```json\n{\n  \"_id\": \"6502e99001515afda2dbd5bf\",\n  \"title\": \"Cap\u00edtulo 03 - Pescados y crust\u00e1ceos, moluscos y otros invertebrados acu\u00e1ticos\",\n  \"label\": [\n    \"Secci\u00f3n: III - Productos del reino animal\",\n    \"Notas: Incluye pescados vivos, frescos, refrigerados, congelados, etc.\"\n  ],\n  \"headings\": [\n    {\n      \"_id\": \"6502e99001515afda2dbd5bf\",\n      \"title\": \"0302 - Pescados, frescos o refrigerados, excluyendo los filetes y dem\u00e1s carne de la partida 0304\",\n      \"label\": [\n        \"- Pescados de la especie Oncorhynchus spp.\"\n      ],\n      \"subheadings\": [\n        {\n          \"_id\": \"6502e99101515afda2dbd5c1\",\n          \"title\": \"0302.11 - Truchas (Oncorhynchus spp.)\",\n          \"label\": [\n            \"-- Salmo trutta\"\n          ]\n        }\n      ]\n    }\n  ]\n}\n```\n",
        "properties": {
          "_id": {
            "description": "**Identificador \u00fanico MongoDB** del cap\u00edtulo.\n\n**Formato**: ObjectId hexadecimal de 24 caracteres\n**Persistencia**: Este ID no cambia entre versiones\n",
            "example": "6501a2b3c4d5e6f7a8b9c0d3",
            "type": "string"
          },
          "headings": {
            "description": "**Lista de partidas** asociadas a este cap\u00edtulo.\n\n**IMPORTANTE**:\n- Puede estar vac\u00eda (`[]`) si el cap\u00edtulo no tiene partidas definidas\n- Cada partida es \u00fanica dentro del cap\u00edtulo\n- El orden generalmente sigue la numeraci\u00f3n HS\n",
            "items": {
              "$ref": "#/components/schemas/HeadingResult"
            },
            "type": "array"
          },
          "label": {
            "description": "**Array de etiquetas descriptivas** con informaci\u00f3n contextual.\n\n**Contenido com\u00fan**:\n- Referencia a la secci\u00f3n padre\n- Notas generales sobre el cap\u00edtulo\n- Informaci\u00f3n sobre alcances y exclusiones\n",
            "example": [
              "Secci\u00f3n: XI - Textiles y sus manufacturas"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "title": {
            "description": "**T\u00edtulo completo del cap\u00edtulo** incluyendo n\u00famero y descripci\u00f3n oficial.\n\n**Formato**: `\"Cap\u00edtulo XX - Descripci\u00f3n completa\"`\n**Ejemplo**: `\"Cap\u00edtulo 52 - Algod\u00f3n\"`\n\n**IMPORTANTE**: El n\u00famero del cap\u00edtulo (01-99) indica la categor\u00eda general del producto.\n",
            "example": "Cap\u00edtulo 52 - Algod\u00f3n",
            "type": "string"
          }
        },
        "required": [
          "_id",
          "title",
          "headings"
        ],
        "type": "object"
      },
      "CheckoutSessionRequest": {
        "properties": {
          "cancel_url": {
            "description": "URL a la que redirigir si el usuario cancela",
            "example": "https://app.example.com/cancel",
            "format": "uri",
            "type": "string"
          },
          "subscription": {
            "description": "C\u00f3digo del plan de suscripci\u00f3n a contratar",
            "example": "premium",
            "type": "string"
          },
          "success_url": {
            "description": "URL a la que redirigir tras pago exitoso",
            "example": "https://app.example.com/success",
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "success_url",
          "cancel_url",
          "subscription"
        ],
        "type": "object"
      },
      "CityResponse": {
        "description": "Informaci\u00f3n de la ciudad m\u00e1s cercana",
        "properties": {
          "adminCode": {
            "description": "C\u00f3digo administrativo",
            "type": "string"
          },
          "country": {
            "description": "C\u00f3digo de pa\u00eds (ISO 3166-1 alpha-2)",
            "type": "string"
          },
          "location": {
            "$ref": "#/components/schemas/POI/properties/location"
          },
          "name": {
            "description": "Nombre de la ciudad",
            "type": "string"
          }
        },
        "type": "object"
      },
      "ComplianceCheck": {
        "properties": {
          "complianceDetails": {
            "properties": {
              "calibrationValid": {
                "properties": {
                  "compliant": {
                    "description": "Indica si la calibraci\u00f3n es reciente (<1 a\u00f1o)",
                    "type": "boolean"
                  },
                  "value": {
                    "description": "Fecha de \u00faltima calibraci\u00f3n",
                    "format": "date-time",
                    "type": "string"
                  }
                },
                "type": "object"
              },
              "cardValid": {
                "properties": {
                  "compliant": {
                    "description": "Indica si la tarjeta est\u00e1 vigente",
                    "type": "boolean"
                  },
                  "value": {
                    "description": "Fecha de caducidad de la tarjeta",
                    "format": "date-time",
                    "type": "string"
                  }
                },
                "type": "object"
              },
              "drivingTime": {
                "properties": {
                  "compliant": {
                    "description": "Indica si cumple el l\u00edmite",
                    "type": "boolean"
                  },
                  "limit": {
                    "description": "L\u00edmite permitido en minutos (540 = 9 horas)",
                    "type": "number"
                  },
                  "value": {
                    "description": "Tiempo de conducci\u00f3n real en minutos",
                    "type": "number"
                  }
                },
                "type": "object"
              },
              "restTime": {
                "properties": {
                  "compliant": {
                    "description": "Indica si cumple el m\u00ednimo",
                    "type": "boolean"
                  },
                  "limit": {
                    "description": "L\u00edmite m\u00ednimo en minutos (660 = 11 horas)",
                    "type": "number"
                  },
                  "value": {
                    "description": "Tiempo de descanso real en minutos",
                    "type": "number"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          },
          "infractions": {
            "description": "Lista de infracciones detectadas",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "isValid": {
            "description": "Indica si el registro es v\u00e1lido para circular",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "Coordinates": {
        "properties": {
          "lat": {
            "$ref": "#/components/schemas/Latitude"
          },
          "lon": {
            "$ref": "#/components/schemas/Longitude"
          }
        },
        "required": [
          "lat",
          "lon"
        ],
        "type": "object"
      },
      "CreateActivityRequest": {
        "properties": {
          "environment": {
            "description": "Entorno de la actividad",
            "example": "production",
            "required": true,
            "type": "string"
          },
          "feature": {
            "description": "Funcionalidad utilizada",
            "example": "user_profile",
            "required": true,
            "type": "string"
          },
          "idUser": {
            "description": "ID del usuario (opcional si viene del JWT)",
            "example": "63d7907cbe76403b35da63df",
            "type": "string"
          },
          "ip": {
            "description": "IP del usuario (opcional, se obtiene autom\u00e1ticamente)",
            "example": "192.168.1.100",
            "type": "string"
          },
          "o_system": {
            "description": "Sistema operativo",
            "enum": [
              "android",
              "linux",
              "windows",
              "apple",
              "etc"
            ],
            "example": "windows",
            "required": true,
            "type": "string"
          },
          "origin": {
            "description": "Origen de la solicitud",
            "example": "web",
            "required": true,
            "type": "string"
          },
          "type": {
            "description": "Tipo de actividad",
            "enum": [
              "C",
              "R",
              "U",
              "D"
            ],
            "example": "C",
            "required": true,
            "type": "string"
          },
          "url": {
            "description": "URL de la actividad",
            "example": "/api/users/profile",
            "required": true,
            "type": "string"
          }
        },
        "required": [
          "environment",
          "type",
          "feature",
          "origin",
          "o_system",
          "url"
        ],
        "type": "object"
      },
      "DateType": {
        "description": "Configuraci\u00f3n de fecha para la ruta.\n\n- `departure`: La fecha indica cu\u00e1ndo sale el conductor\n- `arrival`: La fecha indica cu\u00e1ndo debe llegar (el sistema calcula la salida)\n",
        "properties": {
          "date": {
            "description": "Fecha y hora (ISO 8601)",
            "example": "2025-01-15T08:00:00Z",
            "format": "date-time",
            "type": "string"
          },
          "type": {
            "description": "Tipo de fecha",
            "enum": [
              "departure",
              "arrival"
            ],
            "example": "departure",
            "type": "string"
          }
        },
        "required": [
          "type",
          "date"
        ],
        "type": "object"
      },
      "Discount": {
        "properties": {
          "centsValue": {
            "description": "Valor del descuento en c\u00e9ntimos",
            "example": 0.15,
            "format": "float",
            "minimum": 0,
            "type": "number"
          },
          "createdAt": {
            "description": "Fecha de creaci\u00f3n del descuento",
            "example": "2025-04-29T10:30:00Z",
            "format": "date-time",
            "type": "string"
          },
          "id_discount": {
            "description": "Identificador \u00fanico del descuento",
            "example": "123456789",
            "type": "string"
          },
          "isActive": {
            "description": "Indica si el descuento est\u00e1 activo",
            "example": true,
            "type": "boolean"
          },
          "percentValue": {
            "description": "Valor del descuento en porcentaje",
            "example": 10.0,
            "format": "float",
            "maximum": 100,
            "minimum": 0,
            "type": "number"
          },
          "selected": {
            "description": "Indica si el descuento est\u00e1 seleccionado",
            "example": false,
            "type": "boolean"
          },
          "station": {
            "description": "Identificador de la estaci\u00f3n asociada al descuento",
            "example": "5f8d3b7b3f6b8a1b9c3b7b3f",
            "type": "string"
          },
          "type": {
            "description": "Tipo de descuento",
            "example": "percentage",
            "type": "string"
          },
          "updatedAt": {
            "description": "Fecha de \u00faltima actualizaci\u00f3n del descuento",
            "example": "2025-04-29T10:30:00Z",
            "format": "date-time",
            "type": "string"
          },
          "userId": {
            "description": "Identificador del usuario propietario del descuento",
            "example": "user123",
            "type": "string"
          }
        },
        "required": [
          "id_discount",
          "station",
          "type"
        ],
        "type": "object"
      },
      "DiscountResponse": {
        "properties": {
          "data": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Discount"
              },
              {
                "items": {
                  "$ref": "#/components/schemas/Discount"
                },
                "type": "array"
              }
            ]
          },
          "message": {
            "description": "Mensaje adicional de la operaci\u00f3n",
            "example": "Discount created successfully",
            "type": "string"
          },
          "pagination": {
            "properties": {
              "limit": {
                "description": "L\u00edmite por p\u00e1gina",
                "example": 10,
                "type": "number"
              },
              "page": {
                "description": "P\u00e1gina actual",
                "example": 1,
                "type": "number"
              },
              "pages": {
                "description": "Total de p\u00e1ginas",
                "example": 5,
                "type": "number"
              },
              "total": {
                "description": "Total de documentos",
                "example": 50,
                "type": "number"
              }
            },
            "type": "object"
          },
          "success": {
            "description": "Indica si la operaci\u00f3n fue exitosa",
            "example": true,
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "Driver": {
        "properties": {
          "_id": {
            "description": "Identificador \u00fanico del conductor en formato MongoDB ObjectId.\n**Formato**: 24 caracteres hexadecimales (ej: 5f8d3b7b3f6b8a1b9c3b7b3f)\n**Generado autom\u00e1ticamente** por el sistema al crear el conductor.\n**Requerido** para todas las operaciones que referencien un conductor espec\u00edfico.\n",
            "example": "5f8d3b7b3f6b8a1b9c3b7b3f",
            "type": "string"
          },
          "currentPosition": {
            "description": "\u00daltima posici\u00f3n conocida del conductor.\n**Actualizado autom\u00e1ticamente** cada 5 minutos cuando el conductor est\u00e1 en ruta.\n**Formato**: Objeto con coordenadas y timestamp.\n**Uso principal**: Mostrar ubicaci\u00f3n en tiempo real en mapas.\n",
            "properties": {
              "coordinates": {
                "description": "Coordenadas GPS en formato [longitud, latitud].\n**Precisi\u00f3n m\u00ednima**: 6 decimales (~11cm).\n**Valores v\u00e1lidos**:\n- Latitud: -90 a 90\n- Longitud: -180 a 180\n",
                "example": [
                  -3.7038,
                  40.4168
                ],
                "items": {
                  "format": "float",
                  "type": "number"
                },
                "maxItems": 2,
                "minItems": 2,
                "type": "array"
              },
              "timestamp": {
                "description": "Fecha y hora exacta de la posici\u00f3n en formato ISO 8601 (UTC).\n**Actualizado autom\u00e1ticamente** cada vez que se reporta nueva posici\u00f3n.\n**Usado para**: Calcular velocidad, estimar tiempos de llegada, verificar actualidad.\n",
                "example": "2025-04-30T10:30:00.000Z",
                "format": "date-time",
                "type": "string"
              }
            },
            "type": "object"
          },
          "drivingHistory": {
            "description": "Historial detallado de actividades del conductor.\n**Usado para**: Auditor\u00eda, an\u00e1lisis de productividad, cumplimiento normativo.\n**Retenci\u00f3n**: 6 meses para fines legales.\n",
            "items": {
              "properties": {
                "coordinates": {
                  "description": "Puntos significativos de la ruta durante esta actividad.\n**M\u00e1ximo**: 100 puntos por registro.\n**Formato**: [[lng, lat], [lng, lat], ...]\n",
                  "items": {
                    "items": {
                      "format": "float",
                      "type": "number"
                    },
                    "maxItems": 2,
                    "minItems": 2,
                    "type": "array"
                  },
                  "type": "array"
                },
                "durationMinutes": {
                  "description": "Duraci\u00f3n total en minutos",
                  "type": "number"
                },
                "endTime": {
                  "description": "Hora de fin de la actividad (null si est\u00e1 en curso)",
                  "format": "date-time",
                  "type": "string"
                },
                "startTime": {
                  "description": "Hora de inicio de la actividad",
                  "format": "date-time",
                  "type": "string"
                },
                "type": {
                  "description": "Tipo de actividad registrada.\n- `driving`: Periodo de conducci\u00f3n activa\n- `rest`: Periodo de descanso obligatorio\n",
                  "enum": [
                    "driving",
                    "rest"
                  ],
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "enabled": {
            "description": "Estado de habilitaci\u00f3n del conductor.\n**Por defecto**: `true`\n**Uso**: Para deshabilitar temporalmente un conductor sin eliminarlo.\n**Efecto**: Conductores deshabilitados no aparecen en b\u00fasquedas ni pueden recibir rutas.\n",
            "example": true,
            "type": "boolean"
          },
          "hoursStatus": {
            "description": "Estado actual de las horas de conducci\u00f3n del conductor.\n**Actualizado autom\u00e1ticamente** con cada registro de actividad.\n**Usado para**: Cumplimiento normativo, prevenci\u00f3n de infracciones.\n",
            "properties": {
              "lastDrivingStart": {
                "description": "Fecha y hora de inicio de la \u00faltima sesi\u00f3n de conducci\u00f3n.\n**Usado para**: Calcular tiempo m\u00e1ximo de conducci\u00f3n ininterrumpida.\n**Regulaci\u00f3n**: M\u00e1ximo 4.5 horas sin descanso de 45 minutos.\n",
                "example": "2025-04-30T08:00:00.000Z",
                "format": "date-time",
                "type": "string"
              },
              "lastRestStart": {
                "description": "Fecha y hora de inicio del \u00faltimo periodo de descanso.\n**Usado para**: Calcular cumplimiento de descansos obligatorios.\n**Regulaci\u00f3n**: M\u00ednimo 11 horas de descanso diario.\n",
                "example": "2025-04-30T21:00:00.000Z",
                "format": "date-time",
                "type": "string"
              },
              "remainingBiweeklyHours": {
                "description": "Horas restantes disponibles en el periodo bimensual actual.\n**Regulaci\u00f3n UE**: M\u00e1ximo 90 horas en 2 semanas consecutivas.\n**Calculado autom\u00e1ticamente**.\n",
                "example": 45.0,
                "type": "number"
              },
              "remainingDayHours": {
                "description": "Horas restantes disponibles hoy.\n**Regulaci\u00f3n UE**: M\u00e1ximo 9 horas diarias (puede extenderse a 10 horas 2 veces por semana).\n**Actualizado en tiempo real**.\n",
                "example": 3.5,
                "type": "number"
              },
              "remainingWeeklyHours": {
                "description": "Horas restantes disponibles esta semana (lunes a domingo).\n**Calculado autom\u00e1ticamente** basado en registros de conducci\u00f3n.\n**No puede ser negativo**.\n**Alerta**: Cuando < 4 horas restantes.\n",
                "example": 12.5,
                "type": "number"
              }
            },
            "type": "object"
          },
          "licenseNumber": {
            "description": "N\u00famero de licencia de conducir v\u00e1lida.\n**Requerido** para crear un conductor.\n**\u00danico** en el sistema, no se puede modificar despu\u00e9s de creaci\u00f3n.\n**Formato espec\u00edfico por pa\u00eds**:\n- Espa\u00f1a: 8 d\u00edgitos + 1 letra (ej: 12345678X)\n- Francia: 12 caracteres alfanum\u00e9ricos\n- Alemania: 11 d\u00edgitos\n",
            "example": "12345678X",
            "type": "string"
          },
          "name": {
            "description": "Nombre completo del conductor seg\u00fan documento de identidad.\n**Requerido** para crear/actualizar un conductor.\n**Validaci\u00f3n**: M\u00ednimo 3 caracteres, m\u00e1ximo 100.\n**Formato**: Texto plano, sin caracteres especiales.\n",
            "example": "Juan P\u00e9rez Garc\u00eda",
            "type": "string"
          },
          "owner": {
            "description": "ID del usuario propietario del conductor.\n**Asignado autom\u00e1ticamente** basado en el token JWT del usuario autenticado.\n**No modificable** manualmente.\nUsado para aislamiento de datos entre usuarios.\n",
            "example": "63d7907cbe76403b35da63df",
            "type": "string"
          },
          "positionHistory": {
            "description": "Historial de posiciones del conductor (\u00faltimos 30 d\u00edas).\n**M\u00e1ximo**: 1000 posiciones almacenadas.\n**Purga autom\u00e1tica**: Posiciones mayores a 30 d\u00edas se eliminan.\n**Uso**: An\u00e1lisis de rutas, auditor\u00eda, reconstrucci\u00f3n de trayectos.\n",
            "items": {
              "properties": {
                "coordinates": {
                  "items": {
                    "format": "float",
                    "type": "number"
                  },
                  "maxItems": 2,
                  "minItems": 2,
                  "type": "array"
                },
                "timestamp": {
                  "format": "date-time",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "_id",
          "name",
          "licenseNumber",
          "enabled"
        ],
        "type": "object"
      },
      "DriverActivity": {
        "properties": {
          "coordinates": {
            "description": "Puntos de ruta significativos durante la actividad.\n**Opcional**: Solo para actividades de tipo `driving`\n**M\u00e1ximo**: 50 puntos\n**Uso**: Reconstrucci\u00f3n de ruta, c\u00e1lculo de distancia\n",
            "example": [
              [
                -3.7038,
                40.4168
              ],
              [
                -3.71,
                40.42
              ],
              [
                -3.715,
                40.425
              ]
            ],
            "items": {
              "items": {
                "format": "float",
                "type": "number"
              },
              "maxItems": 2,
              "minItems": 2,
              "type": "array"
            },
            "type": "array"
          },
          "endTime": {
            "description": "Fecha y hora de fin de la actividad.\n**Opcional**: Si no se proporciona, se asume actividad en curso\n**Validaci\u00f3n**: Debe ser posterior a startTime\n",
            "example": "2025-04-30T12:30:00.000Z",
            "format": "date-time",
            "type": "string"
          },
          "startTime": {
            "description": "Fecha y hora de inicio de la actividad.\n**Requerido**: S\u00ed\n**Formato**: ISO 8601 (UTC)\n**Validaci\u00f3n**: No puede superponerse con actividades existentes\n",
            "example": "2025-04-30T08:00:00.000Z",
            "format": "date-time",
            "type": "string"
          },
          "type": {
            "description": "Tipo de actividad a registrar.\n- `driving`: Inicio/fin de periodo de conducci\u00f3n\n- `rest`: Inicio/fin de periodo de descanso\n**Requerido**: S\u00ed\n",
            "enum": [
              "driving",
              "rest"
            ],
            "example": "driving",
            "type": "string"
          }
        },
        "required": [
          "type",
          "startTime"
        ],
        "type": "object"
      },
      "Error": {
        "properties": {
          "error": {
            "description": "Mensaje descriptivo del error en espa\u00f1ol",
            "example": "Par\u00e1metros inv\u00e1lidos o faltantes",
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "type": "object"
      },
      "ErrorResponse": {
        "properties": {
          "message": {
            "description": "Mensaje descriptivo del error",
            "example": "idUser is required",
            "type": "string"
          }
        },
        "type": "object"
      },
      "GamificationMetrics": {
        "description": "M\u00e9tricas de gamificaci\u00f3n del usuario",
        "properties": {
          "achievements": {
            "items": {
              "properties": {
                "description": {
                  "type": "string"
                },
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "unlockedAt": {
                  "format": "date-time",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "co2Saved": {
            "description": "CO2 ahorrado estimado en kg",
            "type": "number"
          },
          "fuelSaved": {
            "description": "Combustible ahorrado estimado en litros",
            "type": "number"
          },
          "totalDistance": {
            "description": "Distancia total recorrida en km",
            "type": "number"
          },
          "totalRoutes": {
            "description": "Total de rutas calculadas",
            "type": "integer"
          },
          "totalTime": {
            "description": "Tiempo total de conducci\u00f3n en horas",
            "type": "number"
          }
        },
        "type": "object"
      },
      "GamificationSummary": {
        "description": "Resumen de gamificaci\u00f3n",
        "properties": {
          "level": {
            "description": "Nivel actual del usuario",
            "type": "integer"
          },
          "nextLevelPoints": {
            "description": "Puntos necesarios para el siguiente nivel",
            "type": "integer"
          },
          "points": {
            "description": "Puntos acumulados",
            "type": "integer"
          },
          "rank": {
            "description": "Rango del usuario",
            "example": "Road Master",
            "type": "string"
          },
          "stats": {
            "$ref": "#/components/schemas/GamificationMetrics"
          }
        },
        "type": "object"
      },
      "HeadingResult": {
        "description": "## \ud83d\udccb Partida HS (Nivel intermedio)\nRepresenta una partida del c\u00f3digo HS, que agrupa subpartidas relacionadas t\u00e9cnicamente. Cada partida pertenece exactamente a un cap\u00edtulo.\n\n### \ud83d\udcdd Campos Principales\n- `_id`: Identificador \u00fanico en la base de datos\n- `title`: Descripci\u00f3n de la partida\n- `label`: Array de etiquetas descriptivas\n- `subheadings`: Lista de subpartidas hijas (puede estar vac\u00eda)\n\n### \ud83c\udfaf Cu\u00e1ndo usar este nivel\n- Para b\u00fasquedas generales de productos\n- Cuando se necesita una clasificaci\u00f3n menos espec\u00edfica\n- Para an\u00e1lisis estad\u00edsticos por categor\u00eda\n\n### \ud83d\udcca Ejemplo Real\n```json\n{\n  \"_id\": \"6502e99001515afda2dbd5bf\",\n  \"title\": \"0302.3 - Atunes (g\u00e9nero thunnus)\",\n  \"label\": [\n    \"- tunas (of the genus thunnus), skipjack tuna (stripe-bellied bonito) (katsuwonus pelamis), excluding edible fish offal of subheadings 0302.91 to 0302.99 :\",\n    \"Secci\u00f3n: III - Pescados y crust\u00e1ceos\"\n  ],\n  \"subheadings\": [\n    {\n      \"_id\": \"6502e99101515afda2dbd5c1\",\n      \"title\": \"0302.31 - Albacora o at\u00fan de aleta larga\",\n      \"label\": [\n        \"-- albacore or longfinned tunas (thunnus alalunga)\"\n      ]\n    }\n  ]\n}\n```\n",
        "properties": {
          "_id": {
            "description": "**Identificador \u00fanico MongoDB** de la partida.\n\n**Formato**: ObjectId hexadecimal de 24 caracteres\n**Persistencia**: Este ID no cambia entre versiones\n",
            "example": "6501a2b3c4d5e6f7a8b9c0d2",
            "type": "string"
          },
          "label": {
            "description": "**Array de etiquetas descriptivas** con informaci\u00f3n adicional.\n\n**Contenido com\u00fan**:\n- Descripciones t\u00e9cnicas detalladas\n- Exclusiones o inclusiones espec\u00edficas\n- Referencias a otras partidas/subpartidas\n",
            "example": [
              "- tunas (of the genus thunnus), skipjack tuna (stripe-bellied bonito) (katsuwonus pelamis)"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "subheadings": {
            "description": "**Lista de subpartidas** asociadas a esta partida.\n\n**IMPORTANTE**:\n- Puede estar vac\u00eda (`[]`) si la partida no tiene subpartidas\n- El orden no tiene significado especial\n- Cada subpartida es \u00fanica y no se repite en otras partidas\n",
            "items": {
              "$ref": "#/components/schemas/SubheadingResult"
            },
            "type": "array"
          },
          "title": {
            "description": "**Nombre descriptivo** de la partida seg\u00fan la nomenclatura HS.\n\n**Formato**: `\"C\u00f3digo - Descripci\u00f3n\"`\n**Ejemplo**: `\"0302.3 - Atunes (g\u00e9nero thunnus)\"`\n",
            "example": "Algod\u00f3n",
            "type": "string"
          }
        },
        "required": [
          "_id",
          "title",
          "subheadings"
        ],
        "type": "object"
      },
      "HealthResponse": {
        "properties": {
          "dbStatus": {
            "description": "Estado de la conexi\u00f3n a la base de datos",
            "enum": [
              "connected",
              "disconnected"
            ],
            "example": "connected",
            "type": "string"
          },
          "status": {
            "description": "Estado del servicio",
            "enum": [
              "ok"
            ],
            "example": "ok",
            "type": "string"
          }
        },
        "type": "object"
      },
      "HistoricalDrivingAnalysis": {
        "properties": {
          "activityBreakdown": {
            "additionalProperties": {
              "type": "number"
            },
            "description": "Desglose de actividades por tipo (minutos)",
            "type": "object"
          },
          "basicInfo": {
            "properties": {
              "driver": {
                "description": "Nombre completo del conductor",
                "type": "string"
              },
              "period": {
                "description": "Periodo de la sesi\u00f3n",
                "type": "string"
              },
              "vehicle": {
                "description": "Matr\u00edcula del veh\u00edculo",
                "type": "string"
              }
            },
            "type": "object"
          },
          "drivingAnalysis": {
            "properties": {
              "averageSpeed": {
                "description": "Velocidad promedio en km/h",
                "type": "number"
              },
              "maxSpeed": {
                "description": "Velocidad m\u00e1xima en km/h",
                "type": "number"
              },
              "speedExcesses": {
                "description": "N\u00famero de excesos de velocidad",
                "type": "number"
              },
              "totalDistance": {
                "description": "Distancia total en km",
                "type": "number"
              },
              "totalTime": {
                "description": "Tiempo total de conducci\u00f3n en minutos",
                "type": "number"
              }
            },
            "type": "object"
          },
          "eventsSummary": {
            "properties": {
              "bySeverity": {
                "additionalProperties": {
                  "type": "number"
                },
                "description": "Eventos agrupados por gravedad",
                "type": "object"
              },
              "byType": {
                "additionalProperties": {
                  "type": "number"
                },
                "description": "Eventos agrupados por tipo",
                "type": "object"
              },
              "total": {
                "description": "Total de eventos registrados",
                "type": "number"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "HistoricalDrivingRecord": {
        "properties": {
          "_id": {
            "description": "ID \u00fanico del registro de tac\u00f3grafo",
            "example": "5f8d3b7b3f6b8a1b9c3b7b3f",
            "type": "string"
          },
          "actividades": {
            "items": {
              "properties": {
                "duracionMinutos": {
                  "description": "Duraci\u00f3n en minutos",
                  "type": "number"
                },
                "fechaFin": {
                  "format": "date-time",
                  "type": "string"
                },
                "fechaInicio": {
                  "format": "date-time",
                  "type": "string"
                },
                "tipo": {
                  "description": "Tipo de actividad registrada",
                  "enum": [
                    "CONDUCCION",
                    "TRABAJO",
                    "DISPONIBILIDAD",
                    "DESCANSO",
                    "DESCONOCIDO"
                  ],
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "archivoDescarga": {
            "properties": {
              "extension": {
                "description": "Formato del archivo de tac\u00f3grafo",
                "enum": [
                  "TGD",
                  "DDD",
                  "C1B",
                  "V1B"
                ],
                "example": "TGD",
                "type": "string"
              },
              "fechaDescarga": {
                "description": "Fecha y hora de la descarga",
                "example": "2025-04-30T10:30:00.000Z",
                "format": "date-time",
                "type": "string"
              },
              "nombre": {
                "description": "Nombre del archivo descargado del tac\u00f3grafo",
                "example": "TACO_20250430_083000",
                "type": "string"
              }
            },
            "type": "object"
          },
          "conduccion": {
            "properties": {
              "distanciaTotal": {
                "description": "Distancia total recorrida en km",
                "example": 450.75,
                "type": "number"
              },
              "tiempoConduccionTotal": {
                "description": "Tiempo total de conducci\u00f3n en minutos",
                "example": 480,
                "type": "number"
              },
              "velocidadMaxima": {
                "description": "Velocidad m\u00e1xima registrada en km/h",
                "example": 95.5,
                "type": "number"
              },
              "velocidadPromedio": {
                "description": "Velocidad promedio en km/h",
                "example": 72.3,
                "type": "number"
              }
            },
            "type": "object"
          },
          "conductor": {
            "properties": {
              "apellidos": {
                "description": "Apellidos del conductor",
                "example": "P\u00e9rez Garc\u00eda",
                "type": "string"
              },
              "fechaCaducidad": {
                "description": "Fecha de caducidad de la tarjeta",
                "example": "2026-12-31T23:59:59.000Z",
                "format": "date-time",
                "type": "string"
              },
              "nombre": {
                "description": "Nombre del conductor",
                "example": "Juan",
                "type": "string"
              },
              "numeroTarjeta": {
                "description": "N\u00famero de tarjeta de conductor (16 d\u00edgitos)",
                "example": "ES123456789012345",
                "type": "string"
              }
            },
            "type": "object"
          },
          "eventos": {
            "items": {
              "properties": {
                "codigo": {
                  "description": "C\u00f3digo del evento",
                  "type": "string"
                },
                "descripcion": {
                  "description": "Descripci\u00f3n del evento",
                  "type": "string"
                },
                "fechaHora": {
                  "format": "date-time",
                  "type": "string"
                },
                "gravedad": {
                  "description": "Gravedad del evento",
                  "enum": [
                    "LEVE",
                    "GRAVE",
                    "MUY_GRAVE"
                  ],
                  "type": "string"
                },
                "tipo": {
                  "description": "Tipo de evento registrado",
                  "enum": [
                    "FALLO_SENSOR",
                    "FALLO_TACOGRAFO",
                    "MANIPULACION",
                    "TARJETA_NO_INSERTADA",
                    "EXCESO_VELOCIDAD",
                    "EVENTO_PERSONALIZADO",
                    "ERROR_DATOS"
                  ],
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "resumenTiempos": {
            "properties": {
              "cumpleNormativa": {
                "description": "Indica si cumple la normativa",
                "type": "boolean"
              },
              "infracciones": {
                "description": "Lista de infracciones detectadas",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "tiempoConduccion": {
                "description": "Tiempo total de conducci\u00f3n en minutos",
                "type": "number"
              },
              "tiempoDescanso": {
                "description": "Tiempo total de descanso en minutos",
                "type": "number"
              }
            },
            "type": "object"
          },
          "sesion": {
            "properties": {
              "fechaExtraccion": {
                "description": "Fecha de extracci\u00f3n de la tarjeta",
                "example": "2025-04-30T17:30:00.000Z",
                "format": "date-time",
                "type": "string"
              },
              "fechaInsercion": {
                "description": "Fecha de inserci\u00f3n de la tarjeta en el tac\u00f3grafo",
                "example": "2025-04-30T08:00:00.000Z",
                "format": "date-time",
                "type": "string"
              },
              "vehiculo": {
                "properties": {
                  "matricula": {
                    "description": "Matr\u00edcula del veh\u00edculo",
                    "example": "1234ABC",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "Invoice": {
        "properties": {
          "amount_due": {
            "description": "Monto total de la factura",
            "type": "number"
          },
          "created": {
            "description": "Timestamp de creaci\u00f3n",
            "type": "number"
          },
          "currency": {
            "description": "Moneda",
            "type": "string"
          },
          "due_date": {
            "description": "Timestamp de vencimiento",
            "type": "number"
          },
          "hosted_invoice_url": {
            "description": "URL para ver la factura",
            "type": "string"
          },
          "id": {
            "description": "ID de la factura en Stripe",
            "type": "string"
          },
          "number": {
            "description": "N\u00famero de factura",
            "type": "string"
          },
          "status": {
            "description": "Estado de la factura (paid, draft, open, etc.)",
            "type": "string"
          }
        },
        "type": "object"
      },
      "Isochrone": {
        "properties": {
          "center": {
            "$ref": "#/components/schemas/Coordinates"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Identificador \u00fanico de la is\u00f3crona",
            "type": "string"
          },
          "polygon": {
            "description": "Pol\u00edgono que define el \u00e1rea alcanzable",
            "items": {
              "$ref": "#/components/schemas/Coordinates"
            },
            "type": "array"
          },
          "travelTime": {
            "description": "Tiempo de viaje en minutos",
            "type": "number"
          }
        },
        "required": [
          "id",
          "center",
          "travelTime",
          "polygon"
        ],
        "type": "object"
      },
      "IsochroneResponse": {
        "description": "Respuesta de c\u00e1lculo de isocrona",
        "properties": {
          "features": {
            "items": {
              "properties": {
                "geometry": {
                  "properties": {
                    "coordinates": {
                      "description": "Coordenadas del pol\u00edgono",
                      "type": "array"
                    },
                    "type": {
                      "example": "Polygon",
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "properties": {
                  "properties": {
                    "contour": {
                      "description": "Valor del contorno (km o minutos)",
                      "type": "number"
                    }
                  },
                  "type": "object"
                },
                "type": {
                  "example": "Feature",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "type": {
            "example": "FeatureCollection",
            "type": "string"
          }
        },
        "type": "object"
      },
      "Language": {
        "default": "es",
        "description": "Idioma para los datos de respuesta\n\n**Idiomas disponibles:**\n- `es`: Espa\u00f1ol (predeterminado)\n- `en`: Ingl\u00e9s\n- `fr`: Franc\u00e9s\n- `de`: Alem\u00e1n\n- `pt`: Portugu\u00e9s\n\n**Ejemplo de uso:**\n```\nGET /events/nearby?lat=40.4168&lng=-3.7038&lang=en\n```\n",
        "enum": [
          "es",
          "en",
          "fr",
          "de",
          "pt"
        ],
        "type": "string"
      },
      "Latitude": {
        "description": "Coordenada de latitud geogr\u00e1fica en grados decimales (est\u00e1ndar WGS84)\n\n**Ejemplos:**\n- Madrid: 40.4168\n- Barcelona: 41.3851\n- Valencia: 39.4699\n",
        "example": 40.4168,
        "format": "float",
        "maximum": 90,
        "minimum": -90,
        "type": "number"
      },
      "Location": {
        "description": "Ubicaci\u00f3n guardada (cliente, almac\u00e9n, etc.)",
        "properties": {
          "_id": {
            "type": "string"
          },
          "address": {
            "description": "Direcci\u00f3n completa",
            "example": "Calle Industrial 25",
            "type": "string"
          },
          "city": {
            "example": "Madrid",
            "type": "string"
          },
          "closingTime": {
            "description": "Hora de cierre (HH:MM)",
            "example": "18:00",
            "type": "string"
          },
          "contactPerson": {
            "description": "Persona de contacto",
            "type": "string"
          },
          "coordinates": {
            "items": {
              "type": "number"
            },
            "maxItems": 2,
            "minItems": 2,
            "type": "array"
          },
          "country": {
            "example": "Espa\u00f1a",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "customerId": {
            "description": "ID del cliente (para integraci\u00f3n)",
            "type": "string"
          },
          "email": {
            "description": "Email de contacto",
            "format": "email",
            "type": "string"
          },
          "latitude": {
            "description": "Latitud como string",
            "example": "40.4168",
            "type": "string"
          },
          "longitude": {
            "description": "Longitud como string",
            "example": "-3.7038",
            "type": "string"
          },
          "name": {
            "description": "Nombre de la ubicaci\u00f3n",
            "example": "Almac\u00e9n Central Madrid",
            "type": "string"
          },
          "openingTime": {
            "description": "Hora de apertura (HH:MM)",
            "example": "08:00",
            "type": "string"
          },
          "owner": {
            "description": "ID del usuario propietario",
            "type": "string"
          },
          "phone": {
            "description": "Tel\u00e9fono de contacto",
            "type": "string"
          },
          "province": {
            "example": "Madrid",
            "type": "string"
          },
          "stopTime": {
            "description": "Tiempo de parada habitual",
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "zipcode": {
            "example": "28001",
            "type": "string"
          }
        },
        "type": "object"
      },
      "LocationCreate": {
        "properties": {
          "address": {
            "minLength": 1,
            "type": "string"
          },
          "city": {
            "minLength": 1,
            "type": "string"
          },
          "closingTime": {
            "description": "Formato HH:MM",
            "pattern": "^([01]?[0-9]|2[0-3]):[0-5][0-9]$",
            "type": "string"
          },
          "contactPerson": {
            "type": "string"
          },
          "coordinates": {
            "items": {
              "type": "number"
            },
            "maxItems": 2,
            "minItems": 2,
            "type": "array"
          },
          "country": {
            "type": "string"
          },
          "customerId": {
            "type": "string"
          },
          "email": {
            "format": "email",
            "type": "string"
          },
          "latitude": {
            "type": "string"
          },
          "longitude": {
            "type": "string"
          },
          "name": {
            "minLength": 1,
            "type": "string"
          },
          "openingTime": {
            "description": "Formato HH:MM",
            "pattern": "^([01]?[0-9]|2[0-3]):[0-5][0-9]$",
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "province": {
            "minLength": 1,
            "type": "string"
          },
          "zipcode": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "address",
          "city",
          "province",
          "latitude",
          "longitude",
          "coordinates",
          "openingTime",
          "closingTime"
        ],
        "type": "object"
      },
      "LocationPoint": {
        "description": "Punto de ubicaci\u00f3n con metadatos",
        "properties": {
          "coordinates": {
            "description": "Coordenadas [latitud, longitud]",
            "items": {
              "type": "number"
            },
            "maxItems": 2,
            "minItems": 2,
            "type": "array"
          },
          "locality": {
            "description": "Localidad/ciudad",
            "type": "string"
          },
          "postalCode": {
            "description": "C\u00f3digo postal",
            "type": "string"
          }
        },
        "type": "object"
      },
      "LocationUpdate": {
        "properties": {
          "address": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "closingTime": {
            "type": "string"
          },
          "contactPerson": {
            "type": "string"
          },
          "coordinates": {
            "items": {
              "type": "number"
            },
            "maxItems": 2,
            "minItems": 2,
            "type": "array"
          },
          "country": {
            "type": "string"
          },
          "customerId": {
            "type": "string"
          },
          "email": {
            "format": "email",
            "type": "string"
          },
          "latitude": {
            "type": "string"
          },
          "longitude": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "openingTime": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "province": {
            "type": "string"
          },
          "zipcode": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "LoginRequest": {
        "properties": {
          "email": {
            "description": "Email del usuario registrado",
            "format": "email",
            "type": "string"
          },
          "password": {
            "description": "Contrase\u00f1a del usuario (m\u00ednimo 6 caracteres)",
            "type": "string"
          }
        },
        "required": [
          "email",
          "password"
        ],
        "type": "object"
      },
      "LoginResponse": {
        "properties": {
          "token": {
            "description": "Token JWT para autenticaci\u00f3n",
            "type": "string"
          },
          "user": {
            "description": "Informaci\u00f3n b\u00e1sica del usuario",
            "type": "object"
          }
        },
        "type": "object"
      },
      "Longitude": {
        "description": "Coordenada de longitud geogr\u00e1fica en grados decimales (est\u00e1ndar WGS84)\n\n**Ejemplos:**\n- Madrid: -3.7038\n- Barcelona: 2.1734\n- Valencia: -0.3763\n",
        "example": -3.7038,
        "format": "float",
        "maximum": 180,
        "minimum": -180,
        "type": "number"
      },
      "MainRoute": {
        "description": "Ruta principal recomendada",
        "properties": {
          "maneuvers": {
            "description": "Lista de maniobras de navegaci\u00f3n",
            "items": {
              "$ref": "#/components/schemas/Maneuver"
            },
            "type": "array"
          },
          "shape": {
            "description": "Polyline encoded de la geometr\u00eda de la ruta",
            "type": "string"
          },
          "stops": {
            "items": {
              "$ref": "#/components/schemas/UnifiedStop"
            },
            "type": "array"
          },
          "summary": {
            "$ref": "#/components/schemas/RouteSummary"
          }
        },
        "type": "object"
      },
      "Maneuver": {
        "description": "Instrucci\u00f3n de navegaci\u00f3n",
        "properties": {
          "begin_shape_index": {
            "description": "\u00cdndice inicial en el polyline",
            "type": "integer"
          },
          "end_shape_index": {
            "description": "\u00cdndice final en el polyline",
            "type": "integer"
          },
          "highway": {
            "description": "Si este tramo es autopista",
            "type": "boolean"
          },
          "instruction": {
            "description": "Instrucci\u00f3n de texto",
            "example": "Gire a la derecha en Calle Mayor",
            "type": "string"
          },
          "length": {
            "description": "Distancia del tramo en km",
            "type": "number"
          },
          "street_names": {
            "description": "Nombres de calles",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "time": {
            "description": "Tiempo del tramo en segundos",
            "type": "number"
          },
          "toll": {
            "description": "Si este tramo tiene peaje",
            "type": "boolean"
          },
          "type": {
            "description": "Tipo de maniobra (c\u00f3digo Valhalla)",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "MapLayers": {
        "description": "Opciones de ruta y capas del mapa.\nControla qu\u00e9 tipo de carreteras evitar y qu\u00e9 POIs mostrar.\n",
        "properties": {
          "allowSecondary": {
            "default": false,
            "description": "Permitir carreteras secundarias",
            "type": "boolean"
          },
          "avoidHighways": {
            "default": false,
            "description": "Evitar autopistas",
            "type": "boolean"
          },
          "avoidTolls": {
            "default": false,
            "description": "Evitar peajes",
            "type": "boolean"
          },
          "avoidTunnels": {
            "default": false,
            "description": "Evitar t\u00faneles",
            "type": "boolean"
          },
          "cafeterias": {
            "default": false,
            "description": "Mostrar cafeter\u00edas",
            "type": "boolean"
          },
          "calculateStops": {
            "default": true,
            "description": "Calcular paradas de descanso autom\u00e1ticamente",
            "type": "boolean"
          },
          "gasStations": {
            "default": false,
            "description": "Mostrar gasolineras",
            "type": "boolean"
          },
          "parkings": {
            "default": false,
            "description": "Mostrar parkings para camiones",
            "type": "boolean"
          },
          "radars": {
            "default": false,
            "description": "Mostrar radares",
            "type": "boolean"
          },
          "shortestRoute": {
            "default": false,
            "description": "Calcular ruta m\u00e1s corta en lugar de m\u00e1s r\u00e1pida",
            "type": "boolean"
          },
          "showers": {
            "default": false,
            "description": "Mostrar duchas/\u00e1reas de descanso",
            "type": "boolean"
          },
          "supermarkets": {
            "default": false,
            "description": "Mostrar supermercados",
            "type": "boolean"
          },
          "trafficDensity": {
            "default": false,
            "description": "Considerar densidad de tr\u00e1fico",
            "type": "boolean"
          },
          "weather": {
            "default": false,
            "description": "Mostrar informaci\u00f3n meteorol\u00f3gica",
            "type": "boolean"
          },
          "workshops": {
            "default": false,
            "description": "Mostrar talleres",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "Merchandise": {
        "description": "Informaci\u00f3n de la mercanc\u00eda transportada",
        "properties": {
          "hsCode": {
            "description": "C\u00f3digo arancelario HS de la mercanc\u00eda",
            "example": "8703",
            "type": "string"
          },
          "isADR": {
            "default": false,
            "description": "Si la mercanc\u00eda es peligrosa (ADR)",
            "type": "boolean"
          },
          "weight": {
            "description": "Peso de la mercanc\u00eda en kg",
            "example": 15000,
            "type": "number"
          }
        },
        "type": "object"
      },
      "OilStation": {
        "properties": {
          "_id": {
            "description": "Identificador \u00fanico de la estaci\u00f3n",
            "example": "5f8d3b7b3f6b8a1b9c3b7b3f",
            "type": "string"
          },
          "address": {
            "description": "Direcci\u00f3n completa",
            "example": "Calle Mayor, 123, 28013 Madrid",
            "type": "string"
          },
          "brand": {
            "description": "Marca de la estaci\u00f3n de servicio",
            "example": "Repsol",
            "type": "string"
          },
          "city": {
            "description": "Ciudad donde se encuentra la estaci\u00f3n",
            "example": "Madrid",
            "type": "string"
          },
          "country": {
            "description": "Pa\u00eds donde se encuentra la estaci\u00f3n",
            "example": "es",
            "type": "string"
          },
          "distance": {
            "description": "Distancia desde el punto de referencia en metros",
            "example": 1250.5,
            "format": "float",
            "type": "number"
          },
          "fuelTypes": {
            "items": {
              "properties": {
                "code": {
                  "description": "C\u00f3digo del tipo de combustible",
                  "enum": [
                    "diesel_regular",
                    "diesel_premium",
                    "gasoline_95",
                    "gasoline_98",
                    "gnc",
                    "gnl",
                    "gpl",
                    "hydrogen",
                    "bioethanol",
                    "biodiesel"
                  ],
                  "example": "diesel_regular",
                  "type": "string"
                },
                "price": {
                  "description": "Precio por litro en euros",
                  "example": 1.459,
                  "format": "float",
                  "minimum": 0,
                  "type": "number"
                },
                "updatedAt": {
                  "description": "Fecha de \u00faltima actualizaci\u00f3n del precio",
                  "example": "2025-04-29T10:30:00Z",
                  "format": "date-time",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "label": {
            "description": "Nombre comercial de la estaci\u00f3n",
            "example": "Repsol Calle Mayor",
            "type": "string"
          },
          "location": {
            "description": "Coordenadas GPS en formato GeoJSON (longitud, latitud)",
            "properties": {
              "coordinates": {
                "example": [
                  -3.7038,
                  40.4168
                ],
                "items": {
                  "format": "float",
                  "type": "number"
                },
                "type": "array"
              },
              "type": {
                "default": "Point",
                "enum": [
                  "Point"
                ],
                "type": "string"
              }
            },
            "type": "object"
          },
          "province": {
            "description": "Provincia donde se encuentra la estaci\u00f3n",
            "example": "Madrid",
            "type": "string"
          }
        },
        "type": "object"
      },
      "POI": {
        "description": "Representa un Punto de Inter\u00e9s gen\u00e9rico",
        "properties": {
          "address": {
            "description": "Direcci\u00f3n completa",
            "example": "Calle Mayor, 1, 28013 Madrid",
            "type": "string"
          },
          "city": {
            "description": "Nombre de la ciudad",
            "example": "Madrid",
            "type": "string"
          },
          "country": {
            "description": "C\u00f3digo de pa\u00eds (ISO 3166-1 alpha-2)",
            "example": "ES",
            "type": "string"
          },
          "details": {
            "additionalProperties": true,
            "description": "Detalles adicionales espec\u00edficos del tipo de POI",
            "example": {
              "cuisine": "Spanish",
              "fuel_types": [
                "diesel",
                "gasoline"
              ],
              "rating": 4.5
            },
            "type": "object"
          },
          "id": {
            "description": "ID del POI",
            "example": "poi-12345",
            "type": "string"
          },
          "location": {
            "properties": {
              "coordinates": {
                "description": "Coordenadas como [longitud, latitud]",
                "example": [
                  -3.70379,
                  40.416775
                ],
                "items": {
                  "format": "double",
                  "type": "number"
                },
                "maxItems": 2,
                "minItems": 2,
                "type": "array"
              },
              "type": {
                "enum": [
                  "Point"
                ],
                "example": "Point",
                "type": "string"
              }
            },
            "type": "object"
          },
          "name": {
            "description": "Nombre del POI",
            "example": "Restaurante El Buen Sabor",
            "type": "string"
          },
          "province": {
            "description": "Provincia/estado",
            "example": "Madrid",
            "type": "string"
          },
          "type": {
            "description": "Tipo de POI",
            "enum": [
              "parking",
              "workshop",
              "restaurant",
              "shower"
            ],
            "example": "restaurant",
            "type": "string"
          },
          "zipcode": {
            "description": "C\u00f3digo postalal",
            "example": "28013",
            "type": "string"
          }
        },
        "type": "object"
      },
      "POIsResponse": {
        "description": "Array de Puntos de Inter\u00e9s",
        "properties": {
          "pois": {
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/Parking"
                },
                {
                  "$ref": "#/components/schemas/Workshop"
                },
                {
                  "$ref": "#/components/schemas/Restaurant"
                },
                {
                  "$ref": "#/components/schemas/Shower"
                }
              ]
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "Parking": {
        "description": "Representa una ubicaci\u00f3n de aparcamiento de camiones",
        "properties": {
          "address": {
            "description": "Direcci\u00f3n completa",
            "type": "string"
          },
          "city": {
            "description": "Nombre de la ciudad",
            "type": "string"
          },
          "country": {
            "description": "C\u00f3digo de pa\u00eds (ISO 3166-1 alpha-2)",
            "example": "ES",
            "type": "string"
          },
          "features": {
            "description": "Lista de caracter\u00edsticas disponibles",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "description": "ID del aparcamiento",
            "type": "string"
          },
          "location": {
            "properties": {
              "coordinates": {
                "description": "Coordenadas como [longitud, latitud]",
                "example": [
                  -3.70379,
                  40.416775
                ],
                "items": {
                  "type": "number"
                },
                "maxItems": 2,
                "minItems": 2,
                "type": "array"
              },
              "type": {
                "enum": [
                  "Point"
                ],
                "type": "string"
              }
            },
            "type": "object"
          },
          "name": {
            "description": "Nombre del aparcamiento",
            "type": "string"
          },
          "province": {
            "description": "Provincia/estado",
            "type": "string"
          },
          "zipcode": {
            "description": "C\u00f3digo postal",
            "type": "string"
          }
        },
        "type": "object"
      },
      "ParkingsResponse": {
        "description": "Array de ubicaciones de aparcamiento",
        "properties": {
          "parkings": {
            "items": {
              "$ref": "#/components/schemas/Parking"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "PortalSessionRequest": {
        "properties": {
          "return_url": {
            "description": "URL a la que redirigir al salir del portal de cliente",
            "example": "https://app.example.com/account",
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "return_url"
        ],
        "type": "object"
      },
      "PositionUpdate": {
        "properties": {
          "coordinates": {
            "description": "Coordenadas GPS actuales en formato [longitud, latitud].\n**Requerido**: S\u00ed\n**Precisi\u00f3n**: M\u00ednimo 6 decimales\n**Ejemplo v\u00e1lido**: [-3.7038, 40.4168] (Madrid, Espa\u00f1a)\n",
            "example": [
              -3.7038,
              40.4168
            ],
            "items": {
              "format": "float",
              "type": "number"
            },
            "maxItems": 2,
            "minItems": 2,
            "type": "array"
          },
          "timestamp": {
            "description": "Fecha y hora exacta de la posici\u00f3n.\n**Requerido**: S\u00ed\n**Formato**: ISO 8601 (UTC)\n**Restricci\u00f3n**: No puede ser fecha futura\n**Diferencia m\u00e1xima**: 5 minutos con tiempo del servidor\n",
            "example": "2025-04-30T10:30:00.000Z",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "coordinates",
          "timestamp"
        ],
        "type": "object"
      },
      "PricingTier": {
        "properties": {
          "_id": {
            "type": "string"
          },
          "basePrice": {
            "description": "Precio base mensual/anual",
            "type": "number"
          },
          "code": {
            "description": "C\u00f3digo \u00fanico del plan, ejemplo: basic o premium",
            "type": "string"
          },
          "excessRoutePrice": {
            "description": "Costo por cada ruta adicional",
            "type": "number"
          },
          "includedRoutes": {
            "description": "Cantidad de rutas incluidas en el precio base",
            "type": "number"
          },
          "isActive": {
            "description": "Indica si el plan est\u00e1 disponible",
            "type": "boolean"
          },
          "name": {
            "description": "Nombre descriptivo del plan",
            "type": "string"
          },
          "type": {
            "description": "Tipo de plan (subscription, pay-per-use, etc.)",
            "type": "string"
          }
        },
        "type": "object"
      },
      "Radar": {
        "properties": {
          "adminName2": {
            "description": "Nombre del \u00e1rea administrativa",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Identificador \u00fanico del radar",
            "type": "string"
          },
          "kilometers": {
            "description": "Punto kilom\u00e9trico donde se encuentra el radar",
            "type": "number"
          },
          "location": {
            "$ref": "#/components/schemas/Coordinates"
          },
          "maxSpeed": {
            "description": "L\u00edmite de velocidad en km/h",
            "type": "number"
          },
          "roadName": {
            "description": "Nombre de la carretera donde se encuentra el radar",
            "type": "string"
          },
          "sense": {
            "description": "Sentido de direcci\u00f3n del radar",
            "enum": [
              "UNKNOWN",
              "POSITIVE",
              "NEGATIVE",
              "BOTH"
            ],
            "type": "string"
          },
          "type": {
            "description": "Tipo de radar",
            "enum": [
              "MOBILE",
              "FIXED"
            ],
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "adminName2",
          "roadName",
          "type",
          "sense",
          "location"
        ],
        "type": "object"
      },
      "Radius": {
        "default": 25000,
        "description": "Radio de b\u00fasqueda en metros desde el punto central\n\n**Escalas t\u00edpicas:**\n- 1.000m: \u00c1rea muy localizada (una intersecci\u00f3n)\n- 5.000m: Barrio o zona urbana\n- 10.000m: Distrito o \u00e1rea metropolitana peque\u00f1a\n- 25.000m: Ciudad mediana o \u00e1rea extensa\n",
        "maximum": 25000,
        "minimum": 1000,
        "type": "number"
      },
      "RegisterRequest": {
        "properties": {
          "company_cif": {
            "description": "CIF/NIF de la empresa (requerido)",
            "type": "string"
          },
          "email": {
            "description": "Email para el nuevo usuario",
            "format": "email",
            "type": "string"
          },
          "password": {
            "description": "Contrase\u00f1a (m\u00ednimo 6 caracteres)",
            "type": "string"
          }
        },
        "required": [
          "email",
          "password",
          "company_cif"
        ],
        "type": "object"
      },
      "Restaurant": {
        "description": "Representa un restaurante para camioneros",
        "properties": {
          "cuisine": {
            "description": "Tipo de cocina",
            "type": "string"
          },
          "id": {
            "description": "ID del restaurante",
            "type": "string"
          },
          "location": {
            "$ref": "#/components/schemas/POI/properties/location"
          },
          "name": {
            "description": "Nombre del restaurante",
            "type": "string"
          },
          "priceRange": {
            "description": "Indicador de rango de precios",
            "enum": [
              "low",
              "medium",
              "high"
            ],
            "type": "string"
          },
          "rating": {
            "description": "Valoraci\u00f3n promedio",
            "format": "float",
            "maximum": 5,
            "minimum": 0,
            "type": "number"
          }
        },
        "type": "object"
      },
      "RoadRestriction": {
        "properties": {
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "restrictionType": {
            "description": "Tipo de restricci\u00f3n",
            "enum": [
              "WEIGHT",
              "HEIGHT",
              "ENVIRONMENTAL",
              "TIME"
            ],
            "type": "string"
          },
          "roadId": {
            "description": "Identificador de la carretera afectada",
            "type": "string"
          },
          "schedule": {
            "description": "Horario para restricciones basadas en tiempo",
            "properties": {
              "days": {
                "description": "D\u00edas de la semana cuando aplica la restricci\u00f3n (0=Domingo)",
                "items": {
                  "maximum": 6,
                  "minimum": 0,
                  "type": "number"
                },
                "type": "array"
              },
              "hours": {
                "description": "Rango de tiempo cuando aplica la restricci\u00f3n",
                "type": "string"
              }
            },
            "type": "object"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "value": {
            "description": "Valor de la restricci\u00f3n (num\u00e9rico o descriptivo)",
            "type": {
              "oneOf": [
                {
                  "type": "number"
                },
                {
                  "type": "string"
                }
              ]
            }
          }
        },
        "required": [
          "roadId",
          "restrictionType",
          "value"
        ],
        "type": "object"
      },
      "RouteCostResponse": {
        "description": "# \ud83d\udccb Respuesta de Costos de Ruta\n\nRespuesta completa con todos los costos calculados para una ruta de transporte.\nIncluye desglose detallado de peajes, combustible, mantenimiento, tiempo y emisiones ambientales.\n\n## \ud83d\udcb0 Campos Principales\n\n- **Costos Totales**: `total_cost` en el objeto `costs`\n- **Duraci\u00f3n**: Incluye descansos legales obligatorios\n- **Distancia**: En metros para precisi\u00f3n\n- **Emisiones**: CO2 en kg para reportes de sostenibilidad\n\n## \ud83c\udfaf Ejemplo de Uso\n\n```json\n{\n  \"duration\": 343,\n  \"distance\": 621456,\n  \"departure\": \"2025-05-15T08:00:00+02:00\",\n  \"arrival\": \"2025-05-15T15:43:00+02:00\",\n  \"total_tolls\": 87.25,\n  \"fuel\": 186.43,\n  \"co2\": 499.62,\n  \"fuel_type\": \"diesel\",\n  \"costs\": {\n    \"total_cost\": 542.89,\n    \"toll\": 87.25,\n    \"fuel\": {\n      \"monetary\": 240.49,\n      \"fuel\": 186.43\n    },\n    // ... m\u00e1s componentes de costo\n  },\n  \"tolls\": [\n    {\n      \"country\": \"ES\",\n      \"cia_name\": \"ASEFA\",\n      \"price\": 12.75,\n      \"currency\": \"EUR\",\n      \"paymentMethods\": [\"cash\", \"card\"]\n    }\n    // ... m\u00e1s peajes\n  ]\n}\n```\n",
        "properties": {
          "arrival": {
            "description": "**\ud83d\udd54 Hora de Llegada Estimada**\n\nFecha y hora estimada de llegada con zona horaria.\nIncluye el efecto de los descansos legales.\n",
            "example": "2025-05-15T15:43:00+02:00",
            "format": "date-time",
            "type": "string"
          },
          "co2": {
            "description": "**\ud83c\udf31 Emisiones de CO2**\n\nKilogramos de CO2 emitidos durante el viaje.\nCalculado seg\u00fan el tipo de combustible y distancia.\n",
            "example": 499.62,
            "type": "number"
          },
          "costs": {
            "description": "# \ud83d\udcb0 Desglose Completo de Costos\n\nObjeto con el desglose detallado de todos los costos operativos del viaje.\n\n## \ud83d\udee0\ufe0f Componentes de Mantenimiento\n\nCostos calculados basados en el desgaste por kil\u00f3metro de cada componente del veh\u00edculo.\n\n## \u26fd Costos de Combustible\n\nIncluye tanto el costo monetario como el consumo en litros.\n\n## \ud83c\udff7\ufe0f Peajes\n\nCosto total de peajes (coincide con `total_tolls`).\n",
            "properties": {
              "aire": {
                "description": "Costo de filtro de aire por km",
                "example": 0.157,
                "type": "number"
              },
              "brake_liquid": {
                "description": "Costo de l\u00edquido de frenos por km",
                "example": 0.31,
                "type": "number"
              },
              "brake_paste": {
                "description": "Costo de pastillas de freno por km",
                "example": 0.31,
                "type": "number"
              },
              "cabine_filter": {
                "description": "Costo de filtro de cabina por km",
                "example": 0.515,
                "type": "number"
              },
              "clutch": {
                "description": "Costo de embrague por km",
                "example": 5.17,
                "type": "number"
              },
              "correa": {
                "description": "Costo de correa de distribuci\u00f3n por km",
                "example": 4.136,
                "type": "number"
              },
              "direccion": {
                "description": "Costo de direcci\u00f3n por km",
                "example": 0.515,
                "type": "number"
              },
              "escape": {
                "description": "Costo de escape por km",
                "example": 0.515,
                "type": "number"
              },
              "fuel": {
                "description": "Costos relacionados con el combustible",
                "properties": {
                  "fuel": {
                    "description": "Litros totales de combustible consumidos",
                    "example": 186.43,
                    "type": "number"
                  },
                  "monetary": {
                    "description": "Costo monetario total del combustible",
                    "example": 240.49,
                    "type": "number"
                  }
                },
                "type": "object"
              },
              "fuel_filter": {
                "description": "Costo de filtro de combustible por km",
                "example": 0.414,
                "type": "number"
              },
              "oil": {
                "description": "Costo de cambio de aceite por km",
                "example": 1.035,
                "type": "number"
              },
              "oil_filter": {
                "description": "Costo de filtro de aceite por km",
                "example": 0.414,
                "type": "number"
              },
              "toll": {
                "description": "Costo total de peajes",
                "example": 87.25,
                "type": "number"
              },
              "total_cost": {
                "description": "**\ud83d\udcb0 Costo Total del Viaje**\n\nSuma de todos los costos: mantenimiento + combustible + peajes.\n\n**F\u00f3rmula**: `sum(mantenimiento) + fuel.monetary + toll`\n",
                "example": 542.89,
                "type": "number"
              },
              "wheels": {
                "description": "Costo de ruedas por km (8 ruedas)",
                "example": 4.367,
                "type": "number"
              }
            },
            "type": "object"
          },
          "departure": {
            "description": "**\ud83d\udd50 Hora de Salida Real**\n\nFecha y hora de inicio del viaje con zona horaria.\nPuede diferir del par\u00e1metro `departure` si se calcula basado en `arrival`.\n",
            "example": "2025-05-15T08:00:00+02:00",
            "format": "date-time",
            "type": "string"
          },
          "distance": {
            "description": "**\ud83d\udccf Distancia Total**\n\nLongitud total de la ruta en **metros**.\n\n**Conversi\u00f3n**: Dividir entre 1000 para obtener kil\u00f3metros\n",
            "example": 621456,
            "type": "number"
          },
          "duration": {
            "description": "**\u23f1\ufe0f Duraci\u00f3n Total del Viaje**\n\nTiempo total en minutos, incluyendo **descansos legales obligatorios**.\n\n- Se a\u00f1aden 45 minutos de descanso por cada 4.5 horas de conducci\u00f3n\n- Basado en normativa europea de transporte\n",
            "example": 343,
            "type": "number"
          },
          "fuel": {
            "description": "**\ud83d\udee2\ufe0f Consumo de Combustible**\n\nTotal de litros de combustible consumidos en el viaje.\nAfectado por `cargo_weight` y `avg_consumption`.\n",
            "example": 186.43,
            "type": "number"
          },
          "fuelCost": {
            "description": "**\ud83d\udcb6 Costo Total de Combustible**\n\nCosto total del combustible consumido en EUR.\nCalculado como: `fuel * fuelPrice`\n",
            "example": 240.49,
            "type": "number"
          },
          "fuelPrice": {
            "description": "**\u26fd Precio del Combustible**\n\nPrecio medio del combustible por litro en EUR.\nBasado en datos de mercado actualizados.\n",
            "example": 1.29,
            "type": "number"
          },
          "fuel_type": {
            "description": "**\ud83d\udd25 Tipo de Combustible Usado**\n\nTipo de combustible considerado para los c\u00e1lculos.\nCoincide con el par\u00e1metro `fuel_type` o usa el valor por defecto.\n",
            "example": "diesel",
            "type": "string"
          },
          "timeCost": {
            "description": "**\ud83d\udcb0 Costo del Tiempo**\n\nValor del tiempo de conducci\u00f3n en minutos.\nUsado para c\u00e1lculos de productividad.\n",
            "example": 343,
            "type": "number"
          },
          "tolls": {
            "description": "**\ud83d\udccb Lista Detallada de Peajes**\n\nArray con informaci\u00f3n individual de cada peaje en la ruta.\nSolo presente cuando `with_tolls=true`.\n\n**Orden**: Aparecen en el orden de la ruta\n",
            "items": {
              "description": "Informaci\u00f3n de un peaje individual",
              "properties": {
                "cia_name": {
                  "description": "Nombre de la compa\u00f1\u00eda gestora del peaje",
                  "example": "ASEFA",
                  "type": "string"
                },
                "country": {
                  "description": "C\u00f3digo ISO del pa\u00eds (ES, FR, PT, etc.)",
                  "example": "ES",
                  "type": "string"
                },
                "currency": {
                  "description": "Moneda del peaje (siempre EUR)",
                  "example": "EUR",
                  "type": "string"
                },
                "paymentMethods": {
                  "description": "M\u00e9todos de pago aceptados en el peaje",
                  "example": [
                    "cash",
                    "card"
                  ],
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                "price": {
                  "description": "Precio del peaje en EUR",
                  "example": 12.75,
                  "type": "number"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "total_tolls": {
            "description": "**\ud83c\udff7\ufe0f Costo Total de Peajes**\n\nSuma de todos los peajes en la ruta en EUR.\nCoincide con `costs.toll` en el desglose detallado.\n",
            "example": 87.25,
            "type": "number"
          }
        },
        "required": [
          "duration",
          "distance",
          "fuelCost",
          "costs",
          "total_tolls",
          "fuel",
          "co2",
          "fuel_type"
        ],
        "type": "object"
      },
      "RouteResponse": {
        "description": "Respuesta completa del c\u00e1lculo de ruta",
        "properties": {
          "alternatives": {
            "description": "Rutas alternativas",
            "items": {
              "$ref": "#/components/schemas/MainRoute"
            },
            "type": "array"
          },
          "black_points": {
            "description": "Puntos negros a lo largo de la ruta",
            "items": {
              "$ref": "#/components/schemas/BlackSpot"
            },
            "type": "array"
          },
          "has_route_limit": {
            "description": "L\u00edmite de rutas mensuales del usuario",
            "example": 100,
            "type": "number"
          },
          "main": {
            "$ref": "#/components/schemas/MainRoute"
          },
          "stops": {
            "description": "Todas las paradas de la ruta principal",
            "items": {
              "$ref": "#/components/schemas/UnifiedStop"
            },
            "type": "array"
          },
          "temp_code": {
            "description": "C\u00f3digo temporal para compartir la ruta",
            "example": "ABC123DEF",
            "type": "string"
          }
        },
        "type": "object"
      },
      "RouteSummary": {
        "description": "Resumen de la ruta calculada",
        "properties": {
          "arrivalDate": {
            "description": "Fecha/hora de llegada",
            "format": "date-time",
            "type": "string"
          },
          "cost": {
            "description": "Coste estimado del viaje (combustible + peajes)",
            "example": 450.0,
            "type": "number"
          },
          "departureDate": {
            "description": "Fecha/hora de salida (calculada si type=arrival)",
            "format": "date-time",
            "type": "string"
          },
          "distance": {
            "description": "Distancia total en km (alias de length)",
            "type": "number"
          },
          "has_ferry": {
            "description": "Si la ruta usa ferry",
            "type": "boolean"
          },
          "has_highway": {
            "description": "Si la ruta usa autopistas",
            "type": "boolean"
          },
          "has_time_restrictions": {
            "description": "Si la ruta tiene restricciones horarias",
            "type": "boolean"
          },
          "has_toll": {
            "description": "Si la ruta tiene peajes",
            "type": "boolean"
          },
          "length": {
            "description": "Distancia total en km",
            "example": 620.5,
            "type": "number"
          },
          "max_lat": {
            "description": "Latitud m\u00e1xima del bounding box",
            "type": "number"
          },
          "max_lon": {
            "description": "Longitud m\u00e1xima del bounding box",
            "type": "number"
          },
          "min_lat": {
            "description": "Latitud m\u00ednima del bounding box",
            "type": "number"
          },
          "min_lon": {
            "description": "Longitud m\u00ednima del bounding box",
            "type": "number"
          },
          "remainingWeeklyDrivingHours": {
            "description": "Horas de conducci\u00f3n restantes en la semana al finalizar",
            "type": "number"
          },
          "remainingWeeklyDrivingHoursFormatted": {
            "description": "Horas restantes formateadas",
            "example": "45:30",
            "type": "string"
          },
          "time": {
            "description": "Tiempo de conducci\u00f3n puro (formato HH:MM)",
            "example": "06:30",
            "type": "string"
          },
          "timeInSeconds": {
            "description": "Tiempo de conducci\u00f3n puro en segundos",
            "example": 23400,
            "type": "integer"
          },
          "timeWithBreaks": {
            "description": "Tiempo total con descansos (formato HH:MM)",
            "example": "10:30",
            "type": "string"
          },
          "timeWithBreaksInSeconds": {
            "description": "Tiempo total incluyendo descansos en segundos",
            "example": 37800,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "RouteUsageRecord": {
        "properties": {
          "quantity": {
            "default": 1,
            "description": "Cantidad de rutas utilizadas",
            "minimum": 1,
            "type": "number"
          },
          "timestamp": {
            "description": "Timestamp Unix del momento de uso (opcional, usa actual si no se proporciona)",
            "type": "number"
          },
          "userId": {
            "description": "ID del usuario que utiliza las rutas",
            "type": "string"
          }
        },
        "required": [
          "userId",
          "quantity"
        ],
        "type": "object"
      },
      "SavedRoute": {
        "description": "Ruta guardada por el usuario",
        "properties": {
          "_id": {
            "description": "ID \u00fanico de la ruta",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "date": {
            "$ref": "#/components/schemas/DateType"
          },
          "description": {
            "description": "Descripci\u00f3n de la ruta",
            "type": "string"
          },
          "destination": {
            "$ref": "#/components/schemas/LocationPoint"
          },
          "driver": {
            "properties": {
              "_id": {
                "type": "string"
              },
              "hoursStatus": {
                "properties": {
                  "remainingBiweeklyHours": {
                    "type": "number"
                  },
                  "remainingWeeklyHours": {
                    "type": "number"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          },
          "mapLayers": {
            "$ref": "#/components/schemas/MapLayers"
          },
          "merchandise": {
            "$ref": "#/components/schemas/Merchandise"
          },
          "optimizedStops": {
            "type": "boolean"
          },
          "origin": {
            "$ref": "#/components/schemas/LocationPoint"
          },
          "owner": {
            "description": "ID del usuario propietario",
            "type": "string"
          },
          "polyline": {
            "description": "Datos de la polyline de la ruta",
            "properties": {
              "alternatives": {
                "description": "Rutas alternativas",
                "type": "array"
              },
              "black_points": {
                "items": {
                  "$ref": "#/components/schemas/BlackSpot"
                },
                "type": "array"
              },
              "main": {
                "description": "Ruta principal",
                "type": "object"
              },
              "selectedRouteId": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "stops": {
            "items": {
              "$ref": "#/components/schemas/LocationPoint"
            },
            "type": "array"
          },
          "title": {
            "description": "T\u00edtulo de la ruta",
            "example": "Madrid - Barcelona semanal",
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "vehicle": {
            "properties": {
              "_id": {
                "type": "string"
              },
              "brand": {
                "type": "string"
              },
              "consumption": {
                "type": "number"
              },
              "fuelType": {
                "type": "string"
              },
              "height": {
                "type": "number"
              },
              "model": {
                "type": "string"
              },
              "weight": {
                "type": "number"
              },
              "width": {
                "type": "number"
              }
            },
            "type": "object"
          },
          "waypoints": {
            "items": {
              "$ref": "#/components/schemas/LocationPoint"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "SavedRouteCreate": {
        "properties": {
          "date": {
            "$ref": "#/components/schemas/DateType"
          },
          "description": {
            "maxLength": 1000,
            "type": "string"
          },
          "destination": {
            "$ref": "#/components/schemas/LocationPoint"
          },
          "driver": {
            "type": "object"
          },
          "mapLayers": {
            "$ref": "#/components/schemas/MapLayers"
          },
          "merchandise": {
            "$ref": "#/components/schemas/Merchandise"
          },
          "origin": {
            "$ref": "#/components/schemas/LocationPoint"
          },
          "polyline": {
            "type": "object"
          },
          "title": {
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "vehicle": {
            "type": "object"
          },
          "waypoints": {
            "items": {
              "$ref": "#/components/schemas/LocationPoint"
            },
            "type": "array"
          }
        },
        "required": [
          "title",
          "origin",
          "destination"
        ],
        "type": "object"
      },
      "SavedRouteUpdate": {
        "properties": {
          "date": {
            "$ref": "#/components/schemas/DateType"
          },
          "description": {
            "type": "string"
          },
          "destination": {
            "$ref": "#/components/schemas/LocationPoint"
          },
          "driver": {
            "type": "object"
          },
          "mapLayers": {
            "$ref": "#/components/schemas/MapLayers"
          },
          "merchandise": {
            "$ref": "#/components/schemas/Merchandise"
          },
          "origin": {
            "$ref": "#/components/schemas/LocationPoint"
          },
          "polyline": {
            "type": "object"
          },
          "title": {
            "type": "string"
          },
          "vehicle": {
            "type": "object"
          },
          "waypoints": {
            "items": {
              "$ref": "#/components/schemas/LocationPoint"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "SearchResponse": {
        "description": "Resultados combinados de b\u00fasqueda para eventos y radares",
        "properties": {
          "events": {
            "description": "Eventos de tr\u00e1fico encontrados en el \u00e1rea de b\u00fasqueda",
            "items": {
              "$ref": "#/components/schemas/TrafficEvent"
            },
            "type": "array"
          },
          "radars": {
            "description": "Radares encontrados en el \u00e1rea de b\u00fasqueda",
            "items": {
              "$ref": "#/components/schemas/Radar"
            },
            "type": "array"
          }
        },
        "required": [
          "events",
          "radars"
        ],
        "type": "object"
      },
      "SearchResult": {
        "description": "## \ud83d\udccb Secci\u00f3n HS (Nivel m\u00e1s alto)\nRepresenta una secci\u00f3n del c\u00f3digo HS, que es el nivel m\u00e1s alto de la jerarqu\u00eda. Cada secci\u00f3n contiene uno o m\u00e1s cap\u00edtulos relacionados por sector econ\u00f3mico.\n\n### \ud83d\udcdd Campos Principales\n- `_id`: Identificador \u00fanico en la base de datos\n- `title`: T\u00edtulo completo de la secci\u00f3n (incluye n\u00famero romano)\n- `label`: Array de etiquetas descriptivas\n- `chapters`: Lista de cap\u00edtulos hijos (siempre tiene al menos uno)\n\n### \ud83c\udfaf Cu\u00e1ndo usar este nivel\n- Para navegaci\u00f3n de alto nivel\n- Para an\u00e1lisis macroecon\u00f3mico\n- Para agrupaci\u00f3n por sectores industriales\n\n### \ud83d\udcca Ejemplo Real\n```json\n{\n  \"_id\": \"6502e98f01515afda2dbd5a1\",\n  \"title\": \"Secci\u00f3n XI - Textiles y sus manufacturas\",\n  \"label\": [\n    \"Alcance: Incluye fibras textiles, hilados, tejidos, prendas de vestir, etc.\",\n    \"Exclusiones: Productos de cuero (Secci\u00f3n VIII)\"\n  ],\n  \"chapters\": [\n    {\n      \"_id\": \"6502e98f01515afda2dbd5a2\",\n      \"title\": \"Cap\u00edtulo 50 - Seda\",\n      \"label\": [\n        \"Secci\u00f3n: XI - Textiles y sus manufacturas\"\n      ],\n      \"headings\": [\n        {\n          \"_id\": \"6502e98f01515afda2dbd5a3\",\n          \"title\": \"5001 - Capullos de seda aptos para el devanado\",\n          \"label\": [\n            \"- Crudos (sin blanquear)\"\n          ],\n          \"subheadings\": [\n            {\n              \"_id\": \"6502e98f01515afda2dbd5a4\",\n              \"title\": \"5001.00 - Capullos de seda aptos para el devanado\",\n              \"label\": [\n                \"-- De Bombyx mori\"\n              ]\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}\n```\n",
        "properties": {
          "_id": {
            "description": "**Identificador \u00fanico MongoDB** de la secci\u00f3n.\n\n**Formato**: ObjectId hexadecimal de 24 caracteres\n**Persistencia**: Este ID no cambia entre versiones\n",
            "example": "6501a2b3c4d5e6f7a8b9c0d4",
            "type": "string"
          },
          "chapters": {
            "description": "**Lista de cap\u00edtulos** asociados a esta secci\u00f3n.\n\n**IMPORTANTE**:\n- Siempre contiene al menos un cap\u00edtulo\n- Los cap\u00edtulos est\u00e1n ordenados por n\u00famero (01-99)\n- Cada cap\u00edtulo pertenece a una sola secci\u00f3n\n",
            "items": {
              "$ref": "#/components/schemas/ChapterResult"
            },
            "type": "array"
          },
          "label": {
            "description": "**Array de etiquetas descriptivas** con informaci\u00f3n general.\n\n**Contenido com\u00fan**:\n- Alcance general de la secci\u00f3n\n- Exclusiones importantes\n- Notas sobre productos incluidos\n",
            "example": [
              "Alcance: Productos textiles y sus manufacturas",
              "Exclusiones: Calzado (Secci\u00f3n XII)"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "title": {
            "description": "**T\u00edtulo completo de la secci\u00f3n** incluyendo n\u00famero romano y descripci\u00f3n.\n\n**Formato**: `\"Secci\u00f3n XX - Descripci\u00f3n\"`\n**Ejemplo**: `\"Secci\u00f3n XI - Textiles y sus manufacturas\"`\n\n**IMPORTANTE**: Hay 21 secciones en total (I a XXI).\n",
            "example": "Secci\u00f3n XI - Textiles",
            "type": "string"
          }
        },
        "required": [
          "_id",
          "title",
          "chapters"
        ],
        "type": "object"
      },
      "Shower": {
        "description": "Representa una instalaci\u00f3n de duchas para camioneros",
        "properties": {
          "genderSeparated": {
            "description": "Si tiene instalaciones separadas por g\u00e9nero",
            "type": "boolean"
          },
          "id": {
            "description": "ID de la ducha",
            "type": "string"
          },
          "location": {
            "$ref": "#/components/schemas/POI/properties/location"
          },
          "name": {
            "description": "Nombre de la instalaci\u00f3n de duchas",
            "type": "string"
          },
          "price": {
            "description": "Precio por uso",
            "format": "float",
            "type": "number"
          }
        },
        "type": "object"
      },
      "SubheadingResult": {
        "description": "## \ud83d\udccb Subpartida HS (Nivel m\u00e1s granular)\nRepresenta una subpartida espec\u00edfica del c\u00f3digo HS, que es el nivel m\u00e1s detallado de la clasificaci\u00f3n arancelaria. Cada subpartida pertenece exactamente a una partida.\n\n### \ud83d\udcdd Campos Principales\n- `_id`: Identificador \u00fanico en la base de datos\n- `title`: Descripci\u00f3n completa de la subpartida\n- `label`: Array de etiquetas descriptivas (puede incluir informaci\u00f3n t\u00e9cnica adicional)\n\n### \ud83c\udfaf Cu\u00e1ndo usar este nivel\n- Para declaraciones aduaneras precisas\n- Cuando se necesita la clasificaci\u00f3n m\u00e1s espec\u00edfica\n- Para c\u00e1lculos de aranceles exactos\n\n### \ud83d\udcca Ejemplo Real\n```json\n{\n  \"_id\": \"6502e99101515afda2dbd5c1\",\n  \"title\": \"0302.31 - Albacora o at\u00fan de aleta larga\",\n  \"label\": [\n    \"-- albacore or longfinned tunas (thunnus alalunga)\",\n    \"C\u00f3digo HS: 0302.31.00\",\n    \"Arancel: 5%\"\n  ]\n}\n```\n",
        "properties": {
          "_id": {
            "description": "**Identificador \u00fanico MongoDB** de la subpartida.\n\n**Formato**: ObjectId hexadecimal de 24 caracteres\n**Ejemplo**: `507f1f77bcf86cd799439011`\n\n**IMPORTANTE**: Este ID es persistente y no cambia entre versiones de la base de datos.\n",
            "example": "6501a2b3c4d5e6f7a8b9c0d1",
            "type": "string"
          },
          "label": {
            "description": "**Array de etiquetas descriptivas** que proporcionan informaci\u00f3n adicional.\n\n**Contenido com\u00fan**:\n- Descripciones en otros idiomas (ingl\u00e9s, franc\u00e9s, etc.)\n- Notas t\u00e9cnicas espec\u00edficas\n- Informaci\u00f3n sobre restricciones o requisitos\n- Referencias a regulaciones\n\n**IMPORTANTE**: Este campo puede estar vac\u00edo (`[]`) si no hay etiquetas adicionales.\n",
            "example": [
              "-- albacore or longfinned tunas (thunnus alalunga)",
              "Fresh or chilled"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "title": {
            "description": "**Nombre descriptivo completo** de la subpartida seg\u00fan la nomenclatura HS oficial.\n\n**Contenido t\u00edpico**:\n- C\u00f3digo num\u00e9rico de la subpartida\n- Descripci\u00f3n en espa\u00f1ol\n- Informaci\u00f3n t\u00e9cnica relevante\n\n**Formato**: `\"C\u00f3digo - Descripci\u00f3n\"`\n**Ejemplo**: `\"0302.31 - Albacora o at\u00fan de aleta larga\"`\n",
            "example": "Algod\u00f3n sin cardar ni peinar",
            "type": "string"
          }
        },
        "required": [
          "_id",
          "title"
        ],
        "type": "object"
      },
      "SubscriptionSummary": {
        "properties": {
          "pricingTier": {
            "properties": {
              "basePrice": {
                "description": "Precio base",
                "type": "number"
              },
              "code": {
                "description": "C\u00f3digo del plan",
                "type": "string"
              },
              "excessRoutePrice": {
                "description": "Precio por ruta adicional",
                "type": "number"
              },
              "includedRoutes": {
                "description": "Rutas incluidas en el plan",
                "type": "number"
              },
              "name": {
                "description": "Nombre del plan",
                "type": "string"
              },
              "type": {
                "description": "Tipo de plan",
                "type": "string"
              }
            },
            "type": "object"
          },
          "subscription": {
            "properties": {
              "amount": {
                "description": "Monto del per\u00edodo",
                "type": "number"
              },
              "cancelAt": {
                "description": "Fecha de cancelaci\u00f3n programada",
                "format": "date-time",
                "type": "string"
              },
              "cancelReason": {
                "description": "Motivo de cancelaci\u00f3n",
                "type": "string"
              },
              "currency": {
                "description": "Moneda, ejemplo: eur",
                "type": "string"
              },
              "currentPeriodEnd": {
                "description": "Fin del per\u00edodo de facturaci\u00f3n",
                "format": "date-time",
                "type": "string"
              },
              "currentPeriodStart": {
                "description": "Inicio del per\u00edodo de facturaci\u00f3n",
                "format": "date-time",
                "type": "string"
              },
              "status": {
                "description": "Estado de la suscripci\u00f3n (active, canceled, etc.)",
                "type": "string"
              }
            },
            "type": "object"
          },
          "totalRoutes": {
            "description": "Total de rutas utilizadas en el per\u00edodo",
            "type": "number"
          }
        },
        "type": "object"
      },
      "SuccessResponse": {
        "properties": {
          "message": {
            "description": "Mensaje descriptivo del resultado",
            "type": "string"
          },
          "success": {
            "description": "Indica si la operaci\u00f3n fue exitosa",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "SyncDiscountRequest": {
        "properties": {
          "discounts": {
            "description": "Array de descuentos a sincronizar",
            "items": {
              "properties": {
                "centsValue": {
                  "description": "Valor del descuento en c\u00e9ntimos",
                  "example": 0.15,
                  "format": "float",
                  "minimum": 0,
                  "type": "number"
                },
                "id": {
                  "description": "Identificador del descuento",
                  "example": "123456789",
                  "type": "string"
                },
                "isActive": {
                  "description": "Indica si el descuento est\u00e1 activo",
                  "example": true,
                  "type": "boolean"
                },
                "percentValue": {
                  "description": "Valor del descuento en porcentaje",
                  "example": 10.0,
                  "format": "float",
                  "maximum": 100,
                  "minimum": 0,
                  "type": "number"
                },
                "selected": {
                  "description": "Indica si el descuento est\u00e1 seleccionado",
                  "example": false,
                  "type": "boolean"
                },
                "station": {
                  "description": "Identificador de la estaci\u00f3n",
                  "example": "5f8d3b7b3f6b8a1b9c3b7b3f",
                  "type": "string"
                },
                "type": {
                  "description": "Tipo de descuento",
                  "example": "percentage",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "userId": {
            "description": "Identificador del usuario",
            "example": "user123",
            "type": "string"
          }
        },
        "required": [
          "discounts",
          "userId"
        ],
        "type": "object"
      },
      "TempRoute": {
        "description": "Ruta temporal compartida por c\u00f3digo",
        "properties": {
          "_id": {
            "type": "string"
          },
          "code": {
            "description": "C\u00f3digo \u00fanico para compartir",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "date": {
            "$ref": "#/components/schemas/DateType"
          },
          "destination": {
            "$ref": "#/components/schemas/LocationPoint"
          },
          "merchandise": {
            "$ref": "#/components/schemas/Merchandise"
          },
          "optimizedStops": {
            "type": "boolean"
          },
          "origin": {
            "$ref": "#/components/schemas/LocationPoint"
          },
          "polyline": {
            "type": "object"
          },
          "stops": {
            "items": {
              "$ref": "#/components/schemas/UnifiedStop"
            },
            "type": "array"
          },
          "userId": {
            "description": "ID del usuario que cre\u00f3 la ruta (opcional)",
            "type": "string"
          },
          "waypoints": {
            "items": {
              "$ref": "#/components/schemas/LocationPoint"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "TokenResponse": {
        "properties": {
          "expiresIn": {
            "description": "Timestamp UNIX de expiraci\u00f3n",
            "format": "int64",
            "type": "integer"
          },
          "token": {
            "description": "Token JWT firmado para autenticaci\u00f3n",
            "type": "string"
          }
        },
        "type": "object"
      },
      "TrafficEvent": {
        "properties": {
          "affectedVehicleTypes": {
            "description": "Tipos de veh\u00edculos afectados por este evento",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "createdAt": {
            "description": "Cuando se cre\u00f3 el evento",
            "format": "date-time",
            "type": "string"
          },
          "expectedDuration": {
            "description": "Duraci\u00f3n esperada en minutos",
            "type": "number"
          },
          "id": {
            "description": "Identificador \u00fanico del evento",
            "type": "string"
          },
          "location": {
            "$ref": "#/components/schemas/Coordinates"
          },
          "severity": {
            "description": "Nivel de severidad (0-100)",
            "maximum": 100,
            "minimum": 0,
            "type": "number"
          },
          "type": {
            "description": "Tipo de evento de tr\u00e1fico",
            "enum": [
              "ACCIDENT",
              "ROAD_CLOSURE",
              "SPECIAL_EVENT"
            ],
            "type": "string"
          },
          "updatedAt": {
            "description": "Cuando se actualiz\u00f3 el evento por \u00faltima vez",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "type",
          "location",
          "severity",
          "expectedDuration",
          "affectedVehicleTypes"
        ],
        "type": "object"
      },
      "TrafficFlowData": {
        "properties": {
          "polyline": {
            "description": "Polil\u00ednea codificada para el segmento de carretera",
            "type": "string"
          },
          "roadClosure": {
            "description": "Si la carretera est\u00e1 cerrada",
            "type": "boolean"
          },
          "speed_current": {
            "description": "Velocidad actual en km/h",
            "type": "number"
          },
          "speed_normal": {
            "description": "Velocidad normal/flujo libre en km/h",
            "type": "number"
          },
          "time_current": {
            "description": "Tiempo de viaje actual en segundos",
            "type": "number"
          },
          "time_normal": {
            "description": "Tiempo de viaje normal en segundos",
            "type": "number"
          }
        },
        "type": "object"
      },
      "UnifiedStop": {
        "description": "Parada unificada en la ruta (puede ser waypoint del usuario o parada calculada).\nIncluye informaci\u00f3n completa de tiempos y estado de horas.\n",
        "properties": {
          "accumulatedMinutes": {
            "description": "Minutos acumulados (conducci\u00f3n + descansos anteriores)",
            "type": "number"
          },
          "arrivalDate": {
            "description": "Fecha/hora de llegada a la parada",
            "format": "date-time",
            "type": "string"
          },
          "coordinates": {
            "description": "Coordenadas [latitud, longitud]",
            "items": {
              "type": "number"
            },
            "maxItems": 2,
            "minItems": 2,
            "type": "array"
          },
          "departureDate": {
            "description": "Fecha/hora de salida de la parada",
            "format": "date-time",
            "type": "string"
          },
          "distanceFromStart": {
            "description": "Distancia desde el inicio en km",
            "type": "number"
          },
          "durationMinutes": {
            "description": "Duraci\u00f3n de la parada en minutos",
            "type": "integer"
          },
          "hoursStatus": {
            "description": "Estado de horas de conducci\u00f3n en este punto",
            "properties": {
              "exceedsDaily": {
                "description": "Si excede el l\u00edmite diario",
                "type": "boolean"
              },
              "needsBreak": {
                "description": "Si necesita un descanso",
                "type": "boolean"
              },
              "remainingDaily": {
                "description": "Horas restantes del d\u00eda",
                "type": "number"
              },
              "remainingWeekly": {
                "description": "Horas restantes de la semana",
                "type": "number"
              }
            },
            "type": "object"
          },
          "isUserDefined": {
            "description": "Si fue definido expl\u00edcitamente por el usuario",
            "type": "boolean"
          },
          "isWaypoint": {
            "description": "Si es un waypoint definido por usuario",
            "type": "boolean"
          },
          "pureDrivingMinutes": {
            "description": "Minutos de conducci\u00f3n pura hasta esta parada",
            "type": "number"
          },
          "stopType": {
            "description": "Tipo de parada:\n- WAYPOINT: Parada definida por usuario\n- SHORT_BREAK: Descanso corto (45 min)\n- LONG_BREAK: Descanso diario (11h)\n- WEEKLY_REST: Descanso semanal (45h)\n",
            "enum": [
              "WAYPOINT",
              "SHORT_BREAK",
              "LONG_BREAK",
              "WEEKLY_REST"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "UserActivity": {
        "properties": {
          "_id": {
            "description": "ID \u00fanico de MongoDB para la actividad",
            "example": "507f1f77bcf86cd799439011",
            "type": "string"
          },
          "createdAt": {
            "description": "Fecha de creaci\u00f3n del registro en la base de datos",
            "example": "2025-01-13T10:30:00.000Z",
            "format": "date-time",
            "type": "string"
          },
          "environment": {
            "description": "Entorno donde se realiz\u00f3 la actividad (development, staging, production)",
            "example": "production",
            "type": "string"
          },
          "feature": {
            "description": "Nombre de la funcionalidad o m\u00f3dulo donde se realiz\u00f3 la actividad",
            "example": "user_management",
            "type": "string"
          },
          "idUser": {
            "description": "ID del usuario que realiz\u00f3 la actividad",
            "example": "63d7907cbe76403b35da63df",
            "type": "string"
          },
          "ip": {
            "description": "Direcci\u00f3n IP del usuario que realiz\u00f3 la actividad",
            "example": "192.168.1.100",
            "type": "string"
          },
          "o_system": {
            "description": "Sistema operativo del dispositivo del usuario",
            "enum": [
              "android",
              "linux",
              "windows",
              "apple",
              "etc"
            ],
            "example": "windows",
            "type": "string"
          },
          "origin": {
            "description": "Origen de la solicitud (web, mobile, api, etc.)",
            "example": "web",
            "type": "string"
          },
          "timestamp": {
            "description": "Fecha y hora exacta cuando se realiz\u00f3 la actividad",
            "example": "2025-01-13T10:30:00.000Z",
            "format": "date-time",
            "type": "string"
          },
          "type": {
            "description": "Tipo de actividad realizada:\n- C: Create (Creaci\u00f3n)\n- R: Read (Lectura)\n- U: Update (Actualizaci\u00f3n)\n- D: Delete (Eliminaci\u00f3n)\n",
            "enum": [
              "C",
              "R",
              "U",
              "D"
            ],
            "example": "C",
            "type": "string"
          },
          "updatedAt": {
            "description": "Fecha de \u00faltima actualizaci\u00f3n del registro",
            "example": "2025-01-13T10:30:00.000Z",
            "format": "date-time",
            "type": "string"
          },
          "url": {
            "description": "URL completa donde se realiz\u00f3 la actividad",
            "example": "/api/users/63d7907cbe76403b35da63df",
            "type": "string"
          }
        },
        "required": [
          "idUser",
          "environment",
          "type",
          "feature",
          "origin",
          "ip",
          "o_system",
          "timestamp",
          "url"
        ],
        "type": "object"
      },
      "Vehicle": {
        "description": "Informaci\u00f3n del veh\u00edculo para c\u00e1lculo de restricciones de ruta.\nLas dimensiones determinan qu\u00e9 carreteras puede usar el veh\u00edculo.\n",
        "properties": {
          "avgSpeed": {
            "description": "Velocidad media estimada en km/h",
            "example": 80,
            "type": "number"
          },
          "axleLoad": {
            "description": "Carga m\u00e1xima por eje en kg",
            "example": 11500,
            "type": "number"
          },
          "consumption": {
            "description": "Consumo en litros/100km",
            "example": 32,
            "type": "number"
          },
          "height": {
            "description": "Alto del veh\u00edculo en metros",
            "example": 4.0,
            "type": "number"
          },
          "length": {
            "description": "Longitud del veh\u00edculo en metros",
            "example": 16.5,
            "type": "number"
          },
          "tankSize": {
            "description": "Capacidad del tanque en litros",
            "example": 600,
            "type": "number"
          },
          "weight": {
            "description": "Peso total del veh\u00edculo en kg",
            "example": 40000,
            "type": "number"
          },
          "width": {
            "description": "Ancho del veh\u00edculo en metros",
            "example": 2.55,
            "type": "number"
          }
        },
        "type": "object"
      },
      "VehicleType": {
        "properties": {
          "code": {
            "description": "C\u00f3digo del tipo de veh\u00edculo",
            "enum": [
              "r3c",
              "tir",
              "rt",
              "r2c",
              "r2d",
              "van",
              "frc",
              "f2c",
              "adr",
              "ft",
              "none"
            ],
            "type": "string"
          },
          "config": {
            "description": "Configuraci\u00f3n",
            "type": "string"
          },
          "image": {
            "description": "Imagen en Base64",
            "type": "string"
          },
          "kg_max": {
            "description": "Peso m\u00e1ximo en kg",
            "type": "number"
          },
          "kg_min": {
            "description": "Peso m\u00ednimo en kg",
            "type": "number"
          },
          "length": {
            "description": "Longitud",
            "type": "number"
          },
          "max": {
            "description": "Valor m\u00e1ximo asociado",
            "type": "number"
          },
          "min": {
            "description": "Valor m\u00ednimo asociado",
            "type": "number"
          },
          "mma": {
            "description": "MMA",
            "type": "number"
          },
          "payload": {
            "description": "Carga \u00fatil",
            "type": "number"
          },
          "volume": {
            "description": "Volumen",
            "type": "number"
          }
        },
        "type": "object"
      },
      "Waypoint": {
        "description": "Punto intermedio en la ruta definido por el usuario",
        "properties": {
          "customBreakMinutes": {
            "default": 0,
            "description": "Tiempo de parada en minutos",
            "example": 30,
            "type": "integer"
          },
          "position": {
            "description": "Coordenadas [latitud, longitud]",
            "example": [
              42.0,
              -7.5
            ],
            "items": {
              "type": "number"
            },
            "maxItems": 2,
            "minItems": 2,
            "type": "array"
          },
          "stopType": {
            "default": "WAYPOINT",
            "description": "Tipo de parada",
            "enum": [
              "WAYPOINT",
              "SHORT_BREAK",
              "LONG_BREAK",
              "WEEKLY_REST"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "Workshop": {
        "description": "Representa una ubicaci\u00f3n de taller de camiones",
        "properties": {
          "address": {
            "description": "Direcci\u00f3n completa",
            "type": "string"
          },
          "emergencyService": {
            "description": "Si proporciona servicio de emergencia",
            "type": "boolean"
          },
          "id": {
            "description": "ID del taller",
            "type": "string"
          },
          "location": {
            "$ref": "#/components/schemas/POI/properties/location"
          },
          "name": {
            "description": "Nombre del taller",
            "type": "string"
          },
          "openingHours": {
            "description": "Informaci\u00f3n de horarios de apertura",
            "type": "string"
          },
          "services": {
            "description": "Lista de servicios disponibles",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "securitySchemes": {
        "bearerAuth": {
          "bearerFormat": "JWT",
          "description": "Autenticaci\u00f3n mediante JWT. \nEl token debe ser obtenido del servicio de autenticaci\u00f3n de Transcend.\nFormato del header:\n```\nAuthorization: Bearer {token}\n```\nEjemplo:\n```\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n```\n",
          "scheme": "bearer",
          "type": "http"
        }
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "description": "Clave API para autenticaci\u00f3n alternativa",
        "in": "header",
        "name": "x-api-key",
        "type": "apiKey"
      },
      "BearerAuth": {
        "bearerFormat": "JWT",
        "description": "Token JWT de autenticaci\u00f3n, obtenido de IAM",
        "scheme": "bearer",
        "type": "http"
      },
      "apiKeyAuth": {
        "description": "API Key proporcionada por Transcend",
        "in": "header",
        "name": "x-api-key",
        "type": "apiKey"
      },
      "bearerAuth": {
        "bearerFormat": "JWT",
        "description": "Token JWT para autenticaci\u00f3n",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "description": "API modular de TRANSCEND con 13 m\u00f3dulos: IAM, Drivers, Route, Vehicles, Traffic, Weather y m\u00e1s.",
    "title": "TRANSCEND API",
    "version": "1.0.0"
  },
  "openapi": "3.0.0",
  "paths": {
    "/": {
      "get": {
        "description": "Obtiene la estructura completa del sistema armonizado, incluyendo todas las secciones,\ncap\u00edtulos, partidas y subpartidas organizadas jer\u00e1rquicamente.\n\nLa respuesta incluye todos los niveles anidados en un solo request:\n- Secciones (nivel m\u00e1s alto)\n- Cap\u00edtulos (dentro de secciones)\n- Partidas (dentro de cap\u00edtulos) \n- Subpartidas (dentro de partidas)\n\nRequiere autenticaci\u00f3n mediante JWT en el header Authorization.\n",
        "operationId": "getAll",
        "parameters": [
          {
            "$ref": "#/components/parameters/authorization"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "_id": "6501a2b3c4d5e6f7a8b9c0d4",
                    "chapters": [
                      {
                        "_id": "6501a2b3c4d5e6f7a8b9c0d3",
                        "headings": [
                          {
                            "_id": "6501a2b3c4d5e6f7a8b9c0d2",
                            "subheadings": [
                              {
                                "_id": "6501a2b3c4d5e6f7a8b9c0d1",
                                "title": "Algod\u00f3n sin cardar ni peinar"
                              }
                            ],
                            "title": "Algod\u00f3n"
                          }
                        ],
                        "title": "Cap\u00edtulo 52 - Algod\u00f3n"
                      }
                    ],
                    "title": "Secci\u00f3n XI - Textiles"
                  }
                ],
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/SearchResult"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Estructura completa recuperada exitosamente.\nDevuelve un array de secciones, cada una con sus cap\u00edtulos anidados,\nque a su vez contienen partidas y subpartidas.\n"
          },
          "401": {
            "content": {
              "application/json": {
                "examples": {
                  "invalidToken": {
                    "value": {
                      "message": "Invalid or expired token"
                    }
                  },
                  "missingToken": {
                    "value": {
                      "message": "Authentication token is required"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No autorizado. Ocurre cuando:\n- No se proporciona el header Authorization\n- El token JWT es inv\u00e1lido o ha expirado\n- El formato del token es incorrecto\n\nEjemplo de respuesta:\n```json\n{\n  \"message\": \"Invalid or expired token\"\n}\n```\n"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error interno del servidor.\nPuede ocurrir si hay problemas al acceder a la base de datos.\n"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Obtener toda la estructura jer\u00e1rquica del HS Code",
        "tags": [
          "profiles"
        ]
      }
    },
    "/account/create-checkout-session": {
      "post": {
        "description": "Crea una sesi\u00f3n de pago en Stripe para que el usuario pueda suscribirse a un plan. Devuelve la URL del checkout de Stripe",
        "operationId": "createCheckoutSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/Module_Authorization"
          },
          {
            "$ref": "#/components/parameters/API_Key"
          },
          {
            "$ref": "#/components/parameters/UserId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckoutSessionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "url": {
                      "description": "URL del checkout de Stripe",
                      "example": "https://checkout.stripe.com/pay/cs_test_a1B2c3D4e5F6...",
                      "format": "uri",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Sesi\u00f3n de checkout creada exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Perfil de usuario no encontrado"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Crear sesi\u00f3n de checkout",
        "tags": [
          "pay"
        ]
      }
    },
    "/account/create-portal-session": {
      "post": {
        "description": "Crea una sesi\u00f3n del portal de cliente de Stripe para que el usuario pueda gestionar su suscripci\u00f3n, m\u00e9todos de pago y facturas",
        "operationId": "createPortalSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/Module_Authorization"
          },
          {
            "$ref": "#/components/parameters/API_Key"
          },
          {
            "$ref": "#/components/parameters/UserId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PortalSessionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "url": {
                      "description": "URL del portal de cliente de Stripe",
                      "example": "https://billing.stripe.com/session/bps_1a2B3c4D5e6F...",
                      "format": "uri",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Sesi\u00f3n del portal creada exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Cliente de Stripe no encontrado para el usuario"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Crear sesi\u00f3n del portal de cliente",
        "tags": [
          "pay"
        ]
      }
    },
    "/account/failed-payments": {
      "get": {
        "description": "Recupera el listado de pagos fallidos para el cliente del usuario, \u00fatil para identificar problemas de facturaci\u00f3n",
        "operationId": "getFailedPayments",
        "parameters": [
          {
            "$ref": "#/components/parameters/Module_Authorization"
          },
          {
            "$ref": "#/components/parameters/API_Key"
          },
          {
            "$ref": "#/components/parameters/UserId"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "description": "Objeto de pago fallido de Stripe",
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de pagos fallidos obtenida exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Cliente de Stripe no encontrado"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Obtener pagos fallidos",
        "tags": [
          "pay"
        ]
      }
    },
    "/account/invoices": {
      "get": {
        "description": "Recupera el historial de facturas del usuario, incluyendo estados y URLs para acceso",
        "operationId": "getUserInvoices",
        "parameters": [
          {
            "$ref": "#/components/parameters/Module_Authorization"
          },
          {
            "$ref": "#/components/parameters/API_Key"
          },
          {
            "$ref": "#/components/parameters/UserId"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Invoice"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de facturas obtenida exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Cliente de Stripe no encontrado"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Obtener facturas del usuario",
        "tags": [
          "pay"
        ]
      }
    },
    "/account/pricing-plans": {
      "get": {
        "description": "Devuelve el listado completo de planes de precios activos, sus caracter\u00edsticas y costos",
        "operationId": "getPricingPlans",
        "parameters": [
          {
            "$ref": "#/components/parameters/Module_Authorization"
          },
          {
            "$ref": "#/components/parameters/API_Key"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/PricingTier"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de planes de precios obtenida exitosamente"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Obtener planes de precios disponibles",
        "tags": [
          "pay"
        ]
      }
    },
    "/account/summary": {
      "get": {
        "description": "Obtiene informaci\u00f3n completa sobre la suscripci\u00f3n actual del usuario, incluyendo estado, per\u00edodo de facturaci\u00f3n, plan contratado y consumo de rutas",
        "operationId": "getAccountSummary",
        "parameters": [
          {
            "$ref": "#/components/parameters/Module_Authorization"
          },
          {
            "$ref": "#/components/parameters/API_Key"
          },
          {
            "$ref": "#/components/parameters/UserId"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionSummary"
                }
              }
            },
            "description": "Resumen de suscripci\u00f3n obtenido exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No se encontr\u00f3 suscripci\u00f3n activa para el usuario"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Obtener resumen de suscripci\u00f3n",
        "tags": [
          "pay"
        ]
      }
    },
    "/aemet/alerts/area": {
      "get": {
        "description": "Alertas meteorol\u00f3gicas de AEMET filtradas por \u00e1rea geogr\u00e1fica",
        "operationId": "getAemetAlertsByArea",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/StartDate"
          },
          {
            "$ref": "#/components/parameters/EndDate"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Alertas por \u00e1rea recuperadas exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get weather alerts by area",
        "tags": [
          "weather"
        ]
      }
    },
    "/aemet/alerts/current": {
      "get": {
        "description": "Alertas meteorol\u00f3gicas actuales proporcionadas por AEMET para ubicaciones en Espa\u00f1a",
        "operationId": "getAemetAlertsCurrent",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Alertas meteorol\u00f3gicas recuperadas exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get current AEMET alerts",
        "tags": [
          "weather"
        ]
      }
    },
    "/aemet/alerts/range": {
      "get": {
        "description": "Alertas meteorol\u00f3gicas hist\u00f3ricas o futuras proporcionadas por AEMET para ubicaciones en Espa\u00f1a",
        "operationId": "getAemetAlertsRange",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/StartDate"
          },
          {
            "$ref": "#/components/parameters/EndDate"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Alertas meteorol\u00f3gicas hist\u00f3ricas o futuras recuperadas exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get AEMET alerts for date range",
        "tags": [
          "weather"
        ]
      }
    },
    "/aemet/current": {
      "get": {
        "description": "Alertas meteorol\u00f3gicas en tiempo real proporcionadas por AEMET para ubicaciones en Espa\u00f1a",
        "operationId": "getAemetCurrent",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Alertas meteorol\u00f3gicas actuales recuperadas exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get current AEMET weather alerts",
        "tags": [
          "weather"
        ]
      }
    },
    "/aemet/extreme": {
      "get": {
        "description": "Valores extremos (m\u00e1ximos y m\u00ednimos) de los par\u00e1metros meteorol\u00f3gicos de AEMET",
        "operationId": "getAemetExtremeValues",
        "parameters": [
          {
            "$ref": "#/components/parameters/Parametro"
          },
          {
            "$ref": "#/components/parameters/StationId"
          },
          {
            "$ref": "#/components/parameters/StartDate"
          },
          {
            "$ref": "#/components/parameters/EndDate"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Valores extremos recuperados exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get extreme weather values",
        "tags": [
          "weather"
        ]
      }
    },
    "/aemet/forecast": {
      "get": {
        "description": "Pron\u00f3sticos meteorol\u00f3gicos proporcionados por AEMET para ubicaciones en Espa\u00f1a",
        "operationId": "getAemetForecast",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/StartDate"
          },
          {
            "$ref": "#/components/parameters/EndDate"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Pron\u00f3stico meteorol\u00f3gico recuperado exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get AEMET weather forecast",
        "tags": [
          "weather"
        ]
      }
    },
    "/aemet/forecast/station": {
      "get": {
        "description": "Pron\u00f3stico meteorol\u00f3gico de AEMET para una estaci\u00f3n meteorol\u00f3gica espec\u00edfica",
        "operationId": "getAemetForecastStation",
        "parameters": [
          {
            "$ref": "#/components/parameters/StationId"
          },
          {
            "$ref": "#/components/parameters/StartDate"
          },
          {
            "$ref": "#/components/parameters/EndDate"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Pron\u00f3stico de la estaci\u00f3n recuperado exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get weather forecast for specific station",
        "tags": [
          "weather"
        ]
      }
    },
    "/aemet/forecast/stations": {
      "get": {
        "description": "Pron\u00f3stico meteorol\u00f3gico de AEMET para todas las estaciones meteorol\u00f3gicas",
        "operationId": "getAemetForecastAllStations",
        "parameters": [
          {
            "$ref": "#/components/parameters/StartDate"
          },
          {
            "$ref": "#/components/parameters/EndDate"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Pron\u00f3stico de todas las estaciones recuperado exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get weather forecast for all stations",
        "tags": [
          "weather"
        ]
      }
    },
    "/aemet/stations": {
      "get": {
        "description": "Lista completa de estaciones meteorol\u00f3gicas disponibles en AEMET",
        "operationId": "getAemetStations",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Lista de estaciones recuperada exitosamente"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get AEMET weather stations list",
        "tags": [
          "weather"
        ]
      }
    },
    "/aemet/stations/{stationId}": {
      "get": {
        "description": "Informaci\u00f3n detallada de una estaci\u00f3n meteorol\u00f3gica espec\u00edfica de AEMET",
        "operationId": "getAemetStationDetails",
        "parameters": [
          {
            "$ref": "#/components/parameters/StationIdPath"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Detalles de la estaci\u00f3n recuperados exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "ID de estaci\u00f3n inv\u00e1lido o faltante"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get AEMET weather station details",
        "tags": [
          "weather"
        ]
      }
    },
    "/alerts/current": {
      "get": {
        "description": "Mant\u00e9ngase informado sobre las alertas meteorol\u00f3gicas activas en su zona.",
        "operationId": "getAlertCurrent",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Alertas meteorol\u00f3gicas recuperadas exitosamente."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error interno del servidor."
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get current weather alerts",
        "tags": [
          "weather"
        ]
      }
    },
    "/alerts/range": {
      "get": {
        "description": "Consultar alertas meteorol\u00f3gicas hist\u00f3ricas o futuras para su ubicaci\u00f3n.",
        "operationId": "getAlertRange",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/StartDate"
          },
          {
            "$ref": "#/components/parameters/EndDate"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Alertas meteorol\u00f3gicas hist\u00f3ricas o futuras recuperadas exitosamente."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error interno del servidor."
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get weather alerts for date range",
        "tags": [
          "weather"
        ]
      }
    },
    "/api/": {
      "get": {
        "description": "Consulta las actividades realizadas por un usuario espec\u00edfico con soporte para paginaci\u00f3n y filtros por fecha.\n\n**PAR\u00c1METROS OBLIGATORIOS:**\n- `idUser`: ID del usuario cuyas actividades quieres consultar\n\n**PAR\u00c1METROS OPCIONALES:**\n- `page`: N\u00famero de p\u00e1gina (por defecto: 1)\n- `pageSize`: Actividades por p\u00e1gina (10, 25, 50, 100 - por defecto: 25)\n- `startDate`: Fecha inicio filtro (formato ISO 8601, ej: \"2025-01-01T00:00:00.000Z\")\n- `endDate`: Fecha fin filtro (formato ISO 8601, ej: \"2025-01-13T23:59:59.999Z\")\n\n**COMPORTAMIENTO DE AUTENTICACI\u00d3N:**\n- Si se proporciona JWT v\u00e1lido: Se pueden consultar actividades de cualquier usuario\n- Si no hay JWT: Solo se permiten consultas sin especificar idUser (retorna error)\n\n**FILTROS AUTOM\u00c1TICOS:**\n- Si no se especifica startDate/endDate: Se filtra por los \u00faltimos 30 d\u00edas\n- pageSize se ajusta autom\u00e1ticamente a valores permitidos (10, 25, 50, 100)\n\n**EJEMPLOS DE USO:**\n- Consultar actividades recientes: `GET /api/?idUser=63d7907cbe76403b35da63df`\n- P\u00e1gina espec\u00edfica: `GET /api/?idUser=63d7907cbe76403b35da63df&page=2&pageSize=50`\n- Rango de fechas: `GET /api/?idUser=63d7907cbe76403b35da63df&startDate=2025-01-01T00:00:00Z&endDate=2025-01-31T23:59:59Z`\n",
        "operationId": "getUserActivities",
        "parameters": [
          {
            "$ref": "#/components/parameters/Authorization"
          },
          {
            "$ref": "#/components/parameters/UserId"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/StartDate"
          },
          {
            "$ref": "#/components/parameters/EndDate"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityResponse"
                }
              }
            },
            "description": "Actividades recuperadas exitosamente. La respuesta incluye:\n- `docs`: Array de actividades encontradas\n- `total`: N\u00famero total de actividades (sin paginaci\u00f3n)\n- `limit`: N\u00famero de actividades por p\u00e1gina\n- `page`: P\u00e1gina actual\n- `pages`: N\u00famero total de p\u00e1ginas disponibles\n"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos. Posibles causas:\n- idUser no proporcionado y sin autenticaci\u00f3n JWT\n- Fechas en formato incorrecto (debe ser ISO 8601)\n- pageSize fuera del rango permitido\n"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Token JWT inv\u00e1lido o expirado"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error interno del servidor"
          }
        },
        "summary": "Obtener actividades de usuario",
        "tags": [
          "activity"
        ]
      },
      "post": {
        "description": "Registra una nueva actividad realizada por un usuario en el sistema.\n\n**CAMPOS OBLIGATORIOS EN EL BODY:**\n- `environment`: Entorno (\"development\", \"staging\", \"production\")\n- `type`: Tipo de actividad (\"C\"=Create, \"R\"=Read, \"U\"=Update, \"D\"=Delete)\n- `feature`: Nombre del m\u00f3dulo/funcionalidad (ej: \"user_management\", \"dashboard\")\n- `origin`: Origen de la solicitud (\"web\", \"mobile\", \"api\")\n- `o_system`: Sistema operativo (\"android\", \"linux\", \"windows\", \"apple\", \"etc\")\n- `url`: URL completa donde se realiz\u00f3 la actividad\n\n**CAMPOS OPCIONALES:**\n- `idUser`: ID del usuario (se obtiene autom\u00e1ticamente del JWT si est\u00e1 presente)\n\n**CAMPOS AUTOM\u00c1TICOS (NO INCLUIR EN REQUEST):**\n- `timestamp`: Se establece autom\u00e1ticamente con la fecha/hora actual\n- `ip`: Se obtiene autom\u00e1ticamente de la solicitud (soporte para proxies)\n\n**VALIDACIONES:**\n- `type` debe ser exactamente: \"C\", \"R\", \"U\", o \"D\"\n- `o_system` debe ser uno de: \"android\", \"linux\", \"windows\", \"apple\", \"etc\"\n- Todos los campos requeridos deben estar presentes y no vac\u00edos\n\n**EJEMPLOS DE USO:**\n\n*Crear usuario:*\n```json\n{\n  \"environment\": \"production\",\n  \"type\": \"C\",\n  \"feature\": \"user_management\",\n  \"origin\": \"web\",\n  \"o_system\": \"windows\",\n  \"url\": \"/api/users\"\n}\n```\n\n*Ver dashboard:*\n```json\n{\n  \"environment\": \"production\",\n  \"type\": \"R\",\n  \"feature\": \"dashboard\",\n  \"origin\": \"mobile\",\n  \"o_system\": \"android\",\n  \"url\": \"/dashboard\"\n}\n```\n",
        "operationId": "createUserActivity",
        "parameters": [
          {
            "$ref": "#/components/parameters/Authorization"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "CreateUser": {
                  "summary": "Registro de creaci\u00f3n de usuario",
                  "value": {
                    "environment": "production",
                    "feature": "user_management",
                    "idUser": "63d7907cbe76403b35da63df",
                    "o_system": "windows",
                    "origin": "web",
                    "type": "C",
                    "url": "/api/users"
                  }
                },
                "UpdateProfile": {
                  "summary": "Actualizaci\u00f3n de perfil de usuario",
                  "value": {
                    "environment": "production",
                    "feature": "user_profile",
                    "o_system": "android",
                    "origin": "mobile",
                    "type": "U",
                    "url": "/api/users/profile"
                  }
                },
                "ViewDashboard": {
                  "summary": "Acceso al dashboard principal",
                  "value": {
                    "environment": "production",
                    "feature": "dashboard_main",
                    "o_system": "linux",
                    "origin": "web",
                    "type": "R",
                    "url": "/dashboard"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/CreateActivityRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserActivity"
                }
              }
            },
            "description": "Actividad registrada exitosamente. Retorna el objeto completo de la actividad creada\nincluyendo el ID generado por MongoDB y los campos autom\u00e1ticos (timestamp, ip, etc.)\n"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Datos inv\u00e1lidos en la solicitud. Posibles causas:\n- Campos requeridos faltantes (environment, type, feature, origin, o_system, url)\n- Valores enum inv\u00e1lidos (type debe ser C/R/U/D, o_system debe ser v\u00e1lido)\n- Formato de datos incorrecto\n- Campos vac\u00edos o nulos\n"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Token JWT inv\u00e1lido o expirado (si se requiere autenticaci\u00f3n)"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error interno del servidor (problemas de base de datos, etc.)"
          }
        },
        "summary": "Registrar nueva actividad",
        "tags": [
          "activity"
        ]
      }
    },
    "/api/apikeys": {
      "delete": {
        "description": "Elimina la API key del usuario",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "API key eliminada exitosamente"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Eliminar mi API key",
        "tags": [
          "iam"
        ]
      },
      "get": {
        "description": "Retorna informaci\u00f3n de la API key del usuario",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Informaci\u00f3n de API key obtenida"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Obtener mi API key",
        "tags": [
          "iam"
        ]
      },
      "post": {
        "description": "Genera una nueva API key para el usuario",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "API key creada exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Crear nueva API key",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/apikeys/verify": {
      "post": {
        "description": "Verifica si una API key es v\u00e1lida",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "API key v\u00e1lida"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "API key inv\u00e1lida"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Verificar API key",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/auth/check-internal-token": {
      "post": {
        "description": "Verifica un token interno del sistema",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Token interno v\u00e1lido"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Verificar token interno",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/auth/login": {
      "post": {
        "description": "Autentica un usuario con email y contrase\u00f1a, retorna token JWT",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/LoginSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Credenciales inv\u00e1lidas"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Demasiadas solicitudes - Rate limit excedido"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Iniciar sesi\u00f3n de usuario",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/auth/me": {
      "get": {
        "description": "Retorna informaci\u00f3n del usuario autenticado",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Informaci\u00f3n del usuario obtenida"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Obtener informaci\u00f3n del usuario actual",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/auth/profile": {
      "put": {
        "description": "Actualiza informaci\u00f3n del usuario autenticado",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Perfil actualizado exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Actualizar perfil de usuario",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/auth/recovery": {
      "post": {
        "description": "Inicia proceso de recuperaci\u00f3n de contrase\u00f1a",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Email de recuperaci\u00f3n enviado"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Solicitar recuperaci\u00f3n de contrase\u00f1a",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/auth/register": {
      "post": {
        "description": "Crea un nuevo usuario y empresa asociada",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LoginResponse"
                }
              }
            },
            "description": "Registro exitoso"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Datos inv\u00e1lidos o duplicados"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Demasiados registros"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Registrar nuevo usuario y empresa",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/auth/renew-token": {
      "post": {
        "description": "Renueva un token JWT v\u00e1lido",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LoginResponse"
                }
              }
            },
            "description": "Token renovado exitosamente"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Renovar token JWT",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/auth/subscription": {
      "post": {
        "description": "Actualiza el tipo de suscripci\u00f3n del usuario autenticado",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Suscripci\u00f3n actualizada exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Actualizar suscripci\u00f3n de usuario",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/auth/validate-token": {
      "get": {
        "description": "Verifica si un token JWT es v\u00e1lido",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Token v\u00e1lido"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Validar token JWT",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/auth/verify-service-token": {
      "post": {
        "description": "Verifica un token de servicio",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Token de servicio v\u00e1lido"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Verificar token de servicio",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/company": {
      "post": {
        "description": "Crea una nueva empresa",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Empresa creada exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Crear nueva empresa",
        "tags": [
          "iam"
        ]
      },
      "put": {
        "description": "Actualiza informaci\u00f3n de la empresa",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Empresa actualizada exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Actualizar empresa",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/company/me": {
      "get": {
        "description": "Retorna informaci\u00f3n de la empresa del usuario autenticado",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Informaci\u00f3n de empresa obtenida"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Obtener informaci\u00f3n de la empresa",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/company/users": {
      "get": {
        "description": "Retorna lista de usuarios de la empresa",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Lista de usuarios obtenida"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Listar usuarios de la empresa",
        "tags": [
          "iam"
        ]
      },
      "post": {
        "description": "Crea un nuevo usuario en la empresa",
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Usuario creado exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Crear usuario en la empresa",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/company/users/{userId}": {
      "delete": {
        "description": "Elimina un usuario espec\u00edfico de la empresa",
        "parameters": [
          {
            "description": "ID del usuario a eliminar",
            "in": "path",
            "name": "userId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Usuario eliminado exitosamente"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Eliminar usuario de la empresa",
        "tags": [
          "iam"
        ]
      },
      "put": {
        "description": "Actualiza informaci\u00f3n de un usuario espec\u00edfico de la empresa",
        "parameters": [
          {
            "description": "ID del usuario a actualizar",
            "in": "path",
            "name": "userId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Usuario actualizado exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Actualizar usuario de la empresa",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/company/webhook": {
      "put": {
        "description": "Configura URL de webhook para notificaciones",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Webhook actualizado exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Actualizar webhook de empresa",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/gamification/metrics": {
      "get": {
        "description": "Obtiene las m\u00e9tricas de gamificaci\u00f3n del usuario autenticado",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GamificationMetrics"
                }
              }
            },
            "description": "M\u00e9tricas de gamificaci\u00f3n"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "User ID requerido"
          }
        },
        "summary": "Obtener m\u00e9tricas de gamificaci\u00f3n",
        "tags": [
          "route"
        ]
      }
    },
    "/api/gamification/metrics/refresh": {
      "post": {
        "description": "Fuerza una actualizaci\u00f3n de las m\u00e9tricas de gamificaci\u00f3n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GamificationMetrics"
                }
              }
            },
            "description": "M\u00e9tricas actualizadas"
          }
        },
        "summary": "Refrescar m\u00e9tricas de gamificaci\u00f3n",
        "tags": [
          "route"
        ]
      }
    },
    "/api/gamification/summary": {
      "get": {
        "description": "Obtiene un resumen de los logros y estad\u00edsticas del usuario",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GamificationSummary"
                }
              }
            },
            "description": "Resumen de gamificaci\u00f3n"
          }
        },
        "summary": "Obtener resumen de gamificaci\u00f3n",
        "tags": [
          "route"
        ]
      }
    },
    "/api/isochrone": {
      "get": {
        "description": "Calcula el \u00e1rea alcanzable desde un punto dado en un tiempo o distancia determinada.\n\u00datil para an\u00e1lisis de cobertura de servicios.\n",
        "parameters": [
          {
            "description": "Latitud del punto central",
            "example": 40.4168,
            "in": "query",
            "name": "lat",
            "required": true,
            "schema": {
              "format": "double",
              "type": "number"
            }
          },
          {
            "description": "Longitud del punto central",
            "example": -3.7038,
            "in": "query",
            "name": "lon",
            "required": true,
            "schema": {
              "format": "double",
              "type": "number"
            }
          },
          {
            "description": "Distancia en km (si isTime=false) o minutos (si isTime=true).\nM\u00e1ximo: 1000km o 120 minutos.\n",
            "example": 50,
            "in": "query",
            "name": "km",
            "required": false,
            "schema": {
              "default": 100,
              "maximum": 1000,
              "type": "number"
            }
          },
          {
            "description": "Si true, km representa minutos en lugar de kil\u00f3metros",
            "in": "query",
            "name": "isTime",
            "required": false,
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "Informaci\u00f3n del veh\u00edculo (JSON-encoded)",
            "in": "query",
            "name": "vehicle",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IsochroneResponse"
                }
              }
            },
            "description": "Isocrona calculada"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos"
          }
        },
        "summary": "Calcular isocronas",
        "tags": [
          "route"
        ]
      }
    },
    "/api/locations": {
      "get": {
        "description": "Obtiene todas las ubicaciones del usuario autenticado",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Location"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de ubicaciones"
          }
        },
        "summary": "Listar ubicaciones",
        "tags": [
          "route"
        ]
      },
      "post": {
        "description": "Guarda una ubicaci\u00f3n frecuente para el usuario (cliente, almac\u00e9n, etc.)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LocationCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Location"
                }
              }
            },
            "description": "Ubicaci\u00f3n creada"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Datos inv\u00e1lidos"
          }
        },
        "summary": "Crear ubicaci\u00f3n",
        "tags": [
          "route"
        ]
      }
    },
    "/api/locations/{id}": {
      "delete": {
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "deleted": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Ubicaci\u00f3n eliminada"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Ubicaci\u00f3n no encontrada"
          }
        },
        "summary": "Eliminar ubicaci\u00f3n",
        "tags": [
          "route"
        ]
      },
      "get": {
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Location"
                }
              }
            },
            "description": "Ubicaci\u00f3n"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Ubicaci\u00f3n no encontrada"
          }
        },
        "summary": "Obtener ubicaci\u00f3n por ID",
        "tags": [
          "route"
        ]
      },
      "put": {
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LocationUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Location"
                }
              }
            },
            "description": "Ubicaci\u00f3n actualizada"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Ubicaci\u00f3n no encontrada"
          }
        },
        "summary": "Actualizar ubicaci\u00f3n",
        "tags": [
          "route"
        ]
      }
    },
    "/api/route": {
      "get": {
        "description": "Calcula una ruta optimizada para camiones entre origen y destino, \nincluyendo waypoints intermedios y paradas de descanso obligatorias.\n\n## Tipos de fecha\n- `departure`: La fecha proporcionada es la hora de salida (default)\n- `arrival`: La fecha proporcionada es la hora de llegada deseada. El sistema\n  calcula autom\u00e1ticamente la hora de salida necesaria.\n\n## C\u00e1lculo de paradas\nEl sistema calcula autom\u00e1ticamente las paradas necesarias seg\u00fan:\n- Horas de conducci\u00f3n restantes del conductor\n- Normativa EU de tiempos de conducci\u00f3n\n- Waypoints definidos por el usuario\n",
        "parameters": [
          {
            "description": "Latitud del punto de origen",
            "example": 42.2406,
            "in": "query",
            "name": "origin_lat",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 90,
              "minimum": -90,
              "type": "number"
            }
          },
          {
            "description": "Longitud del punto de origen",
            "example": -8.7207,
            "in": "query",
            "name": "origin_lon",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 180,
              "minimum": -180,
              "type": "number"
            }
          },
          {
            "description": "Latitud del punto de destino",
            "example": 41.3874,
            "in": "query",
            "name": "destiny_lat",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 90,
              "minimum": -90,
              "type": "number"
            }
          },
          {
            "description": "Longitud del punto de destino",
            "example": 2.1686,
            "in": "query",
            "name": "destiny_lon",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 180,
              "minimum": -180,
              "type": "number"
            }
          },
          {
            "description": "Array de waypoints intermedios (JSON-encoded).\nCada waypoint puede tener un tiempo de parada personalizado.\n",
            "example": "[{\"position\":[42.0,-7.5],\"customBreakMinutes\":30,\"stopType\":\"WAYPOINT\"}]",
            "in": "query",
            "name": "waypoints",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Array de puntos a evitar (JSON-encoded).\nCada punto se define como [latitud, longitud].\nEl sistema crear\u00e1 zonas de evitaci\u00f3n de 1km de radio alrededor de cada punto.\n",
            "example": "[[42.0259,-7.185],[41.5,-2.3]]",
            "in": "query",
            "name": "avoid_points",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Informaci\u00f3n del veh\u00edculo (JSON-encoded).\nUsado para restricciones de altura, peso, anchura en la ruta.\n",
            "example": "{\"width\":2.55,\"height\":4.0,\"weight\":40000,\"length\":16.5,\"axleLoad\":11500,\"tankSize\":600,\"consumption\":32,\"avgSpeed\":80}",
            "in": "query",
            "name": "vehicle",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Estado de horas del conductor (JSON-encoded).\nUsado para calcular cu\u00e1ndo necesita paradas de descanso.\n",
            "example": "{\"hoursStatus\":{\"remainingWeeklyHours\":56,\"remainingBiweeklyHours\":90,\"remainingDayHours\":9}}",
            "in": "query",
            "name": "driver",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Informaci\u00f3n de la mercanc\u00eda (JSON-encoded).\nUsado para rutas ADR (mercanc\u00edas peligrosas).\n",
            "example": "{\"hsCode\":\"8703\",\"weight\":15000,\"isADR\":false}",
            "in": "query",
            "name": "merchandise",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Fecha y tipo de fecha (JSON-encoded).\n- `type: \"departure\"`: La fecha es hora de salida\n- `type: \"arrival\"`: La fecha es hora de llegada deseada\n",
            "example": "{\"type\":\"departure\",\"date\":\"2025-01-15T08:00:00Z\"}",
            "in": "query",
            "name": "date",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Capas del mapa y opciones de ruta (JSON-encoded).\nControla qu\u00e9 evitar y qu\u00e9 POIs mostrar.\n",
            "example": "{\"avoidTolls\":false,\"avoidHighways\":false,\"calculateStops\":true,\"shortestRoute\":false}",
            "in": "query",
            "name": "map_layers",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Incluir puntos negros de peligrosidad en la respuesta",
            "in": "query",
            "name": "show_black_points",
            "required": false,
            "schema": {
              "default": false,
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RouteResponse"
                }
              }
            },
            "description": "Ruta calculada exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No se encontr\u00f3 ruta"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error interno del servidor"
          }
        },
        "summary": "Calcular ruta optimizada",
        "tags": [
          "route"
        ]
      }
    },
    "/api/route/count": {
      "get": {
        "description": "Obtiene el conteo de rutas calculadas en un intervalo de fechas",
        "parameters": [
          {
            "in": "query",
            "name": "startDate",
            "required": true,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "endDate",
            "required": true,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "count": {
                      "example": 150,
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Conteo de rutas"
          }
        },
        "summary": "Contar rutas en intervalo de tiempo",
        "tags": [
          "route"
        ]
      }
    },
    "/api/saved-routes": {
      "get": {
        "description": "Obtiene todas las rutas guardadas del usuario autenticado",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/SavedRoute"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de rutas guardadas"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "User ID requerido"
          }
        },
        "summary": "Listar rutas guardadas",
        "tags": [
          "route"
        ]
      },
      "post": {
        "description": "Guarda una ruta calculada para el usuario autenticado",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SavedRouteCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SavedRoute"
                }
              }
            },
            "description": "Ruta guardada exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Datos inv\u00e1lidos o User ID requerido"
          }
        },
        "summary": "Crear ruta guardada",
        "tags": [
          "route"
        ]
      }
    },
    "/api/saved-routes/{id}": {
      "delete": {
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "deleted": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Ruta eliminada"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Ruta no encontrada"
          }
        },
        "summary": "Eliminar ruta guardada",
        "tags": [
          "route"
        ]
      },
      "get": {
        "parameters": [
          {
            "description": "ID de la ruta guardada (MongoDB ObjectId)",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SavedRoute"
                }
              }
            },
            "description": "Ruta guardada"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Ruta no encontrada"
          }
        },
        "summary": "Obtener ruta guardada por ID",
        "tags": [
          "route"
        ]
      },
      "put": {
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SavedRouteUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SavedRoute"
                }
              }
            },
            "description": "Ruta actualizada"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Ruta no encontrada"
          }
        },
        "summary": "Actualizar ruta guardada",
        "tags": [
          "route"
        ]
      }
    },
    "/api/saved-routes/{id}/gpx": {
      "get": {
        "description": "Descarga la ruta guardada en formato GPX para dispositivos GPS",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/gpx+xml": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "Archivo GPX"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Ruta no encontrada"
          }
        },
        "summary": "Exportar ruta como GPX",
        "tags": [
          "route"
        ]
      }
    },
    "/api/tempCode/{id}": {
      "get": {
        "description": "Obtiene una ruta compartida mediante un c\u00f3digo temporal.\nLas rutas temporales expiran despu\u00e9s de un tiempo configurado.\n",
        "parameters": [
          {
            "description": "C\u00f3digo temporal de la ruta",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TempRoute"
                }
              }
            },
            "description": "Ruta temporal"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Ruta temporal no encontrada o expirada"
          }
        },
        "summary": "Obtener ruta por c\u00f3digo temporal",
        "tags": [
          "route"
        ]
      }
    },
    "/api/token/renew-token": {
      "post": {
        "deprecated": true,
        "description": "**\u26a0\ufe0f DEPRECATED:** Use `/api/auth/renew-token` instead.\nAlias para renovar token JWT.\n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LoginResponse"
                }
              }
            },
            "description": "Token renovado exitosamente"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Renovar token JWT (legacy)",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/token/validate-token": {
      "get": {
        "deprecated": true,
        "description": "**\u26a0\ufe0f DEPRECATED:** Use `/api/auth/validate-token` instead.\nAlias para validar token JWT.\n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Token v\u00e1lido"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Validar token JWT (legacy)",
        "tags": [
          "iam"
        ]
      }
    },
    "/api/traffic/blackspots/area": {
      "get": {
        "description": "Retorna los puntos negros dentro de un radio alrededor de un punto central.\n",
        "parameters": [
          {
            "description": "Latitud del centro del \u00e1rea",
            "example": 40.4168,
            "in": "query",
            "name": "lat",
            "required": true,
            "schema": {
              "format": "double",
              "type": "number"
            }
          },
          {
            "description": "Longitud del centro del \u00e1rea",
            "example": -3.7038,
            "in": "query",
            "name": "lon",
            "required": true,
            "schema": {
              "format": "double",
              "type": "number"
            }
          },
          {
            "description": "Radio de b\u00fasqueda en metros (1000-25000)",
            "example": 10000,
            "in": "query",
            "name": "radius",
            "required": false,
            "schema": {
              "default": 25000,
              "maximum": 25000,
              "minimum": 1000,
              "type": "integer"
            }
          },
          {
            "description": "Nivel m\u00ednimo de peligrosidad (0-100)",
            "in": "query",
            "name": "minDangerLevel",
            "required": false,
            "schema": {
              "default": 30,
              "maximum": 100,
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BlackSpotsResponse"
                }
              }
            },
            "description": "Puntos negros en el \u00e1rea"
          }
        },
        "summary": "Obtener puntos negros en un \u00e1rea",
        "tags": [
          "route"
        ]
      }
    },
    "/api/traffic/blackspots/path": {
      "post": {
        "description": "Retorna los puntos negros (zonas de alta peligrosidad) que se encuentran\na lo largo de una ruta definida por una serie de puntos.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "minDangerLevel": {
                    "default": 30,
                    "description": "Nivel m\u00ednimo de peligrosidad (0-100)",
                    "maximum": 100,
                    "minimum": 0,
                    "type": "integer"
                  },
                  "points": {
                    "description": "Array de coordenadas [longitud, latitud] que definen la ruta",
                    "example": [
                      [
                        -3.7038,
                        40.4168
                      ],
                      [
                        -3.7138,
                        40.4268
                      ],
                      [
                        -3.7238,
                        40.4368
                      ]
                    ],
                    "items": {
                      "items": {
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "minItems": 2,
                    "type": "array"
                  }
                },
                "required": [
                  "points"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BlackSpotsResponse"
                }
              }
            },
            "description": "Puntos negros encontrados"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos"
          }
        },
        "summary": "Obtener puntos negros a lo largo de una ruta",
        "tags": [
          "route"
        ]
      }
    },
    "/api/traffic/health": {
      "get": {
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "service": {
                      "example": "traffic-controller",
                      "type": "string"
                    },
                    "status": {
                      "enum": [
                        "healthy",
                        "unhealthy"
                      ],
                      "type": "string"
                    },
                    "timestamp": {
                      "format": "date-time",
                      "type": "string"
                    },
                    "trafficService": {
                      "enum": [
                        "connected",
                        "disconnected"
                      ],
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Servicio de tr\u00e1fico saludable"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "status": {
                      "example": "unhealthy",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Servicio no disponible"
          }
        },
        "summary": "Health check del servicio de tr\u00e1fico",
        "tags": [
          "route"
        ]
      }
    },
    "/billing/mode": {
      "put": {
        "description": "Actualiza el plan de precios de un usuario existente (requiere autenticaci\u00f3n del usuario)",
        "operationId": "updateBillingMode",
        "parameters": [
          {
            "$ref": "#/components/parameters/Module_Authorization"
          },
          {
            "$ref": "#/components/parameters/API_Key"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillingModeUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "example": "Billing mode updated",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Modo de facturaci\u00f3n actualizado exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Actualizar modo de facturaci\u00f3n",
        "tags": [
          "pay"
        ]
      }
    },
    "/billing/pricing": {
      "get": {
        "description": "Recupera todos los planes de precios activos del sistema (endpoint p\u00fablico para IAM y otros servicios)",
        "operationId": "getAllPricingTiers",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/PricingTier"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de planes de precios obtenida exitosamente"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Obtener todas las tarifas",
        "tags": [
          "pay"
        ]
      }
    },
    "/billing/routes/usage": {
      "post": {
        "description": "Registra el consumo de rutas de un usuario para facturaci\u00f3n. Las operaciones se encolan para procesamiento batch",
        "operationId": "recordRouteUsage",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RouteUsageRecord"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "example": "Usage record queued for user 63d7907cbe76403b35da63df",
                      "type": "string"
                    },
                    "queueSize": {
                      "description": "Tama\u00f1o actual de la cola de procesamiento",
                      "type": "number"
                    },
                    "status": {
                      "example": "queued",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Uso registrado y encolado exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Registrar uso de rutas",
        "tags": [
          "pay"
        ]
      }
    },
    "/billing/users/{userId}/check-stripe": {
      "get": {
        "description": "Verifica si el usuario tiene un cliente Stripe asociado. Si no existe el perfil, lo crea autom\u00e1ticamente",
        "operationId": "checkUserHasStripe",
        "parameters": [
          {
            "$ref": "#/components/parameters/UserId_Path"
          },
          {
            "description": "Email del usuario (requerido si no existe el perfil)",
            "in": "query",
            "name": "email",
            "required": false,
            "schema": {
              "example": "user@example.com",
              "format": "email",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customerId": {
                      "description": "ID del cliente Stripe (si existe)",
                      "type": "string"
                    },
                    "hasStripe": {
                      "description": "Indica si el usuario tiene cliente Stripe",
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Verificaci\u00f3n completada exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No se encontr\u00f3 cliente Stripe para el usuario"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Verificar si el usuario tiene cliente Stripe",
        "tags": [
          "pay"
        ]
      }
    },
    "/billing/users/{userId}/subscription": {
      "get": {
        "description": "Obtiene informaci\u00f3n sobre el tipo de suscripci\u00f3n de un usuario espec\u00edfico. Si no existe el perfil, lo crea autom\u00e1ticamente",
        "operationId": "getUserSubscriptionType",
        "parameters": [
          {
            "$ref": "#/components/parameters/UserId_Path"
          },
          {
            "description": "Email del usuario (requerido si no existe el perfil)",
            "in": "query",
            "name": "email",
            "required": false,
            "schema": {
              "example": "user@example.com",
              "format": "email",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "subscriptionName": {
                      "description": "Nombre del plan ('none' si no tiene)",
                      "type": "string"
                    },
                    "subscriptionType": {
                      "description": "C\u00f3digo del plan de suscripci\u00f3n ('none' si no tiene)",
                      "type": "string"
                    },
                    "userId": {
                      "description": "ID del usuario",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Tipo de suscripci\u00f3n obtenida exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Obtener tipo de suscripci\u00f3n de usuario",
        "tags": [
          "pay"
        ]
      }
    },
    "/blackspots/area": {
      "get": {
        "description": "Recupera puntos negros (lugares con alto nivel de peligrosidad) dentro de un \u00e1rea espec\u00edfica.\n\n**Ejemplo de uso:**\n```\nGET /blackspots/area?lat=40.4168&lng=-3.7038&radius=5000&minDangerLevel=50\n```\n\nEste endpoint es \u00fatil para identificar zonas de alto riesgo en rutas log\u00edsticas.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Radius"
          },
          {
            "description": "Nivel m\u00ednimo de peligrosidad para filtrar (0-100)",
            "in": "query",
            "name": "minDangerLevel",
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "ejemplo_exitoso": {
                    "summary": "Puntos negros encontrados",
                    "value": [
                      {
                        "coordinates": {
                          "lat": 40.4168,
                          "lon": -3.7038
                        },
                        "createdAt": "2024-01-15T10:30:00Z",
                        "dangerLevel": 75,
                        "description": "Intersecci\u00f3n con alta tasa de accidentes",
                        "id": "bs_12345",
                        "updatedAt": "2024-01-15T10:30:00Z"
                      }
                    ]
                  }
                },
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/BlackSpot"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de puntos negros en el \u00e1rea"
          },
          "400": {
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "401": {
            "description": "No autorizado"
          },
          "500": {
            "description": "Error interno del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Obtener puntos negros en un \u00e1rea",
        "tags": [
          "traffic"
        ]
      }
    },
    "/blackspots/path": {
      "post": {
        "description": "Identifica puntos negros a lo largo de una ruta espec\u00edfica, calculando el nivel de peligrosidad para cada segmento.\n\n**Ejemplo de uso:**\n```json\nPOST /blackspots/path\n{\n  \"points\": [\n    [-3.7038, 40.4168],\n    [-3.7138, 40.4268]\n  ],\n  \"minDangerLevel\": 50\n}\n```\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "minDangerLevel": {
                    "default": 50,
                    "description": "Nivel m\u00ednimo de peligrosidad para filtrar",
                    "maximum": 100,
                    "minimum": 0,
                    "type": "number"
                  },
                  "points": {
                    "description": "Array de pares de coordenadas [longitud, latitud]",
                    "items": {
                      "items": {
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "minItems": 2,
                    "type": "array"
                  }
                },
                "required": [
                  "points"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/BlackSpot"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Puntos negros encontrados a lo largo de la ruta"
          },
          "400": {
            "description": "Ruta inv\u00e1lida o par\u00e1metros incorrectos"
          },
          "401": {
            "description": "No autorizado"
          },
          "500": {
            "description": "Error interno del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Obtener puntos negros a lo largo de una ruta",
        "tags": [
          "traffic"
        ]
      }
    },
    "/brands": {
      "get": {
        "description": "Recupera todas las marcas de veh\u00edculos disponibles en el sistema",
        "operationId": "getBrands",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "properties": {
                      "id": {
                        "format": "uuid",
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de marcas recuperada exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get list of vehicle brands",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/brands/with-counts": {
      "get": {
        "description": "Recupera todas las marcas de veh\u00edculos con el conteo de los modelos asociados",
        "operationId": "getBrandsWithModelCount",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "properties": {
                      "id": {
                        "format": "uuid",
                        "type": "string"
                      },
                      "modelCount": {
                        "type": "integer"
                      },
                      "name": {
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de marcas con conteo de modelos recuperada exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get brands with model count",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/brands/{brandId}": {
      "get": {
        "description": "Recupera los detalles de una marca espec\u00edfica por su ID",
        "operationId": "getBrandById",
        "parameters": [
          {
            "description": "ID de la marca",
            "in": "path",
            "name": "brandId",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "id": {
                      "format": "uuid",
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Marca recuperada exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Marca no encontrada"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get brand by ID",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/city/getByName": {
      "get": {
        "description": "Devuelve hasta 5 ciudades que coinciden con el t\u00e9rmino de b\u00fasqueda, incluyendo nombre, c\u00f3digo de pa\u00eds y ubicaci\u00f3n. La b\u00fasqueda no distingue may\u00fasculas y coincide con nombres parciales.",
        "parameters": [
          {
            "description": "Nombre de ciudad o nombre parcial a buscar",
            "example": "Madrid",
            "in": "query",
            "name": "search",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/200City"
          },
          "400": {
            "description": "Falta el par\u00e1metro requerido 'search'"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Search cities by name",
        "tags": [
          "poi"
        ]
      }
    },
    "/city/getNear": {
      "get": {
        "description": "Devuelve informaci\u00f3n sobre la ciudad m\u00e1s cercana a las coordenadas proporcionadas, incluyendo nombre, c\u00f3digo de pa\u00eds y ubicaci\u00f3n.",
        "parameters": [
          {
            "description": "Coordenada de latitud del punto para el cual buscar la ciudad m\u00e1s cercana.",
            "example": 40.416775,
            "in": "query",
            "name": "lat",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 90,
              "minimum": -90,
              "type": "number"
            }
          },
          {
            "description": "Coordenada de longitud del punto para el cual buscar la ciudad m\u00e1s cercana.",
            "example": -3.70379,
            "in": "query",
            "name": "lng",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 180,
              "minimum": -180,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/200City"
          },
          "400": {
            "description": "Par\u00e1metros requeridos faltantes o inv\u00e1lidos. `lat` y/o `lng` deben ser proporcionados y ser coordenadas geogr\u00e1ficas v\u00e1lidas."
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Find nearest city to coordinates",
        "tags": [
          "poi"
        ]
      }
    },
    "/costs": {
      "get": {
        "description": "# \ud83d\udee3\ufe0f C\u00e1lculo de Costos de Ruta\n\nCalcula los costos completos de una ruta de transporte, incluyendo:\n- **Peajes**: Costos detallados por peaje individual\n- **Combustible**: Consumo y costo basado en tipo de veh\u00edculo y carga\n- **Mantenimiento**: Desgaste de componentes del veh\u00edculo\n- **Tiempo**: Duraci\u00f3n del viaje con descansos legales incluidos\n- **Emisiones**: C\u00e1lculo de CO2 emitido durante el trayecto\n\n## \ud83c\udfaf Casos de Uso\n\n- **Transportistas**: Presupuestar costos de operaci\u00f3n\n- **Log\u00edstica**: Comparar rutas alternativas\n- **Finanzas**: Predecir costos operativos\n- **Sostenibilidad**: Reportar huella de carbono\n\n## \ud83d\udcdd Ejemplos Pr\u00e1cticos\n\n### Ejemplo B\u00e1sico (Sin Autenticaci\u00f3n)\n```http\nGET /costs?origin={\"lat\":41.3851,\"lng\":2.1734}&destination={\"lat\":40.4168,\"lng\":-3.7038}\n```\n\n### Ejemplo Avanzado (Con Autenticaci\u00f3n)\n```http\nGET /costs?origin={\"lat\":41.3851,\"lng\":2.1734}&destination={\"lat\":40.4168,\"lng\":-3.7038}&with_tolls=true&avg_consumption=35&cargo_weight=15000&fuel_type=diesel\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n```\n\n### Ejemplo con Horarios Espec\u00edficos\n```http\nGET /costs?origin={\"lat\":41.3851,\"lng\":2.1734}&destination={\"lat\":40.4168,\"lng\":-3.7038}&departure=2025-05-15T08:00:00&arrival=2025-05-15T18:00:00\n```\n\n## \u26a0\ufe0f Consideraciones Importantes\n\n- **Formato Coordenadas**: Usar formato JSON con lat/lng en grados decimales\n- **Distancia M\u00e1xima**: 1,000 km entre origen y destino\n- **Cache**: Resultados cacheados 24 horas para rutas id\u00e9nticas\n- **Tiempos Legales**: Se incluyen descansos obligatorios cada 4.5 horas\n- **Moneda**: Todos los costos en EUR (Euros)\n",
        "operationId": "getRouteCosts",
        "parameters": [
          {
            "$ref": "#/components/parameters/AuthorizationHeader"
          },
          {
            "description": "**\ud83d\udccd Punto de Origen**\n\nCoordenadas de inicio de la ruta en formato JSON.\n\n**Formato**: `{\"lat\": n\u00famero, \"lng\": n\u00famero}`\n**Sistema**: WGS84 (grados decimales)\n**Precisi\u00f3n**: 6 decimales recomendados\n\n**Ejemplos**:\n- Barcelona: `{\"lat\":41.3851,\"lng\":2.1734}`\n- Madrid: `{\"lat\":40.4168,\"lng\":-3.7038}`\n- Valencia: `{\"lat\":39.4699,\"lng\":-0.3763}`\n\n**L\u00edmites**:\n- Latitud: -90 a 90\n- Longitud: -180 a 180\n",
            "example": "{\"lat\":41.3851,\"lng\":2.1734}",
            "in": "query",
            "name": "origin",
            "required": true,
            "schema": {
              "pattern": "^\\{\"lat\":-?\\d+(\\.\\d+)?,\"lng\":-?\\d+(\\.\\d+)?\\}$",
              "type": "string"
            }
          },
          {
            "description": "**\ud83c\udfaf Punto de Destino**\n\nCoordenadas de destino de la ruta en formato JSON.\n\nMismos requisitos y formato que el par\u00e1metro `origin`.\n\n**Validaci\u00f3n**: La distancia entre origen y destino no puede exceder 1,000 km.\n",
            "example": "{\"lat\":40.4168,\"lng\":-3.7038}",
            "in": "query",
            "name": "destination",
            "required": true,
            "schema": {
              "pattern": "^\\{\"lat\":-?\\d+(\\.\\d+)?,\"lng\":-?\\d+(\\.\\d+)?\\}$",
              "type": "string"
            }
          },
          {
            "description": "**\ud83d\udd50 Hora de Salida**\n\nFecha y hora de inicio del viaje. Usado para calcular peajes con tarifas variables por horario.\n\n**Formato**: ISO 8601 `YYYY-MM-DDTHH:MM:SS`\n**Zona Horaria**: Se asume la zona horaria del servidor si no se especifica\n**Valor por Defecto**: Hora actual del servidor\n\n**Ejemplos**:\n- Ma\u00f1ana a las 8:00: `2025-05-15T08:00:00`\n- Tarde a las 14:30: `2025-05-15T14:30:00`\n",
            "example": "2025-05-15T08:00:00",
            "in": "query",
            "name": "departure",
            "required": false,
            "schema": {
              "format": "date-time",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$",
              "type": "string"
            }
          },
          {
            "description": "**\ud83d\udd54 Hora de Llegada Estimada**\n\nFecha y hora estimada de llegada. Si se especifica, se usa para c\u00e1lculo de peajes nocturnos.\n\n**Nota**: No puede usarse simult\u00e1neamente con `departure`\n**Comportamiento**: Si se especifica `arrival`, se ignora `departure`\n",
            "example": "2025-05-15T12:30:00",
            "in": "query",
            "name": "arrival",
            "required": false,
            "schema": {
              "format": "date-time",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$",
              "type": "string"
            }
          },
          {
            "description": "**\ud83d\udcb0 Incluir Detalles de Peajes**\n\nControla si se incluye informaci\u00f3n detallada de cada peaje en la respuesta.\n\n- `true`: Incluye array `tolls` con detalles de cada peaje\n- `false`: Solo incluye costo total en `total_tolls`\n\n**Recomendaci\u00f3n**: Usar `true` para an\u00e1lisis detallado, `false` para respuestas m\u00e1s r\u00e1pidas.\n",
            "example": true,
            "in": "query",
            "name": "with_tolls",
            "required": false,
            "schema": {
              "default": true,
              "type": "boolean"
            }
          },
          {
            "description": "**\ud83d\udd04 Incluir Puntos Intermedios**\n\nIncluye puntos de la ruta para visualizaci\u00f3n en mapas.\n\n**Costo**: Aumenta ligeramente el tiempo de respuesta\n**Uso**: Principalmente para interfaces gr\u00e1ficas\n",
            "example": false,
            "in": "query",
            "name": "with_points",
            "required": false,
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "**\u26fd Consumo Medio de Combustible**\n\nConsumo del veh\u00edculo en litros por cada 100 km.\n\n**Valores T\u00edpicos**:\n- Cami\u00f3n ligero: 25-35 L/100km\n- Cami\u00f3n pesado: 35-45 L/100km\n- Tr\u00e1iler: 40-55 L/100km\n\n**Impacto**: Afecta directamente los costos de combustible y mantenimiento\n",
            "example": 35,
            "in": "query",
            "name": "avg_consumption",
            "required": false,
            "schema": {
              "default": 35,
              "format": "float",
              "maximum": 100,
              "minimum": 20,
              "type": "number"
            }
          },
          {
            "description": "**\ud83d\udce6 Peso de la Carga**\n\nPeso total de la mercanc\u00eda transportada en kilogramos.\n\n**Efecto**: Aumenta el consumo de combustible aproximadamente 0.02 L/100km por kg\n**Ejemplos**:\n- Carga ligera: 5,000 kg\n- Carga media: 15,000 kg\n- Carga completa: 25,000 kg\n",
            "example": 15000,
            "in": "query",
            "name": "cargo_weight",
            "required": false,
            "schema": {
              "format": "float",
              "maximum": 50000,
              "minimum": 0,
              "type": "number"
            }
          },
          {
            "description": "**\ud83d\udd25 Tipo de Combustible**\n\nTipo de combustible utilizado por el veh\u00edculo.\n\n**Opciones**:\n- `diesel`: Gas\u00f3leo (predeterminado)\n- `gasoline`: Gasolina\n- `gnv`: Gas Natural Vehicular\n\n**Impacto**: Afecta el c\u00e1lculo de emisiones de CO2 y costos de combustible\n",
            "example": "diesel",
            "in": "query",
            "name": "fuel_type",
            "required": false,
            "schema": {
              "default": "diesel",
              "enum": [
                "diesel",
                "gasoline",
                "gnv"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RouteCostResponse"
                }
              }
            },
            "description": "C\u00e1lculo exitoso de costos de peaje"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No autorizado (token inv\u00e1lido o faltante)"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error interno del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "\ud83d\udcca Calcular costos completos de ruta",
        "tags": [
          "tolls"
        ]
      }
    },
    "/current": {
      "get": {
        "description": "Obtener las condiciones meteorol\u00f3gicas en tiempo real para su ubicaci\u00f3n exacta.",
        "operationId": "getCurrent",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Condiciones meteorol\u00f3gicas actuales recuperadas exitosamente."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get current weather",
        "tags": [
          "weather"
        ]
      }
    },
    "/discounts": {
      "get": {
        "description": "Recupera una lista paginada de descuentos con opciones de filtrado por estado, estaci\u00f3n o usuario.\n",
        "operationId": "getAllDiscounts",
        "parameters": [
          {
            "description": "N\u00famero de p\u00e1gina para paginaci\u00f3n",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "example": 1,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "L\u00edmite de resultados por p\u00e1gina",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 10,
              "example": 10,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Filtrar por estado activo/inactivo",
            "in": "query",
            "name": "isActive",
            "required": false,
            "schema": {
              "example": true,
              "type": "boolean"
            }
          },
          {
            "description": "Filtrar por identificador de estaci\u00f3n",
            "in": "query",
            "name": "station",
            "required": false,
            "schema": {
              "example": "5f8d3b7b3f6b8a1b9c3b7b3f",
              "type": "string"
            }
          },
          {
            "description": "Filtrar por identificador de usuario",
            "in": "query",
            "name": "userId",
            "required": false,
            "schema": {
              "example": "user123",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "centsValue": 0,
                      "createdAt": "2025-04-29T10:30:00Z",
                      "id_discount": "123456789",
                      "isActive": true,
                      "percentValue": 10.0,
                      "selected": false,
                      "station": "5f8d3b7b3f6b8a1b9c3b7b3f",
                      "type": "percentage",
                      "updatedAt": "2025-04-29T10:30:00Z",
                      "userId": "user123"
                    }
                  ],
                  "pagination": {
                    "limit": 10,
                    "page": 1,
                    "pages": 5,
                    "total": 50
                  },
                  "success": true
                },
                "schema": {
                  "$ref": "#/components/schemas/DiscountResponse"
                }
              }
            },
            "description": "Lista de descuentos recuperada exitosamente"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Error fetching discounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al obtener los descuentos"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Obtener todos los descuentos",
        "tags": [
          "stations"
        ]
      },
      "post": {
        "description": "Crea un nuevo descuento asociado a una estaci\u00f3n de servicio. Los descuentos pueden ser de tipo porcentaje o c\u00e9ntimos y pueden estar activos o inactivos.\n",
        "operationId": "createDiscount",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "centsValue": {
                    "description": "Valor del descuento en c\u00e9ntimos",
                    "example": 0.15,
                    "format": "float",
                    "minimum": 0,
                    "type": "number"
                  },
                  "id": {
                    "description": "Identificador \u00fanico del descuento (opcional, se genera autom\u00e1ticamente)",
                    "example": "123456789",
                    "type": "string"
                  },
                  "isActive": {
                    "description": "Indica si el descuento est\u00e1 activo",
                    "example": true,
                    "type": "boolean"
                  },
                  "percentValue": {
                    "description": "Valor del descuento en porcentaje",
                    "example": 10.0,
                    "format": "float",
                    "maximum": 100,
                    "minimum": 0,
                    "type": "number"
                  },
                  "selected": {
                    "description": "Indica si el descuento est\u00e1 seleccionado",
                    "example": false,
                    "type": "boolean"
                  },
                  "station": {
                    "description": "Identificador de la estaci\u00f3n asociada al descuento",
                    "example": "5f8d3b7b3f6b8a1b9c3b7b3f",
                    "type": "string"
                  },
                  "type": {
                    "description": "Tipo de descuento",
                    "example": "percentage",
                    "type": "string"
                  },
                  "userId": {
                    "description": "Identificador del usuario propietario del descuento",
                    "example": "user123",
                    "type": "string"
                  }
                },
                "required": [
                  "station",
                  "type"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "centsValue": 0,
                    "createdAt": "2025-04-29T10:30:00Z",
                    "id_discount": "123456789",
                    "isActive": true,
                    "percentValue": 10.0,
                    "selected": false,
                    "station": "5f8d3b7b3f6b8a1b9c3b7b3f",
                    "type": "percentage",
                    "updatedAt": "2025-04-29T10:30:00Z",
                    "userId": "user123"
                  },
                  "message": "Discount created successfully",
                  "success": true
                },
                "schema": {
                  "$ref": "#/components/schemas/DiscountResponse"
                }
              }
            },
            "description": "Descuento creado exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Invalid discount data"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Datos de descuento inv\u00e1lidos"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Error creating discount"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al crear el descuento"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Crear un nuevo descuento",
        "tags": [
          "stations"
        ]
      }
    },
    "/discounts/sync": {
      "post": {
        "description": "Sincroniza m\u00faltiples descuentos desde el almacenamiento local del cliente al servidor. \u00datil para mantener la consistencia entre dispositivos.\n",
        "operationId": "syncDiscountsFromLocalStorage",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "discounts": [
                  {
                    "centsValue": 0,
                    "id": "123456789",
                    "isActive": true,
                    "percentValue": 10.0,
                    "selected": false,
                    "station": "5f8d3b7b3f6b8a1b9c3b7b3f",
                    "type": "percentage"
                  }
                ],
                "userId": "user123"
              },
              "schema": {
                "$ref": "#/components/schemas/SyncDiscountRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "centsValue": 0,
                      "createdAt": "2025-04-29T10:30:00Z",
                      "id_discount": "123456789",
                      "isActive": true,
                      "percentValue": 10.0,
                      "selected": false,
                      "station": "5f8d3b7b3f6b8a1b9c3b7b3f",
                      "type": "percentage",
                      "updatedAt": "2025-04-29T10:30:00Z",
                      "userId": "user123"
                    }
                  ],
                  "message": "Discounts synced successfully",
                  "success": true,
                  "synced": 1
                },
                "schema": {
                  "$ref": "#/components/schemas/DiscountResponse"
                }
              }
            },
            "description": "Descuentos sincronizados exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Discounts must be an array"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Datos de sincronizaci\u00f3n inv\u00e1lidos"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Error syncing discounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al sincronizar descuentos"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Sincronizar descuentos desde almacenamiento local",
        "tags": [
          "stations"
        ]
      }
    },
    "/discounts/{id}": {
      "delete": {
        "description": "Elimina permanentemente un descuento identificado por su ID.\n",
        "operationId": "deleteDiscount",
        "parameters": [
          {
            "description": "Identificador \u00fanico del descuento",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "example": "123456789",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "centsValue": 0,
                    "createdAt": "2025-04-29T10:30:00Z",
                    "id_discount": "123456789",
                    "isActive": true,
                    "percentValue": 10.0,
                    "selected": false,
                    "station": "5f8d3b7b3f6b8a1b9c3b7b3f",
                    "type": "percentage",
                    "updatedAt": "2025-04-29T10:30:00Z",
                    "userId": "user123"
                  },
                  "message": "Discount deleted successfully",
                  "success": true
                },
                "schema": {
                  "$ref": "#/components/schemas/DiscountResponse"
                }
              }
            },
            "description": "Descuento eliminado exitosamente"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Discount not found"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Descuento no encontrado"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Error deleting discount"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al eliminar el descuento"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Eliminar descuento",
        "tags": [
          "stations"
        ]
      },
      "get": {
        "description": "Recupera un descuento espec\u00edfico por su identificador \u00fanico.\n",
        "operationId": "getDiscountById",
        "parameters": [
          {
            "description": "Identificador \u00fanico del descuento",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "example": "123456789",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "centsValue": 0,
                    "createdAt": "2025-04-29T10:30:00Z",
                    "id_discount": "123456789",
                    "isActive": true,
                    "percentValue": 10.0,
                    "selected": false,
                    "station": "5f8d3b7b3f6b8a1b9c3b7b3f",
                    "type": "percentage",
                    "updatedAt": "2025-04-29T10:30:00Z",
                    "userId": "user123"
                  },
                  "success": true
                },
                "schema": {
                  "$ref": "#/components/schemas/DiscountResponse"
                }
              }
            },
            "description": "Descuento encontrado"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Discount not found"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Descuento no encontrado"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Error fetching discount"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al obtener el descuento"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Obtener descuento por ID",
        "tags": [
          "stations"
        ]
      },
      "put": {
        "description": "Actualiza un descuento existente identificado por su ID.\n",
        "operationId": "updateDiscount",
        "parameters": [
          {
            "description": "Identificador \u00fanico del descuento",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "example": "123456789",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "centsValue": {
                    "description": "Valor del descuento en c\u00e9ntimos",
                    "example": 0.15,
                    "format": "float",
                    "minimum": 0,
                    "type": "number"
                  },
                  "isActive": {
                    "description": "Indica si el descuento est\u00e1 activo",
                    "example": true,
                    "type": "boolean"
                  },
                  "percentValue": {
                    "description": "Valor del descuento en porcentaje",
                    "example": 10.0,
                    "format": "float",
                    "maximum": 100,
                    "minimum": 0,
                    "type": "number"
                  },
                  "selected": {
                    "description": "Indica si el descuento est\u00e1 seleccionado",
                    "example": false,
                    "type": "boolean"
                  },
                  "station": {
                    "description": "Identificador de la estaci\u00f3n asociada al descuento",
                    "example": "5f8d3b7b3f6b8a1b9c3b7b3f",
                    "type": "string"
                  },
                  "type": {
                    "description": "Tipo de descuento",
                    "example": "percentage",
                    "type": "string"
                  },
                  "userId": {
                    "description": "Identificador del usuario propietario del descuento",
                    "example": "user123",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "centsValue": 0,
                    "createdAt": "2025-04-29T10:30:00Z",
                    "id_discount": "123456789",
                    "isActive": true,
                    "percentValue": 10.0,
                    "selected": false,
                    "station": "5f8d3b7b3f6b8a1b9c3b7b3f",
                    "type": "percentage",
                    "updatedAt": "2025-04-29T10:30:00Z",
                    "userId": "user123"
                  },
                  "message": "Discount updated successfully",
                  "success": true
                },
                "schema": {
                  "$ref": "#/components/schemas/DiscountResponse"
                }
              }
            },
            "description": "Descuento actualizado exitosamente"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Discount not found"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Descuento no encontrado"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Error updating discount"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al actualizar el descuento"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Actualizar descuento existente",
        "tags": [
          "stations"
        ]
      }
    },
    "/drivers": {
      "get": {
        "description": "**Obtiene una lista paginada de todos los conductores** asociados al usuario autenticado.\n\n## \ud83d\udccb Funcionalidades\n- **Paginaci\u00f3n**: Soporta par\u00e1metros `page` y `pageSize`\n- **Ordenamiento**: Por `name`, `createdAt`, `updatedAt` (por defecto: m\u00e1s recientes primero)\n- **Filtros**: Por nombre, estado habilitado, etc.\n- **Aislamiento**: Solo muestra conductores del usuario propietario\n\n## \ud83d\udd0d Par\u00e1metros de consulta\n- `page`: N\u00famero de p\u00e1gina (por defecto: 1)\n- `pageSize`: Resultados por p\u00e1gina (m\u00e1ximo: 100)\n- `sort`: Orden (`asc`/`desc`)\n- `lang`: Idioma de respuesta (`es` por defecto)\n\n## \ud83d\udcca Respuesta\nLista de objetos `Driver` con informaci\u00f3n completa incluyendo posici\u00f3n actual, estado de horas y historial reciente.\n",
        "operationId": "getDrivers",
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/Sort"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "data": [
                      {
                        "_id": "5f8d3b7b3f6b8a1b9c3b7b3f",
                        "currentPosition": {
                          "coordinates": [
                            -3.7038,
                            40.4168
                          ],
                          "timestamp": "2025-04-30T10:30:00.000Z"
                        },
                        "enabled": true,
                        "hoursStatus": {
                          "remainingDayHours": 3.5,
                          "remainingWeeklyHours": 12.5
                        },
                        "licenseNumber": "12345678X",
                        "name": "Juan P\u00e9rez Garc\u00eda",
                        "owner": "63d7907cbe76403b35da63df"
                      },
                      {
                        "_id": "5f8d3b7b3f6b8a1b9c3b7b4f",
                        "currentPosition": {
                          "coordinates": [
                            -3.71,
                            40.42
                          ],
                          "timestamp": "2025-04-30T09:15:00.000Z"
                        },
                        "enabled": true,
                        "hoursStatus": {
                          "remainingDayHours": 2.0,
                          "remainingWeeklyHours": 8.0
                        },
                        "licenseNumber": "87654321Y",
                        "name": "Mar\u00eda L\u00f3pez Ruiz",
                        "owner": "63d7907cbe76403b35da63df"
                      }
                    ],
                    "pagination": {
                      "page": 1,
                      "pageSize": 10,
                      "total": 2,
                      "totalPages": 1
                    }
                  },
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/Driver"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "properties": {
                        "page": {
                          "example": 1,
                          "type": "integer"
                        },
                        "pageSize": {
                          "example": 10,
                          "type": "integer"
                        },
                        "total": {
                          "example": 150,
                          "type": "integer"
                        },
                        "totalPages": {
                          "example": 15,
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Lista de conductores obtenida exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Listar conductores",
        "tags": [
          "drivers"
        ]
      },
      "post": {
        "description": "**Crea un nuevo conductor** en el sistema.\n\n## \ud83d\udcdd Requisitos\n- Nombre completo (3-100 caracteres)\n- N\u00famero de licencia v\u00e1lido (\u00fanico en el sistema)\n- El conductor se asocia autom\u00e1ticamente al usuario autenticado\n\n## \u2705 Validaciones\n- N\u00famero de licencia debe ser \u00fanico\n- Formato de licencia v\u00e1lido seg\u00fan pa\u00eds\n- M\u00e1ximo 1000 conductores por usuario\n\n## \ud83d\udcca Respuesta\nObjeto `Driver` completo con ID generado y campos por defecto.\n",
        "operationId": "createDriver",
        "parameters": [
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "licenseNumber": "12345678X",
                "name": "Juan P\u00e9rez Garc\u00eda"
              },
              "schema": {
                "properties": {
                  "licenseNumber": {
                    "example": "12345678X",
                    "maxLength": 20,
                    "minLength": 8,
                    "type": "string"
                  },
                  "name": {
                    "example": "Juan P\u00e9rez Garc\u00eda",
                    "maxLength": 100,
                    "minLength": 3,
                    "type": "string"
                  }
                },
                "required": [
                  "name",
                  "licenseNumber"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "example": {
                  "_id": "5f8d3b7b3f6b8a1b9c3b7b3f",
                  "currentPosition": null,
                  "drivingHistory": [],
                  "enabled": true,
                  "hoursStatus": {
                    "lastDrivingStart": null,
                    "lastRestStart": null,
                    "remainingBiweeklyHours": 90.0,
                    "remainingDayHours": 9.0,
                    "remainingWeeklyHours": 48.0
                  },
                  "licenseNumber": "12345678X",
                  "name": "Juan P\u00e9rez Garc\u00eda",
                  "owner": "63d7907cbe76403b35da63df",
                  "positionHistory": []
                },
                "schema": {
                  "$ref": "#/components/schemas/Driver"
                }
              }
            },
            "description": "Conductor creado exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "content": {
              "application/json": {
                "example": {
                  "error": "El n\u00famero de licencia ya est\u00e1 registrado"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Conflicto - N\u00famero de licencia ya existe"
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "L\u00edmite de consultas de posici\u00f3n excedido (10 por segundo)"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "L\u00edmite de consultas excedido"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Crear conductor",
        "tags": [
          "drivers"
        ]
      }
    },
    "/drivers/{id}": {
      "delete": {
        "description": "**Elimina permanentemente un conductor del sistema**.\n\n## \u26a0\ufe0f Operaci\u00f3n destructiva\n- **No se puede deshacer**\n- Elimina todos los datos asociados (posiciones, horas, historial)\n\n## \ud83d\udd12 Restricciones\n- Solo el propietario puede eliminar\n- Conductor debe existir\n- No elimina datos de registros hist\u00f3ricos de tac\u00f3grafo\n\n## \ud83d\udccb Consecuencias\n- Se pierden todas las posiciones hist\u00f3ricas\n- Se pierden todos los registros de horas\n- El conductor desaparece de todas las b\u00fasquedas\n",
        "operationId": "deleteDriver",
        "responses": {
          "204": {
            "description": "Conductor eliminado exitosamente (sin contenido)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Conductor no encontrado o sin permisos de acceso"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Conductor no encontrado"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Eliminar conductor",
        "tags": [
          "drivers"
        ]
      },
      "get": {
        "description": "**Obtiene la informaci\u00f3n completa de un conductor espec\u00edfico**.\n\n## \ud83d\udd0d Detalles incluidos\n- Informaci\u00f3n personal y de licencia\n- Posici\u00f3n actual y estado de horas\n- Historial de posiciones (\u00faltimos 30 d\u00edas)\n- Historial de conducci\u00f3n (\u00faltimos 6 meses)\n\n## \ud83d\udd12 Permisos\n- Solo el propietario del conductor puede acceder\n- Validaci\u00f3n autom\u00e1tica por campo `owner`\n",
        "operationId": "getDriverById",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Driver"
                }
              }
            },
            "description": "Informaci\u00f3n del conductor obtenida exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Conductor no encontrado o sin permisos de acceso"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Conductor no encontrado"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Obtener conductor por ID",
        "tags": [
          "drivers"
        ]
      },
      "put": {
        "description": "**Actualiza la informaci\u00f3n de un conductor existente**.\n\n## \ud83d\udcdd Campos actualizables\n- `name`: Nombre completo\n- `enabled`: Estado de habilitaci\u00f3n\n- `licenseNumber`: **No se puede cambiar** (requiere eliminaci\u00f3n y recreaci\u00f3n)\n\n## \u2705 Validaciones\n- El conductor debe existir y pertenecer al usuario\n- Nombre debe tener 3-100 caracteres\n- Solo campos permitidos pueden actualizarse\n\n## \u26a0\ufe0f Consideraciones\n- Cambiar `enabled` a `false` deshabilita el conductor para nuevas rutas\n- No afecta datos hist\u00f3ricos existentes\n",
        "operationId": "updateDriver",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "enabled": true,
                "name": "Juan P\u00e9rez Garc\u00eda"
              },
              "schema": {
                "properties": {
                  "enabled": {
                    "example": true,
                    "type": "boolean"
                  },
                  "name": {
                    "example": "Juan P\u00e9rez Garc\u00eda",
                    "maxLength": 100,
                    "minLength": 3,
                    "type": "string"
                  }
                },
                "required": [
                  "name",
                  "enabled"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Driver"
                }
              }
            },
            "description": "Conductor actualizado exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Conductor no encontrado o sin permisos de acceso"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Conductor no encontrado"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Actualizar conductor",
        "tags": [
          "drivers"
        ]
      }
    },
    "/drivers/{id}/daily-limits": {
      "get": {
        "description": "**Obtiene el estado actual de las horas de conducci\u00f3n del conductor**.\n\n## \u23f1\ufe0f Informaci\u00f3n proporcionada\n- Horas restantes disponibles hoy\n- Horas restantes esta semana (lunes-domingo)\n- Horas restantes en el periodo bimensual\n- Fecha del \u00faltimo descanso obligatorio\n- Fecha del \u00faltimo inicio de conducci\u00f3n\n\n## \ud83d\udcca C\u00e1lculos autom\u00e1ticos\n- Basados en registros de actividad de conducci\u00f3n\n- Cumplimiento normativo UE (Reglamento 561/2006)\n- Actualizaci\u00f3n en tiempo real con cada registro de actividad\n\n## \u26a0\ufe0f Alertas y l\u00edmites\n- **Alerta**: Cuando quedan < 4 horas semanales\n- **M\u00e1ximo diario**: 9 horas (10 horas m\u00e1ximo 2 veces por semana)\n- **M\u00e1ximo semanal**: 48 horas (56 horas m\u00e1ximo 2 veces cada 4 semanas)\n- **M\u00e1ximo bimensual**: 90 horas\n\n## \ud83d\udd04 Actualizaci\u00f3n\n- Se recalcula autom\u00e1ticamente con cada registro de actividad\n- Incluye pausas obligatorias de 45 minutos cada 4.5 horas\n- Descansos diarios m\u00ednimos de 11 horas\n",
        "operationId": "getDriverDailyLimits",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "compliance": {
                      "isCompliant": true,
                      "warnings": []
                    },
                    "lastDrivingStart": "2025-04-30T08:00:00.000Z",
                    "lastRestStart": "2025-04-30T21:00:00.000Z",
                    "remainingBiweeklyHours": 45.0,
                    "remainingDayHours": 3.5,
                    "remainingWeeklyHours": 12.5
                  },
                  "properties": {
                    "compliance": {
                      "properties": {
                        "isCompliant": {
                          "description": "Cumple normativa actual",
                          "example": true,
                          "type": "boolean"
                        },
                        "warnings": {
                          "description": "Alertas de cumplimiento",
                          "example": [
                            "Pr\u00f3ximo descanso obligatorio en 2 horas"
                          ],
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        }
                      },
                      "type": "object"
                    },
                    "lastDrivingStart": {
                      "description": "\u00daltimo inicio de conducci\u00f3n",
                      "example": "2025-04-30T08:00:00.000Z",
                      "format": "date-time",
                      "type": "string"
                    },
                    "lastRestStart": {
                      "description": "\u00daltimo inicio de descanso",
                      "example": "2025-04-30T21:00:00.000Z",
                      "format": "date-time",
                      "type": "string"
                    },
                    "remainingBiweeklyHours": {
                      "description": "Horas restantes en periodo bimensual",
                      "example": 45.0,
                      "type": "number"
                    },
                    "remainingDayHours": {
                      "description": "Horas restantes hoy",
                      "example": 3.5,
                      "type": "number"
                    },
                    "remainingWeeklyHours": {
                      "description": "Horas restantes esta semana",
                      "example": 12.5,
                      "type": "number"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "L\u00edmites diarios obtenidos exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Conductor no encontrado o sin permisos de acceso"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Conductor no encontrado"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Obtener l\u00edmites diarios de horas del conductor",
        "tags": [
          "drivers"
        ]
      }
    },
    "/drivers/{id}/hours-history": {
      "post": {
        "description": "**Registra una nueva actividad de conducci\u00f3n o descanso para el conductor**.\n\n## \ud83d\udcdd Tipos de actividad\n- **`driving`**: Periodo de conducci\u00f3n activa\n- **`rest`**: Periodo de descanso obligatorio\n\n## \u23f1\ufe0f Requisitos de registro\n- **Timestamp de inicio**: Fecha y hora de inicio de la actividad\n- **Timestamp de fin**: Opcional, si la actividad contin\u00faa\n- **Coordenadas**: Opcional para actividades de conducci\u00f3n (m\u00e1ximo 50 puntos)\n- **Tipo de actividad**: `driving` o `rest`\n\n## \u2705 Validaciones\n- No superposici\u00f3n con actividades existentes\n- Duraci\u00f3n m\u00e1xima de conducci\u00f3n continua: 4.5 horas\n- Descanso m\u00ednimo diario: 11 horas\n- Coordenadas v\u00e1lidas si proporcionadas\n\n## \ud83d\udd04 Efectos autom\u00e1ticos\n- Recalcula l\u00edmites de horas disponibles\n- Actualiza estado de cumplimiento normativo\n- Genera alertas si se aproximan l\u00edmites\n- Registra en historial permanente\n",
        "operationId": "addDriverHoursHistory",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "coordinates": [
                  [
                    -3.7038,
                    40.4168
                  ],
                  [
                    -3.71,
                    40.42
                  ],
                  [
                    -3.715,
                    40.425
                  ]
                ],
                "endTime": "2025-04-30T12:30:00.000Z",
                "startTime": "2025-04-30T08:00:00.000Z",
                "type": "driving"
              },
              "schema": {
                "$ref": "#/components/schemas/DriverActivity"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "activity": {
                      "coordinates": [
                        [
                          -3.7038,
                          40.4168
                        ],
                        [
                          -3.71,
                          40.42
                        ]
                      ],
                      "endTime": "2025-04-30T12:30:00.000Z",
                      "startTime": "2025-04-30T08:00:00.000Z",
                      "type": "driving"
                    },
                    "message": "Actividad de conducci\u00f3n registrada correctamente",
                    "success": true,
                    "updatedLimits": {
                      "compliance": {
                        "isCompliant": true,
                        "warnings": []
                      },
                      "remainingDayHours": 2.5,
                      "remainingWeeklyHours": 8.0
                    }
                  },
                  "properties": {
                    "activity": {
                      "$ref": "#/components/schemas/DriverActivity"
                    },
                    "message": {
                      "example": "Actividad de conducci\u00f3n registrada correctamente",
                      "type": "string"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    },
                    "updatedLimits": {
                      "properties": {
                        "compliance": {
                          "properties": {
                            "isCompliant": {
                              "example": true,
                              "type": "boolean"
                            },
                            "warnings": {
                              "example": [],
                              "items": {
                                "type": "string"
                              },
                              "type": "array"
                            }
                          },
                          "type": "object"
                        },
                        "remainingDayHours": {
                          "example": 2.5,
                          "type": "number"
                        },
                        "remainingWeeklyHours": {
                          "example": 8.0,
                          "type": "number"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Actividad registrada exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Conductor no encontrado o sin permisos de acceso"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Conductor no encontrado"
          },
          "409": {
            "content": {
              "application/json": {
                "example": {
                  "error": "La actividad se superpone con registros existentes"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Conflicto de horarios"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Registrar actividad de horas del conductor",
        "tags": [
          "drivers"
        ]
      }
    },
    "/drivers/{id}/position": {
      "get": {
        "description": "**Obtiene la posici\u00f3n GPS m\u00e1s reciente del conductor**.\n\n## \ud83d\udccd Informaci\u00f3n proporcionada\n- Coordenadas actuales (longitud, latitud)\n- Timestamp exacto de la \u00faltima actualizaci\u00f3n\n- Precisi\u00f3n GPS (cuando disponible)\n\n## \u23f0 Actualizaci\u00f3n\n- Las posiciones se actualizan autom\u00e1ticamente cada 5 minutos\n- Si no hay posici\u00f3n reciente (>30 min), se devuelve `null`\n\n## \ud83d\udcca Uso t\u00edpico\n- Mostrar ubicaci\u00f3n en mapas en tiempo real\n- Calcular tiempos estimados de llegada\n- Verificar si el conductor est\u00e1 en ruta activa\n",
        "operationId": "getDriverPosition",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "accuracy": 10,
                  "coordinates": [
                    -3.7038,
                    40.4168
                  ],
                  "timestamp": "2025-04-30T10:30:00.000Z"
                },
                "schema": {
                  "nullable": true,
                  "properties": {
                    "accuracy": {
                      "description": "Precisi\u00f3n GPS en metros (opcional)",
                      "example": 10,
                      "type": "number"
                    },
                    "coordinates": {
                      "description": "[longitud, latitud]",
                      "example": [
                        -3.7038,
                        40.4168
                      ],
                      "items": {
                        "format": "float",
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "timestamp": {
                      "description": "Fecha y hora de la posici\u00f3n",
                      "example": "2025-04-30T10:30:00.000Z",
                      "format": "date-time",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Posici\u00f3n obtenida exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Conductor no encontrado o sin permisos de acceso"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Conductor no encontrado"
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "L\u00edmite de consultas de posici\u00f3n excedido (10 por segundo)"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "L\u00edmite de consultas excedido"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Obtener posici\u00f3n actual del conductor",
        "tags": [
          "drivers"
        ]
      },
      "put": {
        "description": "**Actualiza la posici\u00f3n GPS actual del conductor**.\n\n## \ud83d\udccd Requisitos\n- Coordenadas v\u00e1lidas (latitud -90/+90, longitud -180/+180)\n- Timestamp no futuro (m\u00e1ximo 5 minutos de diferencia)\n- Precisi\u00f3n m\u00ednima: 6 decimales (~11cm)\n\n## \u23f1\ufe0f Limitaciones\n- **M\u00e1ximo 1 actualizaci\u00f3n por minuto** por conductor\n- Las actualizaciones se almacenan autom\u00e1ticamente en el historial\n- Posiciones antiguas (>30 d\u00edas) se purgan autom\u00e1ticamente\n\n## \ud83d\udd04 Efectos\n- Actualiza `currentPosition` del conductor\n- Agrega entrada al `positionHistory`\n- Recalcula estado de horas si est\u00e1 en actividad de conducci\u00f3n\n",
        "operationId": "updateDriverPosition",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "coordinates": [
                  -3.7038,
                  40.4168
                ],
                "timestamp": "2025-04-30T10:30:00.000Z"
              },
              "schema": {
                "$ref": "#/components/schemas/PositionUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "example": "Posici\u00f3n actualizada correctamente",
                      "type": "string"
                    },
                    "position": {
                      "$ref": "#/components/schemas/PositionUpdate"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Posici\u00f3n actualizada exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Conductor no encontrado o sin permisos de acceso"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Conductor no encontrado"
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "L\u00edmite de actualizaciones de posici\u00f3n excedido (1 por minuto)"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "L\u00edmite de actualizaciones excedido"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Actualizar posici\u00f3n del conductor",
        "tags": [
          "drivers"
        ]
      }
    },
    "/drivers/{id}/positions": {
      "get": {
        "description": "**Obtiene el historial completo de posiciones GPS del conductor**.\n\n## \ud83d\udcca Informaci\u00f3n incluida\n- Todas las posiciones registradas en el rango de fechas\n- Coordenadas y timestamps exactos\n- Informaci\u00f3n de precisi\u00f3n GPS (cuando disponible)\n\n## \ud83d\udcc5 Rangos de fecha\n- **Por defecto**: \u00daltimas 24 horas\n- **M\u00e1ximo**: 30 d\u00edas por consulta\n- **Formato**: ISO 8601 (`YYYY-MM-DD` o `YYYY-MM-DDTHH:mm:ssZ`)\n\n## \ud83d\udcc8 Paginaci\u00f3n\n- M\u00e1ximo 1000 posiciones por p\u00e1gina\n- Ordenadas por timestamp descendente (m\u00e1s recientes primero)\n- Total disponible en metadatos de paginaci\u00f3n\n\n## \ud83d\udcbe Retenci\u00f3n\n- Las posiciones se retienen por **30 d\u00edas**\n- Posiciones m\u00e1s antiguas se eliminan autom\u00e1ticamente\n- Datos hist\u00f3ricos se usan para auditor\u00eda y reconstrucci\u00f3n de rutas\n",
        "operationId": "getDriverPositions",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "data": [
                      {
                        "accuracy": 10,
                        "coordinates": [
                          -3.7038,
                          40.4168
                        ],
                        "timestamp": "2025-04-30T10:30:00.000Z"
                      },
                      {
                        "accuracy": 8,
                        "coordinates": [
                          -3.71,
                          40.42
                        ],
                        "timestamp": "2025-04-30T10:25:00.000Z"
                      },
                      {
                        "accuracy": 12,
                        "coordinates": [
                          -3.715,
                          40.425
                        ],
                        "timestamp": "2025-04-30T10:20:00.000Z"
                      }
                    ],
                    "pagination": {
                      "page": 1,
                      "pageSize": 100,
                      "total": 1250,
                      "totalPages": 13
                    }
                  },
                  "properties": {
                    "data": {
                      "items": {
                        "properties": {
                          "accuracy": {
                            "description": "Precisi\u00f3n GPS en metros",
                            "example": 10,
                            "type": "number"
                          },
                          "coordinates": {
                            "example": [
                              -3.7038,
                              40.4168
                            ],
                            "items": {
                              "format": "float",
                              "type": "number"
                            },
                            "maxItems": 2,
                            "minItems": 2,
                            "type": "array"
                          },
                          "timestamp": {
                            "example": "2025-04-30T10:30:00.000Z",
                            "format": "date-time",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "properties": {
                        "page": {
                          "example": 1,
                          "type": "integer"
                        },
                        "pageSize": {
                          "example": 100,
                          "type": "integer"
                        },
                        "total": {
                          "example": 1250,
                          "type": "integer"
                        },
                        "totalPages": {
                          "example": 13,
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Historial de posiciones obtenido exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Conductor no encontrado o sin permisos de acceso"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Conductor no encontrado"
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "L\u00edmite de consultas de posici\u00f3n excedido (10 por segundo)"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "L\u00edmite de consultas excedido"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Obtener historial de posiciones del conductor",
        "tags": [
          "drivers"
        ]
      }
    },
    "/events": {
      "get": {
        "description": "Obtiene eventos de tr\u00e1fico en tiempo real cerca de una ubicaci\u00f3n, con opciones de filtrado avanzado.\n\n**Ejemplo de uso completo:**\n```\nGET /events?lat=40.4168&lng=-3.7038&radius=10000&types[]=ACCIDENT&types[]=ROAD_CLOSURE&minSeverity=30&vehicleTypes[]=TRUCK&lang=es\n```\n\n**Filtros disponibles:**\n- `types`: Tipos de eventos a incluir\n- `minSeverity`: Severidad m\u00ednima (0-100)\n- `vehicleTypes`: Tipos de veh\u00edculos afectados\n- `lang`: Idioma de la respuesta\n\n**Ejemplo pr\u00e1ctico:**\nUna empresa de log\u00edstica filtra solo eventos que afecten a camiones con severidad alta.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Radius"
          },
          {
            "description": "Tipos de eventos a incluir en la b\u00fasqueda",
            "in": "query",
            "name": "types",
            "schema": {
              "items": {
                "enum": [
                  "ACCIDENT",
                  "ROAD_CLOSURE",
                  "SPECIAL_EVENT"
                ],
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "description": "Severidad m\u00ednima del evento (0-100)",
            "in": "query",
            "name": "minSeverity",
            "schema": {
              "maximum": 100,
              "minimum": 0,
              "type": "number"
            }
          },
          {
            "description": "Tipos de veh\u00edculos afectados por el evento",
            "in": "query",
            "name": "vehicleTypes",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "eventos_actuales": {
                    "summary": "Eventos actuales encontrados",
                    "value": [
                      {
                        "affectedVehicleTypes": [
                          "CAR",
                          "BUS"
                        ],
                        "createdAt": "2024-01-15T16:00:00Z",
                        "expectedDuration": 240,
                        "id": "evt_11111",
                        "location": {
                          "lat": 40.4168,
                          "lon": -3.7038
                        },
                        "severity": 60,
                        "type": "SPECIAL_EVENT",
                        "updatedAt": "2024-01-15T16:00:00Z"
                      }
                    ]
                  }
                },
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/TrafficEvent"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de eventos de tr\u00e1fico actuales"
          },
          "400": {
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "401": {
            "description": "No autorizado"
          },
          "500": {
            "description": "Error interno del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Consultar eventos de tr\u00e1fico actuales",
        "tags": [
          "traffic"
        ]
      }
    },
    "/events/all": {
      "get": {
        "description": "Recupera todos los eventos de tr\u00e1fico disponibles sin filtros de ubicaci\u00f3n.\n\n**Advertencia:** Este endpoint puede devolver grandes vol\u00famenes de datos.\n\n**Ejemplo de uso:**\n```\nGET /events/all\n```\n\n**Casos de uso:**\n- Dashboards de monitorizaci\u00f3n general\n- An\u00e1lisis global del estado del tr\u00e1fico\n- Sistemas que necesitan datos completos sin filtros\n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "todos_eventos": {
                    "summary": "Todos los eventos disponibles",
                    "value": [
                      {
                        "affectedVehicleTypes": [
                          "TRUCK",
                          "CAR"
                        ],
                        "createdAt": "2024-01-15T17:30:00Z",
                        "expectedDuration": 90,
                        "id": "evt_22222",
                        "location": {
                          "lat": 39.4699,
                          "lon": -0.3763
                        },
                        "severity": 85,
                        "type": "ACCIDENT",
                        "updatedAt": "2024-01-15T17:30:00Z"
                      },
                      {
                        "affectedVehicleTypes": [
                          "ALL"
                        ],
                        "createdAt": "2024-01-15T18:00:00Z",
                        "expectedDuration": 360,
                        "id": "evt_33333",
                        "location": {
                          "lat": 41.3851,
                          "lon": 2.1734
                        },
                        "severity": 95,
                        "type": "ROAD_CLOSURE",
                        "updatedAt": "2024-01-15T18:00:00Z"
                      }
                    ]
                  }
                },
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/TrafficEvent"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista completa de eventos de tr\u00e1fico"
          },
          "401": {
            "description": "No autorizado"
          },
          "500": {
            "description": "Error interno del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Consultar todos los eventos de tr\u00e1fico",
        "tags": [
          "traffic"
        ]
      }
    },
    "/events/getAlongPath": {
      "post": {
        "description": "Identifica eventos de tr\u00e1fico a lo largo de una ruta espec\u00edfica definida por puntos GPS.\n\n**Ejemplo de uso:**\n```json\nPOST /events/getAlongPath\n{\n  \"points\": [\n    [-3.7038, 40.4168],  // Madrid\n    [-2.1734, 41.3851],  // Barcelona\n    [-0.3763, 39.4699]   // Valencia\n  ],\n  \"lang\": \"es\"\n}\n```\n\n**Caracter\u00edsticas:**\n- Optimizado para rutas largas\n- Filtra puntos cada 15km para eficiencia\n- Elimina duplicados autom\u00e1ticamente\n\n**Uso en navegaci\u00f3n:**\nIdeal para aplicaciones de navegaci\u00f3n que necesitan alertas de tr\u00e1fico en ruta.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "lang": {
                    "default": "es",
                    "description": "Idioma para los datos de respuesta",
                    "enum": [
                      "es",
                      "en",
                      "fr",
                      "de",
                      "pt"
                    ],
                    "type": "string"
                  },
                  "points": {
                    "description": "Array de pares de coordenadas [longitud, latitud]",
                    "items": {
                      "items": {
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "minItems": 2,
                    "type": "array"
                  }
                },
                "required": [
                  "points"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "eventos_ruta": {
                    "summary": "Eventos a lo largo de la ruta",
                    "value": [
                      {
                        "affectedVehicleTypes": [
                          "TRUCK",
                          "BUS"
                        ],
                        "createdAt": "2024-01-15T20:00:00Z",
                        "expectedDuration": 180,
                        "id": "evt_55555",
                        "location": {
                          "lat": 40.4168,
                          "lon": -3.7038
                        },
                        "severity": 80,
                        "type": "ROAD_CLOSURE",
                        "updatedAt": "2024-01-15T20:00:00Z"
                      }
                    ]
                  }
                },
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/TrafficEvent"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Eventos encontrados a lo largo de la ruta"
          },
          "400": {
            "description": "Ruta inv\u00e1lida"
          },
          "401": {
            "description": "No autorizado"
          },
          "500": {
            "description": "Error interno del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Consultar eventos a lo largo de una ruta",
        "tags": [
          "traffic"
        ]
      }
    },
    "/events/historical": {
      "get": {
        "description": "Recupera eventos de tr\u00e1fico que ocurrieron en un rango de fechas espec\u00edfico.\n\n**Ejemplo de uso:**\n```\nGET /events/historical?from=2024-01-01&to=2024-01-31&lat=40.4168&lng=-3.7038&radius=10000\n```\n\n**Casos de uso:**\n- An\u00e1lisis de patrones de tr\u00e1fico en fechas pasadas\n- Investigaci\u00f3n de incidentes ocurridos\n- Planificaci\u00f3n basada en datos hist\u00f3ricos\n\n**Par\u00e1metros importantes:**\n- `from` y `to`: Fechas en formato YYYY-MM-DD\n- `lat` y `lng`: Opcionales para filtrar por ubicaci\u00f3n\n- `radius`: Radio de b\u00fasqueda cuando se especifica ubicaci\u00f3n\n",
        "parameters": [
          {
            "description": "Fecha de inicio del rango (YYYY-MM-DD)",
            "in": "query",
            "name": "from",
            "required": true,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Fecha de fin del rango (YYYY-MM-DD)",
            "in": "query",
            "name": "to",
            "required": true,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Radius"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "ejemplo_historico": {
                    "summary": "Eventos hist\u00f3ricos encontrados",
                    "value": [
                      {
                        "affectedVehicleTypes": [
                          "CAR",
                          "TRUCK"
                        ],
                        "createdAt": "2024-01-15T10:30:00Z",
                        "expectedDuration": 120,
                        "id": "evt_12345",
                        "location": {
                          "lat": 40.4168,
                          "lon": -3.7038
                        },
                        "severity": 75,
                        "type": "ACCIDENT",
                        "updatedAt": "2024-01-15T12:30:00Z"
                      }
                    ]
                  }
                },
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/TrafficEvent"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de eventos de tr\u00e1fico hist\u00f3ricos"
          },
          "400": {
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "401": {
            "description": "No autorizado"
          },
          "500": {
            "description": "Error interno del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Consultar eventos de tr\u00e1fico hist\u00f3ricos",
        "tags": [
          "traffic"
        ]
      }
    },
    "/events/isochrone": {
      "post": {
        "description": "Calcula \u00e1reas alcanzables (isocronas) desde puntos espec\u00edficos a lo largo de una ruta en un tiempo determinado.\n\n**Ejemplo de uso:**\n```json\nPOST /events/isochrone\n{\n  \"points\": [\n    [-3.7038, 40.4168],\n    [-3.7138, 40.4268],\n    [-3.7238, 40.4368]\n  ]\n}\n```\n\n\u00datil para planificar rutas y determinar \u00e1reas de cobertura en tiempo real.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "points": {
                    "description": "Array de pares de coordenadas [longitud, latitud] que definen la ruta",
                    "items": {
                      "items": {
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "minItems": 2,
                    "type": "array"
                  }
                },
                "required": [
                  "points"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "ejemplo_isocronas": {
                    "summary": "Isocronas calculadas",
                    "value": [
                      {
                        "center": {
                          "lat": 40.4168,
                          "lon": -3.7038
                        },
                        "createdAt": "2024-01-15T10:30:00Z",
                        "id": "iso_12345",
                        "polygon": [
                          {
                            "lat": 40.4268,
                            "lon": -3.6938
                          },
                          {
                            "lat": 40.4068,
                            "lon": -3.6938
                          },
                          {
                            "lat": 40.4068,
                            "lon": -3.7138
                          },
                          {
                            "lat": 40.4268,
                            "lon": -3.7138
                          }
                        ],
                        "travelTime": 30
                      }
                    ]
                  }
                },
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Isochrone"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Isocronas calculadas para la ruta"
          },
          "400": {
            "description": "Ruta inv\u00e1lida o par\u00e1metros incorrectos"
          },
          "401": {
            "description": "No autorizado"
          },
          "500": {
            "description": "Error interno del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Calcular isocronas para una ruta",
        "tags": [
          "traffic"
        ]
      }
    },
    "/events/nearby": {
      "get": {
        "description": "Versi\u00f3n simplificada para obtener eventos cerca de una ubicaci\u00f3n sin filtros complejos.\n\n**Ejemplo de uso r\u00e1pido:**\n```\nGET /events/nearby?lat=40.4168&lng=-3.7038&radius=5000&lang=es\n```\n\n**Diferencia con /events:**\n- Menos par\u00e1metros de filtrado\n- M\u00e1s r\u00e1pido para consultas simples\n- Ideal para aplicaciones m\u00f3viles con necesidades b\u00e1sicas\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Radius"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "eventos_cercanos": {
                    "summary": "Eventos cercanos encontrados",
                    "value": [
                      {
                        "affectedVehicleTypes": [
                          "CAR"
                        ],
                        "createdAt": "2024-01-15T19:00:00Z",
                        "expectedDuration": 60,
                        "id": "evt_44444",
                        "location": {
                          "lat": 40.4268,
                          "lon": -3.7138
                        },
                        "severity": 70,
                        "type": "ACCIDENT",
                        "updatedAt": "2024-01-15T19:00:00Z"
                      }
                    ]
                  }
                },
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/TrafficEvent"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de eventos cercanos"
          },
          "400": {
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "401": {
            "description": "No autorizado"
          },
          "500": {
            "description": "Error interno del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Consultar eventos cercanos",
        "tags": [
          "traffic"
        ]
      }
    },
    "/events/type/{type}": {
      "get": {
        "description": "Filtra eventos de tr\u00e1fico por categor\u00eda espec\u00edfica (accidente, cierre de carretera, evento especial).\n\n**Tipos disponibles:**\n- `ACCIDENT`: Accidentes de tr\u00e1fico\n- `ROAD_CLOSURE`: Cierres de carretera\n- `SPECIAL_EVENT`: Eventos especiales (conciertos, manifestaciones, etc.)\n\n**Ejemplo de uso:**\n```\nGET /events/type/ACCIDENT?lat=40.4168&lng=-3.7038&radius=5000\n```\n\n**Escenario t\u00edpico:**\nUn gestor de flota quiere evitar zonas con accidentes recientes para sus veh\u00edculos.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/EventType"
          },
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Radius"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "ejemplo_por_tipo": {
                    "summary": "Eventos filtrados por tipo",
                    "value": [
                      {
                        "affectedVehicleTypes": [
                          "ALL"
                        ],
                        "createdAt": "2024-01-15T14:20:00Z",
                        "expectedDuration": 180,
                        "id": "evt_67890",
                        "location": {
                          "lat": 41.3851,
                          "lon": 2.1734
                        },
                        "severity": 90,
                        "type": "ROAD_CLOSURE",
                        "updatedAt": "2024-01-15T14:20:00Z"
                      }
                    ]
                  }
                },
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/TrafficEvent"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de eventos del tipo especificado"
          },
          "400": {
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "401": {
            "description": "No autorizado"
          },
          "500": {
            "description": "Error interno del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Consultar eventos por tipo espec\u00edfico",
        "tags": [
          "traffic"
        ]
      }
    },
    "/forecast": {
      "get": {
        "description": "Obtener pron\u00f3sticos meteorol\u00f3gicos detallados para el per\u00edodo seleccionado.",
        "operationId": "getForeCast",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/StartDate"
          },
          {
            "$ref": "#/components/parameters/EndDate"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Pron\u00f3stico meteorol\u00f3gico recuperado exitosamente."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get weather forecast",
        "tags": [
          "weather"
        ]
      }
    },
    "/health": {
      "get": {
        "description": "Verifica que el servicio est\u00e9 funcionando correctamente",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "status": {
                      "example": "ok",
                      "type": "string"
                    },
                    "timestamp": {
                      "format": "date-time",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Servicio saludable"
          }
        },
        "summary": "Health check del servicio",
        "tags": [
          "route"
        ]
      }
    },
    "/historical-driving": {
      "get": {
        "description": "**Obtiene una lista paginada de todos los registros hist\u00f3ricos de tac\u00f3grafo**.\n\n## \ud83d\udccb Funcionalidades\n- **Paginaci\u00f3n**: Soporta par\u00e1metros `page` y `pageSize`\n- **Filtros**: Por conductor, veh\u00edculo, rango de fechas\n- **Ordenamiento**: Por fecha de creaci\u00f3n, conductor, veh\u00edculo\n- **Aislamiento**: Solo registros del usuario propietario\n\n## \ud83d\udd0d Par\u00e1metros de consulta\n- `page`: N\u00famero de p\u00e1gina (por defecto: 1)\n- `pageSize`: Resultados por p\u00e1gina (m\u00e1ximo: 100)\n- `startDate`/`endDate`: Rango de fechas\n- `driverCardNumber`: Filtrar por conductor\n- `licensePlate`: Filtrar por veh\u00edculo\n- `sort`: Orden (`asc`/`desc`)\n- `lang`: Idioma de respuesta (`es` por defecto)\n\n## \ud83d\udcca Respuesta\nLista de objetos `HistoricalDrivingRecord` con metadatos de paginaci\u00f3n.\n",
        "operationId": "getHistoricalDrivingRecords",
        "parameters": [
          {
            "$ref": "#/components/parameters/Language"
          },
          {
            "$ref": "#/components/parameters/StartDate"
          },
          {
            "$ref": "#/components/parameters/EndDate"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/Sort"
          },
          {
            "description": "**Filtrar por n\u00famero de tarjeta del conductor**.\n**Formato**: ES12345678901234 (pa\u00eds + 14 d\u00edgitos)\n",
            "in": "query",
            "name": "driverCardNumber",
            "required": false,
            "schema": {
              "example": "ES12345678901234",
              "pattern": "^[A-Z]{2}[0-9]{14}$",
              "type": "string"
            }
          },
          {
            "description": "**Filtrar por matr\u00edcula del veh\u00edculo**.\n**Longitud m\u00e1xima**: 10 caracteres\n",
            "in": "query",
            "name": "licensePlate",
            "required": false,
            "schema": {
              "example": "1234ABC",
              "maxLength": 10,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "data": [
                      {
                        "_id": "5f8d3b7b3f6b8a1b9c3b7b3f",
                        "archivoDescarga": {
                          "extension": "TGD",
                          "fechaDescarga": "2025-04-30T10:30:00.000Z",
                          "nombre": "TACO_20250430_083000"
                        },
                        "conduccion": {
                          "distanciaTotal": 450.75,
                          "tiempoConduccionTotal": 480,
                          "velocidadMaxima": 95.5,
                          "velocidadPromedio": 72.3
                        },
                        "conductor": {
                          "apellidos": "P\u00e9rez Garc\u00eda",
                          "fechaCaducidad": "2026-12-31T23:59:59.000Z",
                          "nombre": "Juan",
                          "numeroTarjeta": "ES123456789012345"
                        },
                        "sesion": {
                          "fechaExtraccion": "2025-04-30T17:30:00.000Z",
                          "fechaInsercion": "2025-04-30T08:00:00.000Z",
                          "vehiculo": {
                            "matricula": "1234ABC"
                          }
                        }
                      }
                    ],
                    "pagination": {
                      "page": 1,
                      "pageSize": 10,
                      "total": 1,
                      "totalPages": 1
                    }
                  },
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/HistoricalDrivingRecord"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "properties": {
                        "page": {
                          "example": 1,
                          "type": "integer"
                        },
                        "pageSize": {
                          "example": 10,
                          "type": "integer"
                        },
                        "total": {
                          "example": 150,
                          "type": "integer"
                        },
                        "totalPages": {
                          "example": 15,
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Registros obtenidos exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Listar registros hist\u00f3ricos de tac\u00f3grafo",
        "tags": [
          "drivers"
        ]
      },
      "post": {
        "description": "**Crea un nuevo registro hist\u00f3rico a partir de datos de tac\u00f3grafo**.\n\n## \ud83d\udcdd Requisitos\n- Archivo de tac\u00f3grafo descargado (TGD, DDD, C1B, V1B)\n- Informaci\u00f3n del conductor (tarjeta v\u00e1lida)\n- Informaci\u00f3n del veh\u00edculo\n- Datos de actividad parseados\n\n## \u2705 Validaciones\n- Archivo v\u00e1lido y no corrupto\n- Tarjeta de conductor vigente\n- Calibraci\u00f3n del tac\u00f3grafo reciente (<1 a\u00f1o)\n- No duplicados (mismo archivo, conductor, fecha)\n\n## \ud83d\udd04 Procesamiento autom\u00e1tico\n- Parseo de datos del tac\u00f3grafo\n- C\u00e1lculo de tiempos de conducci\u00f3n\n- Detecci\u00f3n de infracciones\n- Generaci\u00f3n de resumen y an\u00e1lisis\n- Almacenamiento de eventos registrados\n",
        "operationId": "createHistoricalDrivingRecord",
        "parameters": [
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "archivoDescarga": {
                  "contenido": "U0dEX0ZJTEVfREFUQQ==",
                  "extension": "TGD",
                  "fechaDescarga": "2025-04-30T10:30:00.000Z",
                  "nombre": "TACO_20250430_083000"
                },
                "conductor": {
                  "apellidos": "P\u00e9rez Garc\u00eda",
                  "fechaCaducidad": "2026-12-31T23:59:59.000Z",
                  "nombre": "Juan",
                  "numeroTarjeta": "ES123456789012345"
                },
                "vehiculo": {
                  "matricula": "1234ABC"
                }
              },
              "schema": {
                "properties": {
                  "archivoDescarga": {
                    "properties": {
                      "contenido": {
                        "description": "Contenido del archivo en base64",
                        "example": "U0dEX0ZJTEVfREFUQQ==",
                        "type": "string"
                      },
                      "extension": {
                        "enum": [
                          "TGD",
                          "DDD",
                          "C1B",
                          "V1B"
                        ],
                        "example": "TGD",
                        "type": "string"
                      },
                      "fechaDescarga": {
                        "example": "2025-04-30T10:30:00.000Z",
                        "format": "date-time",
                        "type": "string"
                      },
                      "nombre": {
                        "example": "TACO_20250430_083000",
                        "type": "string"
                      }
                    },
                    "required": [
                      "nombre",
                      "extension",
                      "fechaDescarga",
                      "contenido"
                    ],
                    "type": "object"
                  },
                  "conductor": {
                    "properties": {
                      "apellidos": {
                        "example": "P\u00e9rez Garc\u00eda",
                        "type": "string"
                      },
                      "fechaCaducidad": {
                        "example": "2026-12-31T23:59:59.000Z",
                        "format": "date-time",
                        "type": "string"
                      },
                      "nombre": {
                        "example": "Juan",
                        "type": "string"
                      },
                      "numeroTarjeta": {
                        "example": "ES123456789012345",
                        "pattern": "^[A-Z]{2}[0-9]{14}$",
                        "type": "string"
                      }
                    },
                    "required": [
                      "nombre",
                      "apellidos",
                      "numeroTarjeta",
                      "fechaCaducidad"
                    ],
                    "type": "object"
                  },
                  "vehiculo": {
                    "properties": {
                      "matricula": {
                        "example": "1234ABC",
                        "maxLength": 10,
                        "type": "string"
                      }
                    },
                    "required": [
                      "matricula"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "archivoDescarga",
                  "conductor",
                  "vehiculo"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoricalDrivingRecord"
                }
              }
            },
            "description": "Registro creado exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Ya existe un registro para este conductor y fecha"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Registro duplicado"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Crear registro hist\u00f3rico de tac\u00f3grafo",
        "tags": [
          "drivers"
        ]
      }
    },
    "/historical-driving/{id}": {
      "delete": {
        "description": "**Elimina permanentemente un registro hist\u00f3rico del sistema**.\n\n## \u26a0\ufe0f Operaci\u00f3n destructiva\n- **Requiere justificaci\u00f3n legal para eliminaci\u00f3n**\n- Elimina todos los datos asociados (actividades, eventos, an\u00e1lisis)\n- **No se puede deshacer**\n\n## \ud83d\udd12 Restricciones\n- Solo administradores pueden eliminar registros\n- Registros con m\u00e1s de 2 a\u00f1os requieren aprobaci\u00f3n especial\n- Operaci\u00f3n auditada y registrada\n\n## \ud83d\udccb Consecuencias\n- Se pierden datos de cumplimiento hist\u00f3rico\n- Afecta reportes y an\u00e1lisis retrospectivos\n- Requiere documentaci\u00f3n legal de eliminaci\u00f3n\n",
        "operationId": "deleteHistoricalDrivingRecord",
        "responses": {
          "204": {
            "description": "Registro eliminado exitosamente (sin contenido)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Solo administradores pueden eliminar registros hist\u00f3ricos"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Permisos insuficientes"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Registro hist\u00f3rico no encontrado"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Registro no encontrado"
          },
          "409": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Registro no puede eliminarse por restricciones legales"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No se puede eliminar"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Eliminar registro hist\u00f3rico",
        "tags": [
          "drivers"
        ]
      },
      "get": {
        "description": "**Obtiene la informaci\u00f3n completa de un registro hist\u00f3rico de tac\u00f3grafo**.\n\n## \ud83d\udccb Informaci\u00f3n incluida\n- Metadatos del archivo descargado\n- Informaci\u00f3n del conductor y veh\u00edculo\n- Sesi\u00f3n completa (inicio/fin)\n- Todas las actividades registradas\n- Resumen de conducci\u00f3n (distancia, tiempo, velocidades)\n- Lista completa de eventos registrados\n\n## \ud83d\udd12 Permisos\n- Solo el propietario del registro puede acceder\n- Validaci\u00f3n por campo `owner` (asociado al usuario)\n\n## \ud83d\udcbe Datos hist\u00f3ricos\n- Registros se retienen indefinidamente para cumplimiento legal\n- Incluye datos parseados del tac\u00f3grafo original\n- Eventos categorizados por tipo y gravedad\n",
        "operationId": "getHistoricalDrivingRecord",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoricalDrivingRecord"
                }
              }
            },
            "description": "Registro obtenido exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Registro hist\u00f3rico no encontrado o sin permisos de acceso"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Registro no encontrado"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Obtener registro hist\u00f3rico espec\u00edfico",
        "tags": [
          "drivers"
        ]
      },
      "put": {
        "description": "**Actualiza informaci\u00f3n de un registro hist\u00f3rico existente**.\n\n## \ud83d\udcdd Campos actualizables\n- Informaci\u00f3n del conductor (si cambi\u00f3 la tarjeta)\n- Informaci\u00f3n del veh\u00edculo\n- Notas o comentarios adicionales\n- **No se pueden modificar datos del tac\u00f3grafo original**\n\n## \u2705 Validaciones\n- El registro debe existir y pertenecer al usuario\n- Solo campos permitidos pueden actualizarse\n- Mantiene integridad de datos originales\n\n## \u26a0\ufe0f Restricciones\n- Datos del tac\u00f3grafo son inmutables\n- Solo metadatos administrativos pueden cambiarse\n- Cambios se auditan autom\u00e1ticamente\n",
        "operationId": "updateHistoricalDrivingRecord",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "conductor": {
                  "apellidos": "P\u00e9rez Garc\u00eda",
                  "fechaCaducidad": "2026-12-31T23:59:59.000Z",
                  "nombre": "Juan",
                  "numeroTarjeta": "ES123456789012345"
                },
                "notas": "Registro verificado manualmente - datos correctos",
                "vehiculo": {
                  "matricula": "1234ABC"
                }
              },
              "schema": {
                "properties": {
                  "conductor": {
                    "properties": {
                      "apellidos": {
                        "example": "P\u00e9rez Garc\u00eda",
                        "type": "string"
                      },
                      "fechaCaducidad": {
                        "example": "2026-12-31T23:59:59.000Z",
                        "format": "date-time",
                        "type": "string"
                      },
                      "nombre": {
                        "example": "Juan",
                        "type": "string"
                      },
                      "numeroTarjeta": {
                        "example": "ES123456789012345",
                        "pattern": "^[A-Z]{2}[0-9]{14}$",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "notas": {
                    "description": "Notas administrativas opcionales",
                    "example": "Registro verificado manualmente - datos correctos",
                    "maxLength": 500,
                    "type": "string"
                  },
                  "vehiculo": {
                    "properties": {
                      "matricula": {
                        "example": "1234ABC",
                        "maxLength": 10,
                        "type": "string"
                      }
                    },
                    "type": "object"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoricalDrivingRecord"
                }
              }
            },
            "description": "Registro actualizado exitosamente"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Registro hist\u00f3rico no encontrado o sin permisos de acceso"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Registro no encontrado"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Actualizar registro hist\u00f3rico",
        "tags": [
          "drivers"
        ]
      }
    },
    "/historical-driving/{id}/analysis": {
      "get": {
        "description": "**Obtiene un an\u00e1lisis completo del registro de tac\u00f3grafo**.\nIncluye m\u00e9tricas de conducci\u00f3n, eventos y cumplimiento normativo.\n",
        "operationId": "getHistoricalDrivingAnalysis",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoricalDrivingAnalysis"
                }
              }
            },
            "description": "An\u00e1lisis obtenido exitosamente"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Obtener an\u00e1lisis detallado del registro",
        "tags": [
          "drivers"
        ]
      }
    },
    "/historical-driving/{id}/events": {
      "get": {
        "description": "**Obtiene la lista de eventos registrados en el tac\u00f3grafo**.\nIncluye eventos del tac\u00f3grafo y eventos manuales agregados.\n",
        "operationId": "getHistoricalDrivingEvents",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "items": {
                        "properties": {
                          "descripcion": {
                            "type": "string"
                          },
                          "fechaHora": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "gravedad": {
                            "type": "string"
                          },
                          "tipo": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "properties": {
                        "total": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Eventos obtenidos exitosamente"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Listar eventos del registro hist\u00f3rico",
        "tags": [
          "drivers"
        ]
      }
    },
    "/ipma/alerts/current": {
      "get": {
        "description": "Alertas meteorol\u00f3gicas actuales proporcionadas por IPMA para ubicaciones en Portugal",
        "operationId": "getIpmaAlertCurrent",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Alertas meteorol\u00f3gicas recuperadas exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get current IPMA weather alerts",
        "tags": [
          "weather"
        ]
      }
    },
    "/ipma/alerts/range": {
      "get": {
        "description": "Alertas meteorol\u00f3gicas hist\u00f3ricas o futuras proporcionadas por IPMA para ubicaciones en Portugal",
        "operationId": "getIpmaAlertRange",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/StartDate"
          },
          {
            "$ref": "#/components/parameters/EndDate"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Alertas meteorol\u00f3gicas recuperadas exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get IPMA alerts for date range",
        "tags": [
          "weather"
        ]
      }
    },
    "/ipma/current": {
      "get": {
        "description": "Condiciones meteorol\u00f3gicas actuales proporcionadas por IPMA para ubicaciones en Portugal",
        "operationId": "getIpmaCurrent",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Clima actual recuperado exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get current IPMA weather",
        "tags": [
          "weather"
        ]
      }
    },
    "/ipma/forecast": {
      "get": {
        "description": "Pron\u00f3sticos meteorol\u00f3gicos proporcionados por IPMA para ubicaciones en Portugal",
        "operationId": "getIpmaForecast",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/StartDate"
          },
          {
            "$ref": "#/components/parameters/EndDate"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Pron\u00f3stico meteorol\u00f3gico recuperado exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Par\u00e1metros inv\u00e1lidos o faltantes"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get IPMA weather forecast",
        "tags": [
          "weather"
        ]
      }
    },
    "/models": {
      "get": {
        "description": "Recupera todos los modelos disponibles con informaci\u00f3n de su marca asociada",
        "operationId": "getAllModelsWithBrand",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "properties": {
                      "brand": {
                        "properties": {
                          "id": {
                            "format": "uuid",
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "id": {
                        "format": "uuid",
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Modelos con informaci\u00f3n de marca recuperados exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get all models with brand info",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/models/search": {
      "get": {
        "description": "Realiza una b\u00fasqueda de marcas y modelos de veh\u00edculos utilizando un t\u00e9rmino de b\u00fasqueda",
        "operationId": "searchBrandsAndModels",
        "parameters": [
          {
            "description": "Termo de b\u00fasqueda para filtrar marcas y modelos",
            "in": "query",
            "name": "q",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "brands": {
                      "items": {
                        "properties": {
                          "id": {
                            "format": "uuid",
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "models": {
                      "items": {
                        "properties": {
                          "brand": {
                            "properties": {
                              "id": {
                                "format": "uuid",
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              }
                            },
                            "type": "object"
                          },
                          "id": {
                            "format": "uuid",
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Resultados de la b\u00fasqueda recuperados exitosamente"
          },
          "400": {
            "description": "Par\u00e1metro de b\u00fasqueda ausente o inv\u00e1lido"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Search brands and models",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/models/{brandId}": {
      "get": {
        "description": "Recupera todos los modelos disponibles para una marca espec\u00edfica",
        "operationId": "getModelsByBrand",
        "parameters": [
          {
            "description": "ID de la marca",
            "in": "path",
            "name": "brandId",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "properties": {
                      "id": {
                        "format": "uuid",
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Modelos recuperados exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Marca no encontrada"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get models by brand",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/models/{modelId}/specs": {
      "get": {
        "description": "Recupera las especificaciones t\u00e9cnicas detalladas para un modelo espec\u00edfico",
        "operationId": "getSpecsByModel",
        "parameters": [
          {
            "description": "ID del modelo",
            "in": "path",
            "name": "modelId",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "id": {
                      "format": "uuid",
                      "type": "string"
                    },
                    "specifications": {
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Especificaciones t\u00e9cnicas recuperadas exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Modelo no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get technical specifications by model",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/parking/getAlongPath": {
      "post": {
        "description": "Devuelve aparcamientos cerca de una ruta especificada, definida por una serie de coordenadas.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "category": {
                    "default": "all",
                    "description": "Categor\u00eda del aparcamiento para filtrar resultados",
                    "enum": [
                      "LOW_SIDE",
                      "SECURE",
                      "FREE",
                      "OTHER",
                      "all"
                    ],
                    "example": "SECURE",
                    "type": "string"
                  },
                  "features": {
                    "description": "Array de caracter\u00edsticas espec\u00edficas para filtrar",
                    "example": [
                      "vigilancia",
                      "iluminacion"
                    ],
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "path": {
                    "description": "Array de puntos [longitud, latitud] que definen la ruta. El sistema filtra autom\u00e1ticamente los puntos redundantes manteniendo 25km de distancia m\u00ednima entre ellos.",
                    "example": [
                      [
                        -3.70379,
                        40.416775
                      ],
                      [
                        -3.70123,
                        40.41789
                      ],
                      [
                        -3.69876,
                        40.41901
                      ]
                    ],
                    "items": {
                      "items": {
                        "format": "double",
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "type": "array"
                  },
                  "radius": {
                    "default": 25000,
                    "description": "Radio de b\u00fasqueda en metros desde la ruta. M\u00edn 1000m, M\u00e1x 150000m. Por defecto 25000m (25km).",
                    "example": 25000,
                    "format": "float",
                    "maximum": 150000,
                    "minimum": 1000,
                    "type": "number"
                  }
                },
                "required": [
                  "path"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Parking"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Operaci\u00f3n exitosa"
          },
          "400": {
            "description": "Cuerpo de petici\u00f3n o par\u00e1metros inv\u00e1lidos (ej., ruta faltante, puntos inv\u00e1lidos, radio fuera de rango)"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Find parkings along a path",
        "tags": [
          "poi"
        ]
      }
    },
    "/parking/location": {
      "get": {
        "description": "Devuelve aparcamientos dentro de un radio dado de coordenadas. Los resultados son procesados con el m\u00e9todo .parse()",
        "parameters": [
          {
            "description": "Latitude coordinate",
            "example": 40.416775,
            "in": "query",
            "name": "lat",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 90,
              "minimum": -90,
              "type": "number"
            }
          },
          {
            "description": "Longitude coordinate",
            "example": -3.70379,
            "in": "query",
            "name": "lng",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 180,
              "minimum": -180,
              "type": "number"
            }
          },
          {
            "description": "Radio de b\u00fasqueda en metros. M\u00edn 1000m, M\u00e1x 250000m. Requerido.",
            "example": 25000,
            "in": "query",
            "name": "radius",
            "required": true,
            "schema": {
              "format": "float",
              "maximum": 250000,
              "minimum": 1000,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Parking"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Operaci\u00f3n exitosa"
          },
          "400": {
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Find parkings by location",
        "tags": [
          "poi"
        ]
      }
    },
    "/parking/nearby": {
      "get": {
        "description": "Devuelve aparcamientos dentro de un radio especificado de coordenadas dadas. Por defecto 250km.",
        "parameters": [
          {
            "description": "Latitude coordinate",
            "example": 40.416775,
            "in": "query",
            "name": "lat",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 90,
              "minimum": -90,
              "type": "number"
            }
          },
          {
            "description": "Longitude coordinate",
            "example": -3.70379,
            "in": "query",
            "name": "lng",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 180,
              "minimum": -180,
              "type": "number"
            }
          },
          {
            "description": "Radio de b\u00fasqueda en metros. M\u00edn 1000m, M\u00e1x 250000m. Por defecto 250000m (250km).",
            "example": 10000,
            "in": "query",
            "name": "radius",
            "schema": {
              "default": 250000,
              "format": "float",
              "maximum": 250000,
              "minimum": 1000,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Parking"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Operaci\u00f3n exitosa"
          },
          "400": {
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Find parkings nearby a location",
        "tags": [
          "poi"
        ]
      }
    },
    "/parking/province": {
      "get": {
        "description": "Devuelve aparcamientos en la provincia especificada. Los resultados son procesados con el m\u00e9todo .parse()",
        "parameters": [
          {
            "description": "Nombre de provincia/estado",
            "example": "Madrid",
            "in": "query",
            "name": "province",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Parking"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Operaci\u00f3n exitosa"
          },
          "400": {
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Find parkings by province",
        "tags": [
          "poi"
        ]
      }
    },
    "/parking/zipcode": {
      "get": {
        "description": "Devuelve aparcamientos que coinciden con el c\u00f3digo postal y pa\u00eds. Los resultados son procesados con el m\u00e9todo .parse()",
        "parameters": [
          {
            "description": "C\u00f3digo postal",
            "example": "28013",
            "in": "query",
            "name": "zipcode",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "C\u00f3digo de pa\u00eds (2 letras). Soportados: es, fr, de, pt. Requerido.",
            "example": "es",
            "in": "query",
            "name": "country",
            "required": true,
            "schema": {
              "enum": [
                "es",
                "fr",
                "de",
                "pt"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Parking"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Operaci\u00f3n exitosa"
          },
          "400": {
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Find parkings by zipcode",
        "tags": [
          "poi"
        ]
      }
    },
    "/radar/getAlongPath": {
      "post": {
        "description": "Retrieve radar information along a specified route",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "lang": {
                    "default": "es",
                    "description": "Language for response data",
                    "enum": [
                      "es",
                      "en",
                      "fr",
                      "de",
                      "pt"
                    ],
                    "type": "string"
                  },
                  "points": {
                    "description": "Array of coordinate pairs [longitude, latitude]",
                    "items": {
                      "items": {
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "minItems": 2,
                    "type": "array"
                  }
                },
                "required": [
                  "points"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Radar"
                  },
                  "type": "array"
                }
              }
            },
            "description": "List of radars along the path"
          },
          "400": {
            "description": "Invalid route"
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get radars along a path",
        "tags": [
          "traffic"
        ]
      }
    },
    "/radar/nearby": {
      "get": {
        "description": "Retrieve radar information near a location",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Radius"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Radar"
                  },
                  "type": "array"
                }
              }
            },
            "description": "List of nearby radars"
          },
          "400": {
            "description": "Invalid parameters"
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get nearby radars",
        "tags": [
          "traffic"
        ]
      }
    },
    "/restaurant/getAlongPath": {
      "post": {
        "description": "Devuelve restaurantes cerca de una ruta especificada",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "path": {
                    "description": "Array de puntos [longitud, latitud] que definen la ruta",
                    "example": [
                      [
                        -3.70379,
                        40.416775
                      ],
                      [
                        -3.70123,
                        40.41789
                      ],
                      [
                        -3.69876,
                        40.41901
                      ]
                    ],
                    "items": {
                      "items": {
                        "format": "double",
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "type": "array"
                  },
                  "radius": {
                    "default": 5000,
                    "description": "Radio de b\u00fasqueda en metros desde la ruta. M\u00edn 1000m, M\u00e1x 25000m. Por defecto 5000m (5km).",
                    "format": "float",
                    "maximum": 25000,
                    "minimum": 1000,
                    "type": "number"
                  }
                },
                "required": [
                  "path"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Restaurant"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Operaci\u00f3n exitosa"
          },
          "400": {
            "description": "Cuerpo de petici\u00f3n o par\u00e1metros inv\u00e1lidos"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Find restaurants along a path",
        "tags": [
          "poi"
        ]
      }
    },
    "/restaurant/nearby": {
      "get": {
        "description": "Devuelve restaurantes dentro de un radio dado de coordenadas. Por defecto 250km.",
        "parameters": [
          {
            "description": "Latitude coordinate",
            "example": 40.416775,
            "in": "query",
            "name": "lat",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 90,
              "minimum": -90,
              "type": "number"
            }
          },
          {
            "description": "Longitude coordinate",
            "example": -3.70379,
            "in": "query",
            "name": "lng",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 180,
              "minimum": -180,
              "type": "number"
            }
          },
          {
            "description": "Radio de b\u00fasqueda en metros. M\u00edn 1000m, M\u00e1x 250000m. Por defecto 250000m (250km).",
            "example": 10000,
            "in": "query",
            "name": "radius",
            "schema": {
              "default": 250000,
              "format": "float",
              "maximum": 250000,
              "minimum": 1000,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Restaurant"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Operaci\u00f3n exitosa"
          },
          "400": {
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Find restaurants nearby",
        "tags": [
          "poi"
        ]
      }
    },
    "/route": {
      "post": {
        "description": "Obtener las condiciones meteorol\u00f3gicas a lo largo de una ruta GPS con filtrado opcional por nivel de peligro.\n\n**Funcionalidad de filtrado por peligro:**\n- Si `dangerLevel` no se especifica, se incluyen todos los puntos (equivalente a dangerLevel=0)\n- Si `dangerLevel` se especifica, solo se devuelven los puntos con un nivel de peligro igual o superior al valor\n- El valor se ajusta autom\u00e1ticamente entre 0 y 100 (m\u00ednimo 0, m\u00e1ximo 100)\n\n**Procesamiento de la ruta:**\n- Los puntos GPS se filtran para conservar solo aquellos separados por al menos 25 km\n- Se obtienen datos clim\u00e1ticos y alertas para cada punto filtrado\n- Se calcula una puntuaci\u00f3n de peligro basada en visibilidad, lluvia, nieve, viento, temperatura, nubes y condiciones extremas\n",
        "operationId": "getWeatherAlongPath",
        "parameters": [
          {
            "$ref": "#/components/parameters/Language"
          },
          {
            "$ref": "#/components/parameters/DangerLevel"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "points": {
                    "description": "Array de puntos GPS en el formato [[lat, lon], [lat, lon], ...]",
                    "items": {
                      "example": [
                        40.4168,
                        -3.7038
                      ],
                      "items": {
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "type": "array"
                  }
                },
                "required": [
                  "points"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "alerts": {
                      "description": "Alertas meteorol\u00f3gicas activas en los puntos de la ruta",
                      "type": "array"
                    },
                    "currentWeather": {
                      "description": "Datos clim\u00e1ticos actuales, filtrados por nivel de peligro.\nCada elemento incluye una puntuaci\u00f3n de peligro (0-100) calculada autom\u00e1ticamente.\n",
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Datos meteorol\u00f3gicos recuperados exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Cuerpo de solicitud inv\u00e1lido o puntos GPS mal formados"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Error interno del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get weather along route with danger filtering",
        "tags": [
          "weather"
        ]
      }
    },
    "/search": {
      "get": {
        "description": "Realiza una b\u00fasqueda textual en todos los niveles de la jerarqu\u00eda HS:\n- Secciones\n- Cap\u00edtulos\n- Partidas\n- Subpartidas\n\nDevuelve solo los elementos que coincidan con el t\u00e9rmino de b\u00fasqueda,\nmanteniendo la estructura jer\u00e1rquica entre ellos.\n\nLa b\u00fasqueda no distingue may\u00fasculas/min\u00fasculas y busca coincidencias parciales.\n\nRequiere autenticaci\u00f3n mediante JWT en el header Authorization.\n",
        "operationId": "search",
        "parameters": [
          {
            "description": "T\u00e9rmino de b\u00fasqueda para filtrar resultados.\nM\u00ednimo 3 caracteres. Puede buscar por:\n- N\u00fameros de cap\u00edtulo/partida (ej. \"52\")\n- Descripciones (ej. \"algod\u00f3n\")\n- Combinaciones (ej. \"52 algod\u00f3n\")\n",
            "example": "algod\u00f3n",
            "in": "query",
            "name": "search",
            "required": true,
            "schema": {
              "minLength": 3,
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/authorization"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "_id": "6501a2b3c4d5e6f7a8b9c0d4",
                    "chapters": [
                      {
                        "_id": "6501a2b3c4d5e6f7a8b9c0d3",
                        "headings": [],
                        "title": "Cap\u00edtulo 52 - Algod\u00f3n"
                      }
                    ],
                    "title": "Secci\u00f3n XI - Textiles"
                  }
                ],
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/SearchResult"
                  },
                  "type": "array"
                }
              }
            },
            "description": "B\u00fasqueda realizada exitosamente.\nDevuelve un array de secciones que contienen al menos un elemento\nque coincide con el t\u00e9rmino de b\u00fasqueda, con la estructura jer\u00e1rquica\ncompleta desde la secci\u00f3n hasta el elemento coincidente.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "message": "Search query is required and must be at least 3 characters"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Par\u00e1metros de b\u00fasqueda inv\u00e1lidos.\nOcurre cuando:\n- No se proporciona el par\u00e1metro 'search'\n- El t\u00e9rmino de b\u00fasqueda tiene menos de 3 caracteres\n"
          },
          "401": {
            "content": {
              "application/json": {
                "examples": {
                  "invalidToken": {
                    "value": {
                      "message": "Invalid or expired token"
                    }
                  },
                  "missingToken": {
                    "value": {
                      "message": "Authentication token is required"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No autorizado. Ocurre cuando:\n- No se proporciona el header Authorization\n- El token JWT es inv\u00e1lido o ha expirado\n- El formato del token es incorrecto\n\nEjemplo de respuesta:\n```json\n{\n  \"message\": \"Invalid or expired token\"\n}\n```\n"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error interno del servidor.\nPuede ocurrir si hay problemas al acceder a la base de datos.\n"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Buscar en toda la estructura del HS Code",
        "tags": [
          "profiles"
        ]
      }
    },
    "/search/getAlongPath": {
      "post": {
        "description": "Combined search for traffic events and radars along a specified route",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "lang": {
                    "default": "es",
                    "description": "Language for response data",
                    "enum": [
                      "es",
                      "en",
                      "fr",
                      "de",
                      "pt"
                    ],
                    "type": "string"
                  },
                  "points": {
                    "description": "Array of coordinate pairs [longitude, latitude]",
                    "items": {
                      "items": {
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "minItems": 2,
                    "type": "array"
                  }
                },
                "required": [
                  "points"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            },
            "description": "Combined search results along the path"
          },
          "400": {
            "description": "Invalid route"
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Search events and radars along a path",
        "tags": [
          "traffic"
        ]
      }
    },
    "/search/nearby": {
      "get": {
        "description": "Combined search for traffic events and radars near a location",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Radius"
          },
          {
            "$ref": "#/components/parameters/Language"
          },
          {
            "description": "Filter by event types",
            "in": "query",
            "name": "types",
            "schema": {
              "items": {
                "enum": [
                  "ACCIDENT",
                  "ROAD_CLOSURE",
                  "SPECIAL_EVENT"
                ],
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "description": "Minimum event severity level",
            "in": "query",
            "name": "minSeverity",
            "schema": {
              "maximum": 100,
              "minimum": 0,
              "type": "number"
            }
          },
          {
            "description": "Filter by affected vehicle types",
            "in": "query",
            "name": "vehicleTypes",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            },
            "description": "Combined search results"
          },
          "400": {
            "description": "Invalid parameters"
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Search nearby events and radars",
        "tags": [
          "traffic"
        ]
      }
    },
    "/series/{modelId}": {
      "get": {
        "description": "Recupera todas las series disponibles para un modelo de veh\u00edculo dado",
        "operationId": "getSeriesByModel",
        "parameters": [
          {
            "description": "ID del modelo",
            "in": "path",
            "name": "modelId",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "properties": {
                      "id": {
                        "format": "uuid",
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Series recuperadas exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Modelo no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get series by model",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/shower/getAlongPath": {
      "post": {
        "description": "Devuelve duchas cerca de una ruta especificada",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "path": {
                    "description": "Array de puntos [longitud, latitud] que definen la ruta",
                    "example": [
                      [
                        -3.70379,
                        40.416775
                      ],
                      [
                        -3.70123,
                        40.41789
                      ],
                      [
                        -3.69876,
                        40.41901
                      ]
                    ],
                    "items": {
                      "items": {
                        "format": "double",
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "type": "array"
                  },
                  "radius": {
                    "default": 5000,
                    "description": "Radio de b\u00fasqueda en metros desde la ruta. M\u00edn 1000m, M\u00e1x 25000m. Por defecto 5000m (5km).",
                    "format": "float",
                    "maximum": 25000,
                    "minimum": 1000,
                    "type": "number"
                  },
                  "type": {
                    "description": "Tipo de ducha a buscar",
                    "example": "PARKING",
                    "type": "string"
                  }
                },
                "required": [
                  "path"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Shower"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Operaci\u00f3n exitosa"
          },
          "400": {
            "description": "Cuerpo de petici\u00f3n o par\u00e1metros inv\u00e1lidos"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Find showers along a path",
        "tags": [
          "poi"
        ]
      }
    },
    "/shower/nearby": {
      "get": {
        "description": "Devuelve duchas dentro de un radio dado de coordenadas",
        "parameters": [
          {
            "description": "Latitude coordinate",
            "example": 40.416775,
            "in": "query",
            "name": "lat",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 90,
              "minimum": -90,
              "type": "number"
            }
          },
          {
            "description": "Longitude coordinate",
            "example": -3.70379,
            "in": "query",
            "name": "lng",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 180,
              "minimum": -180,
              "type": "number"
            }
          },
          {
            "description": "Radio de b\u00fasqueda en metros. M\u00edn 1000m, M\u00e1x 25000m. Por defecto 5000m (5km).",
            "in": "query",
            "name": "radius",
            "schema": {
              "default": 5000,
              "format": "float",
              "maximum": 25000,
              "minimum": 1000,
              "type": "number"
            }
          },
          {
            "description": "Tipo de ducha a buscar",
            "example": "PARKING",
            "in": "query",
            "name": "type",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Shower"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Operaci\u00f3n exitosa"
          },
          "400": {
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Find showers nearby",
        "tags": [
          "poi"
        ]
      }
    },
    "/shower/types": {
      "get": {
        "description": "Returns all available shower types/categories",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Successful operation"
          },
          "500": {
            "description": "Server error"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Get shower types",
        "tags": [
          "poi"
        ]
      }
    },
    "/specs/keys": {
      "get": {
        "description": "Recupera todas las claves de especificaciones t\u00e9cnicas disponibles en el sistema",
        "operationId": "getAvailableSpecificationKeys",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": [
                    "engineType",
                    "horsepower",
                    "torque",
                    "fuelType",
                    "transmission"
                  ],
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Claves de especificaci\u00f3n recuperadas exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get available specification keys",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/specs/search": {
      "post": {
        "description": "Realiza una b\u00fasqueda de veh\u00edculos basada en criterios de especificaciones t\u00e9cnicas",
        "operationId": "searchBySpecifications",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "specifications": {
                    "description": "Objeto con pares clave-valor de las especificaciones a buscar",
                    "example": {
                      "engineType": "Diesel",
                      "horsepower": 450
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "specifications"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "properties": {
                      "id": {
                        "format": "uuid",
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      },
                      "specifications": {
                        "type": "object"
                      }
                    },
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Resultados de la b\u00fasqueda recuperados exitosamente"
          },
          "400": {
            "description": "Criterios de b\u00fasqueda inv\u00e1lidos"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Search by specifications",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/stations/best-prices": {
      "get": {
        "description": "Identifica y devuelve un listado de hasta 3 estaciones de servicio que ofrecen los precios m\u00e1s competitivos para un tipo de combustible espec\u00edfico dentro de un \u00e1rea geogr\u00e1fica determinada por latitud, longitud y radio. Es \u00fatil para optimizar costes de repostaje.\n**Limitaciones:** - Radio m\u00ednimo: 15km - Radio m\u00e1ximo: 150km - M\u00e1ximo 3 resultados por petici\u00f3n\n",
        "operationId": "getBestPrices",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Radius"
          },
          {
            "$ref": "#/components/parameters/FuelType"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "_id": "5f8d3b7b3f6b8a1b9c3b7b3f",
                    "address": "Poligono Industrial Sur, 1, 28906 Getafe",
                    "fuelTypes": [
                      {
                        "code": "diesel_regular",
                        "price": 1.42,
                        "updatedAt": "2025-04-29T09:00:00Z"
                      }
                    ],
                    "label": "Repsol Economica",
                    "location": {
                      "coordinates": [
                        -3.732,
                        40.3
                      ],
                      "type": "Point"
                    }
                  },
                  {
                    "_id": "60a2c1b2d3e4f5a6b7c8d9e1",
                    "address": "Carretera de Toledo, km 15, 28905 Getafe",
                    "fuelTypes": [
                      {
                        "code": "diesel_regular",
                        "price": 1.425,
                        "updatedAt": "2025-04-29T09:15:00Z"
                      }
                    ],
                    "label": "Galp Ahorro",
                    "location": {
                      "coordinates": [
                        -3.74,
                        40.295
                      ],
                      "type": "Point"
                    }
                  }
                ],
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/OilStation"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de hasta 3 estaciones con los mejores precios"
          },
          "400": {
            "content": {
              "application/json": {
                "examples": {
                  "INVALID_COORDINATES": {
                    "summary": "Coordenadas Inv\u00e1lidas",
                    "value": {
                      "error": "Invalid coordinates"
                    }
                  },
                  "INVALID_RADIUS": {
                    "summary": "Radio Inv\u00e1lido",
                    "value": {
                      "error": "Radius must be between 15000 and 150000 meters."
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Radio fuera de l\u00edmites o par\u00e1metros inv\u00e1lidos"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Error fetching best prices"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al calcular los mejores precios"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Obtener estaciones con mejores precios en un \u00e1rea",
        "tags": [
          "stations"
        ]
      }
    },
    "/stations/brand/{icon}": {
      "get": {
        "description": "Devuelve el archivo de icono PNG asociado a una marca espec\u00edfica de estaci\u00f3n de servicio. Los iconos est\u00e1n disponibles en formato PNG.\n",
        "operationId": "getStationBrandIcon",
        "parameters": [
          {
            "description": "Identificador del icono de la marca",
            "in": "path",
            "name": "icon",
            "required": true,
            "schema": {
              "example": "iconorepsol",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "image/png": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "Archivo PNG del icono de la marca"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Icon not found"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Icono no encontrado"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal server error"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al obtener el icono"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Obtener icono de marca de estaci\u00f3n de servicio",
        "tags": [
          "stations"
        ]
      }
    },
    "/stations/brands": {
      "get": {
        "description": "Devuelve una lista de todas las marcas de estaciones de servicio disponibles en el sistema, incluyendo sus nombres e iconos asociados.\n",
        "operationId": "getStationBrands",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "icon": "iconorepsol",
                    "label": "Repsol"
                  },
                  {
                    "icon": "iconocepsa",
                    "label": "Cepsa"
                  }
                ],
                "schema": {
                  "items": {
                    "properties": {
                      "icon": {
                        "description": "Identificador del icono de la marca",
                        "example": "iconorepsol",
                        "type": "string"
                      },
                      "label": {
                        "description": "Nombre de la marca",
                        "example": "Repsol",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de marcas de estaciones de servicio"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Error fetching station brands"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al obtener las marcas de estaciones"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Obtener listado de marcas de estaciones de servicio",
        "tags": [
          "stations"
        ]
      }
    },
    "/stations/fuel-types": {
      "get": {
        "description": "Devuelve una lista de todos los tipos de combustible v\u00e1lidos y actualmente gestionados por el sistema. Esta lista puede ser utilizada para filtrar b\u00fasquedas o presentar opciones al usuario.\n",
        "operationId": "getFuelTypes",
        "parameters": [
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  "gnc",
                  "gnl",
                  "gpl",
                  "hydrogen",
                  "bioethanol",
                  "biodiesel",
                  "diesel_regular",
                  "diesel_premium",
                  "gasoline_95",
                  "gasoline_98"
                ],
                "schema": {
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de tipos de combustible disponibles"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Server error fetching fuel types."
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error del servidor al obtener los tipos de combustible."
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Obtener lista de tipos de combustible",
        "tags": [
          "stations"
        ]
      }
    },
    "/stations/getAlongPath": {
      "post": {
        "description": "Busca y devuelve una lista de estaciones de servicio que se encuentran cercanas a una secuencia de puntos GPS que definen una ruta. El sistema optimiza la b\u00fasqueda filtrando puntos redundantes (aquellos que est\u00e1n a menos de 25km entre s\u00ed) y es especialmente \u00fatil para la planificaci\u00f3n de repostajes en rutas largas de transporte.\n**Casos de uso:** - Planificaci\u00f3n de rutas para transportistas - Optimizaci\u00f3n de paradas para repostaje - Integraci\u00f3n con sistemas de navegaci\u00f3n\n**Caracter\u00edsticas:** - Filtra puntos redundantes (m\u00e1s cercanos de 25km) - Devuelve estaciones cercanas a cada punto de la ruta - Optimizado para rutas largas de transporte - Opci\u00f3n de optimizar paradas (solo estaciones con mejores precios)\n",
        "operationId": "getAlongPath",
        "parameters": [
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "fuel_type": "diesel_regular",
                "optimizedStops": true,
                "points": [
                  [
                    -3.7038,
                    40.4168
                  ],
                  [
                    -0.1278,
                    51.5074
                  ]
                ]
              },
              "schema": {
                "properties": {
                  "fuel_type": {
                    "default": "diesel_regular",
                    "description": "Tipo de combustible a buscar. Requerido para filtrar las estaciones a lo largo de la ruta.",
                    "enum": [
                      "diesel_regular",
                      "diesel_premium",
                      "gasoline_95",
                      "gasoline_98"
                    ],
                    "example": "diesel_regular",
                    "type": "string"
                  },
                  "optimizedStops": {
                    "default": false,
                    "description": "Si es `true`, solo devuelve las estaciones con los precios m\u00e1s competitivos (en el 30% m\u00e1s bajo del rango encontrado). Ideal para optimizar costes.",
                    "example": true,
                    "type": "boolean"
                  },
                  "points": {
                    "description": "Array de puntos GPS en formato `[[lng,lat],...]` que definen la ruta. M\u00e1ximo 100 puntos.",
                    "items": {
                      "items": {
                        "format": "float",
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "maxItems": 100,
                    "type": "array"
                  }
                },
                "required": [
                  "points",
                  "fuel_type"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "_id": "5f8d3b7b3f6b8a1b9c3b7b3f",
                    "address": "Autovia A1, Km 100, 09001 Burgos",
                    "fuelTypes": [
                      {
                        "code": "diesel_regular",
                        "price": 1.47,
                        "updatedAt": "2025-04-29T12:00:00Z"
                      }
                    ],
                    "label": "Repsol Ruta Norte",
                    "location": {
                      "coordinates": [
                        -3.688,
                        42.343
                      ],
                      "type": "Point"
                    }
                  },
                  {
                    "_id": "60a2c1b2d3e4f5a6b7c8d9e2",
                    "address": "N-620, Km 50, 34001 Palencia",
                    "fuelTypes": [
                      {
                        "code": "gasoline_98",
                        "price": 1.65,
                        "updatedAt": "2025-04-29T12:30:00Z"
                      }
                    ],
                    "label": "Cepsa Parada Viajero",
                    "location": {
                      "coordinates": [
                        -4.526,
                        42.01
                      ],
                      "type": "Point"
                    }
                  }
                ],
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/OilStation"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de estaciones encontradas a lo largo de la ruta"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "GPS points array is required"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error en los par\u00e1metros. Posibles causas:\n- Formato incorrecto de puntos GPS\n- N\u00famero de puntos excede el l\u00edmite\n"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Unknown error"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al procesar la ruta"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Obtener estaciones a lo largo de una ruta",
        "tags": [
          "stations"
        ]
      }
    },
    "/stations/nearby": {
      "get": {
        "description": "Busca estaciones de servicio dentro de un radio especificado alrededor de unas coordenadas GPS. Ideal para encontrar opciones de repostaje inmediatas.\n**Ejemplo de uso:** - Planificaci\u00f3n de rutas de transporte - Localizar opciones de repostaje cercanas - Integraci\u00f3n con sistemas de navegaci\u00f3n\n",
        "operationId": "getNearbyStations",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Radius"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "_id": "5f8d3b7b3f6b8a1b9c3b7b3f",
                    "address": "Calle Mayor, 123, 28013 Madrid",
                    "fuelTypes": [
                      {
                        "code": "diesel_regular",
                        "price": 1.459,
                        "updatedAt": "2025-04-29T10:30:00Z"
                      }
                    ],
                    "label": "Repsol Calle Mayor",
                    "location": {
                      "coordinates": [
                        -3.7038,
                        40.4168
                      ],
                      "type": "Point"
                    }
                  }
                ],
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/OilStation"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de estaciones encontradas ordenadas por distancia"
          },
          "400": {
            "content": {
              "application/json": {
                "examples": {
                  "INVALID_COORDINATES": {
                    "summary": "Coordenadas Inv\u00e1lidas",
                    "value": {
                      "error": "Invalid coordinates"
                    }
                  },
                  "RADIUS_OUT_OF_BOUNDS": {
                    "summary": "Radio Fuera de L\u00edmites",
                    "value": {
                      "error": "Radius must be between 1000 and 150000 meters."
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error en los par\u00e1metros de entrada. Posibles causas:\n- Coordenadas inv\u00e1lidas o fuera de rango\n- Radio de b\u00fasqueda fuera de l\u00edmites\n"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Error fetching nearby stations"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error temporal del servidor. Intente nuevamente m\u00e1s tarde."
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Obtener estaciones cercanas por ubicaci\u00f3n",
        "tags": [
          "stations"
        ]
      }
    },
    "/stations/province": {
      "get": {
        "description": "Analiza y devuelve los precios m\u00e1s bajos encontrados para un tipo de combustible espec\u00edfico en cada provincia del pa\u00eds seleccionado. Los resultados se presentan ordenados de menor a mayor precio, incluyendo el identificador de la provincia para facilitar su uso en integraciones.\n**Notas:** - Los resultados est\u00e1n ordenados de menor a mayor precio - Incluye identificador de provincia para facilitar integraciones\n",
        "operationId": "getCheapestProvince",
        "parameters": [
          {
            "$ref": "#/components/parameters/FuelType"
          },
          {
            "$ref": "#/components/parameters/Language"
          },
          {
            "$ref": "#/components/parameters/Country"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "properties": {
                      "price": {
                        "description": "Precio m\u00ednimo encontrado en la provincia",
                        "example": 1.429,
                        "format": "float",
                        "type": "number"
                      },
                      "province": {
                        "description": "Nombre de la provincia",
                        "example": "Madrid",
                        "type": "string"
                      },
                      "provinceId": {
                        "description": "Identificador \u00fanico de la provincia",
                        "example": "28",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de provincias con sus precios m\u00ednimos"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Error fetching prices by province"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al calcular los precios por provincia"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Obtener precios m\u00e1s bajos por provincia",
        "tags": [
          "stations"
        ]
      }
    },
    "/stations/search": {
      "get": {
        "description": "Permite una b\u00fasqueda avanzada de estaciones de servicio aplicando m\u00faltiples criterios como tipo de combustible, ubicaci\u00f3n geogr\u00e1fica (latitud, longitud, radio) y un t\u00e9rmino de b\u00fasqueda libre que puede coincidir con el nombre de la estaci\u00f3n, direcci\u00f3n, ciudad o provincia. Los resultados se ordenan por distancia a las coordenadas proporcionadas.\n**Caracter\u00edsticas especiales:** - B\u00fasqueda por texto en direcci\u00f3n, ciudad, provincia o nombre - Filtrado por tipo de combustible disponible - Ordenaci\u00f3n por distancia a las coordenadas proporcionadas\n",
        "operationId": "searchStations",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Radius"
          },
          {
            "$ref": "#/components/parameters/FuelType"
          },
          {
            "$ref": "#/components/parameters/Language"
          },
          {
            "$ref": "#/components/parameters/SearchQuery"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "_id": "5f8d3b7b3f6b8a1b9c3b7b3f",
                    "address": "Calle Mayor, 123, 28013 Madrid",
                    "fuelTypes": [
                      {
                        "code": "diesel_regular",
                        "price": 1.459,
                        "updatedAt": "2025-04-29T10:30:00Z"
                      }
                    ],
                    "label": "Repsol Calle Mayor",
                    "location": {
                      "coordinates": [
                        -3.7038,
                        40.4168
                      ],
                      "type": "Point"
                    }
                  },
                  {
                    "_id": "60a2c1b2d3e4f5a6b7c8d9e0",
                    "address": "Av. de Am\u00e9rica, 10, 28028 Madrid",
                    "fuelTypes": [
                      {
                        "code": "gasoline_95",
                        "price": 1.52,
                        "updatedAt": "2025-04-29T11:00:00Z"
                      },
                      {
                        "code": "diesel_premium",
                        "price": 1.55,
                        "updatedAt": "2025-04-29T11:05:00Z"
                      }
                    ],
                    "label": "Cepsa Av. de America",
                    "location": {
                      "coordinates": [
                        -3.6738,
                        40.4312
                      ],
                      "type": "Point"
                    }
                  }
                ],
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/OilStation"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de estaciones que coinciden con los criterios de b\u00fasqueda"
          },
          "400": {
            "content": {
              "application/json": {
                "examples": {
                  "INVALID_COORDINATES": {
                    "summary": "Coordenadas Inv\u00e1lidas",
                    "value": {
                      "error": "Invalid coordinates"
                    }
                  },
                  "INVALID_FUEL_TYPE": {
                    "summary": "Tipo de Combustible Inv\u00e1lido",
                    "value": {
                      "error": "Invalid fuel type"
                    }
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Par\u00e1metros de b\u00fasqueda inv\u00e1lidos"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "No nearby stations found"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "No se encontraron estaciones que coincidan"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Error searching for stations"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al procesar la b\u00fasqueda"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Buscar estaciones por filtros",
        "tags": [
          "stations"
        ]
      }
    },
    "/stations/{id}": {
      "get": {
        "description": "Recupera informaci\u00f3n detallada y actualizada de una estaci\u00f3n de servicio espec\u00edfica, identificada por su ID \u00fanico (MongoDB ObjectId). Esto incluye su ubicaci\u00f3n precisa, la lista completa de combustibles que ofrece con sus precios actuales, y potencialmente horarios de apertura si est\u00e1n disponibles.\n**Incluye:** - Informaci\u00f3n de ubicaci\u00f3n exacta - Lista completa de combustibles disponibles - Precios actualizados - Horarios de apertura (si disponibles)\n",
        "operationId": "getStationDetails",
        "parameters": [
          {
            "description": "ID \u00fanico de la estaci\u00f3n (MongoDB ObjectId)",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "example": "5f8d3b7b3f6b8a1b9c3b7b3f",
              "pattern": "^[0-9a-fA-F]{24}$",
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "_id": "5f8d3b7b3f6b8a1b9c3b7b3f",
                  "address": "Calle Mayor, 123, 28013 Madrid",
                  "fuelTypes": [
                    {
                      "code": "diesel_regular",
                      "price": 1.459,
                      "updatedAt": "2025-04-29T10:30:00Z"
                    },
                    {
                      "code": "gasoline_95",
                      "price": 1.589,
                      "updatedAt": "2025-04-29T10:32:00Z"
                    }
                  ],
                  "label": "Repsol Calle Mayor",
                  "location": {
                    "coordinates": [
                      -3.7038,
                      40.4168
                    ],
                    "type": "Point"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/OilStation"
                }
              }
            },
            "description": "Detalles completos de la estaci\u00f3n solicitada"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Station ID required"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "ID de estaci\u00f3n inv\u00e1lido o mal formado"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Station not found"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Estaci\u00f3n no encontrada"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Error fetching station details"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Error al recuperar los detalles"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Obtener detalles de una estaci\u00f3n espec\u00edfica",
        "tags": [
          "stations"
        ]
      }
    },
    "/test": {
      "get": {
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "status": {
                      "example": "ok",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Servicio saludable"
          }
        },
        "summary": "Alias de health check",
        "tags": [
          "route"
        ]
      }
    },
    "/token": {
      "get": {
        "deprecated": true,
        "description": "**\u26a0\ufe0f DEPRECATED:** Use `/api/auth/login` instead.\nEndpoint legacy para obtener token JWT.\n",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            },
            "description": "Token obtenido exitosamente"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Obtener token JWT (legacy)",
        "tags": [
          "iam"
        ]
      }
    },
    "/traffic/current": {
      "get": {
        "description": "Retrieve current traffic flow information",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Radius"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrafficFlowData"
                }
              }
            },
            "description": "Current traffic flow data"
          },
          "400": {
            "description": "Invalid parameters"
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get current traffic flow",
        "tags": [
          "traffic"
        ]
      }
    },
    "/traffic/getAlongPath": {
      "post": {
        "description": "Retrieve traffic flow information along a specified route",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "lang": {
                    "default": "es",
                    "description": "Language for response data",
                    "enum": [
                      "es",
                      "en",
                      "fr",
                      "de",
                      "pt"
                    ],
                    "type": "string"
                  },
                  "points": {
                    "description": "Array of coordinate pairs [longitude, latitude]",
                    "items": {
                      "items": {
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "minItems": 2,
                    "type": "array"
                  }
                },
                "required": [
                  "points"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/TrafficFlowData"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Traffic flow data along the path"
          },
          "400": {
            "description": "Invalid route"
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get traffic flow along a path",
        "tags": [
          "traffic"
        ]
      }
    },
    "/traffic/nearby": {
      "get": {
        "description": "Retrieve traffic flow information near a location",
        "parameters": [
          {
            "$ref": "#/components/parameters/Latitude"
          },
          {
            "$ref": "#/components/parameters/Longitude"
          },
          {
            "$ref": "#/components/parameters/Radius"
          },
          {
            "$ref": "#/components/parameters/Language"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrafficFlowData"
                }
              }
            },
            "description": "Traffic flow data near the location"
          },
          "400": {
            "description": "Invalid parameters"
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get nearby traffic flow",
        "tags": [
          "traffic"
        ]
      }
    },
    "/trims/{seriesId}": {
      "get": {
        "description": "Recupera todos los 'trims' (acabados) disponibles para una serie de veh\u00edculos espec\u00edfica",
        "operationId": "getTrimsBySeries",
        "parameters": [
          {
            "description": "ID de la serie",
            "in": "path",
            "name": "seriesId",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "properties": {
                      "id": {
                        "format": "uuid",
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      },
                      "specifications": {
                        "type": "object"
                      }
                    },
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Trims (acabados) recuperados exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Serie no encontrada"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get trims by series",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/trims/{trimId}": {
      "get": {
        "description": "Recupera los detalles completos de un 'trim' (acabado) espec\u00edfico, incluyendo todas sus especificaciones t\u00e9cnicas",
        "operationId": "getTrimById",
        "parameters": [
          {
            "description": "ID del 'trim' (acabado)",
            "in": "path",
            "name": "trimId",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "id": {
                      "format": "uuid",
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "series": {
                      "properties": {
                        "id": {
                          "format": "uuid",
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "specifications": {
                      "example": {
                        "other": {
                          "fuel_consumption": "40"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Trim (acabado) recuperado exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Trim (acabado) no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get trim by ID",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/types": {
      "get": {
        "description": "Recupera todos los tipos de veh\u00edculos disponibles y sus configuraciones",
        "operationId": "getVehicleTypes",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": {
                    "$ref": "#/components/schemas/VehicleType"
                  },
                  "type": "object"
                }
              }
            },
            "description": "Lista de tipos recuperada exitosamente"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get all vehicle types",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/vehicles": {
      "get": {
        "description": "Recupera todos los veh\u00edculos registrados en el sistema con paginaci\u00f3n. \nSe puede filtrar por estado usando el par\u00e1metro de consulta ?status=[active|maintenance|out_of_service].\nPar\u00e1metros de paginaci\u00f3n:\n- page: N\u00famero de p\u00e1gina (por defecto 1)\n- limit: L\u00edmite de elementos por p\u00e1gina (por defecto 10)\n\nEjemplo de respuesta:\n```json\n[\n  {\n    \"id\": \"5f8d0d55b54764421b7156c3\",\n    \"model\": \"Volvo FH 540\",\n    \"licensePlate\": \"5678XYZ\",\n    \"status\": \"active\",\n    \"maxWeight\": 40.0,\n    \"dimensions\": {\n      \"length\": 16.5,\n      \"width\": 2.55,\n      \"height\": 4.0\n    }\n  }\n]\n```\n",
        "operationId": "getVehicles",
        "parameters": [
          {
            "description": "Filtrar los veh\u00edculos por estado operativo",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "active",
                "maintenance",
                "out_of_service"
              ],
              "type": "string"
            }
          },
          {
            "description": "N\u00famero de p\u00e1gina",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "L\u00edmite de elementos por p\u00e1gina",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 10,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "dimensions": {
                      "height": 4.0,
                      "length": 16.5,
                      "width": 2.55
                    },
                    "id": "5f8d0d55b54764421b7156c3",
                    "licensePlate": "5678XYZ",
                    "maxWeight": 40.0,
                    "model": "Volvo FH 540",
                    "status": "active"
                  },
                  {
                    "dimensions": {
                      "height": 3.9,
                      "length": 12.8,
                      "width": 2.5
                    },
                    "id": "6g9e1e66c65875532c8267d4",
                    "licensePlate": "4321ABC",
                    "maxWeight": 26.0,
                    "model": "MAN TGX 26.480",
                    "status": "maintenance"
                  }
                ],
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Vehicle"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Lista de veh\u00edculos recuperada exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get list of vehicles",
        "tags": [
          "vehicles"
        ]
      },
      "post": {
        "description": "Registra un nuevo veh\u00edculo en el sistema. Requiere autenticaci\u00f3n JWT con el rol 'fleet_manager'.\n\nEjemplo de uso:\n```json\n{\n  \"model\": \"Volvo FH 540\",\n  \"licensePlate\": \"5678XYZ\",\n  \"status\": \"active\",\n  \"maxWeight\": 40.0,\n  \"dimensions\": {\n    \"length\": 16.5,\n    \"width\": 2.55,\n    \"height\": 4.0\n  }\n}\n```\n",
        "operationId": "createVehicle",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "dimensions": {
                  "height": 4.0,
                  "length": 16.5,
                  "width": 2.55
                },
                "licensePlate": "5678XYZ",
                "maxWeight": 40.0,
                "model": "Volvo FH 540",
                "status": "active"
              },
              "schema": {
                "$ref": "#/components/schemas/Vehicle"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "example": {
                  "dimensions": {
                    "height": 4.0,
                    "length": 16.5,
                    "width": 2.55
                  },
                  "id": "5f8d0d55b54764421b7156c3",
                  "licensePlate": "5678XYZ",
                  "maxWeight": 40.0,
                  "model": "Volvo FH 540",
                  "status": "active"
                },
                "schema": {
                  "$ref": "#/components/schemas/Vehicle"
                }
              }
            },
            "description": "Veh\u00edculo creado exitosamente"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Datos de veh\u00edculo inv\u00e1lidos"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Create a new vehicle",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/vehicles/specs/model-id/{modelId}": {
      "get": {
        "description": "Endpoint alternativo para recuperar especificaciones t\u00e9cnicas por ID de modelo",
        "operationId": "getSpecsByModelIdAlt",
        "parameters": [
          {
            "description": "ID del modelo",
            "in": "path",
            "name": "modelId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Especificaciones recuperadas exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Modelo no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get specifications by model ID (alternative)",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/vehicles/specs/name/{modelName}": {
      "get": {
        "description": "Recupera las especificaciones t\u00e9cnicas de un modelo espec\u00edfico por su nombre",
        "operationId": "getSpecsByModelName",
        "parameters": [
          {
            "description": "Nombre del modelo",
            "in": "path",
            "name": "modelName",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Especificaciones recuperadas exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Modelo no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get specifications by model name",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/vehicles/specs/{modelId}": {
      "get": {
        "description": "Recupera las especificaciones t\u00e9cnicas de un modelo espec\u00edfico por su ID",
        "operationId": "getSpecsByModelId",
        "parameters": [
          {
            "description": "ID del modelo",
            "in": "path",
            "name": "modelId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Especificaciones recuperadas exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Modelo no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get specifications by model ID",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/vehicles/vehicle-types/{id}": {
      "get": {
        "description": "Recupera la etiqueta descriptiva de un tipo de veh\u00edculo por su ID",
        "operationId": "getVehicleTypeLabel",
        "parameters": [
          {
            "description": "ID del tipo de veh\u00edculo",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Etiqueta de tipo de veh\u00edculo recuperada exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Tipo de veh\u00edculo no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get vehicle type label",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/vehicles/{id}": {
      "delete": {
        "description": "Elimina permanentemente un veh\u00edculo del sistema",
        "operationId": "deleteVehicle",
        "parameters": [
          {
            "$ref": "#/components/parameters/VehicleId"
          }
        ],
        "responses": {
          "204": {
            "description": "Veh\u00edculo eliminado exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Veh\u00edculo no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Delete vehicle",
        "tags": [
          "vehicles"
        ]
      },
      "get": {
        "description": "Recupera los detalles de un veh\u00edculo espec\u00edfico por su ID",
        "operationId": "getVehicleById",
        "parameters": [
          {
            "$ref": "#/components/parameters/VehicleId"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vehicle"
                }
              }
            },
            "description": "Veh\u00edculo recuperado exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Veh\u00edculo no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get vehicle by ID",
        "tags": [
          "vehicles"
        ]
      },
      "put": {
        "description": "Modifica los datos de un veh\u00edculo existente",
        "operationId": "updateVehicle",
        "parameters": [
          {
            "$ref": "#/components/parameters/VehicleId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Vehicle"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vehicle"
                }
              }
            },
            "description": "Veh\u00edculo actualizado exitosamente"
          },
          "400": {
            "description": "Datos de veh\u00edculo inv\u00e1lidos"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Veh\u00edculo no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Update vehicle",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/vehicles/{id}/enabled": {
      "patch": {
        "description": "Activa o desactiva el estado 'enabled' de un veh\u00edculo",
        "operationId": "toggleVehicleEnabled",
        "parameters": [
          {
            "$ref": "#/components/parameters/VehicleId"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "id": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Estado del veh\u00edculo actualizado exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Veh\u00edculo no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Toggle vehicle 'enabled' status",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/vehicles/{id}/position": {
      "get": {
        "description": "Recupera la \u00faltima posici\u00f3n GPS registrada del veh\u00edculo",
        "operationId": "getCurrentPosition",
        "parameters": [
          {
            "$ref": "#/components/parameters/VehicleId"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "latitude": {
                      "type": "number"
                    },
                    "longitude": {
                      "type": "number"
                    },
                    "timestamp": {
                      "format": "date-time",
                      "type": "string"
                    },
                    "vehicleId": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Posici\u00f3n recuperada exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Veh\u00edculo o posici\u00f3n no encontrada"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get current vehicle position",
        "tags": [
          "vehicles"
        ]
      },
      "put": {
        "description": "Registra una nueva posici\u00f3n GPS para el veh\u00edculo",
        "operationId": "updatePosition",
        "parameters": [
          {
            "$ref": "#/components/parameters/VehicleId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "latitude": {
                    "description": "Latitud de la nueva posici\u00f3n",
                    "maximum": 90,
                    "minimum": -90,
                    "type": "number"
                  },
                  "longitude": {
                    "description": "Longitud de la nueva posici\u00f3n",
                    "maximum": 180,
                    "minimum": -180,
                    "type": "number"
                  }
                },
                "required": [
                  "latitude",
                  "longitude"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "latitude": {
                      "type": "number"
                    },
                    "longitude": {
                      "type": "number"
                    },
                    "timestamp": {
                      "format": "date-time",
                      "type": "string"
                    },
                    "vehicleId": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Posici\u00f3n actualizada exitosamente"
          },
          "400": {
            "description": "Datos de posici\u00f3n inv\u00e1lidos"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Veh\u00edculo no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Update vehicle position",
        "tags": [
          "vehicles"
        ]
      }
    },
    "/vehicles/{id}/position/history": {
      "get": {
        "description": "Recupera el historial de posiciones GPS del veh\u00edculo",
        "operationId": "getVehiclePositionHistory",
        "parameters": [
          {
            "$ref": "#/components/parameters/VehicleId"
          },
          {
            "description": "Fecha de inicio del historial",
            "in": "query",
            "name": "startDate",
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Fecha de fin del historial",
            "in": "query",
            "name": "endDate",
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "N\u00famero m\u00e1ximo de entradas a devolver",
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 100,
              "maximum": 1000,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "properties": {
                      "latitude": {
                        "type": "number"
                      },
                      "longitude": {
                        "type": "number"
                      },
                      "timestamp": {
                        "format": "date-time",
                        "type": "string"
                      },
                      "vehicleId": {
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Historial de posiciones recuperado exitosamente"
          },
          "401": {
            "description": "No autorizado - Token inv\u00e1lido o faltante"
          },
          "404": {
            "description": "Veh\u00edculo no encontrado"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get vehicle position history",
        "tags": [
          "vehicles"
        ],
        "x-docusaurus": {
          "sidebar_key": "vehicles-get-position-history"
        }
      }
    },
    "/workshop/getAlongPath": {
      "post": {
        "description": "Devuelve talleres cerca de una ruta especificada",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "path": {
                    "description": "Array de puntos [longitud, latitud] que definen la ruta",
                    "example": [
                      [
                        -3.70379,
                        40.416775
                      ],
                      [
                        -3.70123,
                        40.41789
                      ],
                      [
                        -3.69876,
                        40.41901
                      ]
                    ],
                    "items": {
                      "items": {
                        "format": "double",
                        "type": "number"
                      },
                      "maxItems": 2,
                      "minItems": 2,
                      "type": "array"
                    },
                    "type": "array"
                  },
                  "radius": {
                    "default": 10000,
                    "description": "Radio de b\u00fasqueda en metros desde la ruta. M\u00edn 1000m, M\u00e1x 50000m. Por defecto 10000m (10km).",
                    "format": "float",
                    "maximum": 50000,
                    "minimum": 1000,
                    "type": "number"
                  }
                },
                "required": [
                  "path"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Workshop"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Operaci\u00f3n exitosa"
          },
          "400": {
            "description": "Cuerpo de petici\u00f3n o par\u00e1metros inv\u00e1lidos"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Find workshops along a path",
        "tags": [
          "poi"
        ]
      }
    },
    "/workshop/nearby": {
      "get": {
        "description": "Devuelve talleres dentro de un radio dado de coordenadas",
        "parameters": [
          {
            "description": "Latitude coordinate",
            "example": 40.416775,
            "in": "query",
            "name": "lat",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 90,
              "minimum": -90,
              "type": "number"
            }
          },
          {
            "description": "Longitude coordinate",
            "example": -3.70379,
            "in": "query",
            "name": "lng",
            "required": true,
            "schema": {
              "format": "double",
              "maximum": 180,
              "minimum": -180,
              "type": "number"
            }
          },
          {
            "description": "Radio de b\u00fasqueda en metros. M\u00edn 1000m, M\u00e1x 250000m. Por defecto 250000m (250km).",
            "in": "query",
            "name": "radius",
            "schema": {
              "default": 250000,
              "format": "float",
              "maximum": 250000,
              "minimum": 1000,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Workshop"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Operaci\u00f3n exitosa"
          },
          "400": {
            "description": "Par\u00e1metros inv\u00e1lidos"
          },
          "500": {
            "description": "Error del servidor"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Find workshops nearby",
        "tags": [
          "poi"
        ]
      }
    }
  },
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "servers": [
    {
      "url": "https://back.transcend.cargoffer.com",
      "description": "Producci\u00f3n"
    }
  ],
  "tags": [
    {
      "description": "API del m\u00f3dulo Activity de TransCend. Este m\u00f3dulo es responsable del registro y consulta de actividades de usuario dentro del sistema TransCend.\n\n**Prop\u00f3sito principal:**\n- Registrar todas las actividades realizadas por usuarios en el sistema\n- Proporcionar consultas paginadas de actividades por usuario\n- Facilitar auditor\u00eda y an\u00e1lisis de comportamiento de usuarios\n- Soporte para debugging y monitoreo de uso del sistema\n\n**Tipos de actividades registradas:**\n- C: Create (Creaci\u00f3n de recursos)\n- R: Read (Lectura/Consulta de recursos)\n- U: Update (Actualizaci\u00f3n de recursos)\n- D: Delete (Eliminaci\u00f3n de recursos)\n\n**Caracter\u00edsticas principales:**\n- Autenticaci\u00f3n JWT opcional (permite consultas p\u00fablicas limitadas)\n- Paginaci\u00f3n autom\u00e1tica con mongoose-paginate-v2\n- Filtros por fecha (startDate/endDate)\n- Validaci\u00f3n estricta de tipos de actividad y sistemas operativos\n",
      "name": "activity"
    },
    {
      "description": "# Transcend Drivers Module API - Documentaci\u00f3n Completa\n\nEste API es parte de **TransCend** y corresponde al m\u00f3dulo de gesti\u00f3n de conductores. Proporciona operaciones CRUD para conductores, seguimiento de posici\u00f3n en tiempo real, gesti\u00f3n de horas de conducci\u00f3n y registro completo de historial de conducci\u00f3n con datos de tac\u00f3grafo.\n\n## \ud83c\udfaf Prop\u00f3sito Principal\nCentralizar toda la informaci\u00f3n relacionada con los conductores de la plataforma, incluyendo:\n- Datos personales y licencias\n- Ubicaci\u00f3n actual y seguimiento GPS\n- Horas de servicio y cumplimiento normativo\n- Historial completo de conducci\u00f3n con datos de tac\u00f3grafo\n- An\u00e1lisis de cumplimiento y detecci\u00f3n de infracciones\n\n## \ud83d\udd10 Autenticaci\u00f3n\nTodas las rutas requieren autenticaci\u00f3n mediante:\n1. **JWT Token** (preferido): Token obtenido del m\u00f3dulo IAM\n2. **API Key**: Para integraciones de servicios externos\n\n## \ud83d\udccb Convenciones de Respuesta\n- \u00c9xito: `200` o `201` con datos en formato JSON\n- Error: `4xx` o `5xx` con objeto `{ error: \\\"mensaje descriptivo\\\" }`\n- Validaci\u00f3n: `400` para datos inv\u00e1lidos, `404` para recursos no encontrados\n\n## \u26a0\ufe0f Limitaciones y Cuotas\n- M\u00e1ximo 1000 conductores por usuario\n- Actualizaci\u00f3n de posici\u00f3n: 1 por minuto por conductor\n- Consultas de posici\u00f3n: 10 por segundo por usuario\n- Historial de posiciones: Retenido por 30 d\u00edas\n",
      "name": "drivers"
    },
    {
      "description": "# API de B\u00fasqueda de C\u00f3digos HS - Transcend\n\n## \ud83c\udfaf Prop\u00f3sito Principal\nEste API es parte de la plataforma **Transcend** y proporciona acceso completo a la jerarqu\u00eda del Sistema Armonizado (HS Code) utilizado en comercio internacional. Permite a desarrolladores integrar b\u00fasquedas de c\u00f3digos aduaneros en sus aplicaciones.\n\n## \ud83d\udcca Estructura Jer\u00e1rquica\nEl sistema organiza los c\u00f3digos HS en 4 niveles anidados:\n1. **Secciones** - Nivel m\u00e1s alto (21 secciones totales, ej. \"Secci\u00f3n XI - Textiles\")\n2. **Cap\u00edtulos** - Dentro de secciones (99 cap\u00edtulos, ej. \"Cap\u00edtulo 52 - Algod\u00f3n\")\n3. **Partidas** - Dentro de cap\u00edtulos (ej. \"Algod\u00f3n\")\n4. **Subpartidas** - Nivel m\u00e1s granular (ej. \"Algod\u00f3n sin cardar ni peinar\")\n\n## \ud83d\udd10 Autenticaci\u00f3n Dual\n**IMPORTANTE**: Esta API acepta **DOS m\u00e9todos de autenticaci\u00f3n** (usa solo uno):\n- **M\u00e9todo 1**: Token JWT (para usuarios humanos)\n- **M\u00e9todo 2**: API Key (para integraciones autom\u00e1ticas)\n\n## \ud83d\ude80 Casos de Uso Comunes\n- Integraci\u00f3n en sistemas de facturaci\u00f3n electr\u00f3nica\n- Automatizaci\u00f3n de declaraciones aduaneras\n- B\u00fasqueda de c\u00f3digos para cotizaciones internacionales\n- Validaci\u00f3n de clasificaci\u00f3n arancelaria\n\n## \ud83d\udccb Requisitos T\u00e9cnicos\n- Todas las respuestas son en formato JSON\n- Codificaci\u00f3n: UTF-8\n- Timeout recomendado: 30 segundos\n- Rate limiting: 100 requests/minuto por cliente\n",
      "name": "hscode"
    },
    {
      "description": "# Sistema de Gesti\u00f3n de Identidad y Acceso (IAM) - Transcend\n\nEste API es parte del sistema **Transcend** y corresponde al m\u00f3dulo de Identity and Access Management (IAM).\nProporciona endpoints completos para la gesti\u00f3n de usuarios, autenticaci\u00f3n, empresas y API keys.\n\n## Funcionalidades principales:\n\n### 1. Autenticaci\u00f3n y Gesti\u00f3n de Usuarios\n- Registro de nuevos usuarios con creaci\u00f3n autom\u00e1tica de empresa\n- Login con JWT tokens\n- Recuperaci\u00f3n y reset de contrase\u00f1as\n- Gesti\u00f3n de perfiles de usuario\n- Validaci\u00f3n y renovaci\u00f3n de tokens\n\n### 2. Gesti\u00f3n de Empresas\n- Creaci\u00f3n y actualizaci\u00f3n de informaci\u00f3n de empresa\n- Gesti\u00f3n de usuarios dentro de la empresa\n- Configuraci\u00f3n de webhooks para notificaciones\n\n### 3. Gesti\u00f3n de API Keys\n- Generaci\u00f3n de API keys para integraciones\n- Verificaci\u00f3n de API keys desde otros sistemas\n- Activaci\u00f3n/desactivaci\u00f3n de API keys\n\n## Flujos t\u00edpicos de uso:\n\n### Para usuarios nuevos:\n1. Registro en `/api/auth/register` con datos de empresa\n2. Login en `/api/auth/login` para obtener token JWT\n3. Configurar empresa en `/api/company/`\n4. Crear API key en `/api/apikeys/` para integraciones\n\n### Para integraciones externas:\n1. Autenticaci\u00f3n con API key en header `x-api-key`\n2. Verificaci\u00f3n de API key en `/api/apikeys/verify`\n3. Uso de endpoints protegidos seg\u00fan permisos\n",
      "name": "iam"
    },
    {
      "description": "Este API es parte de **Transcend** y corresponde al m\u00f3dulo de procesamiento de pagos y facturaci\u00f3n.\n\nSu principal misi\u00f3n es gestionar todos los aspectos relacionados con los pagos, suscripciones,\nfacturaci\u00f3n y planes de precios para los servicios de Transcend. Proporciona endpoints para:\n- Gesti\u00f3n de suscripciones y planes de precios\n- Procesamiento de pagos mediante Stripe\n- Facturaci\u00f3n y gesti\u00f3n de facturas\n- Seguimiento de uso y m\u00e9tricas de consumo\n- Gesti\u00f3n de perfiles de usuario y clientes Stripe\n",
      "name": "pay"
    },
    {
      "description": "Esta API es parte de **Transcend** y corresponde al m\u00f3dulo de Puntos de Inter\u00e9s (POI).\n\nProporciona endpoints para buscar Puntos de Inter\u00e9s (POIs), con un enfoque principal actual en aparcamientos para camiones. Las b\u00fasquedas se pueden realizar por:\n- Ubicaci\u00f3n (coordenadas)\n- C\u00f3digo postal (para aparcamientos)\n- Provincia (para aparcamientos)\n- Proximidad a una ruta (path)\n- Cercan\u00eda a una ciudad\n\nLa estructura de la API est\u00e1 dise\u00f1ada para soportar diversos tipos de POIs (ej. aparcamientos, gasolineras, restaurantes), aunque la implementaci\u00f3n actual se centra en aparcamientos para varias rutas.\n\n**Nota**: \n- Todos los endpoints excepto /test y /health requieren autenticaci\u00f3n mediante token JWT.\n",
      "name": "poi"
    },
    {
      "description": "Este API es parte de **Transcend** y corresponde al m\u00f3dulo de gesti\u00f3n de perfiles.\n\nSu principal funci\u00f3n es proporcionar una interfaz para buscar y navegar por la jerarqu\u00eda completa\nde c\u00f3digos HS, que incluye:\n- Secciones\n- Cap\u00edtulos\n- Partidas\n- Subpartidas\n\nLa API permite tanto obtener la estructura completa como realizar b\u00fasquedas espec\u00edficas\npor t\u00e9rminos en cualquier nivel de la jerarqu\u00eda.\n\n**Nota**: \n- La API requiere autenticaci\u00f3n mediante un token JWT.\n- Los endpoints est\u00e1n protegidos y requieren autenticaci\u00f3n.\n",
      "name": "profiles"
    },
    {
      "description": "# Transcend Route API - Documentaci\u00f3n Completa\n\n## Descripci\u00f3n General\n\nLa API de Transcend Route calcula rutas optimizadas para transporte de mercanc\u00edas por carretera, considerando restricciones de veh\u00edculos, normativa EU de tiempos de conducci\u00f3n, y puntos negros de tr\u00e1fico.\n\n## Autenticaci\u00f3n\n\nLa API requiere autenticaci\u00f3n mediante:\n- **JWT Bearer Token**: `Authorization: Bearer <token>`\n- **API Key**: `x-api-key: <api-key>`\n\n## Caracter\u00edsticas principales\n- C\u00e1lculo de rutas optimizadas para camiones\n- Gesti\u00f3n autom\u00e1tica de paradas de descanso seg\u00fan normativa EU\n- Integraci\u00f3n con puntos negros de tr\u00e1fico\n- Isocronas para an\u00e1lisis de cobertura\n- Exportaci\u00f3n de rutas en formato GPX\n\n## Normativa EU de tiempos de conducci\u00f3n\nLa API calcula autom\u00e1ticamente las paradas obligatorias seg\u00fan la normativa europea:\n- **Parada corta**: 45 minutos despu\u00e9s de 4.5 horas de conducci\u00f3n\n- **Descanso diario**: 11 horas (puede ser 9h en descanso reducido)\n- **Descanso semanal**: 45 horas (puede ser 24h en descanso reducido)\n- **M\u00e1ximo semanal**: 56 horas de conducci\u00f3n\n- **M\u00e1ximo bisemanal**: 90 horas de conducci\u00f3n\n\n## Endpoints Principales\n\n### 1. C\u00e1lculo de Ruta (`GET /api/route`)\n\n**Descripci\u00f3n**: Calcula una ruta optimizada entre origen y destino.\n\n**Par\u00e1metros principales**:\n- `origin_lat`, `origin_lon`: Coordenadas de origen (requerido)\n- `destiny_lat`, `destiny_lon`: Coordenadas de destino (requerido)\n- `waypoints`: Array JSON de paradas intermedias\n- `vehicle`: Informaci\u00f3n del veh\u00edculo (dimensiones, peso)\n- `driver`: Estado de horas del conductor\n- `date`: Fecha de salida/llegada\n- `map_layers`: Opciones de ruta (evitar peajes, etc.)\n\n#### Ejemplo B\u00e1sico\n\n```bash\ncurl -X GET \"https://api.transcend.es/route?origin_lat=42.2406&origin_lon=-8.7207&destiny_lat=41.3874&destiny_lon=2.1686\" \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\"\n```\n\n#### Ejemplo Completo con Veh\u00edculo y Conductor\n\n```bash\ncurl -X GET \"https://api.transcend.es/route?\\\norigin_lat=42.2406&\\\norigin_lon=-8.7207&\\\ndestiny_lat=41.3874&\\\ndestiny_lon=2.1686&\\\nvehicle=%7B%22width%22%3A2.55%2C%22height%22%3A4.0%2C%22weight%22%3A40000%2C%22consumption%22%3A32%7D&\\\ndriver=%7B%22hoursStatus%22%3A%7B%22remainingWeeklyHours%22%3A56%2C%22remainingDayHours%22%3A9%7D%7D&\\\ndate=%7B%22type%22%3A%22departure%22%2C%22date%22%3A%222025-01-15T08%3A00%3A00Z%22%7D\" \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\"\n```\n\n#### Ejemplo con Waypoints y Mercanc\u00eda ADR\n\n```bash\ncurl -X GET \"https://api.transcend.es/route?\\\norigin_lat=40.4168&\\\norigin_lon=-3.7038&\\\ndestiny_lat=41.3851&\\\ndestiny_lon=2.1734&\\\nwaypoints=%5B%7B%22position%22%3A%5B40.5%2C-3.5%5D%2C%22customBreakMinutes%22%3A30%7D%5D&\\\nmerchandise=%7B%22hsCode%22%3A%223301%22%2C%22weight%22%3A20000%2C%22isADR%22%3Atrue%7D&\\\nmap_layers=%7B%22avoidTolls%22%3Afalse%2C%22calculateStops%22%3Atrue%7D\" \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\"\n```\n\n### 2. Puntos Negros de Tr\u00e1fico (`POST /api/traffic/blackspots/path`)\n\n**Descripci\u00f3n**: Obtiene zonas de alta peligrosidad a lo largo de una ruta.\n\n```bash\ncurl -X POST \"https://api.transcend.es/api/traffic/blackspots/path\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\" \\\n  -d '{\n    \"points\": [\n      [-3.7038, 40.4168],\n      [-3.7138, 40.4268],\n      [-3.7238, 40.4368]\n    ],\n    \"minDangerLevel\": 30\n  }'\n```\n\n### 3. Isocronas (`GET /api/isochrone`)\n\n**Descripci\u00f3n**: Calcula \u00e1reas alcanzables desde un punto.\n\n```bash\ncurl -X GET \"https://api.transcend.es/api/isochrone?lat=40.4168&lon=-3.7038&km=100&isTime=false\" \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\"\n```\n\n## Casos de Uso Empresarial\n\n### 1. Distribuci\u00f3n Urbana con M\u00faltiples Paradas\n\n**Escenario**: Cami\u00f3n de reparto que debe visitar 5 clientes en Madrid.\n\n```bash\ncurl -X GET \"https://api.transcend.es/api/route?\\\norigin_lat=40.4168&\\\norigin_lon=-3.7038&\\\ndestiny_lat=40.4168&\\\ndestiny_lon=-3.7038&\\\nwaypoints=%5B\\\n%7B%22position%22%3A%5B40.421%2C-3.692%5D%2C%22customBreakMinutes%22%3A15%7D%2C\\\n%7B%22position%22%3A%5B40.430%2C-3.710%5D%2C%22customBreakMinutes%22%3A20%7D%2C\\\n%7B%22position%22%3A%5B40.415%2C-3.725%5D%2C%22customBreakMinutes%22%3A10%7D%2C\\\n%7B%22position%22%3A%5B40.405%2C-3.715%5D%2C%22customBreakMinutes%22%3A25%7D\\\n%5D&\\\nvehicle=%7B%22width%22%3A2.3%2C%22height%22%3A3.5%2C%22weight%22%3A12000%7D&\\\ndate=%7B%22type%22%3A%22departure%22%2C%22date%22%3A%222025-01-15T08%3A00%3A00Z%22%7D\" \\\n  -H \"x-api-key: YOUR_API_KEY\"\n```\n\n### 2. Transporte Internacional con ADR\n\n**Escenario**: Mercanc\u00eda peligrosa desde Espa\u00f1a a Francia.\n\n```bash\ncurl -X GET \"https://api.transcend.es/api/route?\\\norigin_lat=41.3851&\\\norigin_lon=2.1734&\\\ndestiny_lat=43.7102&\\\ndestiny_lon=7.2620&\\\nmerchandise=%7B%22hsCode%22%3A%221201%22%2C%22weight%22%3A24000%2C%22isADR%22%3Atrue%7D&\\\nmap_layers=%7B%22avoidTunnels%22%3Atrue%2C%22avoidHighways%22%3Afalse%2C%22trafficDensity%22%3Atrue%7D&\\\nvehicle=%7B%22weight%22%3A44000%2C%22height%22%3A4.2%7D&\\\nshow_black_points=true\" \\\n  -H \"x-api-key: YOUR_API_KEY\"\n```\n\n### 3. Ruta con Llegada Programada\n\n**Escenario**: Conductor debe llegar a las 17:00 para descarga.\n\n```bash\ncurl -X GET \"https://api.transcend.es/api/route?\\\norigin_lat=37.3891&\\\norigin_lon=-5.9845&\\\ndestiny_lat=40.4168&\\\ndestiny_lon=-3.7038&\\\ndate=%7B%22type%22%3A%22arrival%22%2C%22date%22%3A%222025-01-15T17%3A00%3A00Z%22%7D&\\\ndriver=%7B%22hoursStatus%22%3A%7B%22remainingWeeklyHours%22%3A48%2C%22remainingDayHours%22%3A8%7D%7D\" \\\n  -H \"x-api-key: YOUR_API_KEY\"\n```\n\n## Gesti\u00f3n de Rutas Guardadas\n\n### Crear Ruta Guardada\n\n```bash\ncurl -X POST \"https://api.transcend.es/api/saved-routes\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\" \\\n  -d '{\n    \"title\": \"Madrid - Barcelona semanal\",\n    \"description\": \"Ruta habitual con parada en Zaragoza\",\n    \"origin\": {\n      \"coordinates\": [40.4168, -3.7038],\n      \"locality\": \"Madrid\"\n    },\n    \"destination\": {\n      \"coordinates\": [41.3851, 2.1734],\n      \"locality\": \"Barcelona\"\n    },\n    \"waypoints\": [\n      {\n        \"coordinates\": [41.6561, -0.8773],\n        \"locality\": \"Zaragoza\"\n      }\n    ]\n  }'\n```\n\n### Listar Rutas Guardadas\n\n```bash\ncurl -X GET \"https://api.transcend.es/api/saved-routes\" \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\"\n```\n\n### Exportar Ruta como GPX\n\n```bash\ncurl -X GET \"https://api.transcend.es/api/saved-routes/60f1b2b3c4d5e6f7g8h9i0j1/gpx\" \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\" \\\n  --output route.gpx\n```\n\n## Gesti\u00f3n de Ubicaciones\n\n### Crear Ubicaci\u00f3n\n\n```bash\ncurl -X POST \"https://api.transcend.es/api/locations\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\" \\\n  -d '{\n    \"name\": \"Almac\u00e9n Central Madrid\",\n    \"address\": \"Calle Industrial 25\",\n    \"city\": \"Madrid\",\n    \"province\": \"Madrid\",\n    \"latitude\": \"40.4168\",\n    \"longitude\": \"-3.7038\",\n    \"coordinates\": [40.4168, -3.7038],\n    \"openingTime\": \"08:00\",\n    \"closingTime\": \"18:00\",\n    \"phone\": \"+34 91 123 4567\",\n    \"email\": \"almacen.madrid@empresa.com\"\n  }'\n```\n\n## C\u00f3digos de Error Comunes\n\n| C\u00f3digo | Descripci\u00f3n | Soluci\u00f3n |\n|--------|-------------|----------|\n| 400 | Par\u00e1metros inv\u00e1lidos | Verificar formato de coordenadas y JSON |\n| 401 | No autorizado | Verificar token JWT o API key |\n| 404 | Ruta no encontrada | Verificar coordenadas y conectividad |\n| 429 | Too Many Requests | Esperar antes de reintentar |\n| 500 | Error interno del servidor | Contactar soporte t\u00e9cnico |\n\n## L\u00edmites de Uso\n\n- **Rutas por hora**: 1000 (por usuario)\n- **Waypoints por ruta**: 50 m\u00e1ximo\n- **Distancia m\u00e1xima**: 2000 km\n- **Tiempo de respuesta**: < 30 segundos (normal), < 60 segundos (con optimizaci\u00f3n)\n\n## Formatos de Datos\n\n### Coordenadas\n- **Formato**: `[longitud, latitud]` (GeoJSON standard)\n- **Rango**: Longitud -180 a 180, Latitud -90 a 90\n- **Precisi\u00f3n**: Hasta 6 decimales (cent\u00edmetros)\n\n### Fechas\n- **Formato**: ISO 8601 (`2025-01-15T08:00:00Z`)\n- **Zona horaria**: UTC preferido\n\n### JSON Encoding\nLos par\u00e1metros complejos deben estar URL-encoded:\n\n```javascript\n// JavaScript\nconst params = {\n  vehicle: { width: 2.55, height: 4.0 },\n  driver: { hoursStatus: { remainingWeeklyHours: 56 } }\n};\nconst encoded = encodeURIComponent(JSON.stringify(params));\n// Resultado: %7B%22vehicle%22%3A%7B%22width%22%3A2.55%2C%22height%22%3A4.0%7D%2C%22driver%22%3A%7B%22hoursStatus%22%3A%7B%22remainingWeeklyHours%22%3A56%7D%7D%7D\n```\n\n## SDKs y Librer\u00edas\n\n### JavaScript/TypeScript\n\n```javascript\nclass TranscendRouteAPI {\n  constructor(apiKey, baseUrl = 'https://api.transcend.es') {\n    this.apiKey = apiKey;\n    this.baseUrl = baseUrl;\n  }\n\n  async calculateRoute(origin, destination, options = {}) {\n    const params = new URLSearchParams({\n      origin_lat: origin.lat,\n      origin_lon: origin.lon,\n      destiny_lat: destination.lat,\n      destiny_lon: destination.lon,\n      ...this.encodeOptions(options)\n    });\n\n    const response = await fetch(`${this.baseUrl}/api/route`, {\n      headers: {\n        'x-api-key': this.apiKey\n      }\n    });\n\n    return response.json();\n  }\n\n  encodeOptions(options) {\n    const encoded = {};\n    Object.keys(options).forEach(key => {\n      if (typeof options[key] === 'object') {\n        encoded[key] = encodeURIComponent(JSON.stringify(options[key]));\n      } else {\n        encoded[key] = options[key];\n      }\n    });\n    return encoded;\n  }\n}\n\n// Uso\nconst api = new TranscendRouteAPI('your-api-key');\nconst route = await api.calculateRoute(\n  { lat: 42.2406, lon: -8.7207 },\n  { lat: 41.3874, lon: 2.1686 },\n  {\n    vehicle: { width: 2.55, height: 4.0, weight: 40000 },\n    driver: { hoursStatus: { remainingWeeklyHours: 56 } }\n  }\n);\n```\n\n### Python\n\n```python\nimport requests\nimport json\nfrom urllib.parse import urlencode\n\nclass TranscendRouteAPI:\n    def __init__(self, api_key: str, base_url='https://api.transcend.es'):\n        self.api_key = api_key\n        self.base_url = base_url\n        self.session = requests.Session()\n        self.session.headers.update({'x-api-key': api_key})\n\n    def calculate_route(self, origin, destination, **options):\n        params = {\n            'origin_lat': origin['lat'],\n            'origin_lon': origin['lon'],\n            'destiny_lat': destination['lat'],\n            'destiny_lon': destination['lon']\n        }\n\n        # Encode complex objects as JSON strings\n        for key, value in options.items():\n            if isinstance(value, (dict, list)):\n                params[key] = json.dumps(value)\n            else:\n                params[key] = value\n\n        response = self.session.get(f'{self.base_url}/api/route', params=params)\n        response.raise_for_status()\n        return response.json()\n\n# Uso\napi = TranscendRouteAPI('your-api-key')\nroute = api.calculate_route(\n    {'lat': 42.2406, 'lon': -8.7207},\n    {'lat': 41.3874, 'lon': 2.1686},\n    vehicle={'width': 2.55, 'height': 4.0, 'weight': 40000},\n    driver={'hoursStatus': {'remainingWeeklyHours': 56}}\n)\n```\n\n## Webhooks y Integraciones\n\n### Eventos Disponibles\n- `route.calculated`: Nueva ruta calculada\n- `route.saved`: Ruta guardada por usuario\n- `location.created`: Nueva ubicaci\u00f3n creada\n\n### Configuraci\u00f3n de Webhooks\n\n```bash\ncurl -X POST \"https://api.transcend.es/api/webhooks\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer YOUR_JWT_TOKEN\" \\\n  -d '{\n    \"url\": \"https://your-app.com/webhooks/route-events\",\n    \"events\": [\"route.calculated\", \"route.saved\"],\n    \"secret\": \"your-webhook-secret\"\n  }'\n```\n\n## Soporte y Contacto\n\n- **Documentaci\u00f3n completa**: [OpenAPI Spec](./openapi.yaml)\n- **Email soporte**: support@transcend.es\n- **Portal desarrolladores**: https://developers.transcend.es\n- **Status page**: https://status.transcend.es\n\n## Changelog\n\n### v1.0.0 (2025-01-15)\n- \u2705 C\u00e1lculo de rutas con paradas autom\u00e1ticas\n- \u2705 Soporte para tipos de fecha (departure/arrival)\n- \u2705 Integraci\u00f3n con puntos negros de tr\u00e1fico\n- \u2705 Gesti\u00f3n de rutas guardadas\n- \u2705 API de ubicaciones\n- \u2705 Exportaci\u00f3n GPX\n- \u2705 Gamificaci\u00f3n b\u00e1sica\n- \u2705 Rutas temporales compartidas\n\n## Testing y Validaci\u00f3n\n\n### Estrategia de Testing\n\n#### Niveles de Testing\n\n##### 1. Unit Tests\n- **Cobertura**: Funciones individuales y m\u00e9todos\n- **Herramientas**: Jest, Mocha, Chai\n- **Enfoque**: L\u00f3gica de negocio, c\u00e1lculos, validaciones\n\n##### 2. Integration Tests\n- **Cobertura**: APIs externas, bases de datos, servicios\n- **Herramientas**: Supertest, Testcontainers\n- **Enfoque**: Flujos completos, integraciones\n\n##### 3. End-to-End Tests\n- **Cobertura**: Flujos completos de usuario\n- **Herramientas**: Cypress, Playwright\n- **Enfoque**: Experiencia completa del usuario\n\n##### 4. Performance Tests\n- **Cobertura**: Carga, estr\u00e9s, volumetr\u00eda\n- **Herramientas**: k6, Artillery, JMeter\n- **Enfoque**: Rendimiento bajo carga\n\n### M\u00e9tricas de Calidad\n\n```yaml\n# Objetivos de calidad\ncoverage:\n  statements: 85%\n  branches: 80%\n  functions: 90%\n  lines: 85%\n\nperformance:\n  response_time_p95: 2000ms  # Percentil 95\n  throughput: 100 req/sec\n  error_rate: 0.1%          # M\u00e1ximo 0.1%\n\nreliability:\n  uptime: 99.9%\n  mttr: 15min               # Mean Time To Recovery\n  mtbf: 720h               # Mean Time Between Failures\n```\n\n### Scripts de Testing Automatizado\n\n#### Script de CI/CD\n\n```yaml\n# .github/workflows/test.yml\nname: Tests\n\non:\n  push:\n    branches: [ main, develop ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    services:\n      mongodb:\n        image: mongo:5.0\n        ports:\n          - 27017:27017\n\n    steps:\n    - uses: actions/checkout@v3\n\n    - name: Setup Node.js\n      uses: actions/setup-node@v3\n      with:\n        node-version: '18'\n        cache: 'npm'\n\n    - name: Install dependencies\n      run: npm ci\n\n    - name: Run linting\n      run: npm run lint\n\n    - name: Run unit tests\n      run: npm run test:unit\n      env:\n        NODE_ENV: test\n\n    - name: Run integration tests\n      run: npm run test:integration\n      env:\n        NODE_ENV: test\n        MONGODB_URI: mongodb://localhost:27017/transcend_test\n\n    - name: Generate coverage report\n      run: npm run coverage\n\n    - name: Upload coverage to Codecov\n      uses: codecov/codecov-action@v3\n      with:\n        file: ./coverage/lcov.info\n\n  performance-test:\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs/heads/main'\n\n    steps:\n    - uses: actions/checkout@v3\n\n    - name: Setup Node.js\n      uses: actions/setup-node@v3\n      with:\n        node-version: '18'\n\n    - name: Install dependencies\n      run: npm ci\n\n    - name: Run performance tests\n      run: npm run test:performance\n      env:\n        BASE_URL: ${{ secrets.PERF_TEST_URL }}\n        API_KEY: ${{ secrets.PERF_TEST_API_KEY }}\n\n  security-test:\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs/heads/main'\n\n    steps:\n    - uses: actions/checkout@v3\n\n    - name: Run security scan\n      uses: securecodewarrior/github-actions-gosec@master\n      with:\n        args: './...'\n\n    - name: Run dependency check\n      uses: dependency-check/Dependency-Check_Action@main\n      with:\n        project: 'Transcend Route API'\n        path: '.'\n        format: 'ALL'\n```\n\n#### Script de Smoke Tests\n\n```bash\n#!/bin/bash\n# scripts/smoke-test.sh\n\nset -e\n\necho \"\ud83d\ude80 Running smoke tests for Transcend Route API\"\n\nBASE_URL=\"${BASE_URL:-http://localhost:8086}\"\nAPI_KEY=\"${API_KEY:-test-key}\"\n\necho \"\ud83d\udccd Testing basic route calculation...\"\ncurl -f -s \"${BASE_URL}/api/route?origin_lat=40.4168&origin_lon=-3.7038&destiny_lat=41.3851&destiny_lon=2.1734\" \\\n  -H \"x-api-key: ${API_KEY}\" > /dev/null\necho \"\u2705 Route calculation OK\"\n\necho \"\ud83d\udccd Testing health check...\"\ncurl -f -s \"${BASE_URL}/health\" > /dev/null\necho \"\u2705 Health check OK\"\n\necho \"\ud83d\udccd Testing traffic health...\"\ncurl -f -s \"${BASE_URL}/api/traffic/health\" \\\n  -H \"x-api-key: ${API_KEY}\" > /dev/null\necho \"\u2705 Traffic health OK\"\n\necho \"\ud83d\udccd Testing black spots...\"\ncurl -f -s -X POST \"${BASE_URL}/api/traffic/blackspots/path\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"x-api-key: ${API_KEY}\" \\\n  -d '{\"points\":[[-3.7038,40.4168],[-3.7138,40.4268]],\"minDangerLevel\":30}' > /dev/null\necho \"\u2705 Black spots OK\"\n\necho \"\ud83c\udf89 All smoke tests passed!\"\n```\n\n### Tests de Integraci\u00f3n Detallados\n\n#### Test B\u00e1sico de Ruta\n\n```javascript\n// test/integration/route.integration.test.js\nconst request = require('supertest');\nconst app = require('../../src/index');\n\ndescribe('Route API Integration Tests', () => {\n  describe('GET /api/route', () => {\n    it('should calculate a basic route successfully', async () => {\n      const response = await request(app)\n        .get('/api/route')\n        .query({\n          origin_lat: 40.4168,\n          origin_lon: -3.7038,\n          destiny_lat: 41.3851,\n          destiny_lon: 2.1734\n        })\n        .set('x-api-key', process.env.TEST_API_KEY)\n        .expect(200);\n\n      // Validar estructura de respuesta\n      expect(response.body).toHaveProperty('main');\n      expect(response.body).toHaveProperty('stops');\n      expect(response.body.main).toHaveProperty('summary');\n      expect(response.body.main.summary).toHaveProperty('time');\n      expect(response.body.main.summary).toHaveProperty('length');\n\n      // Validar tipos de datos\n      expect(typeof response.body.main.summary.time).toBe('string');\n      expect(typeof response.body.main.summary.length).toBe('number');\n      expect(response.body.main.summary.length).toBeGreaterThan(0);\n    });\n\n    it('should handle invalid coordinates', async () => {\n      const response = await request(app)\n        .get('/api/route')\n        .query({\n          origin_lat: 91,  // Latitud inv\u00e1lida\n          origin_lon: -3.7038,\n          destiny_lat: 41.3851,\n          destiny_lon: 2.1734\n        })\n        .set('x-api-key', process.env.TEST_API_KEY)\n        .expect(400);\n\n      expect(response.body).toHaveProperty('message');\n      expect(response.body.message).toMatch(/coordenadas/i);\n    });\n  });\n});\n```\n\n#### Test de Puntos Negros\n\n```javascript\n// test/integration/blackspots.integration.test.js\ndescribe('Black Spots API Integration Tests', () => {\n  describe('POST /api/traffic/blackspots/path', () => {\n    it('should return black spots along a route', async () => {\n      const routePoints = [\n        [-3.7038, 40.4168], // Madrid\n        [-3.7138, 40.4268],\n        [-3.7238, 40.4368]\n      ];\n\n      const response = await request(app)\n        .post('/api/traffic/blackspots/path')\n        .send({\n          points: routePoints,\n          minDangerLevel: 30\n        })\n        .set('x-api-key', process.env.TEST_API_KEY)\n        .expect(200);\n\n      expect(response.body).toHaveProperty('blackSpots');\n      expect(response.body).toHaveProperty('metadata');\n      expect(Array.isArray(response.body.blackSpots)).toBe(true);\n    });\n  });\n});\n```\n\n## Integraci\u00f3n con Sistemas ERP\n\n### Sincronizaci\u00f3n de Ubicaciones\n\n```bash\n#!/bin/bash\n# Script para sincronizar ubicaciones desde ERP\n\nERP_API_URL=\"https://erp.empresa.com/api\"\nTRANSCEND_API_URL=\"https://api.transcend.es\"\nAPI_KEY=\"your-api-key\"\n\n# Obtener ubicaciones del ERP\nlocations=$(curl -s \"${ERP_API_URL}/locations\" -H \"Authorization: Bearer ${ERP_TOKEN}\")\n\n# Convertir y enviar a Transcend\necho \"$locations\" | jq -r '.[] | @json' | while read -r location; do\n  # Transformar formato ERP a Transcend\n  transcend_location=$(echo \"$location\" | jq '{\n    name: .name,\n    address: .address,\n    city: .city,\n    province: .province,\n    latitude: (.coordinates.lat | tostring),\n    longitude: (.coordinates.lon | tostring),\n    coordinates: [.coordinates.lon, .coordinates.lat],\n    openingTime: .businessHours.open,\n    closingTime: .businessHours.close,\n    customerId: .id\n  }')\n\n  # Crear en Transcend\n  curl -X POST \"${TRANSCEND_API_URL}/api/locations\" \\\n    -H \"Content-Type: application/json\" \\\n    -H \"x-api-key: ${API_KEY}\" \\\n    -d \"$transcend_location\"\ndone\n```\n\n### C\u00e1lculo Autom\u00e1tico de Rutas para Pedidos\n\n```javascript\n// Node.js - Integraci\u00f3n con sistema de pedidos\n\nconst axios = require('axios');\n\nclass ERPRouteIntegration {\n  constructor(transcendApiKey) {\n    this.transcend = axios.create({\n      baseURL: 'https://api.transcend.es',\n      headers: { 'x-api-key': transcendApiKey }\n    });\n  }\n\n  async calculateRouteForOrder(order) {\n    // Extraer informaci\u00f3n del pedido\n    const origin = await this.getWarehouseLocation(order.warehouseId);\n    const destination = await this.getCustomerLocation(order.customerId);\n    const vehicle = await this.getVehicleForOrder(order);\n\n    // Calcular ruta\n    const routeParams = {\n      origin_lat: origin.lat,\n      origin_lon: origin.lon,\n      destiny_lat: destination.lat,\n      destiny_lon: destination.lon,\n      vehicle: JSON.stringify(vehicle),\n      merchandise: JSON.stringify({\n        weight: order.totalWeight,\n        isADR: order.isHazardous\n      }),\n      date: JSON.stringify({\n        type: 'departure',\n        date: order.requestedDeliveryDate\n      })\n    };\n\n    const response = await this.transcend.get('/api/route', { params: routeParams });\n\n    // Actualizar pedido con informaci\u00f3n de ruta\n    await this.updateOrderWithRoute(order.id, response.data);\n\n    return response.data;\n  }\n\n  async getWarehouseLocation(warehouseId) {\n    // Consultar ubicaci\u00f3n del almac\u00e9n desde ERP\n    const response = await this.erp.get(`/warehouses/${warehouseId}`);\n    return {\n      lat: response.data.latitude,\n      lon: response.data.longitude\n    };\n  }\n\n  async getCustomerLocation(customerId) {\n    // Buscar ubicaci\u00f3n del cliente en Transcend o ERP\n    try {\n      const response = await this.transcend.get(`/api/locations/${customerId}`);\n      return {\n        lat: parseFloat(response.data.latitude),\n        lon: parseFloat(response.data.longitude)\n      };\n    } catch (error) {\n      // Si no existe en Transcend, buscar en ERP\n      const erpLocation = await this.erp.get(`/customers/${customerId}/location`);\n      return {\n        lat: erpLocation.data.lat,\n        lon: erpLocation.data.lon\n      };\n    }\n  }\n}\n\n// Uso\nconst integration = new ERPRouteIntegration('your-api-key');\n\n// Procesar pedido nuevo\nconst order = {\n  id: 'ORD-2025-001',\n  warehouseId: 'WH-MAD',\n  customerId: 'CUST-123',\n  totalWeight: 15000,\n  isHazardous: false,\n  requestedDeliveryDate: '2025-01-20T10:00:00Z'\n};\n\nconst route = await integration.calculateRouteForOrder(order);\nconsole.log('Ruta calculada:', route.summary);\n```\n\n## Optimizaci\u00f3n de Flotas\n\n### Asignaci\u00f3n \u00d3ptima de Veh\u00edculos\n\n```python\nimport requests\nimport json\nfrom typing import List, Dict, Any\n\nclass FleetOptimizer:\n    def __init__(self, api_key: str):\n        self.api_key = api_key\n        self.base_url = \"https://api.transcend.es\"\n        self.session = requests.Session()\n        self.session.headers.update({\"x-api-key\": api_key})\n\n    def optimize_fleet_assignment(self, orders: List[Dict], vehicles: List[Dict], depot: Dict) -> Dict:\n        assignments = {}\n\n        # Ordenar pedidos por urgencia y distancia\n        sorted_orders = sorted(orders, key=lambda x: (x.get('priority', 1), x['distance']))\n\n        for vehicle in vehicles:\n            if not sorted_orders:\n                break\n\n            # Encontrar pedidos que este veh\u00edculo puede atender\n            suitable_orders = [\n                order for order in sorted_orders\n                if self.vehicle_can_handle_order(vehicle, order)\n            ]\n\n            if suitable_orders:\n                # Asignar pedido m\u00e1s cercano\n                assignment = self.assign_order_to_vehicle(vehicle, suitable_orders[0], depot)\n                assignments[vehicle['id']] = assignment\n                sorted_orders.remove(suitable_orders[0])\n\n        return {\n            'assignments': assignments,\n            'unassigned_orders': sorted_orders\n        }\n\n    def vehicle_can_handle_order(self, vehicle: Dict, order: Dict) -> bool:\n        # Verificar capacidad de peso\n        if vehicle['max_weight'] < order['weight']:\n            return False\n\n        # Verificar restricciones ADR\n        if order.get('isADR') and not vehicle.get('adr_certified', False):\n            return False\n\n        # Verificar dimensiones\n        if (vehicle['max_width'] < order.get('width', 0) or\n            vehicle['max_height'] < order.get('height', 0)):\n            return False\n\n        return True\n\n    def assign_order_to_vehicle(self, vehicle: Dict, order: Dict, depot: Dict) -> Dict:\n        # Calcular ruta para este veh\u00edculo y pedido\n        route_params = {\n            'origin_lat': depot['lat'],\n            'origin_lon': depot['lon'],\n            'destiny_lat': order['destination']['lat'],\n            'destiny_lon': order['destination']['lon'],\n            'vehicle': json.dumps({\n                'width': vehicle['width'],\n                'height': vehicle['height'],\n                'weight': vehicle['weight'] + order['weight'],\n                'consumption': vehicle['consumption']\n            }),\n            'merchandise': json.dumps({\n                'weight': order['weight'],\n                'isADR': order.get('isADR', False)\n            }),\n            'date': json.dumps({\n                'type': 'departure',\n                'date': order.get('deadline', '2025-01-15T08:00:00Z')\n            })\n        }\n\n        response = self.session.get('/api/route', params=route_params)\n        route_data = response.json()\n\n        return {\n            'vehicle_id': vehicle['id'],\n            'order_id': order['id'],\n            'route': route_data,\n            'estimated_cost': route_data['main']['summary']['cost'],\n            'estimated_time': route_data['main']['summary']['timeWithBreaks']\n        }\n\n# Ejemplo de uso\noptimizer = FleetOptimizer(\"your-api-key\")\n\norders = [\n    {\n        'id': 'ORD-001',\n        'weight': 8000,\n        'width': 2.2,\n        'height': 2.8,\n        'isADR': False,\n        'priority': 1,\n        'distance': 150,\n        'destination': {'lat': 40.5, 'lon': -3.5},\n        'deadline': '2025-01-15T17:00:00Z'\n    }\n]\n\nvehicles = [\n    {\n        'id': 'VEH-001',\n        'max_weight': 12000,\n        'max_width': 2.5,\n        'max_height': 3.0,\n        'width': 2.3,\n        'height': 2.8,\n        'weight': 4000,  # Tara\n        'consumption': 28,\n        'adr_certified': True\n    }\n]\n\ndepot = {'lat': 40.4168, 'lon': -3.7038}\n\nresult = optimizer.optimize_fleet_assignment(orders, vehicles, depot)\nprint(\"Asignaciones \u00f3ptimas:\", json.dumps(result, indent=2))\n```\n\n## Conclusi\u00f3n\n\nEsta documentaci\u00f3n completa proporciona todo lo necesario para integrar la API de Transcend Route en sistemas empresariales. Desde ejemplos b\u00e1sicos hasta implementaciones avanzadas de optimizaci\u00f3n de flotas, la API est\u00e1 dise\u00f1ada para escalar con las necesidades del negocio del transporte.\n\nPara m\u00e1s informaci\u00f3n, contacta al equipo de soporte en support@transcend.es.\n",
      "name": "route"
    },
    {
      "description": "Este API es parte de **TransCend** y corresponde al m\u00f3dulo de estaciones de servicio. Proporciona informaci\u00f3n sobre ubicaciones, precios y disponibilidad de combustibles en estaciones de servicio.\nSu principal misi\u00f3n es ayudar a los transportistas a encontrar las mejores opciones de repostaje en sus rutas, considerando factores como ubicaci\u00f3n, precios y tipos de combustible disponibles.\n",
      "name": "stations"
    },
    {
      "description": "# \ud83d\ude9b M\u00f3dulo de Gesti\u00f3n de Peajes\n\nEste API es parte de la plataforma **Transcend** y proporciona funcionalidades completas para calcular y gestionar costos de peajes en rutas de transporte de mercanc\u00edas.\n\n## \ud83c\udfaf Casos de Uso Principales\n\n- **Planificaci\u00f3n de Rutas**: Calcular costos totales de transporte incluyendo peajes, combustible y mantenimiento\n- **Presupuestos**: Obtener desglose detallado de costos para propuestas comerciales\n- **Optimizaci\u00f3n**: Identificar rutas m\u00e1s econ\u00f3micas considerando peajes y tiempos\n- **An\u00e1lisis Ambiental**: Calcular emisiones de CO2 para reportes de sostenibilidad\n\n## \ud83d\ude80 Caracter\u00edsticas Principales\n\n- **C\u00e1lculo Inteligente**: Costos de peajes, combustible, mantenimiento y tiempo de conducci\u00f3n\n- **Tiempos Legales**: Incluye tiempos de descanso obligatorios seg\u00fan normativa de transporte\n- **M\u00faltiples Combustibles**: Soporte para diesel, gasolina y gas natural vehicular\n- **Peajes Detallados**: Informaci\u00f3n por peaje individual con m\u00e9todos de pago\n- **Cache Optimizado**: Rutas frecuentes cacheadas por 24 horas\n- **Integraci\u00f3n Geolocalizaci\u00f3n**: Google Maps, Here Maps y sistemas de navegaci\u00f3n\n\n## \ud83d\udcca L\u00edmites Operativos\n\n| Par\u00e1metro | L\u00edmite | Descripci\u00f3n |\n|-----------|--------|-------------|\n| Distancia m\u00e1xima | 1,000 km | Entre origen y destino |\n| Peajes por ruta | 50 | M\u00e1ximo n\u00famero de peajes procesados |\n| Actualizaci\u00f3n precios | 24h | Frecuencia de actualizaci\u00f3n de tarifas |\n| Tasa de requests | 100/min | Por cliente autenticado |\n| Coordenadas | WGS84 | Sistema de coordenadas est\u00e1ndar |\n\n## \ud83d\udd10 Autenticaci\u00f3n\n\nLa autenticaci\u00f3n es **opcional** pero recomendada para acceso a funcionalidades avanzadas y l\u00edmites elevados.\n\n```http\n# Ejemplo de uso con autenticaci\u00f3n\nGET /api/costs?origin={\"lat\":41.3851,\"lng\":2.1734}&destination={\"lat\":40.4168,\"lng\":-3.7038}\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n```\n\n## \ud83c\udfaa Ejemplo Completo\n\n```bash\n# C\u00e1lculo de ruta Barcelona -> Madrid con carga completa\ncurl -X GET \"https://back.transcend.cargoffer.com/tolls/costs?\\\norigin={\\\"lat\\\":41.3851,\\\"lng\\\":2.1734}&\\\ndestination={\\\"lat\\\":40.4168,\\\"lng\\\":-3.7038}&\\\nwith_tolls=true&\\\navg_consumption=35&\\\ncargo_weight=15000&\\\nfuel_type=diesel\" \\\n-H \"Authorization: Bearer your_jwt_token_here\"\n```\n",
      "name": "tolls"
    },
    {
      "description": "Este API es parte de **Transcend** y corresponde con el m\u00f3dulo de tr\u00e1fico, para consultar informaci\u00f3n en tiempo real sobre eventos de tr\u00e1fico, radares, puntos negros y flujo de tr\u00e1fico en carreteras del sector log\u00edstico.\n\n**\u00bfPara qu\u00e9 sirve?**\n- Consultar eventos de tr\u00e1fico actuales (accidentes, cierres de carretera, eventos especiales)\n- Localizar radares fijos y m\u00f3viles en rutas espec\u00edficas\n- Identificar puntos negros (zonas de alta peligrosidad)\n- Calcular isocronas (\u00e1reas alcanzables en tiempo determinado)\n- Obtener informaci\u00f3n de flujo de tr\u00e1fico en tiempo real\n\n**Casos de uso t\u00edpicos:**\n- Planificaci\u00f3n de rutas seguras para transporte de mercanc\u00edas\n- Evitaci\u00f3n de zonas de alto riesgo y congesti\u00f3n\n- Optimizaci\u00f3n de tiempos de entrega\n- Gesti\u00f3n de flotas en tiempo real\n\n**Ejemplo pr\u00e1ctico:**\n```\nUn transportista necesita planificar una ruta desde Madrid a Barcelona.\nUsa este API para:\n1. Consultar eventos de tr\u00e1fico en la ruta\n2. Identificar radares y puntos negros\n3. Verificar el flujo de tr\u00e1fico actual\n4. Calcular \u00e1reas alcanzables en diferentes tiempos\n```\n",
      "name": "traffic"
    },
    {
      "description": "Este API es parte de **Transcend** y corresponde al m\u00f3dulo de gesti\u00f3n de veh\u00edculos. Proporciona operaciones CRUD completas para la flota de veh\u00edculos del sector log\u00edstico.\n\nSu principal misi\u00f3n es mantener un registro actualizado de todos los veh\u00edculos disponibles, sus caracter\u00edsticas t\u00e9cnicas y estado operativo, permitiendo una gesti\u00f3n eficiente de la flota.\n",
      "name": "vehicles"
    },
    {
      "description": "Esta API es parte de **Transcend** y corresponde al m\u00f3dulo de consulta meteorol\u00f3gica, para verificar la situaci\u00f3n meteorol\u00f3gica en un punto dado de las rutas en el sector log\u00edstico.\n\nSu principal misi\u00f3n es verificar la climatolog\u00eda, adem\u00e1s de una consulta habitual para complementar la informaci\u00f3n correspondiente a los otros servicios de TransCend; sirve para determinar si un punto de la ruta presenta problemas *(o representa un peligro)* para el transporte de mercanc\u00edas, proporcionando un valor porcentual de peligrosidad y un nivel de alerta en relaci\u00f3n con la climatolog\u00eda habitual.\n",
      "name": "weather"
    }
  ],
  "x-tagGroups": [
    {
      "name": "Core",
      "tags": [
        "iam",
        "profiles",
        "activity"
      ]
    },
    {
      "name": "Transport",
      "tags": [
        "drivers",
        "vehicles",
        "route",
        "stations",
        "tolls",
        "traffic",
        "weather"
      ]
    },
    {
      "name": "Commerce",
      "tags": [
        "pay",
        "poi",
        "hscode"
      ]
    }
  ]
}