{
  "openapi": "3.1.0",
  "info": {
    "title": "API de administración · Academia Derecho Virtual",
    "version": "1.0.0",
    "description": "Todo lo que se puede hacer a mano en /admin, por API, con la misma autorización y la misma auditoría.\n\nEMPIEZA POR LA RAÍZ: `GET /` cuenta, en castellano y con sólo presentar la clave, a qué proyecto\npertenece, qué autoriza EXACTAMENTE, qué no autoriza y qué ámbito falta para cada cosa. No hace falta\nleer ningún documento aparte. `GET /openapi.json` sirve este contrato ya recortado a esos ámbitos.\n\nAUTENTICACIÓN: `Authorization: Bearer dvak_v1_…`, con o sin el sufijo `@academia.derechovirtual.org`\nque lleva la credencial para decir dónde vive. La clave la emite el dueño desde /admin/api-keys y\nel secreto se enseña UNA sola vez. Toda clave lleva `catalog:read`; lo demás sólo si se marcó al crearla.\n\nESCRITURAS: `Idempotency-Key` (UUID) obligatoria; repetirla con el mismo cuerpo devuelve la misma\nrespuesta durante 24 h y no vuelve a escribir. Las que editan algo existente exigen `If-Match` con la\nversión que devolvió la lectura (cabecera `ETag`): sin ella 428, desfasada 412.\n\nCAUDAL: 60 peticiones/minuto por clave para los ámbitos de lectura y 20/minuto para los de escritura.\n\nDATOS PERSONALES: `/students/{id}/profile`, `/account` y `/payments` exigen ámbitos propios\n(`students:pii`, `payments:read`), no tienen listado en masa y dejan en la auditoría quién consultó a quién."
  },
  "servers": [
    {
      "url": "https://academia.derechovirtual.org/api/admin/v1"
    }
  ],
  "tags": [
    {
      "name": "servicio"
    },
    {
      "name": "courses"
    },
    {
      "name": "modules"
    },
    {
      "name": "lessons"
    },
    {
      "name": "blocks"
    },
    {
      "name": "documents"
    },
    {
      "name": "videos"
    },
    {
      "name": "video"
    },
    {
      "name": "products"
    },
    {
      "name": "plans"
    },
    {
      "name": "coupons"
    },
    {
      "name": "bundles"
    },
    {
      "name": "offers"
    },
    {
      "name": "fiscal"
    },
    {
      "name": "students"
    },
    {
      "name": "enrollments"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "getApiIndex",
        "summary": "Qué es esta API, a qué proyecto pertenece y qué puede hacer EXACTAMENTE la clave que pregunta: capacidades en castellano por área de negocio, lo que NO puede con el ámbito que le falta, las cabeceras obligatorias, qué hacer ante cada error y órdenes ejecutables para comprobarlo. Nunca habla de otra clave ni de ningún alumno.",
        "tags": [
          "servicio"
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndex"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenapiDocument",
        "summary": "Este mismo contrato, FILTRADO a los ámbitos de la clave que pregunta (sólo las operaciones que puede usar, con los esquemas que citan). Con ?completo=1 llega entero.",
        "tags": [
          "servicio"
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "parameters": [
          {
            "name": "completo",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "1"
              ]
            },
            "description": "Con `1` devuelve el contrato ENTERO, sin recortar a los ámbitos de la clave. Por defecto llega filtrado: a quien sólo puede usar seis operaciones no se le enseñan ochenta y nueve."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenapiDocument"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/courses": {
      "get": {
        "operationId": "listCourses",
        "summary": "Lista los cursos, borradores incluidos.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CoursePage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createCourse",
        "summary": "Crea un curso en borrador.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "catalog:write"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CourseInput"
              }
            }
          }
        }
      }
    },
    "/courses/{courseId}": {
      "get": {
        "operationId": "getCourse",
        "summary": "Devuelve un curso con su descripción.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateCourse",
        "summary": "Edita título, descripción o portada.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "catalog:write"
        ],
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CoursePatch"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteCourse",
        "summary": "Borra el curso entero. Exige ?confirmar=N; con matrículas, pedidos, producto, pack o progreso: 409.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "catalog:delete"
        ],
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          },
          {
            "name": "confirmar",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Número de bloques que morirán con la estructura. Si no coincide con el recuento real: 409 confirmation_mismatch con la cifra en el mensaje."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "204": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedStructure"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/courses/{courseId}/status": {
      "post": {
        "operationId": "setCourseStatus",
        "summary": "Publica o despublica un curso.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "catalog:publish"
        ],
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StatusInput"
              }
            }
          }
        }
      }
    },
    "/courses/{courseId}/modules": {
      "get": {
        "operationId": "listCourseModules",
        "summary": "Lista las secciones de un curso.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModulePage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createModule",
        "summary": "Crea una sección al final del temario.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "catalog:write"
        ],
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModuleResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ModuleInput"
              }
            }
          }
        }
      }
    },
    "/courses/{courseId}/modules/order": {
      "get": {
        "operationId": "getModuleOrder",
        "summary": "Orden actual de las secciones y su versión.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "reorderModules",
        "summary": "Reordena las secciones. Exige la lista completa.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "catalog:order"
        ],
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderInput"
              }
            }
          }
        }
      }
    },
    "/modules/{moduleId}": {
      "get": {
        "operationId": "getModule",
        "summary": "Devuelve una sección.",
        "tags": [
          "modules"
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "parameters": [
          {
            "name": "moduleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del sección."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModuleResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateModule",
        "summary": "Edita el título o la descripción de una sección.",
        "tags": [
          "modules"
        ],
        "x-required-scopes": [
          "catalog:write"
        ],
        "parameters": [
          {
            "name": "moduleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del sección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModuleResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ModulePatch"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteModule",
        "summary": "Borra la sección con sus lecciones y bloques. Exige ?confirmar=N con el recuento real de bloques.",
        "tags": [
          "modules"
        ],
        "x-required-scopes": [
          "catalog:delete"
        ],
        "parameters": [
          {
            "name": "moduleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del sección."
          },
          {
            "name": "confirmar",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Número de bloques que morirán con la estructura. Si no coincide con el recuento real: 409 confirmation_mismatch con la cifra en el mensaje."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "204": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedStructure"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/modules/{moduleId}/lessons": {
      "get": {
        "operationId": "listModuleLessons",
        "summary": "Lista las lecciones de una sección.",
        "tags": [
          "modules"
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "parameters": [
          {
            "name": "moduleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del sección."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createLesson",
        "summary": "Crea una lección al final de la sección.",
        "tags": [
          "modules"
        ],
        "x-required-scopes": [
          "catalog:write"
        ],
        "parameters": [
          {
            "name": "moduleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del sección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LessonInput"
              }
            }
          }
        }
      }
    },
    "/modules/{moduleId}/lessons/order": {
      "get": {
        "operationId": "getLessonOrder",
        "summary": "Orden actual de las lecciones y su versión.",
        "tags": [
          "modules"
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "parameters": [
          {
            "name": "moduleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del sección."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "reorderLessons",
        "summary": "Reordena las lecciones. Exige la lista completa.",
        "tags": [
          "modules"
        ],
        "x-required-scopes": [
          "catalog:order"
        ],
        "parameters": [
          {
            "name": "moduleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del sección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderInput"
              }
            }
          }
        }
      }
    },
    "/lessons/{lessonId}": {
      "get": {
        "operationId": "getLessonMetadata",
        "summary": "Metadatos de una lección.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "catalog:read"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateLesson",
        "summary": "Renombra una lección.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "catalog:write"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LessonPatch"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteLesson",
        "summary": "Borra la lección y sus bloques. Exige ?confirmar=N con el recuento real de bloques.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "catalog:delete"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "confirmar",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Número de bloques que morirán con la estructura. Si no coincide con el recuento real: 409 confirmation_mismatch con la cifra en el mensaje."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "204": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedStructure"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/lessons/{lessonId}/move": {
      "patch": {
        "operationId": "moveLesson",
        "summary": "Mueve la lección a otra sección del MISMO curso. If-Match: la versión del orden de la sección de ORIGEN.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "catalog:order"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MoveInput"
              }
            }
          }
        }
      }
    },
    "/courses/{courseId}/image/uploads": {
      "post": {
        "operationId": "createCourseImageUpload",
        "summary": "Firma la subida de una portada (bucket público course-covers) y devuelve la URL pública para PATCH image_url.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "catalog:write"
        ],
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CoverUploadResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CoverUploadInput"
              }
            }
          }
        }
      }
    },
    "/blocks/{blockId}": {
      "delete": {
        "operationId": "deleteBlock",
        "summary": "Borra un bloque de texto, código o embed. Vídeo de Bunny: 403 (desenlazar primero). Heredado: 409.",
        "tags": [
          "blocks"
        ],
        "x-required-scopes": [
          "content:write"
        ],
        "parameters": [
          {
            "name": "blockId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del bloque."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "204": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "getBlock",
        "summary": "Devuelve un bloque nativo con su versión.",
        "tags": [
          "blocks"
        ],
        "x-required-scopes": [
          "content:read"
        ],
        "parameters": [
          {
            "name": "blockId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del bloque."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BlockResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateBlock",
        "summary": "Edita el contenido de un bloque nativo.",
        "tags": [
          "blocks"
        ],
        "x-required-scopes": [
          "content:write"
        ],
        "parameters": [
          {
            "name": "blockId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del bloque."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BlockResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlockPatch"
              }
            }
          }
        }
      }
    },
    "/lessons/{lessonId}/status": {
      "post": {
        "operationId": "setLessonStatus",
        "summary": "Publica o despublica una lección.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "catalog:publish"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StatusInput"
              }
            }
          }
        }
      }
    },
    "/lessons/{lessonId}/preview": {
      "post": {
        "operationId": "setLessonPreview",
        "summary": "Marca la lección como muestra gratuita.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "catalog:publish"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PreviewInput"
              }
            }
          }
        }
      }
    },
    "/lessons/{lessonId}/blocks": {
      "get": {
        "operationId": "listLessonBlocks",
        "summary": "Lista los bloques de una lección.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "content:read"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BlockPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createBlock",
        "summary": "Añade un bloque de texto, de código o embebido.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "content:write"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BlockResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlockInput"
              }
            }
          }
        }
      }
    },
    "/lessons/{lessonId}/quiz": {
      "get": {
        "operationId": "getQuiz",
        "summary": "El cuestionario de la lección CON las respuestas correctas (quiz:null si no hay). Su ETag es la preimagen de PUT, PATCH y DELETE.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "quizzes:read"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuizResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "replaceQuiz",
        "summary": "Crea (201) o reemplaza (200) el cuestionario COMPLETO. Cada pregunta exige al menos una correcta y sin opciones duplicadas. Los intentos de alumnos SOBREVIVEN al reemplazo.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "quizzes:write"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuizResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuizInput"
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateQuizSettings",
        "summary": "Edita sólo los ajustes: título, si puntúa, nota de corte, reintentos y feedback.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "quizzes:write"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuizResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuizSettingsPatch"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteQuiz",
        "summary": "Borra el cuestionario. Con intentos de alumnos: 403 attempts_exist (histórico académico; se decide en el panel). Preimagen en la auditoría.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "quizzes:write"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "204": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedStructure"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/lessons/{lessonId}/documents": {
      "get": {
        "operationId": "listLessonDocuments",
        "summary": "Lista los documentos de una lección.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "media:read"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createDocument",
        "summary": "PASO 3 de 3: registra el documento y lo cuelga de la lección. `source.storage_path` es el que devolvió el ticket del paso 1, y el objeto tiene que estar YA subido (paso 2) o responde 422 storage_object_missing. También admite `source.url` para enlazar uno externo, y entonces no hacen falta los pasos 1 y 2.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "media:write"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentInput"
              }
            }
          }
        }
      }
    },
    "/lessons/{lessonId}/documents/uploads": {
      "post": {
        "operationId": "createDocumentUpload",
        "summary": "PASO 1 de 3: firma un permiso de subida. NO registra nada todavía — devuelve un ticket con `upload_url` (paso 2: PUT de los bytes con el `content_type` del ticket) y `storage_path` (paso 3: POST /lessons/{lessonId}/documents con `source.storage_path`). Sólo `filename` es obligatorio; `content_type` se deduce de la extensión y `size_bytes` sólo sirve para que te avisen antes de subir.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "media:write"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadInput"
              }
            }
          }
        }
      }
    },
    "/documents/{documentId}": {
      "get": {
        "operationId": "getDocument",
        "summary": "Devuelve un documento y su versión.",
        "tags": [
          "documents"
        ],
        "x-required-scopes": [
          "media:read"
        ],
        "parameters": [
          {
            "name": "documentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del documento."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "replaceDocument",
        "summary": "Reemplaza el fichero de un documento.",
        "tags": [
          "documents"
        ],
        "x-required-scopes": [
          "media:write"
        ],
        "parameters": [
          {
            "name": "documentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del documento."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentReplace"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteDocument",
        "summary": "Descuelga el documento. Lo heredado exige media:purge.",
        "tags": [
          "documents"
        ],
        "x-required-scopes": [
          "media:write"
        ],
        "parameters": [
          {
            "name": "documentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del documento."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "204": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/lessons/{lessonId}/videos": {
      "get": {
        "operationId": "listLessonVideos",
        "summary": "Lista los vídeos de una lección.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "video:read"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "attachVideo",
        "summary": "Cuelga un vídeo de Bunny de la lección.",
        "tags": [
          "lessons"
        ],
        "x-required-scopes": [
          "video:write"
        ],
        "parameters": [
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del lección."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VideoInput"
              }
            }
          }
        }
      }
    },
    "/videos/{videoId}": {
      "get": {
        "operationId": "getVideo",
        "summary": "Devuelve un vídeo y su versión.",
        "tags": [
          "videos"
        ],
        "x-required-scopes": [
          "video:read"
        ],
        "parameters": [
          {
            "name": "videoId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del vídeo."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "replaceVideo",
        "summary": "Reemplaza el vídeo de un bloque.",
        "tags": [
          "videos"
        ],
        "x-required-scopes": [
          "video:write"
        ],
        "parameters": [
          {
            "name": "videoId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del vídeo."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VideoInput"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "detachVideo",
        "summary": "Descuelga el vídeo. Lo heredado exige video:purge.",
        "tags": [
          "videos"
        ],
        "x-required-scopes": [
          "video:write"
        ],
        "parameters": [
          {
            "name": "videoId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del vídeo."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "204": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/video/ingest-requests": {
      "get": {
        "operationId": "listIngestRequests",
        "summary": "Lista la cola de ingesta de vídeo (filtro ?status=).",
        "tags": [
          "video"
        ],
        "x-required-scopes": [
          "video:ingest"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestRequestPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createIngestRequest",
        "summary": "Propone una ingesta con su coste mensual estimado. NUNCA la ejecuta.",
        "tags": [
          "video"
        ],
        "x-required-scopes": [
          "video:ingest"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestRequestResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IngestRequestInput"
              }
            }
          }
        }
      }
    },
    "/products": {
      "get": {
        "operationId": "listProducts",
        "summary": "Lista los productos a la venta y los apagados.",
        "tags": [
          "products"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createProduct",
        "summary": "Crea un producto SIN llamar a Stripe (el Price recurrente lo crea el primer cobro). Crear el producto ES crear su checkout: la respuesta añade checkout_url. 409 duplicate_checkout si el destino ya tiene otro producto activo.",
        "tags": [
          "products"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductCreateResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProductInput"
              }
            }
          }
        }
      }
    },
    "/products/{productId}": {
      "get": {
        "operationId": "getProduct",
        "summary": "Producto, su plan por defecto y si el escaparate cuadra.",
        "tags": [
          "products"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del producto."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateProduct",
        "summary": "Edita título, importe anunciado o el interruptor de venta. kind, mode, divisa y destino son inmutables. Cambiar amount_cents exige X-Confirm-Amount-Change (sin ella 428). 409 strikethrough_conflict con tachado publicado; 409 showcase_mismatch si descuadra. Sin DELETE: deliberado.",
        "tags": [
          "products"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del producto."
          },
          {
            "name": "X-Confirm-Amount-Change",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "description": "OBLIGATORIA sólo si el cuerpo trae amount_cents: el importe NUEVO, literal y en céntimos. Sin ella: 428 confirmation_required."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProductPatch"
              }
            }
          }
        }
      }
    },
    "/products/{productId}/strikethrough": {
      "get": {
        "operationId": "getStrikethrough",
        "summary": "El precio tachado publicado (o null), SIEMPRE con el precio de venta contra el que se mide. Su ETag es la preimagen del PUT y del DELETE: cambiar el precio del producto la invalida.",
        "tags": [
          "products"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del producto."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StrikethroughResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "setStrikethrough",
        "summary": "Fija o reemplaza el tachado (UNO por producto), con procedencia obligatoria. precio_venta_cents y currency los pone el servidor. 409 strikethrough_below_price si no SUPERA el precio. 201 al crear, 200 al reemplazar.",
        "tags": [
          "products"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del producto."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StrikethroughResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StrikethroughInput"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "clearStrikethrough",
        "summary": "Quita el tachado (único borrado físico de la familia: decoración que ningún pedido cita). La fila entera queda en admin_api_audit.detail. Responde 200 con el documento (strikethrough:null). 404 si no hay tachado.",
        "tags": [
          "products"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del producto."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StrikethroughResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/products/{productId}/active": {
      "post": {
        "operationId": "setProductActive",
        "summary": "Enciende o apaga un producto. 409 si el escaparate no cuadraría.",
        "tags": [
          "products"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del producto."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ActiveInput"
              }
            }
          }
        }
      }
    },
    "/products/{productId}/plans": {
      "get": {
        "operationId": "listProductPlans",
        "summary": "Lista los planes de precio de un producto.",
        "tags": [
          "products"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del producto."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPlan",
        "summary": "Crea un plan de precio, siempre el último por posición.",
        "tags": [
          "products"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del producto."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlanInput"
              }
            }
          }
        }
      }
    },
    "/products/{productId}/plans/order": {
      "get": {
        "operationId": "getPlanOrder",
        "summary": "Orden actual de los planes y su versión.",
        "tags": [
          "products"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del producto."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanOrderResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "reorderPlans",
        "summary": "Reordena los planes. CAMBIA EL PRECIO POR DEFECTO.",
        "tags": [
          "products"
        ],
        "x-required-scopes": [
          "commerce:order"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del producto."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanOrderResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderInput"
              }
            }
          }
        }
      }
    },
    "/plans/{planId}": {
      "get": {
        "operationId": "getPlan",
        "summary": "Devuelve un plan de precio y su versión.",
        "tags": [
          "plans"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del plan de precio."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updatePlan",
        "summary": "Edita nombre, importe, tope o visibilidad. Tipo y cuotas son inmutables.",
        "tags": [
          "plans"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del plan de precio."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlanPatch"
              }
            }
          }
        }
      }
    },
    "/plans/{planId}/archived": {
      "post": {
        "operationId": "setPlanArchived",
        "summary": "Archiva o restaura un plan. No lo borra nunca.",
        "tags": [
          "plans"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del plan de precio."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ArchivedInput"
              }
            }
          }
        }
      }
    },
    "/coupons": {
      "get": {
        "operationId": "listCoupons",
        "summary": "Lista los cupones de descuento.",
        "tags": [
          "coupons"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CouponPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createCoupon",
        "summary": "Emite un cupón. Sin course_id vale para todo el catálogo.",
        "tags": [
          "coupons"
        ],
        "x-required-scopes": [
          "coupons:write"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CouponResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CouponInput"
              }
            }
          }
        }
      }
    },
    "/coupons/{couponId}": {
      "get": {
        "operationId": "getCoupon",
        "summary": "Cupón con su estado real (caducado, agotado, desactivado).",
        "tags": [
          "coupons"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "couponId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del cupón."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CouponResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateCoupon",
        "summary": "Edición SEGURA: valor, caduca_en, usos_maximos y activo — la lista literal del dueño. El CÓDIGO no se edita jamás (identidad impresa en URLs de venta); tampoco tipo, nombre ni course_id. Cambiar valor exige X-Confirm-Value-Change (sin ella 428). Los pedidos cobrados conservan su descuento_cents congelado; el trigger sube config_revision y la fila previa queda en admin_api_audit.detail. Aviso `warnings` si la cuantía nueva dejaría cobros bajo 0,50 €. Sin DELETE: un cupón se apaga, nunca se borra.",
        "tags": [
          "coupons"
        ],
        "x-required-scopes": [
          "coupons:write"
        ],
        "parameters": [
          {
            "name": "couponId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del cupón."
          },
          {
            "name": "X-Confirm-Value-Change",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "description": "OBLIGATORIA sólo si el cuerpo trae valor: el valor NUEVO, literal (puntos de porcentaje o céntimos según el tipo inmutable del cupón). Sin ella: 428 confirmation_required."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CouponResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CouponPatch"
              }
            }
          }
        }
      }
    },
    "/coupons/{couponId}/active": {
      "post": {
        "operationId": "setCouponActive",
        "summary": "Desactiva o reactiva un cupón. No lo borra nunca.",
        "tags": [
          "coupons"
        ],
        "x-required-scopes": [
          "coupons:write"
        ],
        "parameters": [
          {
            "name": "couponId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del cupón."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CouponResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ActiveInput"
              }
            }
          }
        }
      }
    },
    "/bundles": {
      "get": {
        "operationId": "listBundles",
        "summary": "Lista los packs.",
        "tags": [
          "bundles"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BundlePage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createBundle",
        "summary": "Crea un pack VACÍO, despublicado y sin listar, en última posición. Crear no es vender.",
        "tags": [
          "bundles"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BundleResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BundleInput"
              }
            }
          }
        }
      }
    },
    "/bundles/{bundleId}": {
      "get": {
        "operationId": "getBundle",
        "summary": "Pack con su composición congelada.",
        "tags": [
          "bundles"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "bundleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del pack."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BundleResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/bundles/{bundleId}/composition": {
      "put": {
        "operationId": "setBundleComposition",
        "summary": "Fija la composición COMPLETA y ordenada del pack. Pack contenido en otro (SUMMER⊂BONUS): warning, no bloqueo. Lo vendido está congelado en order_bundle_courses.",
        "tags": [
          "bundles"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "bundleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del pack."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BundleResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BundleCompositionInput"
              }
            }
          }
        }
      }
    },
    "/offers": {
      "get": {
        "operationId": "listOffers",
        "summary": "Lista las ofertas (bump y upsell). Filtro ?active=.",
        "tags": [
          "offers"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createOffer",
        "summary": "Publica una oferta nueva envolviendo admin_publicar_oferta_v1. Descuento>0 exige el interruptor encendido (409 not_chargeable).",
        "tags": [
          "offers"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "201": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferInput"
              }
            }
          }
        }
      }
    },
    "/offers/{offerId}": {
      "get": {
        "operationId": "getOffer",
        "summary": "Devuelve una oferta y su versión (sin source_reference).",
        "tags": [
          "offers"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "offerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del oferta."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "updateOffer",
        "summary": "Reemplaza la configuración COMPLETA de la oferta. Despublicar = activa:false. No hay DELETE: deliberado.",
        "tags": [
          "offers"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "offerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del oferta."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfferInput"
              }
            }
          }
        }
      }
    },
    "/courses/{courseId}/recommendations": {
      "get": {
        "operationId": "getCourseRecommendations",
        "summary": "El conjunto de recomendaciones del curso, en orden. Su ETag es la huella oficial: este GET ES la preimagen del PUT.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "commerce:read"
        ],
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecommendationsResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "setCourseRecommendations",
        "summary": "Reemplaza la lista COMPLETA (máx. 6). activa=true exige ambos cursos publicados (409 target_not_published). >4: aviso, no bloqueo.",
        "tags": [
          "courses"
        ],
        "x-required-scopes": [
          "commerce:write"
        ],
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecommendationsResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecommendationsInput"
              }
            }
          }
        }
      }
    },
    "/fiscal/iva": {
      "get": {
        "operationId": "getFiscalIva",
        "summary": "El tipo de IVA vigente en puntos básicos enteros (2100 = 21 %), con el precepto de exención si el tipo es 0 y las facturas ya emitidas. El documento existe SIEMPRE (configurado:false si aún manda la env INVOICE_IVA_RATE); su ETag es la preimagen del PUT y NO incluye facturas_emitidas (facturar no cambia el tipo).",
        "tags": [
          "fiscal"
        ],
        "x-required-scopes": [
          "fiscal:read"
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FiscalResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "setFiscalIva",
        "summary": "Fija el tipo de IVA de TODAS las facturas futuras. Exige If-Match Y X-Confirm-Tax-Rate con los puntos básicos nuevos, literales (sin ella 428; se exige SIEMPRE). Tipo 0 sin precepto: 422 (art. 6.1.j RD 1619/2012); precepto con tipo distinto de 0: 422. La respuesta lleva `aviso`: las facturas ya emitidas no cambian (tax_rate congelado fila a fila). Antes/después en admin_api_audit.detail. 201 la primera vez, 200 después.",
        "tags": [
          "fiscal"
        ],
        "x-required-scopes": [
          "fiscal:write"
        ],
        "parameters": [
          {
            "name": "X-Confirm-Tax-Rate",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "description": "SIEMPRE obligatoria (no sólo cuando cambia): los puntos básicos nuevos, literales (2100 = 21 %). Sin ella: 428 confirmation_required."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FiscalPutResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FiscalInput"
              }
            }
          }
        }
      }
    },
    "/students": {
      "get": {
        "operationId": "listStudents",
        "summary": "Lista el alumnado con cuenta (id, correo, nombre y alta).",
        "tags": [
          "students"
        ],
        "x-required-scopes": [
          "students:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudentPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/students/{studentId}": {
      "get": {
        "operationId": "getStudent",
        "summary": "Datos básicos de un alumno.",
        "tags": [
          "students"
        ],
        "x-required-scopes": [
          "students:read"
        ],
        "parameters": [
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del alumno."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudentResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/students/{studentId}/profile": {
      "get": {
        "operationId": "getStudentProfile",
        "summary": "FICHA COMPLETA: facturación y datos heredados. Datos personales.",
        "tags": [
          "students"
        ],
        "x-required-scopes": [
          "students:pii"
        ],
        "parameters": [
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del alumno."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudentProfileResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/students/{studentId}/account": {
      "get": {
        "operationId": "getStudentAccount",
        "summary": "Si reclamó su cuenta, su actividad y lo que ha pagado.",
        "tags": [
          "students"
        ],
        "x-required-scopes": [
          "students:pii"
        ],
        "parameters": [
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del alumno."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudentAccountResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/students/{studentId}/payments": {
      "get": {
        "operationId": "listStudentPayments",
        "summary": "Pedidos con su factura y el cupón que usaron.",
        "tags": [
          "students"
        ],
        "x-required-scopes": [
          "payments:read"
        ],
        "parameters": [
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del alumno."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudentPaymentPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/students/{studentId}/enrollments": {
      "get": {
        "operationId": "listStudentEnrollments",
        "summary": "Matrículas de un alumno.",
        "tags": [
          "students"
        ],
        "x-required-scopes": [
          "enrollments:read"
        ],
        "parameters": [
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del alumno."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollmentPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/students/{studentId}/enrollments/{courseId}": {
      "get": {
        "operationId": "getStudentEnrollment",
        "summary": "Una matrícula concreta, con su versión.",
        "tags": [
          "students"
        ],
        "x-required-scopes": [
          "enrollments:read"
        ],
        "parameters": [
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del alumno."
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollmentResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/students/{studentId}/courses/{courseId}/progress": {
      "get": {
        "operationId": "getStudentCourseProgress",
        "summary": "Resumen de progreso en un curso.",
        "tags": [
          "students"
        ],
        "x-required-scopes": [
          "progress:read"
        ],
        "parameters": [
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del alumno."
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProgressResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/students/{studentId}/courses/{courseId}/progress/lessons": {
      "get": {
        "operationId": "listStudentCourseProgressLessons",
        "summary": "Lecciones completadas, una a una.",
        "tags": [
          "students"
        ],
        "x-required-scopes": [
          "progress:read"
        ],
        "parameters": [
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del alumno."
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco devuelto en page.next_cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProgressLessonPage"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/enrollments": {
      "post": {
        "operationId": "enrollStudent",
        "summary": "MATRICULA a un correo en un curso o en un pack. Enrola y matricula en un paso: si el correo ya tiene cuenta se localiza (nunca se duplica) y si no, la cuenta nace cuando el alumno canjee el código. Entrega por la vía estándar de la Academia (pedido → código de 6 dígitos → correo → /activar): `notificar` es TRUE por defecto. Con `notificar:false` concede el acceso en el acto y sin correo, y entonces exige que el alumno ya tenga cuenta (409 alumno_sin_cuenta). `termino` y `motivo` son OBLIGATORIOS; `termino:'hasta_fecha'` responde 409 termino_no_ejecutable (enrollments no tiene caducidad). Exige X-Confirm-Enrollment con el destino literal (sin ella 428). Idempotente por estado: repetir devuelve 200 con creada:false y NO manda un segundo correo. Tope diario por clave que falla cerrado (409 tope_diario). Sin If-Match: no hay documento previo que versionar.",
        "tags": [
          "enrollments"
        ],
        "x-required-scopes": [
          "enrollments:write"
        ],
        "parameters": [
          {
            "name": "X-Confirm-Enrollment",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "SIEMPRE obligatoria: el identificador del DESTINO que se regala (course_id o bundle_id), literal y repetido. Sin ella: 428 confirmation_required."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnrollInput"
              }
            }
          }
        }
      }
    },
    "/students/{studentId}/enrollments/{courseId}/active": {
      "post": {
        "operationId": "setStudentEnrollmentActive",
        "summary": "REVOCA (active:false) o reactiva (active:true) el acceso de un alumno a un curso. Apaga la matrícula, nunca borra la fila. Exige If-Match con la versión que devolvió getStudentEnrollment, X-Confirm-Enrollment-Change con el `active` NUEVO literal (cabecera DISTINTA de la de matricular, a propósito) y `motivo`. Reactivar cuenta contra el tope diario; revocar no. La preimagen queda en admin_api_audit.detail y en admin_audit_log.",
        "tags": [
          "students"
        ],
        "x-required-scopes": [
          "enrollments:write"
        ],
        "parameters": [
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del alumno."
          },
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador del curso."
          },
          {
            "name": "X-Confirm-Enrollment-Change",
            "in": "header",
            "required": true,
            "schema": {
              "enum": [
                "true",
                "false"
              ]
            },
            "description": "SIEMPRE obligatoria: el valor NUEVO de `active`, literal. Sin ella: 428 confirmation_required."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID. Repetir la misma clave con el mismo cuerpo devuelve la misma respuesta durante 24 h."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versión actual del recurso entre comillas. Sin ella: 428. Desfasada: 412."
          }
        ],
        "responses": {
          "200": {
            "description": "Correcto.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                },
                "description": "Versión del recurso para If-Match."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollmentResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "precondition_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid_parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "precondition_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnrollmentActiveInput"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Clave opaca dvak_v1_…"
      }
    },
    "schemas": {
      "Page": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "next_cursor"
        ],
        "properties": {
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "code",
              "message",
              "request_id"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              }
            }
          }
        }
      },
      "ApiIndex": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "proyecto",
          "tu_clave",
          "puedes",
          "no_puedes",
          "como_pedir_lo_que_falta",
          "como_operar",
          "comprueba_que_funciona",
          "contrato"
        ],
        "properties": {
          "proyecto": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "nombre",
              "que_es",
              "web",
              "api",
              "servicio"
            ],
            "description": "A qué negocio pertenece la credencial, dicho en cristiano y no sólo por su dominio.",
            "properties": {
              "nombre": {
                "type": "string"
              },
              "que_es": {
                "type": "string"
              },
              "web": {
                "type": "string"
              },
              "api": {
                "type": "string"
              },
              "servicio": {
                "type": "string"
              },
              "panel_de_claves": {
                "type": "string"
              }
            }
          },
          "tu_clave": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "nombre",
              "caduca_en",
              "dias_hasta_caducar",
              "ambitos"
            ],
            "description": "SIN el secreto, SIN su huella, SIN el identificador de la clave (que es un trozo literal del token) y SIN el actor que la emitió. La raíz habla de la clave que llama y de ninguna otra.",
            "properties": {
              "nombre": {
                "type": "string"
              },
              "caduca_en": {
                "type": "string",
                "format": "date-time"
              },
              "dias_hasta_caducar": {
                "type": "integer"
              },
              "ambitos": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Los ámbitos de ESTA clave, en el orden canónico de scopes.ts."
              }
            }
          },
          "puedes": {
            "type": "array",
            "description": "Lo que la clave SÍ autoriza, en castellano de negocio y agrupado por área.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "area",
                "titulo",
                "capacidades"
              ],
              "properties": {
                "area": {
                  "type": "string"
                },
                "titulo": {
                  "type": "string"
                },
                "capacidades": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "que",
                      "ambito"
                    ],
                    "properties": {
                      "que": {
                        "type": "string"
                      },
                      "ambito": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "no_puedes": {
            "type": "array",
            "description": "Lo que NO autoriza, cada línea con el ámbito que habría que pedir. Lista vacía si la clave lo lleva todo. Vale tanto como la anterior: convierte «prueba y recibe un 403» en «pide la clave correcta».",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "area",
                "titulo",
                "capacidades"
              ],
              "properties": {
                "area": {
                  "type": "string"
                },
                "titulo": {
                  "type": "string"
                },
                "capacidades": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "que",
                      "ambito_que_falta"
                    ],
                    "properties": {
                      "que": {
                        "type": "string"
                      },
                      "ambito_que_falta": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "como_pedir_lo_que_falta": {
            "type": "string"
          },
          "como_operar": {
            "type": "object",
            "description": "Base, cómo se manda la credencial, las seis cabeceras obligatorias con su regla y su valor, paginación, límites de frecuencia, qué significa y qué hacer ante cada código de error, y las reglas de la casa."
          },
          "comprueba_que_funciona": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "que",
                "orden"
              ],
              "properties": {
                "que": {
                  "type": "string"
                },
                "orden": {
                  "type": "string"
                }
              }
            },
            "description": "Órdenes ejecutables con las que confirmar que la credencial vale antes de ponerse a trabajar. La credencial viaja por variable de entorno, nunca escrita dentro de la orden."
          },
          "contrato": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "descripcion",
              "url",
              "url_completo",
              "operaciones_para_tu_clave",
              "operaciones_totales"
            ],
            "properties": {
              "descripcion": {
                "type": "string"
              },
              "url": {
                "type": "string"
              },
              "url_completo": {
                "type": "string"
              },
              "operaciones_para_tu_clave": {
                "type": "integer"
              },
              "operaciones_totales": {
                "type": "integer"
              }
            }
          }
        }
      },
      "OpenapiDocument": {
        "type": "object",
        "description": "Un documento OpenAPI 3.1 completo y válido: este mismo contrato con `paths` recortado a las operaciones que la clave puede usar y `components.schemas` podado a los esquemas que esas operaciones citan (transitivamente). Con ?completo=1 llega sin recortar."
      },
      "Course": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "enum": [
              "draft",
              "published"
            ]
          },
          "position": {
            "type": "integer"
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CourseDetail": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "enum": [
              "draft",
              "published"
            ]
          },
          "position": {
            "type": "integer"
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CourseModule": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "course_id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "position": {
            "type": "integer"
          }
        }
      },
      "LessonMetadata": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "module_id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "enum": [
              "draft",
              "published"
            ]
          },
          "position": {
            "type": "integer"
          },
          "is_preview": {
            "type": "boolean"
          }
        }
      },
      "BlockMetadata": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "lesson_id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string"
          },
          "position": {
            "type": "integer"
          }
        }
      },
      "NativeBlock": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "lesson_id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "enum": [
              "text",
              "code"
            ]
          },
          "position": {
            "type": "integer"
          },
          "payload": {
            "type": "object"
          }
        }
      },
      "MediaDocument": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "lesson_id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "enum": [
              "pdf",
              "image",
              "file"
            ]
          },
          "position": {
            "type": "integer"
          },
          "payload": {
            "type": "object"
          }
        }
      },
      "MediaVideo": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "lesson_id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "const": "video"
          },
          "position": {
            "type": "integer"
          },
          "payload": {
            "type": "object"
          }
        }
      },
      "Order": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "parent_id",
          "ids"
        ],
        "properties": {
          "parent_id": {
            "type": "string",
            "format": "uuid"
          },
          "ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "Product": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "enum": [
              "course",
              "bundle"
            ]
          },
          "course_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "bundle_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 0,
            "description": "Importe en CÉNTIMOS enteros."
          },
          "currency": {
            "type": "string"
          },
          "mode": {
            "enum": [
              "payment",
              "subscription"
            ]
          },
          "active": {
            "type": "boolean"
          },
          "default_plan_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "showcase_consistent": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "false = el producto anunciaría un precio distinto del que cobraría su plan por defecto."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Plan": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "product_id": {
            "type": "string",
            "format": "uuid"
          },
          "nombre": {
            "type": "string"
          },
          "importe_cents": {
            "type": "integer",
            "minimum": 0,
            "description": "Importe en CÉNTIMOS enteros."
          },
          "currency": {
            "type": "string"
          },
          "tipo": {
            "enum": [
              "pago_unico",
              "plazos",
              "suscripcion"
            ]
          },
          "cuotas": {
            "type": [
              "integer",
              "null"
            ]
          },
          "intervalo": {
            "enum": [
              "month",
              "year",
              null
            ]
          },
          "tope_matriculas": {
            "type": [
              "integer",
              "null"
            ]
          },
          "matriculas_usadas": {
            "type": "integer"
          },
          "posicion": {
            "type": "integer",
            "description": "El primer plan comprable y público es el que cobra el checkout."
          },
          "activo": {
            "type": "boolean"
          },
          "archivado_en": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "publico": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Coupon": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "codigo": {
            "type": "string"
          },
          "nombre": {
            "type": "string"
          },
          "tipo": {
            "enum": [
              "porcentaje",
              "importe_fijo"
            ]
          },
          "valor": {
            "type": "integer",
            "description": "1..100 si es porcentaje; CÉNTIMOS si es importe fijo."
          },
          "course_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "caduca_en": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "usos_maximos": {
            "type": [
              "integer",
              "null"
            ]
          },
          "usos_consumidos": {
            "type": "integer"
          },
          "activo": {
            "type": "boolean"
          },
          "config_revision": {
            "type": "integer"
          },
          "estado": {
            "enum": [
              "activo",
              "desactivado",
              "caducado",
              "agotado"
            ]
          },
          "alcance": {
            "enum": [
              "catalogo",
              "curso"
            ],
            "description": "catalogo = sin course_id, vale para TODO."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Bundle": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "publicado": {
            "type": "boolean"
          },
          "listado": {
            "type": "boolean"
          },
          "posicion": {
            "type": "integer"
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "course_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Student": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string"
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Address": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "line": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "region": {
            "type": [
              "string",
              "null"
            ]
          },
          "zipcode": {
            "type": [
              "string",
              "null"
            ]
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "StudentProfile": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string"
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "billing": {
            "anyOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "wants_invoice": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "legal_name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "tax_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "address_line1": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "address_line2": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "postal_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "city": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "province": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "country": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "invoice_email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "updated_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "legacy": {
            "anyOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "teachable_user_id": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "country": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "baja_marketing": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "created_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "billing": {
                    "$ref": "#/components/schemas/Address"
                  },
                  "delivery": {
                    "$ref": "#/components/schemas/Address"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "StudentAccount": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "claimed": {
            "type": "boolean"
          },
          "claimed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "teachable_user_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "legacy_last_login_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "legacy_sign_in_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "enrollments_total": {
            "type": "integer"
          },
          "enrollments_active": {
            "type": "integer"
          },
          "lessons_completed": {
            "type": "integer"
          },
          "last_lesson_completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "orders_paid": {
            "type": "integer"
          },
          "amount_paid_cents": {
            "type": "integer"
          },
          "observed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "StudentPayment": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "status": {
            "enum": [
              "pending",
              "paid",
              "failed",
              "refunded"
            ]
          },
          "kind": {
            "enum": [
              "course",
              "bundle"
            ]
          },
          "provider": {
            "enum": [
              "stripe",
              "sequra"
            ]
          },
          "modality": {
            "enum": [
              "card",
              "sepa",
              "klarna",
              "subscription",
              "sequra",
              null
            ]
          },
          "course_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "bundle_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "product_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "plan_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "amount_cents": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "descuento_cents": {
            "type": "integer"
          },
          "cupon": {
            "anyOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "codigo": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "revision": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "stripe": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "payment_intent_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "invoice_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "subscription_id": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "sequra_order_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "anonimizado_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "invoice": {
            "anyOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "number": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string"
                  },
                  "base_cents": {
                    "type": "integer"
                  },
                  "tax_cents": {
                    "type": "integer"
                  },
                  "amount_cents": {
                    "type": "integer"
                  },
                  "currency": {
                    "type": "string"
                  },
                  "tax_rate": {
                    "type": [
                      "number",
                      "string",
                      "null"
                    ]
                  },
                  "emailed_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "has_pdf": {
                    "type": "boolean"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "Enrollment": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "course_id": {
            "type": "string",
            "format": "uuid"
          },
          "active": {
            "type": "boolean"
          },
          "enrolled_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ProgressSummary": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "course_id": {
            "type": "string",
            "format": "uuid"
          },
          "total_published": {
            "type": "integer"
          },
          "completed_published": {
            "type": "integer"
          },
          "complete": {
            "type": "boolean"
          },
          "percentage": {
            "type": [
              "number",
              "null"
            ]
          },
          "observed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ProgressLesson": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "lesson_id": {
            "type": "string",
            "format": "uuid"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CourseInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "title"
        ],
        "properties": {
          "title": {
            "type": "string",
            "minLength": 3,
            "maxLength": 160
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CoursePatch": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "title": {
            "type": "string",
            "minLength": 3,
            "maxLength": 160
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ModuleInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "title"
        ],
        "properties": {
          "title": {
            "type": "string",
            "minLength": 3,
            "maxLength": 160
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ModulePatch": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "title": {
            "type": "string",
            "minLength": 3,
            "maxLength": 160
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "LessonInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "title"
        ],
        "properties": {
          "title": {
            "type": "string",
            "minLength": 3,
            "maxLength": 160
          }
        }
      },
      "LessonPatch": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "title": {
            "type": "string",
            "minLength": 3,
            "maxLength": 160
          }
        }
      },
      "StatusInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "enum": [
              "draft",
              "published"
            ]
          }
        }
      },
      "PreviewInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "is_preview"
        ],
        "properties": {
          "is_preview": {
            "type": "boolean"
          }
        }
      },
      "OrderInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ids"
        ],
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "minItems": 1,
            "maxItems": 1000,
            "description": "TODOS los elementos del mismo padre, en el orden deseado. Una lista incompleta se rechaza con 409."
          }
        }
      },
      "BlockInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "payload"
        ],
        "properties": {
          "type": {
            "enum": [
              "text",
              "code",
              "embed"
            ]
          },
          "payload": {
            "type": "object",
            "description": "text: {markdown} · code: {language, code} · embed: {url https sin userinfo, title opcional}. El panel no restringe dominios de embed al guardar; la lista blanca actúa al pintar (variables de usuario y Quizlet)."
          }
        }
      },
      "BlockPatch": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "payload"
        ],
        "properties": {
          "payload": {
            "type": "object",
            "description": "Campos del MISMO tipo del bloque: markdown · language/code · url/title. Mezclar vocabularios: 422."
          }
        }
      },
      "DocumentSource": {
        "oneOf": [
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "storage_path"
            ],
            "description": "Un archivo NUESTRO, ya subido al bucket `lesson-files`. Es el camino normal: la Academia sirve los bytes.",
            "properties": {
              "storage_path": {
                "type": "string",
                "maxLength": 320,
                "description": "El `storage_path` que devolvió el ticket de subida, COPIADO TAL CUAL. Forma: `<uuid de la lección>/<nombre del objeto>`, un solo nivel. Tiene que empezar por la lección de la ruta: apuntar a la carpeta de otra es 422. El objeto tiene que existir ya: si no, 422 storage_object_missing."
              }
            }
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "url"
            ],
            "description": "Un documento que vive FUERA y al que sólo se enlaza. No se copia nada: si ese servidor cae, la lección enseña un enlace roto.",
            "properties": {
              "url": {
                "type": "string",
                "format": "uri",
                "maxLength": 2048,
                "description": "URL `https://` (un `http://` es 422: contenido mixto y el navegador lo corta)."
              }
            }
          }
        ],
        "description": "DE DÓNDE SALE EL DOCUMENTO. EXACTAMENTE uno de los dos: `storage_path` (archivo ya subido al almacenamiento) o `url` (externo). Ni los dos a la vez, ni ninguno, ni ningún otro campo dentro. La ficha decide qué pintar mirando cuál de los dos está puesto, así que una fila con los dos —o con ninguno— es una fila que ya no se sabe pintar, y leerla devuelve 503 invalid_snapshot."
      },
      "DocumentInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "source"
        ],
        "properties": {
          "type": {
            "enum": [
              "pdf",
              "image",
              "file"
            ]
          },
          "source": {
            "$ref": "#/components/schemas/DocumentSource"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "alt": {
            "type": [
              "string",
              "null"
            ]
          },
          "allow_download": {
            "type": "boolean"
          }
        }
      },
      "DocumentReplace": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "source"
        ],
        "properties": {
          "source": {
            "$ref": "#/components/schemas/DocumentSource"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "alt": {
            "type": [
              "string",
              "null"
            ]
          },
          "allow_download": {
            "type": "boolean"
          }
        }
      },
      "UploadInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "filename"
        ],
        "description": "PASO 1 de 3 para colgar un documento. Esta llamada NO registra nada: reserva un nombre en el almacenamiento y firma un permiso de subida. Repetirla con la misma `Idempotency-Key` reserva EL MISMO objeto en vez de dejar otro huérfano en el bucket.",
        "properties": {
          "filename": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Nombre del archivo CON su extensión: «Puntos Clave 002 - Introducción.pdf». Se admiten tildes, eñes, espacios y paréntesis. NO se admiten barras (es un nombre, no una ruta), ni `..`, ni empezar por punto o guion. De la extensión sale el `content_type` cuando no se manda."
          },
          "content_type": {
            "enum": [
              "application/pdf",
              "application/zip",
              "application/msword",
              "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
              "application/vnd.ms-excel",
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
              "application/vnd.ms-powerpoint",
              "application/vnd.openxmlformats-officedocument.presentationml.presentation",
              "image/png",
              "image/jpeg",
              "image/webp",
              "image/gif",
              "image/svg+xml",
              "audio/mpeg",
              "audio/mp4",
              "audio/wav",
              "text/plain",
              "text/csv"
            ],
            "description": "OPCIONAL: si no se manda, se deduce de la extensión de `filename`. No hay MIME de vídeo a propósito — el vídeo va por POST /lessons/{id}/videos."
          },
          "size_bytes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 52428800,
            "description": "OPCIONAL, y sólo sirve para que te avisen ANTES de subir: la URL firmada no sabe cuánto va a pesar lo que llegue, y quien corta de verdad es el bucket. Declararlo evita gastar 60 MB de subida por una tubería que los va a rechazar al final. Máximo 50 MiB."
          }
        }
      },
      "UploadTicket": {
        "type": "object",
        "additionalProperties": false,
        "description": "El permiso de subida. NO es el documento: el documento no existe hasta el paso 3 (POST /lessons/{lessonId}/documents).",
        "properties": {
          "storage_path": {
            "type": "string",
            "description": "Dónde quedará el objeto. Es el valor que hay que mandar en `source.storage_path` al registrar el documento (paso 3): cópialo tal cual."
          },
          "upload_url": {
            "type": "string",
            "description": "PASO 2: sube los bytes AQUÍ con PUT y la cabecera `Content-Type` puesta al `content_type` de este mismo ticket. Caduca."
          },
          "token": {
            "type": "string",
            "description": "El token de la URL firmada, por si se sube con el SDK de Supabase (`uploadToSignedUrl`) en vez de con un PUT a pelo."
          },
          "content_type": {
            "enum": [
              "application/pdf",
              "application/zip",
              "application/msword",
              "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
              "application/vnd.ms-excel",
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
              "application/vnd.ms-powerpoint",
              "application/vnd.openxmlformats-officedocument.presentationml.presentation",
              "image/png",
              "image/jpeg",
              "image/webp",
              "image/gif",
              "image/svg+xml",
              "audio/mpeg",
              "audio/mp4",
              "audio/wav",
              "text/plain",
              "text/csv"
            ],
            "description": "El tipo definitivo: el que mandaste, o el deducido de la extensión. Es el `Content-Type` del PUT del paso 2."
          },
          "block_type": {
            "enum": [
              "pdf",
              "image",
              "file"
            ],
            "description": "El tipo de bloque que le corresponde. Es el `type` que hay que mandar al registrar el documento (paso 3)."
          },
          "max_bytes": {
            "type": "integer",
            "description": "Tope por archivo de este camino, en bytes (50 MiB)."
          }
        }
      },
      "VideoInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "provider",
          "video_id",
          "library_id",
          "resolutions"
        ],
        "properties": {
          "provider": {
            "const": "bunny"
          },
          "video_id": {
            "type": "string",
            "format": "uuid"
          },
          "library_id": {
            "type": "string"
          },
          "resolutions": {
            "type": "array",
            "items": {
              "enum": [
                "360p",
                "720p",
                "1080p"
              ]
            },
            "minItems": 1,
            "maxItems": 3
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ActiveInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "active"
        ],
        "properties": {
          "active": {
            "type": "boolean"
          }
        }
      },
      "ArchivedInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "archived"
        ],
        "properties": {
          "archived": {
            "type": "boolean"
          }
        }
      },
      "PlanInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "nombre",
          "importe_cents",
          "currency",
          "tipo"
        ],
        "description": "pago_unico: sin cuotas ni intervalo. plazos: las dos. suscripcion: sólo intervalo.",
        "properties": {
          "nombre": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "importe_cents": {
            "type": "integer",
            "minimum": 0,
            "description": "Importe en CÉNTIMOS enteros."
          },
          "currency": {
            "type": "string",
            "pattern": "^[a-z]{3}$"
          },
          "tipo": {
            "enum": [
              "pago_unico",
              "plazos",
              "suscripcion"
            ]
          },
          "cuotas": {
            "type": "integer",
            "minimum": 2,
            "maximum": 60
          },
          "intervalo": {
            "enum": [
              "month",
              "year"
            ]
          },
          "tope_matriculas": {
            "type": [
              "integer",
              "null"
            ]
          },
          "publico": {
            "type": "boolean"
          }
        }
      },
      "PlanPatch": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "description": "tipo, cuotas e intervalo son INMUTABLES: hay suscripciones vivas apuntando al plan.",
        "properties": {
          "nombre": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "importe_cents": {
            "type": "integer",
            "minimum": 0,
            "description": "Importe en CÉNTIMOS enteros."
          },
          "tope_matriculas": {
            "type": [
              "integer",
              "null"
            ]
          },
          "publico": {
            "type": "boolean"
          }
        }
      },
      "CouponInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "codigo",
          "nombre",
          "tipo",
          "valor"
        ],
        "properties": {
          "codigo": {
            "type": "string",
            "pattern": "^[A-Z0-9_-]{3,40}$",
            "description": "Ya en forma canónica: la API no normaliza por ti."
          },
          "nombre": {
            "type": "string",
            "minLength": 3,
            "maxLength": 120
          },
          "tipo": {
            "enum": [
              "porcentaje",
              "importe_fijo"
            ]
          },
          "valor": {
            "type": "integer",
            "minimum": 1,
            "description": "1..100 si es porcentaje; CÉNTIMOS si es importe fijo."
          },
          "course_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "caduca_en": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "usos_maximos": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "Deleted": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "lesson_id": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        }
      },
      "DeletedStructure": {
        "type": "object",
        "additionalProperties": false,
        "description": "Sin cuerpo. La preimagen íntegra de lo borrado queda en la auditoría (admin_api_audit.metadata).",
        "properties": {}
      },
      "MoveInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "module_id",
          "position"
        ],
        "description": "If-Match: la versión del orden de la sección de ORIGEN (GET /modules/{id}/lessons/order).",
        "properties": {
          "module_id": {
            "type": "string",
            "format": "uuid"
          },
          "position": {
            "type": "integer",
            "minimum": 0,
            "description": "Índice de inserción en la sección DESTINO: 0..N, N = al final. Sólo secciones del mismo curso."
          }
        }
      },
      "CoverUploadInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "filename",
          "content_type",
          "size_bytes"
        ],
        "description": "Mismos límites que el panel: 5 MiB y PNG/JPG/WEBP.",
        "properties": {
          "filename": {
            "type": "string"
          },
          "content_type": {
            "enum": [
              "image/png",
              "image/jpeg",
              "image/webp"
            ]
          },
          "size_bytes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5242880
          }
        }
      },
      "CoverTicket": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "storage_path": {
            "type": "string"
          },
          "upload_url": {
            "type": "string"
          },
          "token": {
            "type": "string"
          },
          "content_type": {
            "type": "string"
          },
          "image_url": {
            "type": "string",
            "description": "URL pública que servirá la portada en cuanto se suban los bytes; ponla en el curso con PATCH /courses/{id} {image_url}."
          },
          "max_bytes": {
            "type": "integer"
          }
        }
      },
      "IngestRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "lesson_id": {
            "type": "string",
            "format": "uuid"
          },
          "block_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "source_kind": {
            "enum": [
              "onedrive",
              "https"
            ]
          },
          "source_ref": {
            "type": "string"
          },
          "status": {
            "enum": [
              "propuesta",
              "aprobada",
              "rechazada",
              "caducada",
              "ingerida",
              "fallida"
            ]
          },
          "duration_seconds": {
            "type": "integer"
          },
          "source_bytes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "est_gb": {
            "type": "number",
            "description": "Estimación del SERVIDOR (banda conservadora medida), no del peticionario."
          },
          "est_usd_month": {
            "type": "number",
            "description": "USD/mes RECURRENTES: se paga mientras el vídeo viva en Bunny."
          },
          "library_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "approved_usd_month": {
            "type": [
              "number",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Offer": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "producto_origen_id": {
            "type": "string",
            "format": "uuid"
          },
          "producto_ofrecido_id": {
            "type": "string",
            "format": "uuid"
          },
          "plan_ofrecido_id": {
            "type": "string",
            "format": "uuid",
            "description": "Plan del producto ofrecido con el que se cobraría. El precio SIEMPRE sale de este plan; descuento_cents se le resta (nunca hay un segundo precio)."
          },
          "momento": {
            "enum": [
              "tras_pagar",
              "en_el_pago"
            ]
          },
          "titulo": {
            "type": "string"
          },
          "gancho": {
            "type": [
              "string",
              "null"
            ]
          },
          "vinetas": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "imagen_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Sólo lectura por esta API: la RPC envuelta no la admite como entrada."
          },
          "posicion": {
            "type": "integer"
          },
          "activa": {
            "type": "boolean"
          },
          "ventana_minutos": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Sólo lectura por esta API: la RPC envuelta no la admite como entrada."
          },
          "modalidad": {
            "enum": [
              "complemento",
              "sustitucion"
            ]
          },
          "descuento_cents": {
            "type": "integer",
            "minimum": 0,
            "description": "Céntimos que se restan del plan. Mientras ofertas_importe_cobrable_v1() devuelva false, sólo 0 es publicable."
          },
          "revision": {
            "type": "integer",
            "minimum": 1
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "OfferRow": {
        "type": "object",
        "additionalProperties": false,
        "description": "Fila de listado: sin gancho, viñetas, imagen ni ventana. El documento trae todo.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "producto_origen_id": {
            "type": "string",
            "format": "uuid"
          },
          "producto_ofrecido_id": {
            "type": "string",
            "format": "uuid"
          },
          "plan_ofrecido_id": {
            "type": "string",
            "format": "uuid"
          },
          "momento": {
            "enum": [
              "tras_pagar",
              "en_el_pago"
            ]
          },
          "titulo": {
            "type": "string"
          },
          "posicion": {
            "type": "integer"
          },
          "activa": {
            "type": "boolean"
          },
          "modalidad": {
            "enum": [
              "complemento",
              "sustitucion"
            ]
          },
          "descuento_cents": {
            "type": "integer",
            "minimum": 0
          },
          "revision": {
            "type": "integer",
            "minimum": 1
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "OfferInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "producto_origen_id",
          "producto_ofrecido_id",
          "plan_ofrecido_id",
          "momento",
          "titulo",
          "posicion",
          "activa"
        ],
        "properties": {
          "producto_origen_id": {
            "type": "string",
            "format": "uuid"
          },
          "producto_ofrecido_id": {
            "type": "string",
            "format": "uuid"
          },
          "plan_ofrecido_id": {
            "type": "string",
            "format": "uuid"
          },
          "momento": {
            "enum": [
              "tras_pagar",
              "en_el_pago"
            ],
            "description": "en_el_pago = bump (suma al PaymentIntent: exige plan de pago único, excluye Legal Prime). tras_pagar = upsell (cobro aparte: admite cualquier plan)."
          },
          "titulo": {
            "type": "string",
            "minLength": 2,
            "maxLength": 120
          },
          "gancho": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 400
          },
          "vinetas": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          },
          "posicion": {
            "type": "integer",
            "minimum": 0,
            "maximum": 999
          },
          "activa": {
            "type": "boolean",
            "description": "true exige extremos activos/publicados y plan con plazas (409 target_unavailable si no)."
          },
          "modalidad": {
            "enum": [
              "complemento",
              "sustitucion"
            ]
          },
          "descuento_cents": {
            "type": "integer",
            "minimum": 0,
            "maximum": 999999999,
            "description": ">0 sólo con el interruptor de descuentos encendido; hoy devuelve 409 not_chargeable."
          },
          "plan_importe_cents": {
            "type": "integer",
            "minimum": 0,
            "description": "ASERCIÓN opcional: «publico contando con que el plan cobra ESTO». Si el plan vivo cobra otra cosa: 409 plan_changed y no se escribe."
          }
        }
      },
      "Recommendations": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "parent_id",
          "items"
        ],
        "properties": {
          "parent_id": {
            "type": "string",
            "format": "uuid",
            "description": "El curso de origen."
          },
          "items": {
            "type": "array",
            "maxItems": 6,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "course_id",
                "activa",
                "posicion"
              ],
              "properties": {
                "course_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "activa": {
                  "type": "boolean"
                },
                "posicion": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 5
                }
              }
            }
          }
        }
      },
      "RecommendationsInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "items"
        ],
        "description": "La lista COMPLETA en orden de posición: esto REEMPLAZA, no añade. Máx. 6 (tope de la tabla y de recomendaciones_publicas_v1 LIMIT 6). Guardar 5-6 es reserva legal y la respuesta lo avisa.",
        "properties": {
          "items": {
            "type": "array",
            "maxItems": 6,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "course_id",
                "activa"
              ],
              "properties": {
                "course_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "activa": {
                  "type": "boolean"
                }
              }
            }
          }
        }
      },
      "BundleInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "title"
        ],
        "properties": {
          "title": {
            "type": "string",
            "minLength": 3,
            "maxLength": 160
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 4000
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "maxLength": 2048
          },
          "publicado": {
            "type": "boolean",
            "default": false
          },
          "listado": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "BundleCompositionInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "course_ids"
        ],
        "properties": {
          "course_ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Completa, ordenada (el orden ES la posición) y sin duplicados. Cursos inexistentes: 404."
          }
        }
      },
      "ProductInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "title",
          "kind",
          "amount_cents",
          "mode"
        ],
        "description": "kind=course exige course_id (y no bundle_id); kind=bundle al revés. Sin campos stripe_*: los ids de Stripe los escribe el cobro, nunca el cliente.",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 2,
            "maxLength": 200
          },
          "kind": {
            "enum": [
              "course",
              "bundle"
            ]
          },
          "course_id": {
            "type": "string",
            "format": "uuid"
          },
          "bundle_id": {
            "type": "string",
            "format": "uuid"
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 0,
            "description": "Importe en CÉNTIMOS enteros."
          },
          "currency": {
            "type": "string",
            "pattern": "^[a-z]{3}$",
            "default": "eur"
          },
          "mode": {
            "enum": [
              "payment",
              "subscription"
            ]
          },
          "active": {
            "type": "boolean",
            "default": true,
            "description": "Con true, el checkout /checkout/{id} nace vivo. Con true y otro producto activo del mismo destino: 409 duplicate_checkout."
          }
        }
      },
      "ProductPatch": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "description": "kind, mode, divisa y destino son INMUTABLES: para vender otra cosa se crea otro producto y se apaga éste. amount_cents exige la cabecera X-Confirm-Amount-Change.",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 2,
            "maxLength": 200
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 0
          },
          "active": {
            "type": "boolean"
          }
        }
      },
      "ProductCreateResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "checkout_url"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Product"
          },
          "checkout_url": {
            "type": "string",
            "description": "El checkout que nace de este producto: /checkout/{id}. Vivo si active=true (y el escaparate cuadra)."
          }
        }
      },
      "Strikethrough": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "product_id",
          "precio_venta_cents",
          "currency",
          "strikethrough"
        ],
        "properties": {
          "product_id": {
            "type": "string",
            "format": "uuid"
          },
          "precio_venta_cents": {
            "type": "integer",
            "description": "El precio ACTUAL del producto, contra el que se mide el tachado. Forma parte del ETag."
          },
          "currency": {
            "type": "string"
          },
          "strikethrough": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "importe_cents": {
                    "type": "integer",
                    "description": "SIEMPRE > precio_venta_cents."
                  },
                  "currency": {
                    "type": "string"
                  },
                  "clase": {
                    "enum": [
                      "precio_anterior",
                      "valor_incluido"
                    ]
                  },
                  "etiqueta": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "El rótulo LITERAL de la página de venta, o null si no pone ninguno. No se inventa."
                  },
                  "url_origen": {
                    "type": "string",
                    "description": "La página que publica el tachado: la respuesta a «¿de dónde sale este número?»."
                  },
                  "veredicto": {
                    "enum": [
                      "acreditable",
                      "no_acreditable",
                      "no_se_sabe"
                    ]
                  },
                  "acreditacion": {
                    "type": [
                      "object",
                      "null"
                    ]
                  },
                  "fuente": {
                    "type": "string"
                  },
                  "observado_en": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "updated_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            ]
          }
        }
      },
      "StrikethroughInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "importe_cents",
          "clase",
          "url_origen",
          "veredicto",
          "fuente",
          "observado_en"
        ],
        "description": "precio_venta_cents y currency NO viajan: los pone el servidor leyendo el producto. acreditable exige acreditacion; valor_incluido exige veredicto=no_acreditable.",
        "properties": {
          "importe_cents": {
            "type": "integer",
            "minimum": 1,
            "maximum": 99999999
          },
          "clase": {
            "enum": [
              "precio_anterior",
              "valor_incluido"
            ]
          },
          "etiqueta": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "url_origen": {
            "type": "string",
            "pattern": "^https://"
          },
          "veredicto": {
            "enum": [
              "acreditable",
              "no_acreditable",
              "no_se_sabe"
            ]
          },
          "acreditacion": {
            "type": [
              "object",
              "null"
            ]
          },
          "fuente": {
            "type": "string",
            "minLength": 3,
            "maxLength": 300
          },
          "observado_en": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CouponPatch": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "description": "Sólo configuración: codigo, tipo, nombre, course_id y usos_consumidos son INMUTABLES por esta vía. `valor` exige la cabecera X-Confirm-Value-Change (el valor nuevo, literal: puntos de porcentaje o céntimos según el tipo inmutable del cupón).",
        "properties": {
          "valor": {
            "type": "integer",
            "minimum": 1,
            "description": "Puntos de porcentaje (1..100 si tipo=porcentaje, validado contra el tipo vivo en el SQL) o céntimos enteros (tipo=importe_fijo)."
          },
          "caduca_en": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "ISO con zona horaria, o null para quitar la caducidad."
          },
          "usos_maximos": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "description": "null = sin tope. Un tope por debajo de usos_consumidos deja el cupón agotado de inmediato (legítimo para cortarlo)."
          },
          "activo": {
            "type": "boolean"
          }
        }
      },
      "Fiscal": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "configurado",
          "tipo_iva_bp",
          "precepto_exencion",
          "facturas_emitidas",
          "updated_at"
        ],
        "properties": {
          "configurado": {
            "type": "boolean",
            "description": "false = no hay fila y manda la env INVOICE_IVA_RATE (lib/fiscal.ts)."
          },
          "tipo_iva_bp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 10000,
            "description": "Puntos básicos ENTEROS: 2100 = 21 %. Nunca fracción con coma flotante."
          },
          "precepto_exencion": {
            "type": [
              "string",
              "null"
            ],
            "description": "Mención del art. 6.1.j RD 1619/2012; obligatoria si el tipo es 0 y prohibida si no lo es."
          },
          "facturas_emitidas": {
            "type": "integer",
            "minimum": 0,
            "description": "Para el diff «las N de hoy no cambian». NO entra en el ETag."
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          }
        }
      },
      "FiscalInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "tipo_iva_bp",
          "precepto_exencion"
        ],
        "description": "El documento COMPLETO deseado, no un parche: precepto_exencion es obligatorio en el cuerpo (aunque sea null) para que nadie cambie el tipo sin decidir sobre la exención.",
        "properties": {
          "tipo_iva_bp": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000
          },
          "precepto_exencion": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          }
        }
      },
      "FiscalPutResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "aviso"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Fiscal"
          },
          "aviso": {
            "type": "string",
            "description": "Literal del contrato: «Las facturas ya emitidas (N hasta hoy) no cambian: su tax_rate está congelado fila a fila. Toda factura futura saldrá con el desglose del tipo nuevo desde este instante.»"
          }
        }
      },
      "Quiz": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "lesson_id",
          "quiz"
        ],
        "description": "UN documento por lección: quiz:null si no hay cuestionario. Ese GET es la preimagen (If-Match) de PUT, PATCH y DELETE — también del PUT que crea.",
        "properties": {
          "lesson_id": {
            "type": "string",
            "format": "uuid"
          },
          "quiz": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "title": {
                    "type": "string"
                  },
                  "is_graded": {
                    "type": "boolean"
                  },
                  "pass_percent": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100
                  },
                  "max_attempts": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 99,
                    "description": "0 = ilimitados."
                  },
                  "show_feedback": {
                    "type": "boolean"
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "questions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "additionalProperties": false,
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "position": {
                          "type": "integer"
                        },
                        "prompt": {
                          "type": "string"
                        },
                        "explanation": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Lo que se le enseña al alumno DESPUÉS de contestar cuando ACIERTA (o deja en blanco). null si no hay."
                        },
                        "wrong_explanation": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Lo que se le enseña cuando FALLA. null si no hay. Una respuesta mal NUNCA toma prestada la explicación de la buena."
                        },
                        "options": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": false,
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "position": {
                                "type": "integer"
                              },
                              "label": {
                                "type": "string"
                              },
                              "is_correct": {
                                "type": "boolean",
                                "description": "AQUÍ SÍ viaja: superficie de administración con ámbito propio. La RPC del aula la esconde."
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            ]
          }
        }
      },
      "QuizInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "title",
          "is_graded",
          "pass_percent",
          "max_attempts",
          "show_feedback",
          "questions"
        ],
        "description": "REEMPLAZO COMPLETO y sin valores por defecto: quien reemplaza, afirma. Los intentos de alumnos sobreviven (la fila del quiz se actualiza, no se recrea).",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 160
          },
          "is_graded": {
            "type": "boolean"
          },
          "pass_percent": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "max_attempts": {
            "type": "integer",
            "minimum": 0,
            "maximum": 99
          },
          "show_feedback": {
            "type": "boolean"
          },
          "questions": {
            "type": "array",
            "minItems": 1,
            "maxItems": 500,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "prompt",
                "options"
              ],
              "properties": {
                "prompt": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 1000
                },
                "explanation": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "minLength": 1,
                  "maxLength": 8000,
                  "description": "OPCIONAL. Lo que se le enseña al alumno DESPUÉS de contestar cuando acierta o deja en blanco; nunca antes. «No hay» se dice omitiéndola o con null, NUNCA con cadena vacía."
                },
                "wrong_explanation": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "minLength": 1,
                  "maxLength": 8000,
                  "description": "OPCIONAL. Lo que se le enseña cuando FALLA. Si falta, al que falla no se le enseña la de acertar: se calla."
                },
                "options": {
                  "type": "array",
                  "minItems": 2,
                  "maxItems": 50,
                  "items": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "label",
                      "is_correct"
                    ],
                    "properties": {
                      "label": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 500
                      },
                      "is_correct": {
                        "type": "boolean"
                      }
                    }
                  },
                  "description": "Al menos una correcta y sin etiquetas duplicadas: una pregunta sin correcta es imposible de aprobar y las duplicadas obligarían a marcar las dos copias."
                }
              }
            },
            "description": "El orden del array ES el orden del examen: las posiciones las asigna el servidor."
          }
        }
      },
      "QuizSettingsPatch": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 160
          },
          "is_graded": {
            "type": "boolean"
          },
          "pass_percent": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "max_attempts": {
            "type": "integer",
            "minimum": 0,
            "maximum": 99
          },
          "show_feedback": {
            "type": "boolean"
          }
        }
      },
      "EnrollmentGrant": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "creada",
          "email",
          "user_id",
          "kind",
          "course_id",
          "bundle_id",
          "termino",
          "motivo",
          "notificar",
          "entrega",
          "order_id",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Fila del libro mayor `matriculas_api`. null cuando no se creó nada."
          },
          "creada": {
            "type": "boolean",
            "description": "false = idempotencia: ya estaba matriculado, o ya tenía un código sin canjear para el mismo destino. No se ha mandado un segundo correo."
          },
          "email": {
            "type": "string"
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "El perfil, si ya existía. null = la cuenta nacerá cuando canjee el código en /activar."
          },
          "kind": {
            "enum": [
              "course",
              "bundle"
            ]
          },
          "course_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "bundle_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "termino": {
            "enum": [
              "vitalicio",
              "hasta_fecha"
            ],
            "description": "Hoy sólo sale `vitalicio`: ver 409 termino_no_ejecutable."
          },
          "motivo": {
            "type": "string"
          },
          "notificar": {
            "type": "boolean"
          },
          "entrega": {
            "enum": [
              "correo_con_codigo",
              "acceso_inmediato",
              "ya_tenia_acceso"
            ],
            "description": "correo_con_codigo = hay pedido y el alumno recibe su código (el acceso llega al activar). acceso_inmediato = concedido en el acto, sin correo. ya_tenia_acceso = no había nada que hacer."
          },
          "order_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "El pedido de la entrega estándar. Es el argumento de reenviarCodigoDeAcceso() si el correo no llegara a salir."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EnrollInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "email",
          "termino",
          "motivo"
        ],
        "description": "Exactamente uno de course_id / bundle_id. Un pack sin cursos: 404.",
        "properties": {
          "email": {
            "type": "string",
            "minLength": 3,
            "maxLength": 320,
            "description": "El alumno. Puede no tener cuenta todavía: se normaliza a minúsculas y es la identidad de la entrega."
          },
          "course_id": {
            "type": "string",
            "format": "uuid"
          },
          "bundle_id": {
            "type": "string",
            "format": "uuid"
          },
          "termino": {
            "enum": [
              "vitalicio",
              "hasta_fecha"
            ],
            "description": "OBLIGATORIO y explícito. `hasta_fecha` responde 409 termino_no_ejecutable: `enrollments` no tiene expires_at y ninguna regla de acceso mira caducidad, así que concederlo sería dar acceso perpetuo llamándolo temporal."
          },
          "termino_caduca_en": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Sólo con `hasta_fecha`, que hoy se rechaza."
          },
          "motivo": {
            "type": "string",
            "minLength": 3,
            "maxLength": 200,
            "description": "OBLIGATORIO. «pagó por Woo», «colaborador», «incidencia 1.241 €». Va a matriculas_api, a admin_api_audit.detail y a admin_audit_log."
          },
          "notificar": {
            "type": "boolean",
            "default": true,
            "description": "true (POR DEFECTO) = entrega estándar: pedido, código de 6 dígitos y correo; el acceso llega cuando el alumno activa. false = migración silenciosa: concede el acceso en el acto y no manda nada, y exige que el alumno ya tenga cuenta."
          }
        }
      },
      "EnrollmentActiveInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "active",
          "motivo"
        ],
        "properties": {
          "active": {
            "type": "boolean"
          },
          "motivo": {
            "type": "string",
            "minLength": 3,
            "maxLength": 200,
            "description": "OBLIGATORIO, también para revocar."
          }
        }
      },
      "IngestRequestInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "lesson_id",
          "source",
          "duration_seconds"
        ],
        "description": "El coste NO se manda: lo calcula el servidor a partir de la duración y los bytes.",
        "properties": {
          "lesson_id": {
            "type": "string",
            "format": "uuid"
          },
          "block_id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "kind",
                  "item_id"
                ],
                "properties": {
                  "kind": {
                    "const": "onedrive"
                  },
                  "item_id": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 256
                  }
                }
              },
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "kind",
                  "url"
                ],
                "properties": {
                  "kind": {
                    "const": "https"
                  },
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "pattern": "^https://[^\\s?#]+$",
                    "description": "SIN query ni fragmento: la cola no guarda URLs firmadas; el runner firma tarde."
                  }
                }
              }
            ]
          },
          "duration_seconds": {
            "type": "integer",
            "minimum": 1,
            "maximum": 86400
          },
          "source_bytes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 137438953472
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 160
          },
          "note": {
            "type": "string",
            "maxLength": 2000
          }
        }
      },
      "CoursePage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Course"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "ModulePage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CourseModule"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "LessonPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LessonMetadata"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "BlockPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BlockMetadata"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "DocumentPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MediaDocument"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "VideoPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MediaVideo"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "ProductPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Product"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "PlanPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Plan"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "CouponPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Coupon"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "BundlePage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Bundle"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "StudentPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Student"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "StudentPaymentPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StudentPayment"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "EnrollmentPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Enrollment"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "ProgressLessonPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProgressLesson"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "IngestRequestPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IngestRequest"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "OfferPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "page"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OfferRow"
            }
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          }
        }
      },
      "CourseResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/CourseDetail"
          }
        }
      },
      "ModuleResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/CourseModule"
          }
        }
      },
      "LessonResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/LessonMetadata"
          }
        }
      },
      "BlockResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/NativeBlock"
          }
        }
      },
      "DocumentResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/MediaDocument"
          }
        }
      },
      "VideoResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/MediaVideo"
          }
        }
      },
      "UploadResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/UploadTicket"
          }
        }
      },
      "CoverUploadResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/CoverTicket"
          }
        }
      },
      "OrderResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Order"
          }
        }
      },
      "PlanOrderResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Order"
          }
        }
      },
      "ProductResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Product"
          }
        }
      },
      "PlanResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Plan"
          }
        }
      },
      "StudentResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Student"
          }
        }
      },
      "StudentProfileResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/StudentProfile"
          }
        }
      },
      "StudentAccountResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/StudentAccount"
          }
        }
      },
      "EnrollmentResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Enrollment"
          }
        }
      },
      "ProgressResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/ProgressSummary"
          }
        }
      },
      "IngestRequestResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/IngestRequest"
          }
        }
      },
      "StrikethroughResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Strikethrough"
          }
        }
      },
      "QuizResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Quiz"
          }
        }
      },
      "FiscalResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Fiscal"
          }
        }
      },
      "CouponResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Coupon"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Avisos que NO bloquean; la operación está hecha. Ausente cuando no hay ninguno."
          }
        }
      },
      "BundleResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Bundle"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Avisos que NO bloquean; la operación está hecha. Ausente cuando no hay ninguno."
          }
        }
      },
      "OfferResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Offer"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Avisos que NO bloquean; la operación está hecha. Ausente cuando no hay ninguno."
          }
        }
      },
      "RecommendationsResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Recommendations"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Avisos que NO bloquean; la operación está hecha. Ausente cuando no hay ninguno."
          }
        }
      },
      "EnrollResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/EnrollmentGrant"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Avisos que NO bloquean; la operación está hecha. Ausente cuando no hay ninguno."
          }
        }
      }
    }
  }
}
