{
  "openapi": "3.1.0",
  "info": {
    "title": "Plataforma de gestión — API pública v1",
    "version": "1.1.0",
    "description": "API REST para integraciones externas (ERP, BI, portales). Auth con Bearer api_key generada desde el panel admin del tenant.\n\n**Aislamiento multitenant**: cada api_key pertenece a un único tenant. Todos los endpoints devuelven y operan solo sobre datos de ese tenant.\n\n**Scopes**: la API key puede tener uno o más scopes:\n- `read` — GET (lectura). Rate limit: 60 req/min.\n- `write` — POST + PUT (creación y modificación). Rate limit: 30 req/min.\n- `admin` — DELETE (archivado). Rate limit: 30 req/min.\n\n**Paginación**: cursor-based con `?cursor=<uuid>&limit=<n>` (limit ≤ 200, default 50).\n\n**Errores comunes**: `400` validación, `401` sin auth, `403` sin scope, `404` no encontrado, `409` conflicto (NIF duplicado), `429` rate limit.\n\nRefs: SRS §16.9 Hito 8.",
    "contact": { "name": "Soporte", "email": "soporte@obras.example" },
    "license": { "name": "UNLICENSED (proprietary)" }
  },
  "servers": [
    { "url": "https://app.obras.example/api/v1", "description": "Produccion" },
    { "url": "http://localhost:3000/api/v1", "description": "Desarrollo local" }
  ],
  "security": [{ "ApiKeyBearer": [] }],
  "components": {
    "securitySchemes": {
      "ApiKeyBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ak_<64-hex>",
        "description": "Bearer token con formato `ak_xxxxxxxx...` generado desde el panel admin. El scope determina qué operaciones puede realizar."
      }
    },
    "schemas": {
      "Cliente": {
        "type": "object",
        "required": ["id", "nombre", "estado"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "nombre": { "type": "string", "maxLength": 200 },
          "nif": { "type": "string", "maxLength": 20, "nullable": true },
          "sector": { "type": "string", "maxLength": 80, "nullable": true },
          "direccion": { "type": "string", "maxLength": 500, "nullable": true },
          "notasInternas": { "type": "string", "maxLength": 2000, "nullable": true },
          "estado": { "type": "string", "enum": ["activo", "inactivo", "archivado"] },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "CreateClienteBody": {
        "type": "object",
        "required": ["nombre"],
        "properties": {
          "nombre": { "type": "string", "minLength": 1, "maxLength": 200 },
          "nif": { "type": "string", "maxLength": 20 },
          "sector": { "type": "string", "maxLength": 80 },
          "direccion": { "type": "string", "maxLength": 500 },
          "notasInternas": { "type": "string", "maxLength": 2000 },
          "estado": { "type": "string", "enum": ["activo", "inactivo", "archivado"], "default": "activo" }
        }
      },
      "UpdateClienteBody": {
        "type": "object",
        "description": "Solo los campos que se quieren modificar (PATCH semantics).",
        "properties": {
          "nombre": { "type": "string", "minLength": 1, "maxLength": 200 },
          "nif": { "type": "string", "maxLength": 20 },
          "sector": { "type": "string", "maxLength": 80 },
          "direccion": { "type": "string", "maxLength": 500 },
          "notasInternas": { "type": "string", "maxLength": 2000 },
          "estado": { "type": "string", "enum": ["activo", "inactivo", "archivado"] }
        }
      },
      "Proyecto": {
        "type": "object",
        "required": ["id", "clienteId", "nombre", "tipo", "estado"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "clienteId": { "type": "string", "format": "uuid" },
          "nombre": { "type": "string", "maxLength": 200 },
          "tipo": { "type": "string", "enum": ["llave_en_mano", "mantenimiento", "asistencia", "otro"] },
          "descripcion": { "type": "string", "maxLength": 2000, "nullable": true },
          "estado": { "type": "string", "enum": ["borrador", "activo", "en_pausa", "cerrado", "archivado"] },
          "fechaAlta": { "type": "string", "format": "date-time" },
          "fechaCierrePrevista": { "type": "string", "format": "date-time", "nullable": true },
          "importe": { "type": "number", "nullable": true },
          "responsableUserId": { "type": "string", "format": "uuid", "nullable": true },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "CreateProyectoBody": {
        "type": "object",
        "required": ["clienteId", "nombre", "tipo"],
        "properties": {
          "clienteId": { "type": "string", "format": "uuid" },
          "nombre": { "type": "string", "minLength": 1, "maxLength": 200 },
          "tipo": { "type": "string", "enum": ["llave_en_mano", "mantenimiento", "asistencia", "otro"] },
          "descripcion": { "type": "string", "maxLength": 2000 },
          "fechaCierrePrevista": { "type": "string", "format": "date-time" },
          "importe": { "type": "number" },
          "responsableUserId": { "type": "string", "format": "uuid" },
          "estado": { "type": "string", "enum": ["borrador", "activo", "en_pausa", "cerrado", "archivado"], "default": "borrador" }
        }
      },
      "UpdateProyectoBody": {
        "type": "object",
        "description": "Solo los campos que se quieren modificar.",
        "properties": {
          "nombre": { "type": "string", "minLength": 1, "maxLength": 200 },
          "tipo": { "type": "string", "enum": ["llave_en_mano", "mantenimiento", "asistencia", "otro"] },
          "descripcion": { "type": "string", "maxLength": 2000 },
          "fechaCierrePrevista": { "type": "string", "format": "date-time" },
          "importe": { "type": "number" },
          "responsableUserId": { "type": "string", "format": "uuid" },
          "estado": { "type": "string", "enum": ["borrador", "activo", "en_pausa", "cerrado", "archivado"] }
        }
      },
      "Obra": {
        "type": "object",
        "required": ["id", "proyectoId", "nombre", "estado", "perfilServicio"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "proyectoId": { "type": "string", "format": "uuid" },
          "nombre": { "type": "string", "maxLength": 200 },
          "estado": {
            "type": "string",
            "enum": ["borrador", "planificada", "en_curso", "pausada", "completada", "cerrada", "archivada"]
          },
          "perfilServicio": { "type": "string", "enum": ["estandar", "prioritario", "critico_24_7"] },
          "ubicacion": {
            "type": "object",
            "required": ["direccion", "lat", "lng"],
            "properties": {
              "direccion": { "type": "string" },
              "lat": { "type": "number" },
              "lng": { "type": "number" }
            }
          },
          "fechaInicio": { "type": "string", "format": "date-time", "nullable": true },
          "fechaFinPrevista": { "type": "string", "format": "date-time", "nullable": true },
          "responsableUserId": { "type": "string", "format": "uuid", "nullable": true },
          "observaciones": { "type": "string", "maxLength": 2000, "nullable": true },
          "contratoUrl": { "type": "string", "format": "uri", "nullable": true },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "CreateObraBody": {
        "type": "object",
        "required": ["proyectoId", "nombre", "ubicacion"],
        "properties": {
          "proyectoId": { "type": "string", "format": "uuid" },
          "nombre": { "type": "string", "minLength": 1, "maxLength": 200 },
          "ubicacion": {
            "type": "object",
            "required": ["direccion", "lat", "lng"],
            "properties": {
              "direccion": { "type": "string", "minLength": 1, "maxLength": 500 },
              "lat": { "type": "number", "minimum": -90, "maximum": 90 },
              "lng": { "type": "number", "minimum": -180, "maximum": 180 }
            }
          },
          "fechaInicio": { "type": "string", "format": "date-time" },
          "fechaFinPrevista": { "type": "string", "format": "date-time" },
          "perfilServicio": { "type": "string", "enum": ["estandar", "prioritario", "critico_24_7"], "default": "estandar" },
          "responsableUserId": { "type": "string", "format": "uuid" },
          "observaciones": { "type": "string", "maxLength": 2000 },
          "contratoUrl": { "type": "string", "format": "uri" },
          "estado": {
            "type": "string",
            "enum": ["borrador", "planificada", "en_curso", "pausada", "completada", "cerrada", "archivada"],
            "default": "borrador"
          }
        }
      },
      "UpdateObraBody": {
        "type": "object",
        "description": "Solo los campos que se quieren modificar.",
        "properties": {
          "nombre": { "type": "string", "minLength": 1, "maxLength": 200 },
          "ubicacion": {
            "type": "object",
            "properties": {
              "direccion": { "type": "string" },
              "lat": { "type": "number" },
              "lng": { "type": "number" }
            }
          },
          "fechaInicio": { "type": "string", "format": "date-time" },
          "fechaFinPrevista": { "type": "string", "format": "date-time" },
          "perfilServicio": { "type": "string", "enum": ["estandar", "prioritario", "critico_24_7"] },
          "responsableUserId": { "type": "string", "format": "uuid" },
          "observaciones": { "type": "string", "maxLength": 2000 },
          "contratoUrl": { "type": "string", "format": "uri" },
          "estado": {
            "type": "string",
            "enum": ["borrador", "planificada", "en_curso", "pausada", "completada", "cerrada", "archivada"]
          }
        }
      },
      "Log": {
        "type": "object",
        "required": ["id", "tipo", "ocurridoEn"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "tipo": {
            "type": "string",
            "enum": ["texto", "voz", "foto", "video", "mixto", "comunicacion_cliente", "comunicacion_interna", "evento_sistema", "evento_agente"]
          },
          "contenidoTexto": { "type": "string", "nullable": true },
          "transcripcion": { "type": "string", "nullable": true },
          "ocurridoEn": { "type": "string", "format": "date-time" },
          "adjuntos": { "type": "array", "items": { "type": "object" } }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "items": { "type": "array" },
          "nextCursor": { "type": "string", "format": "uuid", "nullable": true }
        }
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "example": "validation_error" },
          "details": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "campo": { "type": "string" },
                "mensaje": { "type": "string" },
                "codigo": { "type": "string" }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "detail": { "type": "string" }
        }
      }
    },
    "parameters": {
      "ResourceId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "string", "format": "uuid" }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "schema": { "type": "string", "format": "uuid" },
        "required": false,
        "description": "ID del ultimo item de la pagina anterior para paginacion cursor-based."
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 },
        "required": false
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "API key invalida, ausente o revocada.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "La API key no tiene el scope requerido para esta operacion.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Recurso no encontrado en este tenant.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Conflict": {
        "description": "Conflicto de unicidad (ej. NIF duplicado).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ValidationError": {
        "description": "Body invalido: uno o mas campos no pasan la validacion.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidationError" } } }
      },
      "RateLimited": {
        "description": "Rate limit excedido (60 req/min lectura, 30 req/min escritura).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  },
  "paths": {
    "/clientes": {
      "get": {
        "operationId": "listClientes",
        "summary": "Lista paginada de clientes del tenant.",
        "description": "Requiere scope `read`.",
        "parameters": [
          { "$ref": "#/components/parameters/Cursor" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Pagination" },
                    { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Cliente" } } } }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "operationId": "createCliente",
        "summary": "Crea un cliente nuevo.",
        "description": "Requiere scope `write`. Devuelve el cliente creado con status 201. Si el NIF ya existe en el tenant devuelve 409.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/CreateClienteBody" } }
          }
        },
        "responses": {
          "201": {
            "description": "Cliente creado.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Cliente" } } }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/clientes/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
      "get": {
        "operationId": "getCliente",
        "summary": "Obtiene un cliente por id.",
        "description": "Requiere scope `read`. Devuelve 404 si no existe en el tenant.",
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Cliente" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "put": {
        "operationId": "updateCliente",
        "summary": "Actualiza un cliente (PATCH semantics).",
        "description": "Requiere scope `write`. Solo los campos incluidos en el body se modifican. Devuelve el cliente actualizado.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/UpdateClienteBody" } }
          }
        },
        "responses": {
          "200": {
            "description": "Cliente actualizado.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Cliente" } } }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "delete": {
        "operationId": "archiveCliente",
        "summary": "Archiva (borrado logico) un cliente.",
        "description": "Requiere scope `admin`. No elimina datos: cambia `estado` a `archivado` y rellena `deletedAt`. Devuelve 204 sin cuerpo.",
        "responses": {
          "204": { "description": "Cliente archivado correctamente." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/proyectos": {
      "get": {
        "operationId": "listProyectos",
        "summary": "Lista paginada de proyectos del tenant.",
        "description": "Requiere scope `read`.",
        "parameters": [
          {
            "name": "clienteId",
            "in": "query",
            "schema": { "type": "string", "format": "uuid" },
            "description": "Filtra por cliente."
          },
          { "$ref": "#/components/parameters/Cursor" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Pagination" },
                    { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Proyecto" } } } }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "operationId": "createProyecto",
        "summary": "Crea un proyecto nuevo.",
        "description": "Requiere scope `write`. El `clienteId` debe existir en el tenant. Devuelve el proyecto creado con status 201.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/CreateProyectoBody" } }
          }
        },
        "responses": {
          "201": {
            "description": "Proyecto creado.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Proyecto" } } }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/proyectos/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
      "get": {
        "operationId": "getProyecto",
        "summary": "Obtiene un proyecto por id (incluye cliente).",
        "description": "Requiere scope `read`.",
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Proyecto" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "put": {
        "operationId": "updateProyecto",
        "summary": "Actualiza un proyecto (PATCH semantics).",
        "description": "Requiere scope `write`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/UpdateProyectoBody" } }
          }
        },
        "responses": {
          "200": {
            "description": "Proyecto actualizado.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Proyecto" } } }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "delete": {
        "operationId": "archiveProyecto",
        "summary": "Archiva (borrado logico) un proyecto.",
        "description": "Requiere scope `admin`. Devuelve 204 sin cuerpo.",
        "responses": {
          "204": { "description": "Proyecto archivado correctamente." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/obras": {
      "get": {
        "operationId": "listObras",
        "summary": "Lista paginada de obras del tenant.",
        "description": "Requiere scope `read`.",
        "parameters": [
          {
            "name": "proyectoId",
            "in": "query",
            "schema": { "type": "string", "format": "uuid" },
            "description": "Filtra por proyecto."
          },
          {
            "name": "estado",
            "in": "query",
            "schema": { "type": "string", "enum": ["borrador", "planificada", "en_curso", "pausada", "completada", "cerrada", "archivada"] }
          },
          { "$ref": "#/components/parameters/Cursor" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Pagination" },
                    { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Obra" } } } }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "operationId": "createObra",
        "summary": "Crea una obra nueva.",
        "description": "Requiere scope `write`. El `proyectoId` debe existir en el tenant. Devuelve la obra creada con status 201.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/CreateObraBody" } }
          }
        },
        "responses": {
          "201": {
            "description": "Obra creada.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Obra" } } }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/obras/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
      "get": {
        "operationId": "getObra",
        "summary": "Obtiene una obra por id (incluye proyecto y cliente).",
        "description": "Requiere scope `read`.",
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Obra" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "put": {
        "operationId": "updateObra",
        "summary": "Actualiza una obra (PATCH semantics).",
        "description": "Requiere scope `write`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/UpdateObraBody" } }
          }
        },
        "responses": {
          "200": {
            "description": "Obra actualizada.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Obra" } } }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "delete": {
        "operationId": "archiveObra",
        "summary": "Archiva (borrado logico) una obra.",
        "description": "Requiere scope `admin`. Devuelve 204 sin cuerpo.",
        "responses": {
          "204": { "description": "Obra archivada correctamente." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/logs": {
      "get": {
        "operationId": "listLogs",
        "summary": "Lista paginada de logs compartibles de una obra.",
        "description": "Requiere scope `read`. Solo devuelve logs con `sensibilidad='compartible'`. El acceso a `interno`/`sensible` requiere scope adicional (no MVP).",
        "parameters": [
          {
            "name": "obraId",
            "in": "query",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "Filtra por obra. Parametro obligatorio."
          },
          { "$ref": "#/components/parameters/Cursor" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Pagination" },
                    { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/Log" } } } }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Falta parametro obraId.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  }
}
