{"openapi":"3.1.0","info":{"title":"EduRails API","version":"1.0.0","description":"API v1 del monorepo **EduRails** (api.edurails.com).\n\n- Prefijo de negocio: `/api/v1`. Probes `/health`, `/ready` y `/version` SIN prefijo.\n- Autenticación por cookies **HttpOnly**: `access_token` (JWT, 15 min) +\n  `refresh_token` (opaco, rotativo, hasheado en DB). `SameSite=Lax`,\n  `Secure` según `COOKIE_SECURE`. Esquema único: `cookieAuth`, global.\n- Operaciones públicas marcadas con `security: []` (health/ready/version, verificación\n  de certificado, perfil público, plantilla pública de certificado, código por\n  email, métodos de acceso y OAuth, webhooks firmados de proveedores).\n- Errores SIEMPRE con envelope `{\"error\":{\"code\",\"message\",\"request_id?\"}}`.\n","license":{"name":"Proprietary — Valfiguer","url":"https://edurails.com"},"contact":{"name":"Valfiguer (EduRails)","url":"https://edurails.com"}},"servers":[{"url":"https://api.edurails.com","description":"Producción"},{"url":"https://api.staging.edurails.com","description":"Staging (sin desplegar todavía; el host queda reservado en el contrato)"},{"url":"http://localhost:8000","description":"Desarrollo local (uvicorn / docker compose)"}],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"tags":[{"name":"health","description":"Probes de proceso e infraestructura (sin prefijo /api/v1)"},{"name":"auth","description":"Acceso (código por email, Google, GitHub), sesión por cookies y perfil propio"},{"name":"users","description":"Perfiles, avatar, racha/actividad, valoraciones, leaderboard, XP"},{"name":"courses","description":"Catálogo de cursos, lecciones, quizzes y valoraciones"},{"name":"search","description":"Búsqueda de contenido dentro del catálogo visible de la organización"},{"name":"progress","description":"Inscripción, avance de lecciones y envío de quizzes"},{"name":"exams","description":"Exámenes proctorizados (sesiones, preguntas y corrección)"},{"name":"certificates","description":"Certificados de curso y programa, verificación y plantilla"},{"name":"gamification","description":"Insignias, títulos, retos, misiones, recompensas diarias y metas"},{"name":"flashcards","description":"Repaso espaciado SM-2 y mazos por curso"},{"name":"games","description":"Modos de juego individuales (supervivencia/velocidad/riesgo)"},{"name":"duels","description":"Duelos 1v1 en tiempo real (emparejamiento y juego)"},{"name":"paths","description":"Rutas de aprendizaje y programas (certificado profesional / capstone)"},{"name":"cohorts","description":"Cohortes, institutos y su CRUD de administración"},{"name":"teacher","description":"Paneles de profesor sobre cohortes asignadas"},{"name":"editor","description":"Autoría de cursos y programas (lecciones, quizzes, examen, flashcards, certificado)"},{"name":"notes","description":"Notas personales por lección + exportación markdown"},{"name":"social","description":"Feed de actividad y feed social con reacciones"},{"name":"notifications","description":"Avisos (bandeja in-app, preferencias por categoría, horas de silencio), web push VAPID, push nativo y baja en un clic"},{"name":"stats","description":"Estadísticas de plataforma / por usuario / por curso"},{"name":"organizations","description":"Organizaciones (instituciones), mis organizaciones, uso del plan (A1)"},{"name":"moderation","description":"Cola de moderación del feed de una organización (F9)"},{"name":"members","description":"Miembros de una organización, roles e importación CSV (A1)"},{"name":"invitations","description":"Invitaciones por email a una organización (A1)"},{"name":"audit","description":"Registro de auditoría (quién, qué, organización, huella de IP), sin datos personales"},{"name":"privacy","description":"Derechos RGPD y privacidad (consentimientos, exportación, supresión, tramo de edad, menores)"},{"name":"announcements","description":"Anuncios de plataforma vigentes, vistos desde la app"},{"name":"webhooks","description":"Eventos firmados de proveedores externos (Resend)"},{"name":"public","description":"Formularios y catálogo públicos de la landing (sin sesión)"},{"name":"reports","description":"Informes de la organización para su admin (A10), con exportación asíncrona"},{"name":"avatars","description":"Avatares Open Peeps (DiceBear) generados en el servidor y personalizables (EDFRO-264)"},{"name":"support","description":"Canal de soporte in-app hacia Projekt Support o el buzón de soporte (E0.7)"},{"name":"telemetry","description":"Web Vitals y errores de cliente del front, sin datos personales"},{"name":"api-keys","description":"Tokens de acceso personales (PAT `edr_live_`) y la credencial de la petición (A2c)"},{"name":"oauth","description":"Authorization Server OAuth 2.1 del MCP remoto (DCR, PKCE, discovery RFC 8414/9728)"},{"name":"tutor","description":"Tutor IA sobre el contenido del curso (SSE y citas) y propuestas de quiz con IA (A12/A12b/A12d)"},{"name":"ai-usage","description":"Consumo y cupos de IA por persona, organización y plataforma (A12c)"}],"paths":{"/health":{"get":{"operationId":"getHealth","tags":["health"],"summary":"Liveness — proceso vivo","description":"Devuelve 200 si el proceso atiende peticiones. No toca DB ni Redis.","security":[],"responses":{"200":{"description":"Proceso vivo.","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","examples":["ok"]}}}}}}}}},"/ready":{"get":{"operationId":"getReady","tags":["health"],"summary":"Readiness — DB y Redis OK","description":"Devuelve 200 solo si MySQL y Redis responden; 503 en caso contrario.","security":[],"responses":{"200":{"description":"Dependencias OK (DB + Redis).","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","examples":["ok"]}}}}}},"503":{"description":"DB o Redis no disponibles.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/version":{"get":{"operationId":"getVersion","tags":["health"],"summary":"Versión desplegada (metadata de build)","description":"Qué código está corriendo: versión, commit (inyectado en el build de la imagen con `APP_GIT_SHA`), hora del build y la revisión de Alembic que espera este código (la cabeza de la cadena de migraciones, leída de los ficheros; no consulta la base). Lo usan el humo del despliegue, el rollback y la monitorización. Público y sin dependencias: nunca expone secretos ni configuración, solo texto fijado en build.","security":[],"responses":{"200":{"description":"Metadata de build.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionOut"}}}}}}},"/api/v1/auth/providers":{"get":{"operationId":"getAuthProviders","tags":["auth"],"summary":"Métodos de acceso disponibles","description":"Qué métodos de acceso ofrece este despliegue. El código por email está siempre; Google y GitHub solo cuando el proveedor tiene credenciales configuradas (si no, su `start` responde 503 y el front esconde el botón). Público: no revela nada de ningún usuario.","security":[],"responses":{"200":{"description":"Métodos disponibles.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthProviders"}}}}}}},"/api/v1/auth/refresh":{"post":{"operationId":"refreshSession","tags":["auth"],"summary":"Refresh de sesión","description":"Usa la cookie HttpOnly `refresh_token` (no la de acceso) para emitir un nuevo `access_token` y ROTAR el `refresh_token` (el anterior queda invalidado en DB).","security":[],"responses":{"200":{"description":"Sesión renovada; cookies rotadas.","headers":{"Set-Cookie":{"description":"Nuevos `access_token` y `refresh_token` (rotación completa) y la cookie de presencia `edr_session`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"Refresh token ausente, inválido, caducado o ya rotado. La respuesta borra las cookies de sesión (incluida la de presencia).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/auth/logout":{"post":{"operationId":"logoutUser","tags":["auth"],"summary":"Logout","description":"Revoca el refresh token en DB y limpia ambas cookies. Idempotente.","security":[],"responses":{"204":{"description":"Sesión cerrada; cookies limpiadas.","headers":{"Set-Cookie":{"description":"`access_token`, `refresh_token` y `edr_session` vaciadas (Max-Age=0).","schema":{"type":"string"}}}}}}},"/api/v1/auth/me":{"get":{"operationId":"getCurrentUser","x-tool":{"name":"whoami","domain":"org","profile":"core","sensitive":false},"tags":["auth"],"summary":"Usuario actual","description":"Devuelve el usuario autenticado por la cookie `access_token`.","responses":{"200":{"description":"Usuario autenticado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"No autenticado (cookie ausente, inválida o caducada).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/credential":{"get":{"operationId":"getCurrentCredential","tags":["api-keys"],"summary":"La credencial de esta petición","description":"Con cookie, `kind: session`. Con `Authorization: Bearer edr_live_…|edr_oat_…`, la organización a la que está acotada, el rol ahí y su acceso.","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CurrentCredential"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/v1/me/profile":{"patch":{"operationId":"updateMyProfile","tags":["auth"],"summary":"Editar mi perfil","description":"El usuario autenticado edita SU PROPIO perfil (nunca el de otro: el sujeto es siempre la sesión). Campos parciales; se ignora lo no enviado. NO permite cambiar email ni contraseña. Devuelve el `User` actualizado. Longitudes máximas: `name` 255, `headline` 160, `location` 120, `bio` 500, `avatar_url` 512. Enviar `null` en `headline`/`bio`/`location`/`avatar_url` los borra. `locale` (`es`|`en`) cambia el idioma de los correos. `public_profile` activa o apaga el perfil ante su organización.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Todos los campos son opcionales; se aplican solo los presentes.","properties":{"name":{"type":"string","description":"Nombre visible (no puede quedar vacío si se envía).","minLength":1,"maxLength":255},"headline":{"type":["string","null"],"description":"Titular/rol; `null` lo borra.","maxLength":160},"bio":{"type":["string","null"],"description":"Biografía; `null` la borra.","maxLength":500},"location":{"type":["string","null"],"description":"Ubicación; `null` la borra.","maxLength":120},"avatar_url":{"type":["string","null"],"description":"URL del avatar; `null` lo borra.","maxLength":512},"locale":{"type":"string","enum":["es","en"],"description":"Idioma de la persona; manda en sus correos transaccionales (usuario > organización > `es`). No admite `null`."},"public_profile":{"type":"boolean","description":"Activa o apaga el perfil ante los compañeros de su organización (`GET /organizations/{org_id}/profiles/{username}`). Apagado de fábrica (privacidad por defecto). No admite `null`."}}}}}},"responses":{"200":{"description":"Perfil actualizado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"No autenticado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Cuerpo inválido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/otp/request":{"post":{"operationId":"requestEmailOtp","tags":["auth"],"summary":"Solicitar código de acceso por email","description":"Si el email casa un usuario, envía un código de 6 dígitos (caduca en 10 min, máximo 5 intentos). El correo no lleva enlace de acceso. Pedir un código nuevo invalida el anterior. Responde SIEMPRE 200 con un mensaje genérico, exista o no el email (no filtra enumeración). Limitado por IP y por email (`too_many_requests`).","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"locale":{"type":"string","enum":["es","en"],"default":"es","description":"Idioma del correo."}}}}}},"responses":{"200":{"description":"Solicitud aceptada (mensaje genérico; no revela si el email existe).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtpRequestResult"}}}},"422":{"description":"Cuerpo inválido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas solicitudes para esta IP o este email (`too_many_requests`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/otp/verify":{"post":{"operationId":"verifyEmailOtp","tags":["auth"],"summary":"Verificar código de acceso","description":"Canjea el código: si es correcto, lo marca usado y abre sesión emitiendo las cookies `access_token` + `refresh_token` (idénticas a un login normal). Código erróneo, caducado, ya usado, con los intentos agotados o de un email sin código pendiente → 400 `invalid_otp` (siempre el mismo error, para no revelar si el email existe).","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","code"],"properties":{"email":{"type":"string","format":"email"},"code":{"type":"string","pattern":"^[0-9]{6}$","description":"Código de 6 dígitos recibido por email."}}}}}},"responses":{"200":{"description":"Sesión abierta; cookies emitidas.","headers":{"Set-Cookie":{"description":"`access_token` (JWT 15 min) y `refresh_token` (opaco rotativo).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"description":"Código inválido, caducado, usado o con intentos agotados (`invalid_otp`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Código correcto de una cuenta con 2FA (`two_factor_required`): aún no hay sesión. Deja el reto en la cookie HttpOnly `edr_2fa` (5 min); el segundo paso es `POST /api/v1/auth/two-factor/verify`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Cuerpo inválido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiados intentos para esta IP o este email (`too_many_requests`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/two-factor/verify":{"post":{"operationId":"verifyTwoFactorLogin","tags":["auth"],"summary":"Segundo paso del acceso","description":"Tras un acceso correcto de una cuenta con 2FA (401 `two_factor_required` con la cookie HttpOnly `edr_2fa`, 5 min), canjea un código TOTP o uno de recuperación y abre la sesión. 400 `invalid_totp`; 401 si falta el reto o caducó.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwoFactorCode"}}}},"responses":{"200":{"description":"Sesión abierta; cookies emitidas.","headers":{"Set-Cookie":{"description":"`access_token` y `refresh_token`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/auth/step-up/request":{"post":{"operationId":"requestStepUp","tags":["auth"],"summary":"Pedir confirmación de identidad","description":"Antes de una acción sensible. Con 2FA responde `totp`; sin él manda un código de 6 dígitos al email de la cuenta y responde `email_code`.","responses":{"200":{"description":"Con qué confirmar.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StepUpChallenge"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/auth/step-up/verify":{"post":{"operationId":"verifyStepUp","tags":["auth"],"summary":"Confirmar identidad","description":"Canjea el TOTP o el código por email y deja la cookie HttpOnly `edr_step_up` (10 min). 400 `invalid_totp` / `invalid_otp`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StepUpVerify"}}}},"responses":{"204":{"description":"Confirmado.","headers":{"Set-Cookie":{"description":"`edr_step_up`.","schema":{"type":"string"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/me/email-change":{"post":{"operationId":"requestEmailChange","tags":["auth"],"summary":"Pedir el cambio de email","description":"EDFRO-84. Exige step-up (403 `step_up_required`). Manda un código de 6 dígitos a la dirección NUEVA (10 min, 5 intentos). La respuesta es siempre 202 `sent`: si la dirección ya es de otra cuenta no se envía nada, pero no se dice. 422 `same_email` si es la misma. Cupo por cuenta y por IP (429).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailChangeRequest"}}}},"responses":{"202":{"description":"Código enviado (si procede).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailChangeRequested"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/me/email-change/verify":{"post":{"operationId":"verifyEmailChange","tags":["auth"],"summary":"Confirmar el email nuevo","description":"EDFRO-84. Exige step-up. Canjea el código de ESA dirección para ESTA cuenta: cambia el email, cierra las demás sesiones y renueva la de este aparato (cookies). 400 `invalid_otp`; 409 `email_taken` si otra cuenta la ocupó entretanto.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailChangeVerify"}}}},"responses":{"200":{"description":"Email cambiado.","headers":{"Set-Cookie":{"description":"`access_token`, `refresh_token` y `edr_session` renovadas.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailChanged"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"El email ya lo usa otra cuenta (`email_taken`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/auth/impersonation":{"get":{"operationId":"getMyImpersonation","tags":["auth"],"summary":"¿Es una sesión de soporte?","description":"Para el banner de la web. `active=false` en una sesión normal.","responses":{"200":{"description":"Estado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImpersonationStatus"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/auth/impersonation/end":{"post":{"operationId":"endMyImpersonation","tags":["auth"],"summary":"Salir de la sesión de soporte","description":"Termina la impersonación (si la hay) y borra las cookies. Idempotente.","responses":{"204":{"description":"Terminada."},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/me/sessions":{"get":{"operationId":"listMySessions","tags":["auth"],"summary":"Mis sesiones","description":"Sesiones vivas de mi cuenta, una por dispositivo, marcando la actual.","responses":{"200":{"description":"Sesiones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/v1/me/sessions/revoke-others":{"post":{"operationId":"revokeMyOtherSessions","tags":["auth"],"summary":"Cerrar las demás sesiones","description":"Revoca todas mis sesiones salvo esta y sube `token_version`: los accesos de los demás dispositivos dejan de valer en su siguiente petición. Esta sesión recibe cookies nuevas.","responses":{"200":{"description":"Cuántas se cerraron; cookies de esta sesión renovadas.","headers":{"Set-Cookie":{"description":"`access_token` y `refresh_token` nuevos de esta sesión.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionsRevoked"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/v1/me/sessions/{session_id}":{"parameters":[{"name":"session_id","in":"path","required":true,"description":"Id de la sesión (`Session.id`).","schema":{"type":"string"}}],"delete":{"operationId":"revokeMySession","tags":["auth"],"summary":"Cerrar una sesión","description":"Revoca esa sesión: su siguiente `/auth/refresh` da 401 (su acceso vive como mucho 15 min). Si es la actual, además borra las cookies. La sesión de otra persona no existe (404).","responses":{"204":{"description":"Cerrada."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/me/two-factor":{"get":{"operationId":"getMyTwoFactor","tags":["auth"],"summary":"Estado de mi 2FA","description":"Si tengo TOTP, cuántos códigos de recuperación me quedan y si la política me lo exige.","responses":{"200":{"description":"Estado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwoFactorStatus"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/v1/me/two-factor/setup":{"post":{"operationId":"setupMyTwoFactor","tags":["auth"],"summary":"Empezar el alta del 2FA","description":"Genera un secreto TOTP (cifrado en la base) y devuelve su QR. No pide nada al entrar hasta confirmarlo con `enable`. 409 `two_factor_already_enabled`.","responses":{"200":{"description":"Secreto, URI y QR.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwoFactorSetup"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Ya activado (`two_factor_already_enabled`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/two-factor/enable":{"post":{"operationId":"enableMyTwoFactor","tags":["auth"],"summary":"Confirmar el alta del 2FA","description":"Confirma con un código TOTP correcto y devuelve 10 códigos de recuperación (solo esta vez). 400 `invalid_totp`; 409 `two_factor_not_started`. Avisa por correo, en su idioma.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwoFactorCode"}}}},"responses":{"200":{"description":"2FA activo; códigos de recuperación.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryCodes"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"No hay alta empezada (`two_factor_not_started`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/me/two-factor/disable":{"post":{"operationId":"disableMyTwoFactor","tags":["auth"],"summary":"Quitar el 2FA","description":"Exige step-up reciente (403 `step_up_required`). Avisa por correo.","responses":{"204":{"description":"Quitado."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/v1/me/two-factor/recovery-codes":{"post":{"operationId":"regenerateMyRecoveryCodes","tags":["auth"],"summary":"Códigos de recuperación nuevos","description":"Anula los anteriores. Exige step-up reciente. 409 `two_factor_not_enabled`.","responses":{"200":{"description":"Códigos nuevos (solo esta vez).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryCodes"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Sin 2FA (`two_factor_not_enabled`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/onboarding":{"get":{"operationId":"getMyOnboarding","tags":["auth"],"summary":"Mi onboarding","description":"Si ya terminé el onboarding del primer acceso (la web lleva a `/onboarding` si no).","responses":{"200":{"description":"Estado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Onboarding"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"put":{"operationId":"completeMyOnboarding","tags":["auth"],"summary":"Terminar el onboarding","description":"Guarda nombre e idioma, registra «Soy mayor de 18 años» (consentimiento `adult_declaration` con la versión vigente de los términos) y marca el onboarding como hecho. Sin `adult_confirmed: true` → `422 adult_declaration_required` (ADR 0036).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingComplete"}}}},"responses":{"200":{"description":"Hecho.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Onboarding"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/me/integrations":{"get":{"operationId":"listMyIntegrations","tags":["users"],"summary":"Mis integraciones","description":"Lo que EduRails comparte de mí con un sistema externo. Hoy solo la integración STEP/Projekt (A13b): si soy alumnado de prácticas de la organización «Valfiguer STEP», mi progreso de cursos y mis certificados de esa organización se publican para mi tutor de prácticas en Projekt. `shared_since` es la primera publicación (nulo si aún no se ha publicado nada); `publishing_enabled` dice si la publicación está encendida ahora. Solo los datos de quien llama; sin integraciones, lista vacía.","responses":{"200":{"description":"Mis integraciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyIntegrationList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/v1/me/onboarding/first-steps":{"put":{"operationId":"updateMyFirstSteps","tags":["auth"],"summary":"Mis primeros pasos","description":"Primeros pasos por rol del onboarding (EDFRO-69): marca pasos hechos o pendientes y oculta o enseña la lista del panel. Idempotente; saltarla y volver retoma donde se quedó.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FirstStepsUpdate"}}}},"responses":{"200":{"description":"Estado del onboarding.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Onboarding"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/auth/oauth/{provider}/start":{"parameters":[{"name":"provider","in":"path","required":true,"description":"Proveedor OAuth.","schema":{"type":"string","enum":["google","github"]}}],"get":{"operationId":"oauthStart","tags":["auth"],"summary":"Iniciar OAuth (redirect al proveedor)","description":"Genera un `state` anti-CSRF (solo su SHA-256 se guarda) y redirige (302) a la URL de autorización del proveedor. Si el proveedor no está configurado (sin client_id) responde 503. Liga el `state` a este navegador con la cookie HttpOnly `edr_oauth_state` (SameSite=Lax, ruta `/api/v1/auth`): el callback sin ella o con otra vuelve a `/login?error=oauth_invalid_state` (SEC-05, login CSRF). Cupo propio por IP (429).","security":[],"responses":{"302":{"description":"Redirección a la pantalla de consentimiento del proveedor.","headers":{"Location":{"description":"URL de autorización del proveedor (con client_id, redirect_uri, scope, state).","schema":{"type":"string"}}}},"400":{"description":"Proveedor desconocido (`unknown_provider`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"description":"OAuth no configurado para el proveedor (`oauth_not_configured`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/oauth/{provider}/callback":{"parameters":[{"name":"provider","in":"path","required":true,"description":"Proveedor OAuth.","schema":{"type":"string","enum":["google","github"]}},{"name":"code","in":"query","required":false,"description":"Código de autorización devuelto por el proveedor.","schema":{"type":"string"}},{"name":"state","in":"query","required":false,"description":"State anti-CSRF emitido en `start` (se valida contra su hash en DB).","schema":{"type":"string"}}],"get":{"operationId":"oauthCallback","tags":["auth"],"summary":"Callback de OAuth (abre sesión y redirige)","description":"Valida el `state`, canjea el `code` por un token del proveedor, obtiene el email verificado y hace find-or-create del usuario (sin duplicar; entra a la cuenta existente/migrada si el email casa). Emite las cookies de sesión y redirige (302) a `{WEB_URL}/dashboard`; ante cualquier fallo redirige a `{WEB_URL}/login?error=<code>` (sin filtrar detalles).","security":[],"responses":{"302":{"description":"Sesión abierta → `/dashboard` (con cookies); cuenta con 2FA → `/login/two-factor` (reto en la cookie `edr_2fa`); `/login?error=…` en fallo.","headers":{"Set-Cookie":{"description":"`access_token` + `refresh_token` (solo en el caso de éxito).","schema":{"type":"string"}},"Location":{"description":"`{WEB_URL}/dashboard` en éxito, o `{WEB_URL}/login?error=…` en fallo.","schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/auth/sso/discover":{"post":{"operationId":"discoverSso","tags":["auth"],"summary":"¿Este email entra por el SSO de su institución?","description":"Home realm discovery. Se decide por el DOMINIO del email (verificado por una organización con conexión activa), nunca por la cuenta: no revela si el email tiene cuenta. Con `enforced`, el código por email y Google/GitHub responden 403 `sso_required` para ese dominio (salvo los owners de la organización).","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoDiscoverRequest"}}}},"responses":{"200":{"description":"Resultado del descubrimiento.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoDiscovery"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/auth/sso/{connection_id}/start":{"parameters":[{"name":"connection_id","in":"path","required":true,"description":"Conexión SSO (la que devolvió `discover`).","schema":{"type":"string"}},{"name":"login_hint","in":"query","required":false,"description":"Email para rellenar la pantalla del IdP.","schema":{"type":["string","null"],"maxLength":255}}],"get":{"operationId":"startSso","tags":["auth"],"summary":"Ir al IdP de la institución","description":"Guarda un `state` de un solo uso (solo su SHA-256; nonce y PKCE S256 se derivan de él) y redirige (302) al IdP. 404 si la conexión no existe o está apagada (o `feature_disabled` sin SSO en el despliegue). Liga el `state` a este navegador con la cookie HttpOnly `edr_sso_state` (SameSite=Lax, ruta `/api/v1/auth`); el callback de un login sin ella vuelve a `/login?error=sso_invalid_state` (SEC-05). Cupo propio por IP (429).","security":[],"responses":{"302":{"description":"Redirección a la pantalla de login del IdP.","headers":{"Location":{"description":"URL de autorización del IdP.","schema":{"type":"string"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/auth/sso/callback":{"parameters":[{"name":"code","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":4096}},{"name":"state","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":512}},{"name":"error","in":"query","required":false,"description":"Error devuelto por el IdP (p. ej. el usuario canceló).","schema":{"type":["string","null"],"maxLength":200}}],"get":{"operationId":"ssoCallback","tags":["auth"],"summary":"Vuelta del IdP (abre sesión y redirige)","description":"Valida el `state` (un solo uso), canjea el código con PKCE, valida el id_token (firma JWKS, `iss`, `aud`, `exp`, `iat`, `nonce`, `hd` de Google o `tid` de Entra) y exige que el dominio del email esté verificado por la organización de la conexión. Resuelve la cuenta (identidad vinculada, email o alta nueva), da de alta la membresía por JIT y abre sesión. Redirige SIEMPRE: `/dashboard` (o `/login/two-factor`), `/login?error=<code>` si falla, y `/org/sso?test=ok` al volver de una prueba de conexión.","security":[],"responses":{"302":{"description":"Vuelta a la web.","headers":{"Set-Cookie":{"description":"`access_token` + `refresh_token` (solo al abrir sesión).","schema":{"type":"string"}},"Location":{"description":"Destino en la web.","schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/users/{id}/avatar.svg":{"parameters":[{"name":"id","in":"path","required":true,"description":"ID de la persona.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getUserAvatar","tags":["avatars"],"summary":"SVG del avatar de una persona","description":"El pepo de una persona, generado en nuestro servidor (sin terceros) y saneado. Lo ve ella misma y quien comparte con ella la organización de la petición; a cualquier otro, 404 (no se confirma que exista). ETag fuerte (`If-None-Match` → 304) y `Cache-Control: private, max-age=600`; `v` es solo para romper la caché del navegador tras guardar. Es el pepo aunque la persona tenga foto: la foto (si la hay) va en `avatar_url` de su perfil.","parameters":[{"name":"v","in":"query","required":false,"description":"Versión (ETag corto) para romper la caché.","schema":{"type":["string","null"],"maxLength":32}}],"responses":{"200":{"description":"SVG del avatar (saneado).","headers":{"ETag":{"schema":{"type":"string"}},"Cache-Control":{"schema":{"type":"string"}}},"content":{"image/svg+xml":{"schema":{"type":"string"}}}},"304":{"description":"Sin cambios desde el ETag de `If-None-Match`."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/avatars/options":{"get":{"operationId":"getAvatarCatalog","tags":["avatars"],"summary":"Catálogo de opciones del avatar","description":"Rasgos, colores y fondos que admite el estilo Open Peeps instalado en el servidor, con su versión y su licencia (CC0 1.0). Cualquier sesión.","responses":{"200":{"description":"Catálogo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvatarCatalog"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/avatars/preview.svg":{"get":{"operationId":"previewAvatar","tags":["avatars"],"summary":"Vista previa de un pepo","description":"Pinta en el servidor el SVG de unas opciones SIN guardarlas (la vista previa en vivo del editor). Mismas reglas que `PUT /api/v1/me/avatar`: una opción que el estilo no tiene es 422. Sin datos personales en la query: solo semilla y nombres de variante. Con ETag y caché privada.","parameters":[{"name":"seed","in":"query","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,64}$"}},{"name":"head","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$"}},{"name":"expression","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$"}},{"name":"facial_hair","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$"}},{"name":"accessories","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$"}},{"name":"mask","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$"}},{"name":"skin_color","in":"query","required":false,"schema":{"type":["string","null"],"pattern":"^#?[0-9A-Fa-f]{6}$"}},{"name":"hair_color","in":"query","required":false,"schema":{"type":["string","null"],"pattern":"^#?[0-9A-Fa-f]{6}$"}},{"name":"clothing_color","in":"query","required":false,"schema":{"type":["string","null"],"pattern":"^#?[0-9A-Fa-f]{6}$"}},{"name":"background","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":24,"pattern":"^[a-z]+-[0-9]{2,3}$"}}],"responses":{"200":{"description":"SVG del avatar (saneado).","headers":{"ETag":{"schema":{"type":"string"}},"Cache-Control":{"schema":{"type":"string"}}},"content":{"image/svg+xml":{"schema":{"type":"string"}}}},"304":{"description":"Sin cambios desde el ETag de `If-None-Match`."},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/certificate-template":{"get":{"operationId":"publicCertificateTemplate","tags":["certificates"],"summary":"Plantilla pública de certificado","description":"Campos de presentación de la plantilla global de certificado (sin auth).","security":[],"responses":{"200":{"description":"Plantilla pública de certificado.","content":{"application/json":{"schema":{"type":"object","required":["template"],"properties":{"template":{"type":"object","properties":{"issuer_name":{"type":["string","null"]},"signature_name":{"type":["string","null"]},"signature_title":{"type":["string","null"]},"logo_url":{"type":["string","null"]},"accent_color":{"type":["string","null"],"pattern":"^#[0-9A-Fa-f]{6}$"},"footer_text":{"type":["string","null"]}}}}}}}}}}},"/api/v1/public/certificates/verify/{code}":{"parameters":[{"name":"code","in":"path","required":true,"description":"Código de verificación. Se normaliza (mayúsculas, sin espacios). Formatos: `EDU-XXXX-XXXX`, `3XA-XXXX-XXXX` y los heredados de EduRails (ADR 0014).","schema":{"type":"string","maxLength":64,"examples":["EDU-7K2M-9QWE"]}}],"get":{"operationId":"verifyPublicCertificate","x-tool":{"name":"verify_certificate","domain":"learning","profile":"core","sensitive":false},"tags":["certificates"],"summary":"Verificar un certificado (público)","description":"Sin sesión. Devuelve el estado (`valid|revoked|replaced`) y lo mínimo para comprobarlo: nombre del titular (ninguno si está revocado), credencial, emisor y fechas. Sin email, ids ni nota. 30 peticiones por minuto e IP; `Cache-Control: no-store`.","security":[],"responses":{"200":{"description":"Certificado encontrado (válido, revocado o sustituido).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCertificateVerification"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/organizations/{org_id}/issued-certificates":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización.","schema":{"type":"string"}}],"get":{"operationId":"listOrganizationCertificates","tags":["certificates"],"summary":"Certificados emitidos por la organización","description":"owner/admin de la organización: todos; docente: solo los de la gente de SU cohorte (EDAPI-185); alumno → 403; no-miembro → 404. Lo más reciente primero. Filtros por curso, programa, cohorte (de esta organización; otra → 404), estado y tipo.","parameters":[{"name":"course_id","in":"query","required":false,"description":"Solo los de este curso.","schema":{"type":["string","null"]}},{"name":"path_id","in":"query","required":false,"description":"Solo los de este programa.","schema":{"type":["string","null"]}},{"name":"cohort_id","in":"query","required":false,"description":"Solo los de la gente de esta cohorte (de esta organización).","schema":{"type":["string","null"]}},{"name":"status","in":"query","required":false,"description":"Filtra por estado.","schema":{"type":["string","null"],"enum":["active","revoked","replaced",null]}},{"name":"type","in":"query","required":false,"description":"Curso o programa.","schema":{"type":["string","null"],"enum":["course","program",null]}},{"name":"limit","in":"query","required":false,"description":"Tamaño de página (1..200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","required":false,"description":"Desplazamiento.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Página de certificados emitidos por la organización.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssuedCertificateList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/issued-certificates/{certificate_id}/revoke":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización.","schema":{"type":"string"}},{"name":"certificate_id","in":"path","required":true,"description":"Id del certificado.","schema":{"type":"string"}}],"post":{"operationId":"revokeOrganizationCertificate","tags":["certificates"],"summary":"Revocar un certificado emitido por la organización","description":"owner/admin de la organización, sobre certificados que emitió ella (otro → 404). Motivo obligatorio. Irreversible por la organización; queda en la auditoría (`certificate.revoked`). Ya revocado → 409 `certificate_already_revoked`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeCertificateInput"}}}},"responses":{"200":{"description":"Certificado revocado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssuedCertificate"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Conflicto de estado (código estable en `error.code`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/issued-certificates/{certificate_id}/reissue":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización.","schema":{"type":"string"}},{"name":"certificate_id","in":"path","required":true,"description":"Id del certificado.","schema":{"type":"string"}}],"post":{"operationId":"reissueOrganizationCertificate","tags":["certificates"],"summary":"Reemitir un certificado (corrección de nombre)","description":"owner/admin. Crea un certificado NUEVO con código nuevo y el nombre corregido (el resto, igual que el original); el anterior queda `replaced` y su verificación pública enlaza al nuevo (`replaced_by_code`). Solo de un certificado vigente: revocado o ya sustituido → 409 `certificate_already_revoked`. Auditado (`certificate.reissued`, con el motivo). EDAPI-181.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReissueCertificateInput"}}}},"responses":{"201":{"description":"El certificado nuevo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssuedCertificate"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Conflicto de estado (código estable en `error.code`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/issued-certificates/export":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización.","schema":{"type":"string"}}],"get":{"operationId":"exportOrganizationCertificates","tags":["certificates"],"summary":"Exportar los certificados emitidos (CSV)","description":"Mismas reglas de acceso y filtros que el listado (docente: solo su cohorte). CSV con BOM, celdas neutralizadas contra fórmulas y cabeceras en el idioma pedido (`lang`, si no el del navegador, si no el de la organización); `sep=semicolon` para el Excel en español. Hasta 10 000 filas (si no, 413 `export_too_large`). Queda en la auditoría (`certificates.exported`). EDAPI-185.","parameters":[{"name":"course_id","in":"query","required":false,"description":"Solo los de este curso.","schema":{"type":["string","null"]}},{"name":"path_id","in":"query","required":false,"description":"Solo los de este programa.","schema":{"type":["string","null"]}},{"name":"cohort_id","in":"query","required":false,"description":"Solo los de la gente de esta cohorte (de esta organización).","schema":{"type":["string","null"]}},{"name":"status","in":"query","required":false,"schema":{"type":["string","null"],"enum":["active","revoked","replaced",null]}},{"name":"type","in":"query","required":false,"schema":{"type":["string","null"],"enum":["course","program",null]}},{"name":"lang","in":"query","required":false,"schema":{"type":["string","null"],"pattern":"^(es|en)$"}},{"name":"sep","in":"query","required":false,"schema":{"type":["string","null"],"enum":["comma","semicolon",null]}}],"responses":{"200":{"description":"CSV de los certificados emitidos.","content":{"text/csv":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"description":"Demasiadas filas para exportar de una vez (`export_too_large`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/me/certificates":{"get":{"operationId":"listAllMyCertificates","tags":["certificates"],"summary":"Mis certificados de todas mis organizaciones","description":"Los certificados emitidos a la persona autenticada, de cualquier organización (los certificados son de su titular), con la organización que los emitió. Lo más reciente primero. EDAPI-185.","responses":{"200":{"description":"Mis certificados.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyCertificatesEverywhere"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/v1/me/certificates/{certificate_id}/pdf":{"parameters":[{"name":"certificate_id","in":"path","required":true,"description":"Id del certificado.","schema":{"type":"string"}}],"get":{"operationId":"getMyCertificatePdfAnywhere","tags":["certificates"],"summary":"Descargar en PDF un certificado propio, de cualquier organización","description":"Solo su titular; el de otra persona no existe (404). Con la plantilla de la organización que lo emitió. EDAPI-185.","responses":{"200":{"description":"PDF del certificado.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/organizations/{org_id}/programs/{pathId}/progress":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización.","schema":{"type":"string"}},{"name":"pathId","in":"path","required":true,"description":"Id o slug del programa.","schema":{"type":"string"}}],"get":{"operationId":"getProgramProgress","tags":["paths"],"summary":"Mi progreso en un programa","description":"Cursos del programa (requeridos o no, completados o no), capstone y certificado de quien pregunta. Programa que la organización no ve → 404.","responses":{"200":{"description":"Progreso en el programa.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramProgress"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/programs/{pathId}/credential":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización.","schema":{"type":"string"}},{"name":"pathId","in":"path","required":true,"description":"Id o slug del programa.","schema":{"type":"string"}}],"post":{"operationId":"claimProgramCredential","tags":["paths"],"summary":"Completar un programa y obtener su credencial","description":"Idempotente. Con todos los cursos requeridos completados (y el capstone aprobado, si lo tiene) marca el programa como completado, concede su insignia y, si emite credencial, el certificado de programa. Sin requisitos → 409 `program_not_completed`; capstone pendiente → 409 `capstone_required`.","responses":{"200":{"description":"Progreso tras completar el programa.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramProgress"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Conflicto de estado (código estable en `error.code`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/institutes":{"get":{"operationId":"listInstitutes","tags":["cohorts"],"summary":"Listar institutos","description":"Institutos VINCULADOS a la organización de la sesión: el de su\n`legacy_institute_id` o los que tienen alguna de sus cohortes, con el recuento\nde SUS cohortes. Desde SEC-03 (w12) pide sesión: antes era anónimo y publicaba\nlos de todas las organizaciones.\n","responses":{"200":{"description":"Lista de institutos.","content":{"application/json":{"schema":{"type":"object","required":["institutes"],"properties":{"institutes":{"type":"array","items":{"$ref":"#/components/schemas/Institute"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/institutes/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"ID del instituto.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getInstitute","tags":["cohorts"],"summary":"Obtener instituto","description":"Un instituto vinculado a la organización de la sesión, con SOLO sus cohortes.\nUno no vinculado → 404 (SEC-03, w12: antes era anónimo).\n","responses":{"200":{"description":"Instituto con sus cohortes.","content":{"application/json":{"schema":{"type":"object","required":["id","name","cohorts"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"city":{"type":["string","null"]},"contact_email":{"type":["string","null"],"format":"email"},"website":{"type":["string","null"],"format":"uri"},"cohorts_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"cohorts":{"type":"array","items":{"$ref":"#/components/schemas/Cohort"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/editor/seo/rebuild-all":{"post":{"operationId":"editorRebuildAllSeo","tags":["editor"],"summary":"Reconstruir todas las páginas SEO","description":"Regenera las páginas SEO estáticas de todos los cursos publicados.","responses":{"200":{"description":"Resultado de la reconstrucción.","content":{"application/json":{"schema":{"type":"object","required":["built","skipped","slugs"],"properties":{"built":{"type":"integer"},"skipped":{"type":"integer"},"slugs":{"type":"array","items":{"type":"string"}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/notifications/unsubscribe":{"get":{"operationId":"unsubscribeNotification","tags":["notifications"],"summary":"Darse de baja de notificaciones por email","description":"Da de baja un email a partir de un token firmado; devuelve una página HTML de confirmación (siempre 200).","security":[],"parameters":[{"name":"token","in":"query","required":true,"description":"Token firmado de baja.","schema":{"type":"string","pattern":"^[a-f0-9]{64}$"}},{"name":"type","in":"query","required":true,"description":"Tipo de notificación de la que darse de baja.","schema":{"type":"string","enum":["course_complete","badge_earned","streak_reminder","weekly_summary","new_course","all"]}}],"responses":{"200":{"description":"Página HTML de confirmación.","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/push/vapid-key":{"get":{"operationId":"getPushVapidKey","tags":["notifications"],"summary":"Obtener clave pública VAPID","description":"Devuelve la clave pública VAPID para suscribirse a notificaciones push. Sin claves en el servidor, 404 `push_disabled` (antes devolvía una clave de ejemplo que no servía para nada; A11).","security":[],"responses":{"200":{"description":"Clave pública VAPID.","content":{"application/json":{"schema":{"type":"object","required":["public_key"],"properties":{"public_key":{"type":"string"}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/organizations":{"get":{"operationId":"listMyOrganizations","tags":["organizations"],"summary":"Mis organizaciones","description":"Organizaciones de las que soy miembro, con mi rol en cada una, y la organización activa (preferencia de presentación: no autoriza nada).","x-tool":{"name":"list_my_organizations","domain":"learning","profile":"core","sensitive":false},"responses":{"200":{"description":"Mis organizaciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyOrganizations"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/me/active-organization":{"put":{"operationId":"setActiveOrganization","tags":["organizations"],"summary":"Cambiar de organización activa","description":"Fija la organización activa (`users.last_organization_id`). 404 si no soy miembro; 403 `organization_suspended` si está suspendida.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActiveOrganizationUpdate"}}}},"responses":{"200":{"description":"Mis organizaciones con la activa actualizada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyOrganizations"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/me/invitations":{"get":{"operationId":"listMyInvitations","tags":["invitations"],"summary":"Mis invitaciones pendientes","description":"Invitaciones pendientes dirigidas al email de mi cuenta.","responses":{"200":{"description":"Invitaciones pendientes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyInvitations"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/invitations/preview":{"post":{"operationId":"previewInvitation","tags":["invitations"],"summary":"Ver una invitación","description":"Organización (nombre, logo, color), rol, email invitado enmascarado y si ya tiene cuenta.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationToken"}}}},"responses":{"200":{"description":"La invitación.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationPreview"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Ya aceptada (`invitation_already_accepted`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Caducada (`invitation_expired`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/invitations/code/request":{"post":{"operationId":"requestInvitationCode","tags":["invitations"],"summary":"Pedir el código para aceptar","description":"Manda un código de 6 dígitos (10 min, 5 intentos) al email INVITADO, tenga o no cuenta. Respuesta genérica.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationCodeRequest"}}}},"responses":{"200":{"description":"Solicitud aceptada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtpRequestResult"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Ya aceptada (`invitation_already_accepted`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Caducada (`invitation_expired`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/invitations/code/verify":{"post":{"operationId":"acceptInvitationWithCode","tags":["invitations"],"summary":"Aceptar la invitación con el código","description":"Canjea el código: crea la cuenta si el email invitado no tenía, acepta la invitación (membresía con su rol; la organización queda como activa) y abre la sesión. Si la cuenta tiene 2FA, la invitación queda aceptada y responde 401 `two_factor_required` con el reto. 400 `invalid_otp`.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationSignup"}}}},"responses":{"200":{"description":"Dentro de la organización; cookies emitidas.","headers":{"Set-Cookie":{"description":"`access_token` y `refresh_token`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyOrganization"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Ya aceptada (`invitation_already_accepted`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Caducada (`invitation_expired`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/invitations/accept":{"post":{"operationId":"acceptInvitation","tags":["invitations"],"summary":"Aceptar una invitación","description":"Canjea el token del correo (va en el CUERPO, nunca en la URL del API). Un solo uso; caduca. 404 `invitation_invalid` si no existe, caducó, se revocó o ya se usó; 403 `invitation_email_mismatch` si es para otra cuenta.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptInvitation"}}}},"responses":{"200":{"description":"Organización a la que me he unido y mi rol.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyOrganization"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}}],"get":{"operationId":"getOrganization","tags":["organizations"],"summary":"Obtener organización","description":"Cualquier miembro (student+).","x-tool":{"name":"get_organization","domain":"admin","profile":"core","sensitive":false},"responses":{"200":{"description":"Organización.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Organization"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"operationId":"updateOrganization","tags":["organizations"],"summary":"Actualizar organización","description":"Ajustes de la organización: admin+. `require_admin_two_factor` (EDFRO-84) hace que sus owners y admins necesiten la verificación en dos pasos para administrar (403 `two_factor_setup_required` en lo que pide rol admin hasta configurarla); encenderlo sin tener tú el TOTP da 409 `two_factor_setup_required`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationUpdate"}}}},"responses":{"200":{"description":"Organización actualizada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Organization"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`two_factor_setup_required`: encender la 2FA de admins sin tener tú el TOTP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"operationId":"deleteOrganization","tags":["organizations"],"summary":"Borrar organización","description":"Borrado lógico (`deleted_at`): solo owner. Sus miembros dejan de entrar al instante.","responses":{"204":{"description":"Borrada."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/roles":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}}],"get":{"operationId":"listOrganizationRoles","tags":["organizations"],"summary":"Roles de la organización","description":"Escalera de roles `owner > admin > teacher > student` con su rango.","responses":{"200":{"description":"Roles.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationRoles"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/usage":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}}],"get":{"operationId":"getOrganizationUsage","tags":["organizations"],"summary":"Uso y límites del plan","description":"Plazas (miembros + invitaciones pendientes), cursos privados y almacenamiento frente a los límites del plan (sin billing). admin+.","x-tool":{"name":"get_organization_usage","domain":"admin","profile":"core","sensitive":false,"roles":["owner","admin"]},"responses":{"200":{"description":"Uso del plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanUsage"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/members":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}}],"get":{"operationId":"listOrganizationMembers","tags":["members"],"summary":"Miembros","description":"Lista de miembros: teacher+ (el alumnado no ve la lista de su clase: datos mínimos, regla 26).","x-tool":{"name":"list_organization_members","domain":"admin","profile":"core","sensitive":true,"roles":["owner","admin"]},"parameters":[{"name":"limit","in":"query","required":false,"description":"Tamaño de página (1..200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","required":false,"description":"Desplazamiento.","schema":{"type":"integer","minimum":0,"default":0}},{"name":"q","in":"query","required":false,"description":"Busca en nombre o email (F9).","schema":{"type":["string","null"],"maxLength":100}},{"name":"role","in":"query","required":false,"description":"Filtra por rol en la organización (F9).","schema":{"type":["string","null"],"enum":["owner","admin","teacher","student"]}},{"name":"status","in":"query","required":false,"description":"Filtra por estado de la membresía (F9).","schema":{"type":["string","null"],"enum":["active","suspended"]}}],"responses":{"200":{"description":"Miembros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/members/{user_id}":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}},{"name":"user_id","in":"path","required":true,"description":"Id del usuario miembro.","schema":{"type":"string"}}],"get":{"operationId":"getOrganizationMember","tags":["members"],"summary":"Un miembro","description":"teacher+ (como la lista). Alguien que no es miembro de ESTA organización → 404.","responses":{"200":{"description":"Miembro.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Member"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"operationId":"updateOrganizationMember","tags":["members"],"summary":"Cambiar el rol de un miembro","description":"admin+. Solo un owner da o quita el rol owner. 409 `last_owner` si se degrada al último owner.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberUpdate"}}}},"responses":{"200":{"description":"Miembro actualizado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Member"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Conflicto con el estado actual (código en el envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"operationId":"removeOrganizationMember","tags":["members"],"summary":"Expulsar a un miembro (o salir)","description":"admin+ para expulsar; cualquiera para salir (su propio user_id). 409 `last_owner` si es el último owner.","x-tool":{"name":"remove_organization_member","domain":"admin","profile":"all","sensitive":true,"roles":["owner","admin"]},"responses":{"204":{"description":"Fuera de la organización."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Conflicto con el estado actual (código en el envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/members/{user_id}/status":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"user_id","in":"path","required":true,"description":"Id del usuario miembro.","schema":{"type":"string"}}],"put":{"operationId":"setOrganizationMemberStatus","tags":["members"],"summary":"Suspender o reactivar a un miembro","description":"admin+. Una membresía suspendida no entra en la organización (la cuenta sigue viva en las demás). No se aplica a uno mismo (403). Suspender o reactivar a un owner solo lo hace un owner; el último owner activo no se suspende (409 `last_owner`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberStatusUpdate"}}}},"responses":{"200":{"description":"Miembro actualizado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Member"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Conflicto con el estado actual (`last_owner`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/overview":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getOrganizationOverview","tags":["organizations"],"summary":"Resumen del admin de organización","description":"KPIs de la organización calculados en el servidor: alumnado activo en 7 días, cursos completados, certificados emitidos (activos), cohortes activas, invitaciones pendientes y actividad de las últimas 8 semanas (lecciones completadas y personas distintas). admin+.","responses":{"200":{"description":"Resumen.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationOverview"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/features":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getOrganizationFeatures","tags":["organizations"],"summary":"Funciones de la organización","description":"Estado de cada función (social, gamificación, tutor IA): el interruptor de la organización (`enabled`), si plataforma la permite (`allowed`) y el resultado (`active` = allowed Y enabled). Cualquier miembro (la web esconde lo apagado).","responses":{"200":{"description":"Funciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationFeatureStates"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"operationId":"updateOrganizationFeatures","tags":["organizations"],"summary":"Encender o apagar funciones de la organización","description":"admin+. Solo cambia lo que llega. Apagar siempre se puede; encender una función que plataforma no permite responde 409 `feature_not_allowed`. `ai_tutor` es el mismo interruptor que `PATCH …/privacy`. Cada cambio queda en la auditoría de la organización.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationFeaturesUpdate"}}}},"responses":{"200":{"description":"Funciones tras el cambio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationFeatureStates"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"La función no está permitida por plataforma (`feature_not_allowed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/sso":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getOrganizationSso","tags":["organizations"],"summary":"Configuración del SSO de la organización","description":"Conexión (sin secreto), dominios con su registro TXT y la URL de callback que se registra en el IdP.","responses":{"200":{"description":"Configuración.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConfig"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No es miembro de la organización o el recurso no es suyo (`not_found`), o el SSO no está disponible en este despliegue (`feature_disabled`). Nunca 403 entre organizaciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/sso/connection":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"put":{"operationId":"upsertOrganizationSsoConnection","tags":["organizations"],"summary":"Crear o cambiar la conexión SSO","description":"Una conexión por organización. Cambiar proveedor, tenant, cliente o secreto borra la última prueba y QUITA el forzado: hay que volver a probar. Apagarla también quita el forzado.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConnectionUpsert"}}}},"responses":{"200":{"description":"Conexión guardada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConnection"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No es miembro de la organización o el recurso no es suyo (`not_found`), o el SSO no está disponible en este despliegue (`feature_disabled`). Nunca 403 entre organizaciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"operationId":"deleteOrganizationSsoConnection","tags":["organizations"],"summary":"Borrar la conexión SSO","responses":{"204":{"description":"Borrada."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No es miembro de la organización o el recurso no es suyo (`not_found`), o el SSO no está disponible en este despliegue (`feature_disabled`). Nunca 403 entre organizaciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/sso/connection/test":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"testOrganizationSsoConnection","tags":["organizations"],"summary":"Probar la conexión SSO","description":"Devuelve la URL del IdP con un `state` de PRUEBA. La vuelta valida todo el flujo (incluido que el email sea de un dominio verificado) y marca `tested_at`, sin abrir ni cambiar ninguna sesión.","responses":{"200":{"description":"URL de la prueba.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoTestStart"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No es miembro de la organización o el recurso no es suyo (`not_found`), o el SSO no está disponible en este despliegue (`feature_disabled`). Nunca 403 entre organizaciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/sso/connection/enforcement":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"put":{"operationId":"setOrganizationSsoEnforcement","tags":["organizations"],"summary":"Forzar (o no) el SSO en los dominios verificados","description":"Forzar exige una prueba correcta con la conexión encendida (409 `sso_test_required`) y al menos un dominio verificado (409 `sso_domain_required`). Los owners de la organización siguen pudiendo entrar con código (cuenta de emergencia).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoEnforcementUpdate"}}}},"responses":{"200":{"description":"Conexión tras el cambio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConnection"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No es miembro de la organización o el recurso no es suyo (`not_found`), o el SSO no está disponible en este despliegue (`feature_disabled`). Nunca 403 entre organizaciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Falta la prueba (`sso_test_required`) o un dominio verificado (`sso_domain_required`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/sso/domains":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"addOrganizationSsoDomain","tags":["organizations"],"summary":"Añadir un dominio de la institución","description":"Queda pendiente hasta publicar el TXT y verificarlo. Un dominio de correo público no se admite (422 `domain_not_allowed`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoDomainCreate"}}}},"responses":{"201":{"description":"Dominio añadido (sin verificar).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoDomain"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No es miembro de la organización o el recurso no es suyo (`not_found`), o el SSO no está disponible en este despliegue (`feature_disabled`). Nunca 403 entre organizaciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"El dominio ya está en la lista (`conflict`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/sso/domains/{domain_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"domain_id","in":"path","required":true,"schema":{"type":"string"}}],"patch":{"operationId":"updateOrganizationSsoDomain","tags":["organizations"],"summary":"Encender o apagar la unión automática de un dominio","description":"Con la unión automática, quien entra con código, Google o GitHub con un email de este dominio queda unido a la organización como `student` (idempotente; una membresía suspendida no se reactiva; sin plaza en el plan no se une). Encenderla exige el dominio verificado (409 `domain_not_verified`). Queda en la auditoría (`sso.domain_auto_join_changed`; cada unión, `auth.joined_verified_domain`). EDAPI-41.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoDomainUpdate"}}}},"responses":{"200":{"description":"Dominio tras el cambio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoDomain"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No es miembro de la organización o el recurso no es suyo (`not_found`), o el SSO no está disponible en este despliegue (`feature_disabled`). Nunca 403 entre organizaciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"El dominio no está verificado (`domain_not_verified`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"operationId":"deleteOrganizationSsoDomain","tags":["organizations"],"summary":"Quitar un dominio","description":"Si era el último verificado, el forzado se apaga.","responses":{"204":{"description":"Quitado."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No es miembro de la organización o el recurso no es suyo (`not_found`), o el SSO no está disponible en este despliegue (`feature_disabled`). Nunca 403 entre organizaciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/sso/domains/{domain_id}/verify":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"domain_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"operationId":"verifyOrganizationSsoDomain","tags":["organizations"],"summary":"Verificar un dominio por su registro DNS TXT","responses":{"200":{"description":"Dominio verificado (o ya lo estaba).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoDomain"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No es miembro de la organización o el recurso no es suyo (`not_found`), o el SSO no está disponible en este despliegue (`feature_disabled`). Nunca 403 entre organizaciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"TXT no encontrado o DNS sin respuesta (`domain_verification_failed`), o verificado ya por otra organización (`domain_already_claimed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/catalog":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getOrganizationCatalog","tags":["organizations"],"summary":"Catálogo global para la organización","description":"admin+. Cursos publicados y programas activos del catálogo global de Valfiguer, cada uno con `enabled` (si la organización lo ofrece) y la matrícula activa de la organización. Más el interruptor global de ops (`global_catalog_enabled`) y los cursos privados frente a su tope.","responses":{"200":{"description":"Catálogo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationCatalog"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/catalog/courses/{course_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"course_id","in":"path","required":true,"description":"Id de un curso del catálogo GLOBAL (uno privado o que no existe → 404).","schema":{"type":"string"}}],"put":{"operationId":"setOrganizationCatalogCourse","tags":["organizations"],"summary":"Habilitar o deshabilitar un curso global","description":"admin+. Deshabilitado, el curso deja de existir para la organización (listados, búsqueda y acceso por id → 404). El progreso hecho se conserva y vuelve al rehabilitarlo. Auditado.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogToggle"}}}},"responses":{"200":{"description":"Estado del curso para la organización.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationCatalogItem"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/catalog/programs/{path_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"path_id","in":"path","required":true,"description":"Id de un programa del catálogo GLOBAL (uno privado o que no existe → 404).","schema":{"type":"string"}}],"put":{"operationId":"setOrganizationCatalogProgram","tags":["organizations"],"summary":"Habilitar o deshabilitar un programa global","description":"admin+. Como el de cursos, para un programa (learning path) global. Habilitarlo habilita también, en la misma transacción, sus cursos REQUERIDOS que la organización tuviera deshabilitados (EDAPI-85); los opcionales no se tocan. Auditado.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogToggle"}}}},"responses":{"200":{"description":"Estado del programa para la organización.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationCatalogItem"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/support-requests":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"createSupportRequest","tags":["support"],"summary":"Contactar con soporte","description":"Cualquier miembro de la organización. Guarda la solicitud y la entrega en segundo plano (Projekt Support o correo) con el contexto técnico: organización, rol, pantalla (solo la ruta; query y fragmento se descartan), versión y request_id. (El 403 `support_minor_contact_org` se retiró con el ADR 0036.) Limitado a 5 por hora y persona y 30 por hora y organización (`too_many_requests`). Sin sesión → 401; organización ajena → 404.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportRequestCreate"}}}},"responses":{"202":{"description":"Solicitud aceptada; sale en segundo plano.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportRequestAccepted"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/organizations/{org_id}/moderation/reports":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"listModerationReports","tags":["moderation"],"summary":"Cola de moderación de la organización","description":"admin+. Denuncias de ESTA organización (entradas del feed y personas), las más recientes primero. Quién denuncia no viaja nunca.","parameters":[{"name":"status","in":"query","required":false,"description":"Estado de la denuncia (`open` por defecto; `escalated` = pasadas a plataforma).","schema":{"type":"string","enum":["open","escalated","hidden","dismissed","actioned","restored"],"default":"open"}},{"name":"target_type","in":"query","required":false,"description":"Solo denuncias de entradas (`feed_item`) o de personas (`user`).","schema":{"type":["string","null"],"enum":["feed_item","user",null]}},{"name":"limit","in":"query","required":false,"description":"Tamaño de página (1..200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","required":false,"description":"Desplazamiento.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Denuncias.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationReportList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}},"post":{"operationId":"createModerationReport","tags":["moderation"],"summary":"Denunciar una entrada del feed o a una persona","description":"Cualquier miembro, también un menor (denunciar es protección, no función social). Motivo de una lista cerrada, sin texto libre (datos mínimos, regla 26). Denunciar dos veces el mismo objetivo devuelve la denuncia que ya existe. Una entrada o una persona de otra organización → 404; denunciarse a uno mismo → 422. `inappropriate_avatar` retira el avatar de la persona hasta que se revise. Más de 20 denuncias por hora → 429. Auditado (`moderation.report_created`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationReportCreate"}}}},"responses":{"201":{"description":"Denuncia registrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationReport"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/organizations/{org_id}/moderation/reports/{report_id}/resolve":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"report_id","in":"path","required":true,"description":"Id de la denuncia.","schema":{"type":"string"}}],"post":{"operationId":"resolveModerationReport","tags":["moderation"],"summary":"Resolver una denuncia","description":"admin+. `hide` oculta la entrada del feed para toda la organización; `restore` la vuelve a enseñar (solo una denuncia `hidden`); `dismiss` la descarta (y devuelve el avatar retenido); `warn` avisa y `suspend_social` (con `days`) suspende lo social a la persona afectada; `reset_avatar` le quita el avatar; `suspend_account` suspende su membresía. Cerrar una denuncia cierra con la misma decisión las demás abiertas del mismo objetivo. No se sanciona a uno mismo ni a un admin/owner (409 `sanction_not_allowed`); una acción que no aplica al objetivo → 422 `action_not_applicable`. Auditado con una fila (`moderation.report_resolved`). Una denuncia de otra organización → 404.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationResolve"}}}},"responses":{"200":{"description":"Denuncia resuelta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationReport"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"La denuncia ya está resuelta (`conflict`) o la persona no se puede sancionar aquí (`sanction_not_allowed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/moderation/reports/{report_id}/escalate":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"report_id","in":"path","required":true,"description":"Id de la denuncia.","schema":{"type":"string"}}],"post":{"operationId":"escalateModerationReport","tags":["moderation"],"summary":"Escalar una denuncia a plataforma","description":"admin+. Pasa una denuncia `open` a la cola de ops (Valfiguer) con un motivo de una lista cerrada; sigue visible en la cola de la organización como `escalated` y la organización puede resolverla igualmente. Auditado (`moderation.report_escalated`). Otra organización → 404; no abierta → 409.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationEscalate"}}}},"responses":{"200":{"description":"Denuncia escalada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationReport"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"La denuncia no está abierta (`conflict`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/moderation/sanctions":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"listModerationSanctions","tags":["moderation"],"summary":"Sanciones de la organización","description":"admin+. Avisos y suspensiones de lo social que la organización ha puesto, las más recientes primero (E0.5b). Con `user_id`, solo las de esa persona; con `active=true`, solo las suspensiones en vigor, con su nombre.","parameters":[{"name":"user_id","in":"query","required":false,"description":"Solo las sanciones de esta persona.","schema":{"type":["string","null"]}},{"name":"active","in":"query","required":false,"description":"Solo las suspensiones en vigor.","schema":{"type":"boolean","default":false}},{"name":"limit","in":"query","required":false,"description":"Tamaño de página (1..200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","required":false,"description":"Desplazamiento.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Sanciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationSanctionList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/moderation/sanctions/{sanction_id}/revoke":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"sanction_id","in":"path","required":true,"description":"Id de la sanción.","schema":{"type":"string"}}],"post":{"operationId":"revokeModerationSanction","tags":["moderation"],"summary":"Revocar una suspensión de lo social","description":"admin+. Termina YA una suspensión de lo social en vigor: la persona vuelve a reaccionar, valorar y jugar duelos en la organización. Auditado (`moderation.sanction_revoked`). Otra organización → 404; un aviso o una suspensión ya terminada → 409 `sanction_not_active`.","responses":{"200":{"description":"Suspensión terminada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationSanction"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"No es una suspensión en vigor (`sanction_not_active`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/moderation/my-reports":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"listMyModerationReports","tags":["moderation"],"summary":"Mis denuncias y si ya se revisaron","description":"Cualquier miembro. Las denuncias que hizo la persona en esta organización, con `pending` o `reviewed`: es el aviso al denunciante, que NO dice qué se decidió ni si hubo sanción (datos de otra persona). Sin sesión → 401; organización ajena → 404.","parameters":[{"name":"limit","in":"query","required":false,"description":"Tamaño de página (1..200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","required":false,"description":"Desplazamiento.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Mis denuncias.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyModerationReportList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/me/blocks":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"listMyBlocks","tags":["moderation"],"summary":"Personas que he bloqueado","description":"Cualquier miembro. Los bloqueos que hizo la persona en esta organización (con su nombre). Quién te ha bloqueado a ti no se enseña nunca.","parameters":[{"name":"limit","in":"query","required":false,"description":"Tamaño de página (1..200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","required":false,"description":"Desplazamiento.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Bloqueos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserBlockList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/me/blocks/{user_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"user_id","in":"path","required":true,"description":"Id de la persona (miembro de la organización).","schema":{"type":"string"}}],"post":{"operationId":"blockUser","tags":["moderation"],"summary":"Bloquear a una persona","description":"Cualquier miembro, también un menor. Efecto, en cualquier organización y en los dos sentidos: no se ven en el feed (sus entradas y su feed de persona son 404 entre ellas, así que tampoco se reacciona), su ficha es 404 entre ellas, no se emparejan en duelos y no se pueden valorar (403 `interaction_not_allowed`). Idempotente: bloquear otra vez devuelve el bloqueo que ya existe. Persona de otra organización → 404; a uno mismo o a alguien del equipo (docente, admin, owner) → 409 `block_not_allowed`. Auditado (`moderation.user_blocked`).","responses":{"201":{"description":"Bloqueo registrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserBlock"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"No se puede bloquear a esa persona (`block_not_allowed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"unblockUser","tags":["moderation"],"summary":"Desbloquear a una persona","description":"Deshace un bloqueo propio hecho en esta organización. Sin bloqueo → 404. Auditado (`moderation.user_unblocked`).","responses":{"204":{"description":"Desbloqueado."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/members/invite":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}}],"post":{"operationId":"inviteOrganizationMember","tags":["invitations"],"summary":"Invitar por email","description":"admin+. El correo sale en el idioma del invitado o, si no tiene cuenta, en el de la organización. 409 `already_member` / `invite_pending`; 403 `plan_limit_reached`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteCreate"}}}},"x-tool":{"name":"invite_organization_member","domain":"admin","profile":"core","sensitive":false,"roles":["owner","admin"]},"responses":{"201":{"description":"Invitación creada (el token solo viaja en el correo).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invite"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Conflicto con el estado actual (código en el envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/members/invites":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}}],"get":{"operationId":"listOrganizationInvites","tags":["invitations"],"summary":"Invitaciones pendientes","description":"admin+.","x-tool":{"name":"list_organization_invites","domain":"admin","profile":"core","sensitive":false,"roles":["owner","admin"]},"parameters":[{"name":"limit","in":"query","required":false,"description":"Tamaño de página (1..200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","required":false,"description":"Desplazamiento.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Invitaciones pendientes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/members/invites/{invite_id}":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}},{"name":"invite_id","in":"path","required":true,"description":"Id de la invitación.","schema":{"type":"string"}}],"delete":{"operationId":"revokeOrganizationInvite","tags":["invitations"],"summary":"Revocar invitación","description":"admin+. El enlace deja de valer.","responses":{"204":{"description":"Revocada."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/members/invites/{invite_id}/resend":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}},{"name":"invite_id","in":"path","required":true,"description":"Id de la invitación.","schema":{"type":"string"}}],"post":{"operationId":"resendOrganizationInvite","tags":["invitations"],"summary":"Reenviar invitación","description":"admin+. Token nuevo (el anterior deja de valer) y caducidad renovada.","responses":{"200":{"description":"Invitación reenviada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invite"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/members/import":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}}],"post":{"operationId":"importOrganizationMembers","tags":["members"],"summary":"Alta masiva por CSV","description":"admin+. CSV `email,nombre,rol,cohorte` (cabeceras es/en, máx. 1 MB y 5.000 filas). Con `dry_run=true` (por defecto) solo valida y devuelve el informe sin escribir nada; con `false` encola el job y devuelve su estado. Las celdas del informe salen neutralizadas contra fórmulas. 403 `plan_limit_reached` si no caben.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"}}}}}},"parameters":[{"name":"dry_run","in":"query","required":false,"description":"Solo valida y devuelve el informe; no escribe nada.","schema":{"type":"boolean","default":true}}],"responses":{"200":{"description":"Informe de validación o estado de la importación.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberImport"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/members/import/{import_id}":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}},{"name":"import_id","in":"path","required":true,"description":"Id de la importación.","schema":{"type":"string"}}],"get":{"operationId":"getOrganizationMemberImport","tags":["members"],"summary":"Estado de una importación","description":"admin+. Progreso e informe.","responses":{"200":{"description":"Estado de la importación.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberImport"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/members/import/{import_id}/report":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}},{"name":"import_id","in":"path","required":true,"description":"Id de la importación.","schema":{"type":"string"}}],"get":{"operationId":"downloadOrganizationMemberImportReport","tags":["members"],"summary":"Descargar el informe (CSV)","description":"admin+. CSV con cada fila, su estado y el motivo; celdas neutralizadas.","responses":{"200":{"description":"Informe CSV.","content":{"text/csv":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/legal/documents":{"get":{"operationId":"listLegalDocuments","security":[],"tags":["privacy"],"summary":"Documentos legales vigentes","description":"Tipo, versión (fecha ISO) y si exige aceptación. Público. Espejo de `LEGAL_VERSIONS` del web (un test los compara).","responses":{"200":{"description":"Documentos vigentes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegalDocuments"}}}}}}},"/api/v1/me/privacy":{"get":{"operationId":"getMyPrivacy","tags":["privacy"],"summary":"Mi privacidad","description":"Tramo de edad (siempre `adult`, ADR 0036), la política efectiva y el estado de mi baja.","responses":{"200":{"description":"Mi privacidad.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyPrivacy"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/me/consents":{"get":{"operationId":"listMyConsents","tags":["privacy"],"summary":"Mis consentimientos","description":"Última decisión por tipo y documentos cuya versión vigente falta por aceptar.","responses":{"200":{"description":"Mis consentimientos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyConsents"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"recordMyConsent","tags":["privacy"],"summary":"Registrar un consentimiento","description":"Append-only. Solo la versión vigente de un documento (409 `legal_version_outdated`). Términos y privacidad no se rechazan: eso es pedir la baja (422). Un `user_id` en el cuerpo es un campo extra (422).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentCreate"}}},"required":true},"responses":{"201":{"description":"Mis consentimientos tras registrar la decisión.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyConsents"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"Conflicto con el estado actual (código en el envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/me/exports":{"get":{"operationId":"listMyDataExports","tags":["privacy"],"summary":"Mis exportaciones","description":"Las 10 más recientes, con su estado. Una sesión impersonada de soporte recibe 403 `impersonation_read_only` (el enlace firmado no se enseña a soporte).","responses":{"200":{"description":"Mis exportaciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataExportList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"operationId":"requestMyDataExport","tags":["privacy"],"summary":"Pedir la exportación de mis datos","description":"Se genera en segundo plano y llega un correo con un enlace firmado, de un solo uso, que caduca a los 7 días. Una cada 24 h (429 `export_rate_limited`).","responses":{"202":{"description":"Exportación encolada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataExport"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/me/exports/{export_id}":{"get":{"operationId":"getMyDataExport","tags":["privacy"],"summary":"Estado de una exportación mía","description":"404 si no existe o es de otra persona. `download_url` solo mientras está lista y sin descargar. Sesión impersonada de soporte → 403 `impersonation_read_only`.","parameters":[{"name":"export_id","in":"path","required":true,"schema":{"type":"string"},"description":"Id de la exportación."}],"responses":{"200":{"description":"Exportación.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataExport"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/me/exports/{export_id}/download":{"get":{"operationId":"downloadMyDataExport","tags":["privacy"],"summary":"Descargar mi exportación","description":"Exige la sesión de su titular Y el `token` firmado del enlace (404 si falta cualquiera de los dos). De un solo uso: 410 `export_already_downloaded`; caducada: 410 `export_expired`. Una sesión impersonada de soporte recibe 403 `impersonation_read_only`.","parameters":[{"name":"export_id","in":"path","required":true,"schema":{"type":"string"},"description":"Id de la exportación."},{"name":"token","in":"query","required":true,"schema":{"type":"string","minLength":10,"maxLength":64,"pattern":"^[0-9]+-[0-9a-f]+$"},"description":"Firma del enlace (caducidad-HMAC)."}],"responses":{"200":{"description":"ZIP con data.json.","content":{"application/zip":{}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"description":"Ya no está disponible (código en el envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/me/deletion":{"get":{"operationId":"getMyDeletion","tags":["privacy"],"summary":"Estado de mi baja","responses":{"200":{"description":"Estado de la baja.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletionStatus"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"requestMyDeletion","tags":["privacy"],"summary":"Pedir la baja de mi cuenta","description":"Re-autenticación con un código de acceso recién pedido a mi email (`invalid_otp` si no casa). Programa el borrado a 30 días, cierra todas las sesiones y envía el acuse. 409 `deletion_already_requested`; 409 `last_owner` si soy el último owner de una organización.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletionRequest"}}},"required":true},"responses":{"202":{"description":"Baja programada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletionStatus"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"Conflicto con el estado actual (código en el envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"operationId":"cancelMyDeletion","tags":["privacy"],"summary":"Cancelar mi baja","description":"Durante el periodo de gracia. 404 si no hay baja programada.","responses":{"200":{"description":"Baja cancelada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletionStatus"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/members/{user_id}/age-band":{"patch":{"operationId":"setOrganizationMemberAgeBand","tags":["members","privacy"],"summary":"Fijar el tramo de edad de un miembro","description":"owner/admin de la organización. 404 si no es miembro de ESTA organización. Desde el ADR 0036 (EduRails es solo para mayores de 18) el único tramo admitido es `adult`; cualquier otro es `422 minors_not_allowed`. Auditado.","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string"},"description":"Id del usuario miembro."},{"name":"org_id","in":"path","required":true,"schema":{"type":"string"},"description":"Id de la organización."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberAgeBandUpdate"}}}},"responses":{"200":{"description":"Tramo actualizado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberAgeBand"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Reservado (`age_band_downgrade_forbidden`); desde el ADR 0036 no se da.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/privacy":{"get":{"operationId":"getOrganizationPrivacy","tags":["privacy"],"summary":"Privacidad de la organización","description":"teacher+. Tutor IA, alumnado menor y si hay DPA firmado.","parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string"},"description":"Id de la organización."}],"responses":{"200":{"description":"Privacidad de la organización.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationPrivacy"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}},"patch":{"operationId":"updateOrganizationPrivacy","tags":["privacy"],"summary":"Cambiar la privacidad de la organización","description":"owner/admin. Encender o apagar el tutor IA (apagado de fábrica; implica al subencargado LLM). Auditado.","parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string"},"description":"Id de la organización."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationPrivacyUpdate"}}}},"responses":{"200":{"description":"Privacidad actualizada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationPrivacy"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/audit-logs":{"parameters":[{"name":"org_id","in":"path","required":true,"description":"Id de la organización.","schema":{"type":"string"}}],"get":{"operationId":"listOrganizationAuditLogs","tags":["audit"],"summary":"Registro de auditoría de la organización","description":"owner/admin de la organización (403 al resto de miembros; 404 a no-miembros). Excluye las acciones internas del panel de ops (`platform.*`). Sin huella de IP: eso es forense de la plataforma.","parameters":[{"name":"limit","in":"query","required":false,"description":"Tamaño de página (1..200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","required":false,"description":"Desplazamiento.","schema":{"type":"integer","minimum":0,"default":0}},{"name":"action","in":"query","required":false,"description":"Código de acción exacto (`member.role_changed`…).","schema":{"type":["string","null"],"maxLength":100}},{"name":"actor_id","in":"query","required":false,"description":"Usuario que actuó.","schema":{"type":["string","null"],"maxLength":36}},{"name":"date_from","in":"query","required":false,"description":"created_at >= (ISO 8601).","schema":{"type":["string","null"],"format":"date-time"}},{"name":"date_to","in":"query","required":false,"description":"created_at <= (ISO 8601).","schema":{"type":["string","null"],"format":"date-time"}}],"responses":{"200":{"description":"Página del registro, lo más reciente primero.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditLogList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/webhooks/resend":{"post":{"operationId":"receiveResendWebhook","tags":["webhooks"],"summary":"Eventos de Resend (rebotes, quejas y correo entrante)","description":"Recibe los eventos firmados de Resend. `email.bounced` con rebote permanente y `email.complained` añaden la dirección a la lista de supresión (solo se guarda un hash) y quedan registrados; un rebote transitorio y `email.delivered` solo se registran; `email.received` (correo entrante) con algún destinatario del dominio propio se registra y se reenvía en segundo plano a INBOUND_FORWARD_TO; cualquier otro tipo se acepta sin hacer nada. La cuenta de Resend es compartida con otros productos: un evento de salida cuyo remitente (`data.from`) no es del dominio propio, o que no lo trae, y un `email.received` sin destinatario propio se aceptan (200) sin suprimir ni registrar nada. Idempotente por `svix-id`: una reentrega responde `duplicate: true`. Sin secreto configurado responde 503 (`webhook_not_configured`); firma ausente, inválida o fuera de ventana, 401 (`invalid_signature`).","security":[],"parameters":[{"name":"svix-id","in":"header","required":true,"schema":{"type":"string"},"description":"Id único de la entrega (clave de idempotencia)."},{"name":"svix-timestamp","in":"header","required":true,"schema":{"type":"string"},"description":"Marca de tiempo Unix de la firma."},{"name":"svix-signature","in":"header","required":true,"schema":{"type":"string"},"description":"Una o varias firmas `v1,<base64>` separadas por espacio."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Evento de Resend (`type`, `created_at`, `data`). Se verifica sobre el cuerpo crudo.","required":["type"],"properties":{"type":{"type":"string","examples":["email.bounced"]},"created_at":{"type":"string"},"data":{"type":"object","additionalProperties":true}},"additionalProperties":true}}}},"responses":{"200":{"description":"Evento aceptado (procesado, duplicado o ignorado).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendWebhookAck"}}}},"400":{"description":"Firma válida pero cuerpo que no es JSON (`invalid_payload`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Firma ausente, inválida o fuera de ventana (`invalid_signature`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"RESEND_WEBHOOK_SECRET sin configurar (`webhook_not_configured`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/announcements":{"get":{"operationId":"listMyAnnouncements","tags":["announcements"],"summary":"Anuncios vigentes","description":"Anuncios de plataforma vigentes ahora mismo para cualquier persona con sesión, con el título y el cuerpo en el idioma pedido (`es` por defecto). No depende de la organización ni revela nada de nadie.","parameters":[{"name":"locale","in":"query","required":false,"description":"Idioma del texto.","schema":{"type":"string","enum":["es","en"],"default":"es"}}],"responses":{"200":{"description":"Anuncios vigentes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActiveAnnouncementList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/public/demo-requests":{"post":{"operationId":"createDemoRequest","tags":["public"],"summary":"Solicitar una demo","description":"Registra una solicitud de demo de una institución. Responde 202: la solicitud queda guardada y el aviso al equipo (correo a hola@edurails.com y, si está configurado, tarea en Projekt) sale en segundo plano. Limitado por IP y por email (`too_many_requests`). El campo `website` es una trampa anti-spam: debe llegar vacío u omitido; si llega relleno la respuesta es la misma pero no se guarda nada.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoRequestCreate"}}}},"responses":{"202":{"description":"Solicitud aceptada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoRequestAccepted"}}}},"422":{"description":"Cuerpo inválido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas solicitudes para esta IP o este email (`too_many_requests`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/courses":{"get":{"operationId":"listPublicCourses","tags":["public"],"summary":"Catálogo público de cursos","description":"Cursos del catálogo global de Valfiguer publicados, con las facetas de nivel y etiqueta. Nunca incluye cursos privados de una organización ni borradores. Sin sesión.","security":[],"parameters":[{"name":"level","in":"query","required":false,"schema":{"type":["string","null"]}},{"name":"tag","in":"query","required":false,"schema":{"type":["string","null"]}},{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Cursos del catálogo global.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCourseList"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/public/courses/{slug}":{"get":{"operationId":"getPublicCourse","tags":["public"],"summary":"Ficha pública de curso","description":"Un curso global publicado por su slug, con el temario (títulos de lección, sin contenido). Un curso privado, un borrador o un slug que no existe responden igual: 404.","security":[],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Ficha del curso.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCourseDetail"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/public/programs":{"get":{"operationId":"listPublicPrograms","tags":["public"],"summary":"Programas públicos","description":"Programas (rutas de aprendizaje) globales activos. Sin sesión.","security":[],"parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Programas globales.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicProgramList"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/public/programs/{slug}":{"get":{"operationId":"getPublicProgram","tags":["public"],"summary":"Ficha pública de programa","description":"Un programa global activo por su slug, con sus cursos globales publicados en orden. Los cursos que no son del catálogo global no se enseñan. Un programa privado o inactivo responde 404.","security":[],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Ficha del programa.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicProgramDetail"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/telemetry/web":{"post":{"operationId":"reportWebTelemetry","tags":["telemetry"],"summary":"Anotar Web Vitals y errores de cliente","description":"Lo manda el navegador al ocultarse la pestaña (`keepalive`). Responde 204 siempre que el cuerpo sea válido, también si el almacén no está disponible (una medición perdida no es un error de nadie). Rechaza con 422 claves desconocidas (p. ej. `email`, `name`, `user_id`) y rutas con identificadores sin sustituir. Limitado por IP (`too_many_requests`).","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebTelemetryIn"}}}},"responses":{"204":{"description":"Anotado (o descartado en silencio si el almacén no responde)."},"422":{"description":"Cuerpo inválido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiados envíos desde esta IP (`too_many_requests`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/telemetry/events":{"post":{"operationId":"reportWebEvent","tags":["telemetry"],"summary":"Contar un evento de la web pública","description":"Un `page_view` por navegación (también las del App Router) o un evento del catálogo cerrado (`generate_lead`, `view_course`, `cta_login`). Responde 204 siempre que el cuerpo sea válido, también si el almacén no responde. Rechaza con 422 claves desconocidas (`email`, `name`, `user_id`…), rutas con identificadores sin sustituir y una `label` en un evento que no la admite. Limitado por IP (`too_many_requests`).","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebEventIn"}}}},"responses":{"204":{"description":"Contado (o descartado en silencio si el almacén no responde)."},"422":{"description":"Cuerpo inválido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiados envíos desde esta IP (`too_many_requests`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me":{"get":{"operationId":"getMyUser","tags":["users"],"summary":"Perfil propio","description":"Devuelve el usuario autenticado con sus estadísticas de progreso.","responses":{"200":{"description":"Usuario con estadísticas de progreso.","content":{"application/json":{"schema":{"type":"object","required":["user","progress_stats"],"properties":{"user":{"$ref":"#/components/schemas/User"},"progress_stats":{"type":"object","required":["total_courses","completed_courses","in_progress_courses","total_lessons_completed","total_quizzes_passed","average_progress","average_quiz_score"],"properties":{"total_courses":{"type":"integer"},"completed_courses":{"type":"integer"},"in_progress_courses":{"type":"integer"},"total_lessons_completed":{"type":"integer"},"total_quizzes_passed":{"type":"integer"},"average_progress":{"type":"integer"},"average_quiz_score":{"type":"integer"}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"updateMyUser","tags":["users"],"summary":"Actualizar perfil propio","description":"Actualiza nombre y/o avatar del usuario autenticado.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":100},"avatar":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"Usuario actualizado.","content":{"application/json":{"schema":{"type":"object","required":["user"],"properties":{"user":{"$ref":"#/components/schemas/User"}}}}}},"400":{"description":"Petición inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Foto propia (`avatar`): nadie la tiene, todo el mundo usa su pepo (`avatar_upload_not_allowed`, EDFRO-266).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Cuerpo inválido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/avatar":{"get":{"operationId":"getMyAvatar","tags":["avatars"],"summary":"Mi pepo","description":"Semilla y rasgos de mi avatar Open Peeps (EDFRO-264), con la ruta de su SVG. Quien aún no lo ha tocado tiene el de su semilla estable.","responses":{"200":{"description":"Mi pepo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyAvatar"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"put":{"operationId":"updateMyAvatar","tags":["avatars"],"summary":"Guardar mi pepo","description":"Sustituye semilla y rasgos de MI avatar (solo el propio: no hay forma de cambiar el de otra persona). Cada valor se valida contra el estilo Open Peeps instalado (`GET /api/v1/avatars/options`); uno que no existe → 422 `validation_error` con el campo. Guardar el pepo lo convierte en tu avatar: si mostrabas una foto, deja de mostrarse.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvatarOptions"}}}},"responses":{"200":{"description":"Pepo guardado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyAvatar"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/ValidationError"}}},"post":{"operationId":"uploadMyAvatar","tags":["users"],"summary":"Subir avatar","description":"Sube la imagen de avatar del usuario (máximo 5 MB).","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["avatar"],"properties":{"avatar":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Avatar actualizado.","content":{"application/json":{"schema":{"type":"object","required":["user"],"properties":{"user":{"$ref":"#/components/schemas/User"}}}}}},"400":{"description":"Petición inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nadie sube foto: todo el mundo usa su pepo (`avatar_upload_not_allowed`, EDFRO-266).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"El fichero no es una imagen admitida por sus bytes (`invalid_image`); se guarda re-codificada y sin EXIF.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/notifications/preferences":{"get":{"operationId":"getMyNotificationPreferences","tags":["notifications"],"summary":"Obtener preferencias de notificación","description":"Devuelve las preferencias de email del usuario autenticado.","responses":{"200":{"description":"Preferencias de notificación del usuario.","content":{"application/json":{"schema":{"type":"object","required":["preferences"],"properties":{"preferences":{"type":"object","required":["email_course_complete","email_badge_earned","email_streak_reminder","email_weekly_summary","email_new_course"],"properties":{"email_course_complete":{"type":"boolean"},"email_badge_earned":{"type":"boolean"},"email_streak_reminder":{"type":"boolean"},"email_weekly_summary":{"type":"boolean"},"email_new_course":{"type":"boolean"}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"updateMyNotificationPreferences","tags":["notifications"],"summary":"Actualizar preferencias de notificación","description":"Actualiza las preferencias de email del usuario autenticado.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email_course_complete":{"type":"boolean"},"email_badge_earned":{"type":"boolean"},"email_streak_reminder":{"type":"boolean"},"email_weekly_summary":{"type":"boolean"},"email_new_course":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Preferencias de notificación actualizadas.","content":{"application/json":{"schema":{"type":"object","required":["preferences"],"properties":{"preferences":{"type":"object","required":["email_course_complete","email_badge_earned","email_streak_reminder","email_weekly_summary","email_new_course"],"properties":{"email_course_complete":{"type":"boolean"},"email_badge_earned":{"type":"boolean"},"email_streak_reminder":{"type":"boolean"},"email_weekly_summary":{"type":"boolean"},"email_new_course":{"type":"boolean"}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/push/subscribe":{"post":{"operationId":"subscribeMyPush","tags":["notifications"],"summary":"Suscribirse a notificaciones push","description":"Registra una suscripción push web para el usuario autenticado.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["endpoint"],"properties":{"endpoint":{"type":"string","format":"uri"},"keys":{"type":"object","required":["p256dh","auth"],"properties":{"p256dh":{"type":"string"},"auth":{"type":"string"}}}}}}}},"responses":{"200":{"description":"Suscripción registrada.","content":{"application/json":{"schema":{"type":"object","required":["subscribed"],"properties":{"subscribed":{"type":"boolean"}}}}}},"400":{"description":"Petición inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/push/test":{"post":{"operationId":"testMyPushNotification","tags":["notifications"],"summary":"Enviar notificación push de prueba","description":"Envía una notificación push de prueba a las suscripciones del usuario autenticado.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Notificaciones de prueba enviadas.","content":{"application/json":{"schema":{"type":"object","required":["sent"],"properties":{"sent":{"type":"integer"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/push/unsubscribe":{"post":{"operationId":"unsubscribeMyPush","tags":["notifications"],"summary":"Cancelar suscripción push","description":"Elimina una suscripción push web del usuario autenticado.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["endpoint"],"properties":{"endpoint":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Suscripción cancelada.","content":{"application/json":{"schema":{"type":"object","required":["unsubscribed"],"properties":{"unsubscribed":{"type":"boolean"}}}}}},"400":{"description":"Petición inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/notifications/capabilities":{"get":{"operationId":"getNotificationCapabilities","tags":["notifications"],"summary":"Qué canales de push tiene el servidor","description":"Público. Sin claves VAPID, `web_push` es false y la web no ofrece los avisos del navegador; sin APNs/FCM, `native_push` es false y el cascarón no pide permiso.","security":[],"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationCapabilities"}}}}}}},"/api/v1/notifications/unsubscribe/{token}":{"parameters":[{"name":"token","in":"path","required":true,"description":"Token firmado de (persona, organización, categoría).","schema":{"type":"string","maxLength":300}}],"get":{"operationId":"getNotificationUnsubscribePage","tags":["notifications"],"summary":"Página de baja de un correo de avisos","description":"Público. Enseña la confirmación con un botón (un GET no da de baja: los filtros de correo abren los enlaces). Siempre 200 con HTML en el idioma de la persona.","security":[],"responses":{"200":{"description":"Página HTML.","content":{"text/html":{"schema":{"type":"string"}}}}}},"post":{"operationId":"unsubscribeNotificationOneClick","tags":["notifications"],"summary":"Baja en un clic (RFC 8058)","description":"Público. Lo llama el cliente de correo (`List-Unsubscribe-Post`) o el botón de la página: apaga el CORREO de esa categoría en esa organización. Idempotente; siempre 200 con HTML.","security":[],"responses":{"200":{"description":"Página HTML.","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/api/v1/organizations/{org_id}/notifications":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"listOrgNotifications","tags":["notifications"],"summary":"Mis avisos en la organización","description":"La bandeja in-app de la persona en ESTA organización, más recientes primero.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":50,"default":20}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}},{"name":"unread_only","in":"query","required":false,"schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/notifications/unread-count":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getOrgNotificationsUnreadCount","tags":["notifications"],"summary":"Avisos sin leer","description":"El número de la campanita (y del globo del icono de la app).","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationUnreadCount"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/notifications/read-all":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"markAllOrgNotificationsRead","tags":["notifications"],"summary":"Marcar todos como leídos","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationUnreadCount"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/notifications/stream":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"streamOrgNotifications","tags":["notifications"],"summary":"Avisos en tiempo real (SSE)","description":"`text/event-stream`. Al conectar, `unread` con el contador; después, un `notification` por aviso nuevo (con el objeto `Notification`) seguido del `unread` actualizado, y `heartbeat` cada 25 s. El cliente reconecta solo.","responses":{"200":{"description":"Stream `text/event-stream` con los avisos.","content":{"text/event-stream":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/notifications/{notification_id}/read":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"notification_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"operationId":"markOrgNotificationRead","tags":["notifications"],"summary":"Marcar un aviso como leído","description":"Idempotente. Un aviso de otra persona u otra organización es 404.","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Notification"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/notification-preferences":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"listOrgNotificationPreferences","tags":["notifications"],"summary":"Mis preferencias de avisos en la organización","description":"Una por categoría, con los canales que admite para esta persona y la ventana de silencio de la organización.","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferenceList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"updateOrgNotificationPreference","tags":["notifications"],"summary":"Cambiar una preferencia de avisos","description":"La categoría va en el cuerpo; solo cambian los campos que llegan. Un canal que la categoría no admite se guarda pero no se aplica.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferenceUpdate"}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreference"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/notification-settings":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getOrgNotificationSettings","tags":["notifications"],"summary":"Horas de silencio de la organización","description":"Solo admin u owner.","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrgNotificationSettings"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"updateOrgNotificationSettings","tags":["notifications"],"summary":"Cambiar las horas de silencio","description":"Solo admin u owner; queda en la auditoría. Dentro de la ventana no sale push ni correo inmediato: se difieren al final. Las dos horas o ninguna (`422 quiet_hours_incomplete`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrgNotificationSettingsUpdate"}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrgNotificationSettings"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/device-tokens":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"registerOrgDeviceToken","tags":["notifications"],"summary":"Registrar el token de push nativo de este teléfono","description":"Lo llama el cascarón (`apps/mobile`) con la organización ACTIVA. Idempotente por token: registrarlo otra vez lo reapunta a esta persona y organización (un teléfono, un token).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceTokenIn"}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceTokenResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/device-tokens/remove":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"removeOrgDeviceToken","tags":["notifications"],"summary":"Dar de baja el token de push nativo (cierre de sesión)","description":"Idempotente; solo borra un token propio en esta organización (`ok` false si no había).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceTokenRemoveIn"}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceTokenResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/me/username":{"post":{"operationId":"setMyUsername","tags":["users"],"summary":"Fijar username","description":"El usuario autenticado fija su username público (slug-safe, único en la plataforma). Solo letras minúsculas, dígitos y guiones; sin guión inicial ni final; máximo 50 caracteres.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["username"],"properties":{"username":{"type":"string","description":"Username slug-safe (a-z, 0-9, guiones; sin guión inicial ni final).","minLength":1,"maxLength":50}}}}}},"responses":{"200":{"description":"Username fijado.","content":{"application/json":{"schema":{"type":"object","required":["user"],"properties":{"user":{"$ref":"#/components/schemas/User"}}}}}},"400":{"description":"Username inválido (`invalid_username`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Username ya tomado (`username_taken`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Cuerpo inválido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/badges":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgListBadges","tags":["gamification"],"summary":"Listar insignias","description":"Devuelve el catálogo de insignias disponibles.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Lista de insignias.","content":{"application/json":{"schema":{"type":"object","required":["badges"],"properties":{"badges":{"type":"array","items":{"$ref":"#/components/schemas/Badge"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/badges/{id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la insignia.","schema":{"type":"string"}}],"get":{"operationId":"orgGetBadge","tags":["gamification"],"summary":"Obtener insignia","description":"Devuelve una insignia por ID.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Insignia.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Badge"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/certificates":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgMyCertificates","x-tool":{"name":"list_my_certificates","domain":"learning","profile":"core","sensitive":false,"superficies":["mcp","tutor"]},"tags":["certificates"],"summary":"Mis certificados","description":"Lista los certificados emitidos al usuario autenticado.","responses":{"200":{"description":"Certificados del usuario.","content":{"application/json":{"schema":{"type":"object","required":["certificates"],"properties":{"certificates":{"type":"array","items":{"$ref":"#/components/schemas/Certificate"}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/certificates/course/{courseId}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"courseId","in":"path","required":true,"description":"ID del curso.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgGetOrCreateCertificate","tags":["certificates"],"summary":"Obtener o crear certificado de curso","description":"Obtiene o crea (lazy) el certificado del curso para el usuario; requiere haber aprobado el examen.","responses":{"200":{"description":"Certificado del curso.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Certificate"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/certificates/{id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del certificado.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgGetCertificate","tags":["certificates"],"summary":"Obtener certificado","description":"Devuelve un certificado por ID; solo su propietario puede consultarlo.","responses":{"200":{"description":"Certificado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Certificate"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos; no es el propietario (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/certificates/{id}/pdf":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del certificado.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgGetCertificatePdf","tags":["certificates"],"summary":"Descargar certificado en PDF","description":"Genera y descarga el PDF del certificado.\n\n**Acceso**: el propietario autenticado, un admin, o cualquier visitante\nanónimo que conozca el UUID del certificado (equivalente a la verificación\npública — el UUID largo actúa como token de acceso implícito).\n","responses":{"200":{"description":"PDF del certificado.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Token inválido (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos; no es el propietario (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/organizations/{org_id}/challenges":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgListChallenges","tags":["gamification"],"summary":"Listar retos","description":"Devuelve los retos disponibles.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}},{"name":"active","in":"query","required":false,"description":"Filtra por retos activos.","schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Lista de retos.","content":{"application/json":{"schema":{"type":"object","required":["challenges"],"properties":{"challenges":{"type":"array","items":{"type":"object","required":["id","title","description","type","target","min_score","target_course_id","xp_reward","badge_reward","start_date","end_date","active"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"]},"type":{"type":"string","enum":["lessons","quizzes","streak","course","xp","courses_completed","quiz_perfect","level","lessons_week"]},"target":{"type":"integer"},"min_score":{"type":["integer","null"]},"target_course_id":{"type":["string","null"],"format":"uuid"},"xp_reward":{"type":"integer"},"badge_reward":{"type":["string","null"]},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"active":{"type":"boolean"},"user_progress":{"type":["object","null"],"properties":{"current":{"type":"integer"},"target":{"type":"integer"},"percentage":{"type":"integer"},"completed":{"type":"boolean"},"claimed":{"type":"boolean"}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/challenges/{id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del reto.","schema":{"type":"string"}}],"get":{"operationId":"orgGetChallenge","tags":["gamification"],"summary":"Obtener reto","description":"Devuelve un reto por ID.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Reto.","content":{"application/json":{"schema":{"type":"object","required":["id","title","description","type","target","min_score","target_course_id","xp_reward","badge_reward","start_date","end_date","active"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"]},"type":{"type":"string","enum":["lessons","quizzes","streak","course","xp","courses_completed","quiz_perfect","level","lessons_week"]},"target":{"type":"integer"},"min_score":{"type":["integer","null"]},"target_course_id":{"type":["string","null"],"format":"uuid"},"xp_reward":{"type":"integer"},"badge_reward":{"type":["string","null"]},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"active":{"type":"boolean"},"user_progress":{"type":["object","null"],"properties":{"current":{"type":"integer"},"target":{"type":"integer"},"percentage":{"type":"integer"},"completed":{"type":"boolean"},"claimed":{"type":"boolean"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/cohorts":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgListCohorts","x-tool":{"name":"list_cohorts","domain":"teaching","profile":"core","sensitive":false,"roles":["owner","admin","teacher"]},"tags":["cohorts"],"summary":"Listar cohortes","description":"Devuelve las cohortes; opcionalmente filtradas por estado activo.","parameters":[{"name":"active","in":"query","required":false,"description":"Filtra por estado activo (`true`/`false`).","schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Lista de cohortes.","content":{"application/json":{"schema":{"type":"object","required":["cohorts"],"properties":{"cohorts":{"type":"array","items":{"$ref":"#/components/schemas/Cohort"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"orgAdminCreateCohort","tags":["cohorts"],"summary":"Crear cohorte","description":"Crea una cohorte (administración).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"institute":{"type":"string","description":"Instituto heredado (opcional desde EDAPI-21; sin él, la cohorte es solo de la organización)."},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"active":{"type":"boolean","default":true}}}}}},"responses":{"201":{"description":"Cohorte creada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cohort"}}}},"400":{"description":"Solicitud inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Institución no encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/cohorts/{id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la cohorte.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgGetCohort","tags":["cohorts"],"summary":"Obtener cohorte","description":"Devuelve una cohorte por ID.","responses":{"200":{"description":"Cohorte.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cohort"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"orgAdminUpdateCohort","tags":["cohorts"],"summary":"Actualizar cohorte","description":"Actualiza una cohorte (administración).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"institute":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Cohorte actualizada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cohort"}}}},"400":{"description":"Solicitud inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"orgAdminDeleteCohort","tags":["cohorts"],"summary":"Eliminar cohorte","description":"Elimina una cohorte; devuelve 409 si aún tiene estudiantes.","responses":{"200":{"description":"Cohorte eliminada.","content":{"application/json":{"schema":{"type":"object","required":["deleted","id"],"properties":{"deleted":{"type":"boolean"},"id":{"type":"string","format":"uuid"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto; la cohorte aún tiene estudiantes (`conflict`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/cohorts/{id}/add-student":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la cohorte.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgAdminAddStudentToCohort","tags":["cohorts"],"summary":"Añadir estudiante a la cohorte","description":"Añade un estudiante a la cohorte (administración).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_id"],"properties":{"user_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Estudiante añadido.","content":{"application/json":{"schema":{"type":"object","required":["cohort_id","user_id","added"],"properties":{"cohort_id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"added":{"type":"boolean"}}}}}},"400":{"description":"Solicitud inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cohorte o usuario no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/cohorts/{id}/leaderboard":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la cohorte.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgGetCohortLeaderboard","x-tool":{"name":"get_cohort_leaderboard","domain":"teaching","profile":"core","sensitive":true,"superficies":["mcp","tutor"],"roles":["owner","admin","teacher"]},"tags":["cohorts"],"summary":"Ranking de la cohorte","description":"Devuelve el ranking de estudiantes de la cohorte y la posición del usuario; cada persona sale con su nombre (el alias de menores se retiró con el ADR 0036).","parameters":[{"name":"limit","in":"query","required":false,"description":"Número máximo de entradas del ranking.","schema":{"type":"integer","minimum":1,"maximum":50,"default":10}},{"name":"X-Lang","in":"header","required":false,"description":"Sin efecto desde el ADR 0036 (era el idioma de los alias de menores); se acepta y se ignora (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Ranking de la cohorte.","content":{"application/json":{"schema":{"type":"object","required":["cohort_id","cohort_name","leaderboard","user_position"],"properties":{"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"},"leaderboard":{"type":"array","items":{"type":"object","required":["rank","username","name","avatar","level","xp","badges"],"properties":{"rank":{"type":"integer"},"username":{"type":["string","null"]},"name":{"type":"string"},"avatar":{"type":["string","null"]},"user_id":{"type":["string","null"],"description":"Id de la persona para pintar su pepo (`/users/{id}/avatar.svg`). Desde el ADR 0036 siempre presente (el `null` de los alias de menores ya no se da)."},"level":{"type":"integer"},"xp":{"type":"integer"},"badges":{"type":"integer"}}}},"user_position":{"description":"Posición del usuario en el ranking, o `null` si no participa.","oneOf":[{"type":"object","required":["rank","username","name","avatar","level","xp","badges"],"properties":{"rank":{"type":"integer"},"username":{"type":["string","null"]},"name":{"type":"string"},"avatar":{"type":["string","null"]},"user_id":{"type":["string","null"],"description":"Id de la persona para pintar su pepo (`/users/{id}/avatar.svg`). Desde el ADR 0036 siempre presente (el `null` de los alias de menores ya no se da)."},"level":{"type":"integer"},"xp":{"type":"integer"},"badges":{"type":"integer"}}},{"type":"null"}]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/cohorts/{id}/remove-student":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la cohorte.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgAdminRemoveStudentFromCohort","tags":["cohorts"],"summary":"Quitar estudiante de la cohorte","description":"Quita un estudiante de la cohorte (administración).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_id"],"properties":{"user_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Estudiante quitado.","content":{"application/json":{"schema":{"type":"object","required":["cohort_id","user_id","removed"],"properties":{"cohort_id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"removed":{"type":"boolean"}}}}}},"400":{"description":"Solicitud inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cohorte o usuario no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/cohorts/{id}/stats":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la cohorte.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgGetCohortStats","tags":["cohorts"],"summary":"Estadísticas de la cohorte","description":"Devuelve métricas agregadas de la cohorte.","responses":{"200":{"description":"Estadísticas de la cohorte.","content":{"application/json":{"schema":{"type":"object","required":["cohort_id","name","total_students","total_xp","average_xp","average_level","total_courses_completed","total_lessons_completed","top_performer"],"properties":{"cohort_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"total_students":{"type":"integer"},"total_xp":{"type":"integer"},"average_xp":{"type":"number"},"average_level":{"type":"number"},"total_courses_completed":{"type":"integer"},"total_lessons_completed":{"type":"integer"},"top_performer":{"type":["object","null"],"description":"Estudiante con más XP, o `null` si la cohorte está vacía.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"xp":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/cohorts/{id}/students":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la cohorte.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgGetCohortStudents","tags":["cohorts"],"summary":"Estudiantes de la cohorte","description":"Devuelve los estudiantes pertenecientes a la cohorte.","responses":{"200":{"description":"Estudiantes de la cohorte.","content":{"application/json":{"schema":{"type":"object","required":["cohort_id","students"],"properties":{"cohort_id":{"type":"string","format":"uuid"},"students":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/cohorts/{id}/toggle-active":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la cohorte.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgAdminToggleCohortActive","tags":["cohorts"],"summary":"Alternar estado activo de la cohorte","description":"Alterna el estado activo de la cohorte (administración).","responses":{"200":{"description":"Estado actualizado.","content":{"application/json":{"schema":{"type":"object","required":["id","active"],"properties":{"id":{"type":"string","format":"uuid"},"active":{"type":"boolean"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/courses":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgListCourses","x-tool":{"name":"list_courses","domain":"learning","profile":"core","sensitive":false,"superficies":["mcp","tutor"]},"tags":["courses"],"summary":"Listar cursos","description":"Devuelve los cursos publicados con filtros de facetas disponibles.","parameters":[{"name":"level","in":"query","required":false,"description":"Filtra por nivel (`Principiante`|`Intermedio`|`Avanzado`).","schema":{"type":"string"}},{"name":"tag","in":"query","required":false,"description":"Filtra por tag.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Búsqueda por texto en título/descripción.","schema":{"type":"string"}},{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Lista de cursos y facetas de filtrado.","content":{"application/json":{"schema":{"type":"object","required":["courses","filters"],"properties":{"courses":{"type":"array","items":{"$ref":"#/components/schemas/Course"}},"filters":{"type":"object","required":["levels","tags"],"properties":{"levels":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/courses/{id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) o `slug` del curso.","schema":{"type":"string"}}],"get":{"operationId":"orgGetCourse","x-tool":{"name":"get_course","domain":"learning","profile":"core","sensitive":false,"superficies":["mcp","tutor"]},"tags":["courses"],"summary":"Detalle de curso","description":"Devuelve el curso con lecciones, módulos, progreso y valoración del usuario.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Detalle del curso.","content":{"application/json":{"schema":{"type":"object","required":["id","title","slug","level","xp","cert_enabled","tags","lessons_count","lessons","modules"],"properties":{"cover_image_alt":{"type":["string","null"],"description":"Texto alternativo de la portada."},"content_lang":{"type":"string","enum":["es","en"],"description":"Idioma en el que sale el contenido de las lecciones (el pedido si todas están traducidas; si no, el base)."},"base_lang":{"type":"string","enum":["es","en"],"description":"Idioma en el que está escrito el curso."},"lang":{"type":"string","enum":["es","en"],"description":"Idioma en el que sale el contenido (X-Lang > Accept-Language > idioma de la persona > es)."},"fallback":{"type":"boolean","description":"Algún campo no tenía traducción servible y salió en el idioma base del curso."},"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":["string","null"]},"level":{"type":"string"},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"cover_image_url":{"type":["string","null"]},"cert_enabled":{"type":"boolean"},"cert_title":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}},"lessons_count":{"type":"integer"},"lessons":{"type":"array","items":{"type":"object","required":["id","db_id","title","xp","module","has_quiz","completed"],"properties":{"id":{"type":"integer","description":"Número de lección (`lesson_number`), base 1."},"db_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"module":{"type":"integer"},"module_name":{"type":["string","null"]},"has_quiz":{"type":"boolean"},"completed":{"type":"boolean"}}}},"modules":{"type":"array","items":{"type":"object","required":["module","lessons"],"properties":{"module":{"type":"integer"},"module_name":{"type":["string","null"]},"lessons":{"type":"array","items":{"type":"integer"}}}}},"user_progress":{"type":["object","null"],"required":["progress","completed_lessons"],"properties":{"progress":{"type":"integer"},"completed_lessons":{"type":"array","items":{"type":"integer"}},"current_lesson":{"type":["integer","null"]},"started_at":{"type":["string","null"],"format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"quiz_results":{"type":"object","additionalProperties":true}}},"rating":{"type":"object","required":["average","count","distribution"],"properties":{"average":{"type":["number","null"]},"count":{"type":"integer"},"distribution":{"type":"object","additionalProperties":{"type":"integer"}}}},"user_rating":{"type":["integer","null"]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Curso no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/courses/{id}/lessons":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) o `slug` del curso.","schema":{"type":"string"}}],"get":{"operationId":"orgListCourseLessons","x-tool":{"name":"list_course_lessons","domain":"learning","profile":"core","sensitive":false,"superficies":["mcp","tutor"]},"tags":["courses"],"summary":"Listar lecciones del curso","description":"Devuelve las lecciones del curso con su contenido y recursos.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Lecciones del curso.","content":{"application/json":{"schema":{"type":"object","required":["course_id","lessons"],"properties":{"lang":{"type":"string","enum":["es","en"],"description":"Idioma en el que sale el contenido (X-Lang > Accept-Language > idioma de la persona > es)."},"fallback":{"type":"boolean","description":"Algún campo no tenía traducción servible y salió en el idioma base del curso."},"course_id":{"type":"string"},"lessons":{"type":"array","items":{"type":"object","required":["id","db_id","title","xp","module","has_quiz","completed"],"properties":{"id":{"type":"integer","description":"Número de lección (`lesson_number`), base 1."},"db_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"module":{"type":"integer"},"module_name":{"type":["string","null"]},"has_quiz":{"type":"boolean"},"completed":{"type":"boolean"},"content":{"type":["string","null"]},"resources":{"type":["array","null"],"items":{"type":"string"}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Curso no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/courses/{id}/lessons/{lessonId}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) o `slug` del curso.","schema":{"type":"string"}},{"name":"lessonId","in":"path","required":true,"description":"Número de lección (`lesson_number`), base 1.","schema":{"type":"integer"}}],"get":{"operationId":"orgGetCourseLesson","x-tool":{"name":"get_course_lesson","domain":"learning","profile":"core","sensitive":false,"superficies":["mcp","tutor"]},"tags":["courses"],"summary":"Detalle de lección","description":"Devuelve una lección del curso con su contenido, recursos y resultado de quiz.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Detalle de la lección.","content":{"application/json":{"schema":{"type":"object","required":["id","db_id","title","xp","module","has_quiz","completed"],"properties":{"lang":{"type":"string","enum":["es","en"],"description":"Idioma en el que sale el contenido (X-Lang > Accept-Language > idioma de la persona > es)."},"fallback":{"type":"boolean","description":"Algún campo no tenía traducción servible y salió en el idioma base del curso."},"id":{"type":"integer","description":"Número de lección (`lesson_number`), base 1."},"db_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"content":{"type":["string","null"]},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"module":{"type":"integer"},"module_name":{"type":["string","null"]},"resources":{"type":["array","null"],"items":{"type":"string"}},"has_quiz":{"type":"boolean"},"completed":{"type":"boolean"},"quiz_id":{"type":["string","null"]},"quiz_result":{"type":["object","null"],"properties":{"score":{"type":"integer"},"passed":{"type":"boolean"},"completed_at":{"type":["string","null"],"format":"date-time"},"attempts":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Curso o lección no encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/courses/{id}/quizzes":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) o `slug` del curso.","schema":{"type":"string"}}],"get":{"operationId":"orgListCourseQuizzes","tags":["courses"],"summary":"Listar quizzes del curso","description":"Devuelve los quizzes del curso (sin las preguntas).","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Quizzes del curso.","content":{"application/json":{"schema":{"type":"object","required":["course_id","quizzes"],"properties":{"course_id":{"type":"string"},"quizzes":{"type":"array","items":{"type":"object","required":["id","title","lesson_id","passing_score","questions_count"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"lesson_id":{"type":"integer"},"passing_score":{"type":"integer"},"questions_count":{"type":"integer"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Curso no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/courses/{id}/rate":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) o `slug` del curso.","schema":{"type":"string"}}],"post":{"operationId":"orgRateCourse","tags":["courses"],"summary":"Valorar curso","description":"El usuario autenticado fija su valoración (1-5) del curso.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rating"],"properties":{"rating":{"type":"integer","minimum":1,"maximum":5}}}}}},"responses":{"200":{"description":"Valoración registrada.","content":{"application/json":{"schema":{"type":"object","required":["average","count","distribution"],"properties":{"average":{"type":["number","null"]},"count":{"type":"integer"},"distribution":{"type":"object","additionalProperties":{"type":"integer"}},"user_rating":{"type":["integer","null"]}}}}}},"400":{"description":"Valoración fuera de rango (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso para valorar (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"operationId":"orgDeleteCourseRating","tags":["courses"],"summary":"Eliminar mi valoración","description":"El usuario autenticado elimina su valoración del curso (`user_rating` queda `null`).","responses":{"200":{"description":"Valoración eliminada.","content":{"application/json":{"schema":{"type":"object","required":["average","count","distribution"],"properties":{"average":{"type":["number","null"]},"count":{"type":"integer"},"distribution":{"type":"object","additionalProperties":{"type":"integer"}},"user_rating":{"type":["integer","null"]}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/courses/{id}/rating":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) o `slug` del curso.","schema":{"type":"string"}}],"get":{"operationId":"orgGetCourseRating","tags":["courses"],"summary":"Valoración del curso","description":"Devuelve la media, el recuento, la distribución y la valoración del usuario.","responses":{"200":{"description":"Valoración agregada del curso.","content":{"application/json":{"schema":{"type":"object","required":["average","count","distribution"],"properties":{"average":{"type":["number","null"]},"count":{"type":"integer"},"distribution":{"type":"object","additionalProperties":{"type":"integer"}},"user_rating":{"type":["integer","null"]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/duels/create":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"orgCreateDuel","tags":["duels"],"summary":"Crear o entrar a un duelo","description":"Crea o entra a un duelo 1v1 (emparejamiento).","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"course_id":{"type":["integer","null"],"description":"Curso opcional sobre el que emparejar."},"course":{"type":["string","null"],"maxLength":36,"description":"Curso (su id) sobre el que emparejar y del que salen las preguntas (EDFRO-120). Solo cursos que ve la organización; uno ajeno o sin preguntas bastantes responde 409 `game_questions_unavailable` (no se confirma que exista). `course_id` (entero heredado) queda sin uso."}}}}}},"responses":{"201":{"description":"Duelo creado o unido.","content":{"application/json":{"schema":{"type":"object","required":["duel_id","status","questions_count","challenger","opponent","expires_at","started_at"],"properties":{"duel_id":{"type":"string"},"status":{"type":"string","enum":["waiting","active"]},"questions_count":{"type":"integer"},"challenger":{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},"opponent":{"oneOf":[{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},{"type":"null"}]},"expires_at":{"type":["string","null"],"format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"409":{"description":"No hay preguntas suficientes en los cursos de la organización (`game_questions_unavailable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno al crear el duelo (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/duels/history":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetDuelHistory","tags":["duels"],"summary":"Historial de duelos","description":"Devuelve los duelos completados del usuario y su balance.","parameters":[{"name":"limit","in":"query","required":false,"description":"Número máximo de duelos a devolver.","schema":{"type":"integer","minimum":1,"maximum":50,"default":10}},{"name":"offset","in":"query","required":false,"description":"Duelos a saltar (paginación).","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Historial de duelos.","content":{"application/json":{"schema":{"type":"object","required":["duels","has_more","stats"],"properties":{"duels":{"type":"array","items":{"type":"object","required":["duel_id","status","opponent","my_score","their_score","outcome","xp_earned","created_at","completed_at"],"properties":{"duel_id":{"type":"string"},"status":{"type":"string","enum":["completed"]},"opponent":{"oneOf":[{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"},"profile_username":{"type":["string","null"],"description":"Username del rival SOLO si su perfil es visible para quien pregunta (EDAPI-92: perfil activado, miembro activo de esta organización y sin bloqueo entre los dos); si no, `null` y no se enlaza."}}},{"type":"null"}]},"my_score":{"type":"integer"},"their_score":{"type":"integer"},"outcome":{"type":["string","null"],"enum":["win","loss","draw",null]},"xp_earned":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"}}}},"has_more":{"type":"boolean","description":"Hay más duelos después de esta página (`offset` + `limit`)."},"stats":{"type":"object","required":["wins","losses","draws","total"],"properties":{"wins":{"type":"integer"},"losses":{"type":"integer"},"draws":{"type":"integer"},"total":{"type":"integer"}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/duels/{id}/answer":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del duelo.","schema":{"type":"string"}}],"post":{"operationId":"orgSubmitDuelAnswer","tags":["duels"],"summary":"Responder pregunta del duelo","description":"Corrige la respuesta y devuelve el estado del duelo, con resultados si concluye.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["question_index","selected_option"],"properties":{"question_index":{"type":"integer","minimum":0},"selected_option":{"type":"integer","minimum":0},"time_taken_ms":{"type":"integer","minimum":0,"maximum":120000,"default":0}}}}}},"responses":{"200":{"description":"Respuesta procesada.","content":{"application/json":{"schema":{"type":"object","required":["correct","correct_answer","my_score","my_answered","my_finished","opponent_answered","opponent_finished","duel_complete","results"],"properties":{"correct":{"type":"boolean"},"correct_answer":{"type":"integer"},"my_score":{"type":"integer"},"my_answered":{"type":"integer"},"my_finished":{"type":"boolean"},"opponent_answered":{"type":"integer"},"opponent_finished":{"type":"boolean"},"duel_complete":{"type":"boolean"},"results":{"oneOf":[{"type":"object","required":["duel_id","status","winner_id","challenger","opponent","challenger_score","opponent_score","challenger_time_ms","opponent_time_ms","challenger_xp","opponent_xp","questions_count"],"properties":{"duel_id":{"type":"string"},"status":{"type":"string","enum":["completed"]},"winner_id":{"type":["string","null"],"format":"uuid"},"challenger":{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},"opponent":{"oneOf":[{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},{"type":"null"}]},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"challenger_time_ms":{"type":"integer"},"opponent_time_ms":{"type":"integer"},"challenger_xp":{"type":"integer"},"opponent_xp":{"type":"integer"},"questions_count":{"type":"integer"}}},{"type":"null"}]}}}}}},"400":{"description":"Solicitud inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"409":{"description":"Esa pregunta ya tiene respuesta: dos envíos a la vez cuentan una (`duel_answer_already_recorded`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/duels/{id}/decline":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del duelo.","schema":{"type":"string"}}],"post":{"operationId":"orgDeclineDuel","tags":["duels"],"summary":"Rechazar o cancelar duelo","description":"Rechaza o cancela un duelo en espera.","responses":{"200":{"description":"Duelo rechazado.","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["declined"]}}}}}},"400":{"description":"Solicitud inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"}}}},"/api/v1/organizations/{org_id}/duels/{id}/question":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del duelo.","schema":{"type":"string"}}],"get":{"operationId":"orgGetDuelQuestion","tags":["duels"],"summary":"Pregunta actual del duelo","description":"Devuelve la siguiente pregunta pendiente del duelo para el usuario.","responses":{"200":{"description":"Pregunta actual del duelo.","content":{"application/json":{"schema":{"type":"object","required":["completed","question_index","total_questions","answered_count","question","message"],"properties":{"completed":{"type":"boolean"},"question_index":{"type":"integer"},"total_questions":{"type":"integer"},"answered_count":{"type":"integer"},"question":{"type":["object","null"],"properties":{"id":{"type":"integer"},"question":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"difficulty":{"type":"string","enum":["easy","medium","hard"]}}},"message":{"type":["string","null"]}}}}}},"400":{"description":"Solicitud inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"}}}},"/api/v1/organizations/{org_id}/duels/{id}/results":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del duelo.","schema":{"type":"string"}}],"get":{"operationId":"orgGetDuelResults","tags":["duels"],"summary":"Resultados del duelo","description":"Devuelve el resultado final del duelo completado.","responses":{"200":{"description":"Resultado del duelo.","content":{"application/json":{"schema":{"type":"object","required":["duel_id","status","winner_id","challenger","opponent","challenger_score","opponent_score","challenger_time_ms","opponent_time_ms","challenger_xp","opponent_xp","questions_count"],"properties":{"duel_id":{"type":"string"},"status":{"type":"string","enum":["completed"]},"winner_id":{"type":["string","null"],"format":"uuid"},"challenger":{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},"opponent":{"oneOf":[{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},{"type":"null"}]},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"challenger_time_ms":{"type":"integer"},"opponent_time_ms":{"type":"integer"},"challenger_xp":{"type":"integer"},"opponent_xp":{"type":"integer"},"questions_count":{"type":"integer"}}}}}},"400":{"description":"Solicitud inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"}}}},"/api/v1/organizations/{org_id}/duels/{id}/status":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del duelo.","schema":{"type":"string"}}],"get":{"operationId":"orgGetDuelStatus","tags":["duels"],"summary":"Estado del duelo","description":"Devuelve el estado actual del duelo y sus resultados si ha terminado.","responses":{"200":{"description":"Estado del duelo.","content":{"application/json":{"schema":{"type":"object","required":["duel_id","status","questions_count","challenger","opponent","challenger_answered","opponent_answered","challenger_score","opponent_score","created_at","started_at","expires_at","results"],"properties":{"duel_id":{"type":"string"},"status":{"type":"string","enum":["waiting","active","completed","expired","declined"]},"questions_count":{"type":"integer"},"challenger":{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},"opponent":{"oneOf":[{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},{"type":"null"}]},"challenger_answered":{"type":"integer"},"opponent_answered":{"type":"integer"},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"results":{"type":["object","null"],"properties":{"winner_id":{"type":["string","null"],"format":"uuid"},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"challenger_time_ms":{"type":"integer"},"opponent_time_ms":{"type":"integer"},"challenger_xp":{"type":"integer"},"opponent_xp":{"type":"integer"}}}}}}}},"400":{"description":"Solicitud inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"}}}},"/api/v1/organizations/{org_id}/duels/{id}/events":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del duelo.","schema":{"type":"string"}}],"get":{"operationId":"orgStreamDuelEvents","tags":["duels"],"summary":"Eventos de un duelo en tiempo real (SSE)","description":"Server-Sent Events del duelo (ADR 0031). Solo los dos participantes; el resto recibe 404 (no se confirma que exista). Primer frame con `retry: 2000` y el estado entero (`state`); al reconectar se vuelve a mandar. Eventos:\n\n- `state` y `matched` → `DuelStreamState`.\n- `opponent_progress` → `DuelStreamProgress` (el rival respondió una más).\n- `duel_completed`, `duel_expired`, `duel_declined` → `DuelStreamState`; después el servidor cierra el stream.\n- `heartbeat` → `{}` cada ~15 s sin otra cosa que decir.\n\nEl servidor mira el duelo cada 0,5 s y cierra la conexión a los 10 min (el cliente reconecta). Cabeceras `Cache-Control: no-cache` y `X-Accel-Buffering: no`: nginx no debe acumular el stream. Cliente: `EventSource` nativo con `withCredentials` (openapi-fetch no consume SSE) y, tras varios fallos, sondeo de `…/status`.","responses":{"200":{"description":"Stream `text/event-stream` con los eventos del duelo.","content":{"text/event-stream":{"schema":{"type":"string"}}},"x-eventos":{"state":{"type":"object","required":["duel_id","status","questions_count","challenger","opponent","challenger_answered","opponent_answered","challenger_score","opponent_score","created_at","started_at","expires_at","results"],"properties":{"duel_id":{"type":"string"},"status":{"type":"string","enum":["waiting","active","completed","expired","declined"]},"questions_count":{"type":"integer"},"challenger":{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},"opponent":{"oneOf":[{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},{"type":"null"}]},"challenger_answered":{"type":"integer"},"opponent_answered":{"type":"integer"},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"results":{"type":["object","null"],"properties":{"winner_id":{"type":["string","null"],"format":"uuid"},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"challenger_time_ms":{"type":"integer"},"opponent_time_ms":{"type":"integer"},"challenger_xp":{"type":"integer"},"opponent_xp":{"type":"integer"}}}},"title":"DuelStreamState","description":"Estado completo de un duelo en el stream (misma forma que `orgGetDuelStatus`)."},"matched":{"type":"object","required":["duel_id","status","questions_count","challenger","opponent","challenger_answered","opponent_answered","challenger_score","opponent_score","created_at","started_at","expires_at","results"],"properties":{"duel_id":{"type":"string"},"status":{"type":"string","enum":["waiting","active","completed","expired","declined"]},"questions_count":{"type":"integer"},"challenger":{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},"opponent":{"oneOf":[{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},{"type":"null"}]},"challenger_answered":{"type":"integer"},"opponent_answered":{"type":"integer"},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"results":{"type":["object","null"],"properties":{"winner_id":{"type":["string","null"],"format":"uuid"},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"challenger_time_ms":{"type":"integer"},"opponent_time_ms":{"type":"integer"},"challenger_xp":{"type":"integer"},"opponent_xp":{"type":"integer"}}}},"title":"DuelStreamState","description":"Estado completo de un duelo en el stream (misma forma que `orgGetDuelStatus`)."},"opponent_progress":{"type":"object","description":"El rival respondió una pregunta más.","required":["answered","score","finished"],"properties":{"answered":{"type":"integer","description":"Preguntas que lleva respondidas."},"score":{"type":"integer","description":"Sus puntos hasta ahora."},"finished":{"type":"boolean","description":"Ya respondió todas."}}},"duel_completed":{"type":"object","required":["duel_id","status","questions_count","challenger","opponent","challenger_answered","opponent_answered","challenger_score","opponent_score","created_at","started_at","expires_at","results"],"properties":{"duel_id":{"type":"string"},"status":{"type":"string","enum":["waiting","active","completed","expired","declined"]},"questions_count":{"type":"integer"},"challenger":{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},"opponent":{"oneOf":[{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},{"type":"null"}]},"challenger_answered":{"type":"integer"},"opponent_answered":{"type":"integer"},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"results":{"type":["object","null"],"properties":{"winner_id":{"type":["string","null"],"format":"uuid"},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"challenger_time_ms":{"type":"integer"},"opponent_time_ms":{"type":"integer"},"challenger_xp":{"type":"integer"},"opponent_xp":{"type":"integer"}}}},"title":"DuelStreamState","description":"Estado completo de un duelo en el stream (misma forma que `orgGetDuelStatus`)."},"duel_expired":{"type":"object","required":["duel_id","status","questions_count","challenger","opponent","challenger_answered","opponent_answered","challenger_score","opponent_score","created_at","started_at","expires_at","results"],"properties":{"duel_id":{"type":"string"},"status":{"type":"string","enum":["waiting","active","completed","expired","declined"]},"questions_count":{"type":"integer"},"challenger":{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},"opponent":{"oneOf":[{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},{"type":"null"}]},"challenger_answered":{"type":"integer"},"opponent_answered":{"type":"integer"},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"results":{"type":["object","null"],"properties":{"winner_id":{"type":["string","null"],"format":"uuid"},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"challenger_time_ms":{"type":"integer"},"opponent_time_ms":{"type":"integer"},"challenger_xp":{"type":"integer"},"opponent_xp":{"type":"integer"}}}},"title":"DuelStreamState","description":"Estado completo de un duelo en el stream (misma forma que `orgGetDuelStatus`)."},"duel_declined":{"type":"object","required":["duel_id","status","questions_count","challenger","opponent","challenger_answered","opponent_answered","challenger_score","opponent_score","created_at","started_at","expires_at","results"],"properties":{"duel_id":{"type":"string"},"status":{"type":"string","enum":["waiting","active","completed","expired","declined"]},"questions_count":{"type":"integer"},"challenger":{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},"opponent":{"oneOf":[{"type":"object","required":["id","name","avatar","level"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"}}},{"type":"null"}]},"challenger_answered":{"type":"integer"},"opponent_answered":{"type":"integer"},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"results":{"type":["object","null"],"properties":{"winner_id":{"type":["string","null"],"format":"uuid"},"challenger_score":{"type":"integer"},"opponent_score":{"type":"integer"},"challenger_time_ms":{"type":"integer"},"opponent_time_ms":{"type":"integer"},"challenger_xp":{"type":"integer"},"opponent_xp":{"type":"integer"}}}},"title":"DuelStreamState","description":"Estado completo de un duelo en el stream (misma forma que `orgGetDuelStatus`)."},"heartbeat":{"type":"object"}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso para la interacción (`social_suspended`, `interaction_not_allowed`). `social_features_disabled` ya no se emite (ADR 0036).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"}}}},"/api/v1/organizations/{org_id}/editor/certificate-template":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgEditorGetCertificateTemplate","tags":["editor"],"summary":"Obtener plantilla de certificado","description":"Devuelve la plantilla completa de certificado (admin).","responses":{"200":{"description":"Plantilla de certificado.","content":{"application/json":{"schema":{"type":"object","required":["template"],"properties":{"template":{"type":"object","required":["id"],"properties":{"id":{"type":"integer"},"issuer_name":{"type":["string","null"]},"signature_name":{"type":["string","null"]},"signature_title":{"type":["string","null"]},"logo_url":{"type":["string","null"]},"accent_color":{"type":["string","null"]},"footer_text":{"type":["string","null"]},"updated_at":{"type":["string","null"],"format":"date-time"}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"orgEditorUpdateCertificateTemplate","tags":["editor"],"summary":"Actualizar plantilla de certificado","description":"Actualiza los campos de la plantilla de certificado (admin). El acento pasa la MISMA guardia de contraste AA que el color de la organización (422 `brand_color_low_contrast`) y el logo solo se admite por https (422 `validation_error`), EDFRO-180: lo emitido no cambia nunca (regla 25).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"issuer_name":{"type":"string","maxLength":255},"signature_name":{"type":"string","maxLength":255},"signature_title":{"type":"string","maxLength":255},"logo_url":{"type":"string","maxLength":500},"accent_color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"footer_text":{"type":"string","maxLength":500}}}}}},"responses":{"200":{"description":"Plantilla de certificado actualizada.","content":{"application/json":{"schema":{"type":"object","required":["template"],"properties":{"template":{"type":"object","required":["id"],"properties":{"id":{"type":"integer"},"issuer_name":{"type":["string","null"]},"signature_name":{"type":["string","null"]},"signature_title":{"type":["string","null"]},"logo_url":{"type":["string","null"]},"accent_color":{"type":["string","null"]},"footer_text":{"type":["string","null"]},"updated_at":{"type":["string","null"],"format":"date-time"}}}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/certificate-template/logo":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"orgEditorUploadCertificateLogo","tags":["editor"],"summary":"Subir el logo de la plantilla de certificado","description":"EDFRO-180. Sube el logo de la plantilla de certificado de la institución como fichero (admin de ella). Solo PNG, JPEG o WebP comprobado por su firma y hasta 1 MB (422 `unsupported_media_type`, `file_too_large` o `empty_file`). Lo guarda el StorageService con una key que construye el servidor y deja en `logo_url` su URL pública estable (`publicCertificateLogo`). Lo ya emitido no cambia (regla 25): el logo anterior sigue servido.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["logo"],"properties":{"logo":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Plantilla con el logo nuevo.","content":{"application/json":{"schema":{"type":"object","required":["template"],"properties":{"template":{"type":"object","required":["id"],"properties":{"id":{"type":"integer"},"issuer_name":{"type":["string","null"]},"signature_name":{"type":["string","null"]},"signature_title":{"type":["string","null"]},"logo_url":{"type":["string","null"]},"accent_color":{"type":["string","null"]},"footer_text":{"type":["string","null"]},"updated_at":{"type":["string","null"],"format":"date-time"}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden` o `two_factor_setup_required`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgEditorListCourses","tags":["editor"],"summary":"Listar cursos (editor)","description":"Devuelve todos los cursos visibles para el editor (publicados o no).","responses":{"200":{"description":"Lista de cursos del editor.","content":{"application/json":{"schema":{"type":"object","required":["courses"],"properties":{"courses":{"type":"array","items":{"type":"object","required":["id","title","slug","level","published"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"level":{"type":"string"},"published":{"type":"boolean"},"instructor_id":{"type":["string","null"],"format":"uuid"},"cover_image_url":{"type":["string","null"]},"status":{"type":"string","enum":["draft","published","archived"],"description":"Estado del curso (borrador, publicado o archivado)."},"scope":{"type":"string","enum":["global","organization"],"description":"Catálogo global de Valfiguer o curso privado de la organización."},"tags":{"type":"array","description":"Etiquetas del curso (para autocompletar en el editor).","items":{"type":"string"}}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"orgEditorCreateCourse","tags":["editor"],"summary":"Crear curso","description":"Crea un nuevo curso.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"slug":{"type":"string","pattern":"^[a-z0-9-]+$"},"description":{"type":"string"},"level":{"type":"string","default":"Principiante"},"duration":{"type":"string"},"xp":{"type":"integer","default":0},"published":{"type":"boolean","default":false,"description":"true publica en el alta (mismas validaciones que publicar)."},"cover_image_url":{"type":"string"},"cert_enabled":{"type":"boolean","default":true},"cert_title":{"type":"string"},"instructor_id":{"type":"string"}}}}}},"responses":{"201":{"description":"Curso creado.","content":{"application/json":{"schema":{"type":"object","required":["id","title","slug","level","xp","published","cert_enabled","lessons","quizzes","exam","revision","status"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":["string","null"]},"level":{"type":"string"},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"published":{"type":"boolean"},"cover_image_url":{"type":["string","null"]},"cert_enabled":{"type":"boolean"},"revision":{"type":"integer","description":"Revisión optimista (ADR 0028); va en `If-Match` al guardar."},"status":{"type":"string","enum":["draft","published","archived"],"description":"Ciclo de vida (EDAPI-130)."},"cover_image_alt":{"type":["string","null"],"description":"Texto alternativo de la portada; obligatorio para publicar si hay portada (ADR 0028)."},"exam_badge_id":{"type":["string","null"],"description":"Insignia que se concede (una vez) al aprobar el examen final del curso (EDAPI-167). Global o de la misma organización que el curso; `null` la quita."},"cert_title":{"type":["string","null"]},"instructor_id":{"type":["string","null"],"format":"uuid"},"lessons":{"type":"array","items":{"type":"object","required":["id","lesson_number","module","title","xp","has_quiz","revision"],"properties":{"id":{"type":"integer"},"lesson_number":{"type":"integer"},"module":{"type":"integer"},"module_name":{"type":["string","null"]},"title":{"type":"string"},"content":{"type":["string","null"]},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"has_quiz":{"type":"boolean"},"uid":{"type":["string","null"],"description":"Id estable (UUID) de la lección; `id` es el heredado o 0."},"revision":{"type":"integer","description":"Revisión optimista (ADR 0028); va en `If-Match` al guardar."}}}},"quizzes":{"type":"array","items":{"type":"object","required":["id","lesson_id","title","passing_score","questions"],"properties":{"id":{"type":"string","format":"uuid"},"lesson_id":{"type":"integer"},"title":{"type":"string"},"passing_score":{"type":"integer"},"questions":{"type":"array","items":{"type":"object","required":["question_number","question","options","correct_answer"],"properties":{"question_number":{"type":"integer"},"question":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"correct_answer":{"type":"integer"},"explanation":{"type":["string","null"]}}}}}}},"tags":{"type":"array","description":"Etiquetas del curso (normalizadas, como en `PUT .../tags`).","items":{"type":"string"}},"exam":{"type":"object","properties":{"type":{"type":["object","null"]}}}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgEditorGetCourse","tags":["editor"],"summary":"Obtener curso (editor)","description":"Devuelve el detalle completo del curso con lecciones, quizzes y examen.","responses":{"200":{"description":"Detalle del curso.","content":{"application/json":{"schema":{"type":"object","required":["id","title","slug","level","xp","published","cert_enabled","lessons","quizzes","exam","revision","status"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":["string","null"]},"level":{"type":"string"},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"published":{"type":"boolean"},"cover_image_url":{"type":["string","null"]},"cert_enabled":{"type":"boolean"},"revision":{"type":"integer","description":"Revisión optimista (ADR 0028); va en `If-Match` al guardar."},"status":{"type":"string","enum":["draft","published","archived"],"description":"Ciclo de vida (EDAPI-130)."},"cover_image_alt":{"type":["string","null"],"description":"Texto alternativo de la portada; obligatorio para publicar si hay portada (ADR 0028)."},"exam_badge_id":{"type":["string","null"],"description":"Insignia que se concede (una vez) al aprobar el examen final del curso (EDAPI-167). Global o de la misma organización que el curso; `null` la quita."},"cert_title":{"type":["string","null"]},"instructor_id":{"type":["string","null"],"format":"uuid"},"lessons":{"type":"array","items":{"type":"object","required":["id","lesson_number","module","title","xp","has_quiz","revision"],"properties":{"id":{"type":"integer"},"lesson_number":{"type":"integer"},"module":{"type":"integer"},"module_name":{"type":["string","null"]},"title":{"type":"string"},"content":{"type":["string","null"]},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"has_quiz":{"type":"boolean"},"uid":{"type":["string","null"],"description":"Id estable (UUID) de la lección; `id` es el heredado o 0."},"revision":{"type":"integer","description":"Revisión optimista (ADR 0028); va en `If-Match` al guardar."}}}},"quizzes":{"type":"array","items":{"type":"object","required":["id","lesson_id","title","passing_score","questions"],"properties":{"id":{"type":"string","format":"uuid"},"lesson_id":{"type":"integer"},"title":{"type":"string"},"passing_score":{"type":"integer"},"questions":{"type":"array","items":{"type":"object","required":["question_number","question","options","correct_answer"],"properties":{"question_number":{"type":"integer"},"question":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"correct_answer":{"type":"integer"},"explanation":{"type":["string","null"]}}}}}}},"tags":{"type":"array","description":"Etiquetas del curso (normalizadas, como en `PUT .../tags`).","items":{"type":"string"}},"exam":{"type":"object","properties":{"type":{"type":["object","null"]}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Curso no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}},"put":{"operationId":"orgEditorUpdateCourse","tags":["editor"],"summary":"Actualizar curso","description":"Actualiza los campos del curso indicado.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"slug":{"type":"string","pattern":"^[a-z0-9-]+$"},"description":{"type":"string"},"level":{"type":"string"},"duration":{"type":"string"},"xp":{"type":"integer"},"published":{"type":"boolean"},"cert_enabled":{"type":"boolean"},"cert_title":{"type":"string"},"cover_image_alt":{"type":["string","null"],"maxLength":255,"description":"Texto alternativo de la portada (accesibilidad)."},"exam_badge_id":{"type":["string","null"],"description":"Insignia que se concede (una vez) al aprobar el examen final del curso (EDAPI-167). Global o de la misma organización que el curso; `null` la quita."}}}}}},"parameters":[{"name":"If-Match","in":"header","required":false,"description":"Revisión que se editó (`revision` de la última lectura). Si ya no es la actual, 412 `stale_version` y no se guarda nada (ADR 0028).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Curso actualizado.","content":{"application/json":{"schema":{"type":"object","required":["id","title","slug","level","xp","published","cert_enabled","lessons","quizzes","exam","revision","status"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":["string","null"]},"level":{"type":"string"},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"published":{"type":"boolean"},"cover_image_url":{"type":["string","null"]},"cert_enabled":{"type":"boolean"},"revision":{"type":"integer","description":"Revisión optimista (ADR 0028); va en `If-Match` al guardar."},"status":{"type":"string","enum":["draft","published","archived"],"description":"Ciclo de vida (EDAPI-130)."},"cover_image_alt":{"type":["string","null"],"description":"Texto alternativo de la portada; obligatorio para publicar si hay portada (ADR 0028)."},"exam_badge_id":{"type":["string","null"],"description":"Insignia que se concede (una vez) al aprobar el examen final del curso (EDAPI-167). Global o de la misma organización que el curso; `null` la quita."},"cert_title":{"type":["string","null"]},"instructor_id":{"type":["string","null"],"format":"uuid"},"lessons":{"type":"array","items":{"type":"object","required":["id","lesson_number","module","title","xp","has_quiz","revision"],"properties":{"id":{"type":"integer"},"lesson_number":{"type":"integer"},"module":{"type":"integer"},"module_name":{"type":["string","null"]},"title":{"type":"string"},"content":{"type":["string","null"]},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"has_quiz":{"type":"boolean"},"uid":{"type":["string","null"],"description":"Id estable (UUID) de la lección; `id` es el heredado o 0."},"revision":{"type":"integer","description":"Revisión optimista (ADR 0028); va en `If-Match` al guardar."}}}},"quizzes":{"type":"array","items":{"type":"object","required":["id","lesson_id","title","passing_score","questions"],"properties":{"id":{"type":"string","format":"uuid"},"lesson_id":{"type":"integer"},"title":{"type":"string"},"passing_score":{"type":"integer"},"questions":{"type":"array","items":{"type":"object","required":["question_number","question","options","correct_answer"],"properties":{"question_number":{"type":"integer"},"question":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"correct_answer":{"type":"integer"},"explanation":{"type":["string","null"]}}}}}}},"tags":{"type":"array","description":"Etiquetas del curso (normalizadas, como en `PUT .../tags`).","items":{"type":"string"}},"exam":{"type":"object","properties":{"type":{"type":["object","null"]}}}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Curso no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"El recurso cambió desde que se leyó (`stale_version`); hay que recargar.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"operationId":"orgEditorDeleteCourse","tags":["editor"],"summary":"Eliminar curso","description":"Elimina el curso indicado.","responses":{"200":{"description":"Curso eliminado.","content":{"application/json":{"schema":{"type":"object","required":["deleted","id"],"properties":{"deleted":{"type":"boolean"},"id":{"type":"string","format":"uuid"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Curso no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/cover":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgEditorUploadCover","tags":["editor"],"summary":"Subir portada del curso","description":"Sube la imagen de portada del curso. En un curso privado, sus bytes cuentan en la cuota de almacenamiento de la organización: sin hueco en el tope del plan → 403 `plan_limit_reached` y no se guarda nada (EDAPI-276).","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["cover"],"properties":{"cover":{"type":"string","format":"binary"},"alt":{"type":"string","maxLength":255,"description":"Texto alternativo de la portada (a11y). Obligatorio para publicar un curso con portada (ADR 0028)."}}}}}},"responses":{"200":{"description":"Portada subida.","content":{"application/json":{"schema":{"type":"object","required":["cover_url"],"properties":{"cover_url":{"type":"string","format":"uri"}}}}}},"400":{"description":"Petición inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/exam":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgEditorGetCourseExam","tags":["editor"],"summary":"Obtener examen del curso (editor)","description":"Devuelve el examen del curso con sus preguntas, o `null` si no existe.","responses":{"200":{"description":"Examen del curso.","content":{"application/json":{"schema":{"type":"object","required":["exam"],"properties":{"exam":{"oneOf":[{"type":"object","required":["id","title","passing_score","time_limit_minutes","questions_count","max_attempts","active","questions"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["string","null"]},"passing_score":{"type":"integer"},"time_limit_minutes":{"type":"integer"},"questions_count":{"type":"integer"},"max_attempts":{"type":"integer"},"active":{"type":"boolean"},"questions":{"type":"array","items":{"type":"object","required":["question","options","correct_answer","difficulty"],"properties":{"question":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"correct_answer":{"type":"integer"},"difficulty":{"type":"string","enum":["easy","medium","hard"]},"topic_tag":{"type":["string","null"]}}}}}},{"type":"null"}]}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}},"put":{"operationId":"orgEditorUpsertCourseExam","tags":["editor"],"summary":"Crear o actualizar examen del curso","description":"Crea o actualiza la configuración del examen del curso.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","title"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"passing_score":{"type":"integer","minimum":1,"maximum":100,"default":80},"time_limit_minutes":{"type":"integer","minimum":1,"maximum":600,"default":60},"questions_count":{"type":"integer"},"max_attempts":{"type":"integer","minimum":1,"maximum":100,"default":3},"active":{"type":"boolean","default":true}}}}}},"responses":{"200":{"description":"Examen guardado.","content":{"application/json":{"schema":{"type":"object","required":["exam"],"properties":{"exam":{"type":"object","required":["id","title","passing_score","time_limit_minutes","questions_count","max_attempts","active"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["string","null"]},"passing_score":{"type":"integer"},"time_limit_minutes":{"type":"integer"},"questions_count":{"type":"integer"},"max_attempts":{"type":"integer"},"active":{"type":"boolean"}}}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Curso no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/exam/questions":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgEditorSetCourseExamQuestions","tags":["editor"],"summary":"Fijar preguntas del examen","description":"Reemplaza el conjunto de preguntas del examen del curso.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["questions"],"properties":{"questions":{"type":"array","items":{"type":"object","required":["question","options","correct_answer"],"properties":{"question":{"type":"string"},"options":{"type":"array","minItems":1,"items":{"type":"string"}},"correct_answer":{"type":"integer","minimum":0},"difficulty":{"type":"string","enum":["easy","medium","hard"],"default":"medium"},"topic_tag":{"type":["string","null"]}}}}}}}}},"responses":{"200":{"description":"Preguntas guardadas.","content":{"application/json":{"schema":{"type":"object","required":["saved","count"],"properties":{"saved":{"type":"boolean"},"count":{"type":"integer"}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Curso no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/flashcards":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgEditorListFlashcards","tags":["editor"],"summary":"Listar flashcards (editor)","description":"Devuelve las flashcards del curso.","responses":{"200":{"description":"Flashcards del curso.","content":{"application/json":{"schema":{"type":"object","required":["flashcards"],"properties":{"flashcards":{"type":"array","items":{"type":"object","required":["id","front","back","difficulty"],"properties":{"id":{"type":"integer"},"lesson_id":{"type":["integer","null"]},"front":{"type":"string"},"back":{"type":"string"},"uid":{"type":["string","null"],"description":"Id estable (UUID) de la flashcard; `id` es el heredado o 0."},"tags":{"type":["string","null"]},"difficulty":{"type":"string","enum":["easy","medium","hard"]}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}},"post":{"operationId":"orgEditorUpsertFlashcard","tags":["editor"],"summary":"Crear o actualizar flashcard","description":"Crea o actualiza una flashcard del curso.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["front","back"],"properties":{"id":{"type":"integer"},"front":{"type":"string"},"back":{"type":"string"},"uid":{"type":["string","null"],"description":"Id estable (UUID) de una flashcard existente del curso; si viene, se actualiza esa (manda sobre `id`)."},"lesson_id":{"type":"integer","minimum":1},"tags":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"difficulty":{"type":"string","enum":["easy","medium","hard"],"default":"medium"}}}}}},"responses":{"200":{"description":"Flashcard guardada.","content":{"application/json":{"schema":{"type":"object","required":["flashcard"],"properties":{"flashcard":{"type":"object","required":["id","front","back","difficulty"],"properties":{"id":{"type":"integer"},"lesson_id":{"type":["integer","null"]},"front":{"type":"string"},"back":{"type":"string"},"uid":{"type":["string","null"],"description":"Id estable (UUID) de la flashcard; `id` es el heredado o 0."},"tags":{"type":["string","null"]},"difficulty":{"type":"string","enum":["easy","medium","hard"]}}}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Curso o lección no encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/flashcards/{flashcardId}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}},{"name":"flashcardId","in":"path","required":true,"description":"ID de la flashcard.","schema":{"type":"integer"}}],"delete":{"operationId":"orgEditorDeleteFlashcard","tags":["editor"],"summary":"Eliminar flashcard","description":"Elimina la flashcard indicada del curso.","parameters":[{"name":"uid","in":"query","required":false,"description":"Id estable (UUID) de la flashcard. Si viene, se borra esa y el id numérico del path se ignora (manda 0): las creadas en EduRails no tienen id heredado.","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Flashcard eliminada.","content":{"application/json":{"schema":{"type":"object","required":["deleted","flashcard_id"],"properties":{"deleted":{"type":"boolean"},"flashcard_id":{"type":"integer"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Flashcard no encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/image":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgEditorUploadContentImage","tags":["editor"],"summary":"Subir imagen de contenido","description":"Sube una imagen para usar en el contenido de las lecciones del curso. En un curso privado, sus bytes cuentan en la cuota de almacenamiento de la organización: sin hueco en el tope del plan → 403 `plan_limit_reached` y no se guarda nada (EDAPI-276).","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Imagen subida.","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"}}}}}},"400":{"description":"Petición inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/instructor":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgEditorAssignInstructor","tags":["editor"],"summary":"Asignar instructor al curso","description":"Asigna o elimina el instructor del curso.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_id"],"properties":{"user_id":{"type":["string","null"],"description":"ID del instructor, o `null` para quitar."}}}}}},"responses":{"200":{"description":"Instructor actualizado.","content":{"application/json":{"schema":{"type":"object","required":["id","instructor_id"],"properties":{"id":{"type":"string","format":"uuid"},"instructor_id":{"type":["string","null"],"format":"uuid"}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Curso o usuario no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/lessons":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgEditorListLessons","tags":["editor"],"summary":"Listar lecciones (editor)","description":"Devuelve las lecciones del curso.","responses":{"200":{"description":"Lecciones del curso.","content":{"application/json":{"schema":{"type":"object","required":["lessons"],"properties":{"lessons":{"type":"array","items":{"type":"object","required":["id","lesson_number","module","title","xp","has_quiz","revision"],"properties":{"id":{"type":"integer"},"lesson_number":{"type":"integer"},"module":{"type":"integer"},"module_name":{"type":["string","null"]},"title":{"type":"string"},"content":{"type":["string","null"]},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"has_quiz":{"type":"boolean"},"uid":{"type":["string","null"],"description":"Id estable (UUID) de la lección; `id` es el heredado o 0."},"revision":{"type":"integer","description":"Revisión optimista (ADR 0028); va en `If-Match` al guardar."}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}},"post":{"operationId":"orgEditorUpsertLesson","tags":["editor"],"summary":"Crear o actualizar lección","description":"Crea o actualiza una lección por su `lesson_number`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["lesson_number","title"],"properties":{"lesson_number":{"type":"integer","minimum":1},"title":{"type":"string"},"module":{"type":"integer","default":1},"module_name":{"type":"string"},"content":{"type":"string","maxLength":200000},"duration":{"type":"string"},"xp":{"type":"integer","default":25},"resources":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"has_quiz":{"type":"boolean","default":true}}}}}},"parameters":[{"name":"If-Match","in":"header","required":false,"description":"Revisión que se editó (`revision` de la última lectura). Si ya no es la actual, 412 `stale_version` y no se guarda nada (ADR 0028).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Lección guardada.","content":{"application/json":{"schema":{"type":"object","required":["lesson"],"properties":{"lesson":{"type":"object","required":["id","lesson_number","module","title","xp","has_quiz","revision"],"properties":{"id":{"type":"integer"},"lesson_number":{"type":"integer"},"module":{"type":"integer"},"module_name":{"type":["string","null"]},"title":{"type":"string"},"content":{"type":["string","null"]},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"has_quiz":{"type":"boolean"},"uid":{"type":["string","null"],"description":"Id estable (UUID) de la lección; `id` es el heredado o 0."},"revision":{"type":"integer","description":"Revisión optimista (ADR 0028); va en `If-Match` al guardar."}}}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"412":{"description":"El recurso cambió desde que se leyó (`stale_version`); hay que recargar.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/lessons/reorder":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgEditorReorderLessons","tags":["editor"],"summary":"Reordenar lecciones","description":"Reordena las lecciones del curso según la permutación indicada.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["order"],"properties":{"order":{"type":"array","description":"Permutación completa de `lesson_number`.","items":{"type":"integer"}}}}}}},"responses":{"200":{"description":"Lecciones reordenadas.","content":{"application/json":{"schema":{"type":"object","required":["reordered","order"],"properties":{"reordered":{"type":"boolean"},"order":{"type":"array","items":{"type":"integer"}}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/structure":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"put":{"operationId":"orgEditorSaveCourseStructure","tags":["editor"],"summary":"Guardar la estructura del curso (módulos y lecciones)","description":"Editor de estructura (EDFRO-211). Fija de una vez, en una transacción, el orden de los módulos, su nombre y qué lecciones van en cada uno y en qué orden: `module` = posición del módulo (1..n), `lesson_number` = posición global de la lección. Las lecciones se nombran por su `lesson_number` actual y tienen que salir todas exactamente una vez (si no, 422 `invalid_reorder`). Sube la revisión de las lecciones que cambian (un autoguardado abierto con la revisión vieja recibe 412) y queda en la auditoría (`content.structure_saved`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseStructure"}}}},"responses":{"200":{"description":"Estructura guardada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseStructureResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"La estructura no nombra cada lección una vez (`invalid_reorder`) o el cuerpo no es válido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/lesson-versions":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgEditorListLessonVersions","tags":["editor"],"summary":"Historial de versiones de una lección","description":"Versiones guardadas de la lección (EDFRO-236), de la más reciente a la más antigua (las últimas 50). El autoguardado de la misma persona en diez minutos actualiza su última versión. Lección de otro curso → 404.","parameters":[{"name":"lesson_uid","in":"query","required":true,"description":"Id estable (UUID) de la lección.","schema":{"type":"string","maxLength":36}}],"responses":{"200":{"description":"Versiones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LessonVersionList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/lesson-versions/{version_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}},{"name":"version_id","in":"path","required":true,"description":"Id de la versión.","schema":{"type":"string"}}],"get":{"operationId":"orgEditorGetLessonVersion","tags":["editor"],"summary":"Una versión de una lección, con su contenido","description":"Para compararla con la actual. Versión de otro curso → 404.","responses":{"200":{"description":"La versión.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LessonVersion"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/lesson-versions/{version_id}/restore":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}},{"name":"version_id","in":"path","required":true,"description":"Id de la versión.","schema":{"type":"string"}}],"post":{"operationId":"orgEditorRestoreLessonVersion","tags":["editor"],"summary":"Restaurar una versión de una lección","description":"No es destructivo: guarda el título y el contenido de esa versión como una revisión NUEVA de la lección y deja una versión nueva que dice de cuál salió. Queda en la auditoría (`content.lesson_restored`).","responses":{"200":{"description":"Lección restaurada (revisión nueva).","content":{"application/json":{"schema":{"type":"object","required":["lesson"],"properties":{"lesson":{"type":"object","required":["id","lesson_number","module","title","xp","has_quiz","revision"],"properties":{"id":{"type":"integer"},"lesson_number":{"type":"integer"},"module":{"type":"integer"},"module_name":{"type":["string","null"]},"title":{"type":"string"},"content":{"type":["string","null"]},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"has_quiz":{"type":"boolean"},"uid":{"type":["string","null"],"description":"Id estable (UUID) de la lección; `id` es el heredado o 0."},"revision":{"type":"integer","description":"Revisión optimista (ADR 0028); va en `If-Match` al guardar."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/lessons/{lessonId}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}},{"name":"lessonId","in":"path","required":true,"description":"ID de la lección.","schema":{"type":"integer"}}],"delete":{"operationId":"orgEditorDeleteLesson","tags":["editor"],"summary":"Eliminar lección","description":"Elimina la lección indicada del curso.","parameters":[{"name":"uid","in":"query","required":false,"description":"Id estable (UUID) de la lección. Si viene, se borra esa y el id numérico del path se ignora (manda 0): las creadas en EduRails no tienen id heredado.","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Lección eliminada.","content":{"application/json":{"schema":{"type":"object","required":["deleted","lesson_id"],"properties":{"deleted":{"type":"boolean"},"lesson_id":{"type":"integer"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Lección no encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/public/courses/{course_id}/media/{kind}/{name}":{"parameters":[{"name":"course_id","in":"path","required":true,"description":"Id del curso.","schema":{"type":"string"}},{"name":"kind","in":"path","required":true,"description":"Portada (`cover`) o imagen de lección (`images`).","schema":{"type":"string","pattern":"^(cover|images)$"}},{"name":"name","in":"path","required":true,"description":"Fichero que generó el servidor al subirlo (`<32 hex>.<jpg|png|webp|gif>`).","schema":{"type":"string","pattern":"^[0-9a-f]{32}\\.(jpg|png|webp|gif)$"}}],"get":{"operationId":"publicCourseMedia","security":[],"tags":["courses"],"summary":"Imagen de un curso del catálogo global","description":"Portada o imagen de lección de un curso del catálogo global de Valfiguer. Sin sesión y con caché pública. Un curso privado es 404 aquí siempre.","responses":{"200":{"description":"La imagen.","content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"image/gif":{"schema":{"type":"string","format":"binary"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/public/certificate-logos/{organization_id}/{name}":{"parameters":[{"name":"organization_id","in":"path","required":true,"description":"Id de la institución dueña de la plantilla.","schema":{"type":"string"}},{"name":"name","in":"path","required":true,"description":"Fichero que generó el servidor al subirlo (`<32 hex>.<png|jpg|webp>`).","schema":{"type":"string","pattern":"^[0-9a-f]{32}\\.(png|jpg|webp)$"}}],"get":{"operationId":"publicCertificateLogo","security":[],"tags":["certificates"],"summary":"Logo de la plantilla de certificado de una institución","description":"Sin sesión y con caché pública. Un logo sustituido se sigue sirviendo (los certificados emitidos no cambian, regla 25). Nombre o institución inválidos o sin fichero, 404.","responses":{"200":{"description":"El logo.","content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/courses/{course_id}/media/{kind}/{name}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"course_id","in":"path","required":true,"description":"Id del curso.","schema":{"type":"string"}},{"name":"kind","in":"path","required":true,"description":"Portada (`cover`) o imagen de lección (`images`).","schema":{"type":"string","pattern":"^(cover|images)$"}},{"name":"name","in":"path","required":true,"description":"Fichero que generó el servidor al subirlo (`<32 hex>.<jpg|png|webp|gif>`).","schema":{"type":"string","pattern":"^[0-9a-f]{32}\\.(jpg|png|webp|gif)$"}}],"get":{"operationId":"orgCourseMedia","tags":["courses"],"summary":"Imagen de un curso de la organización","description":"Portada o imagen de lección de un curso que existe para la organización (su catálogo global si lo tiene encendido y sus cursos privados). Miembros con sesión; caché privada. El curso de otra organización es 404.","responses":{"200":{"description":"La imagen.","content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"image/gif":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/search":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgSearchContent","x-tool":{"name":"search_content","domain":"learning","profile":"core","sensitive":false,"superficies":["mcp","tutor"]},"tags":["search"],"summary":"Buscar en el contenido","description":"Busca cursos (título y descripción) y lecciones (título; el contenido solo en los cursos que la persona ya ha empezado o tiene matriculados) dentro del catálogo que la organización ve (global si lo tiene encendido + sus cursos privados, solo publicados), en el idioma base y en las traducciones servibles del idioma pedido. Un curso privado de otra organización no aparece nunca. Menos de 2 caracteres devuelve listas vacías.","parameters":[{"name":"q","in":"query","required":false,"description":"Texto a buscar (literal; sin comodines).","schema":{"type":"string","maxLength":100,"default":""}},{"name":"limit","in":"query","required":false,"description":"Máximo de cursos y de lecciones (cada lista).","schema":{"type":"integer","minimum":1,"maximum":50,"default":20}},{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Cursos y lecciones que coinciden, en el idioma pedido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentSearch"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/archive":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgEditorArchiveCourse","tags":["editor"],"summary":"Archivar curso","description":"Saca del catálogo un curso publicado de la organización (published → archived). Quien ya lo tenía empezado lo sigue viendo y sus certificados no cambian. Para volver a publicarlo, primero pasa a borrador (`publish` con `published=false`).","responses":{"200":{"description":"Curso archivado.","content":{"application/json":{"schema":{"type":"object","required":["id","published"],"properties":{"id":{"type":"string","format":"uuid"},"published":{"type":"boolean"},"status":{"type":"string","enum":["draft","published","archived"]},"version":{"type":"integer"},"published_at":{"type":["string","null"],"format":"date-time"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Transición no permitida (`invalid_transition`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/editors":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgEditorListCourseEditors","tags":["editor"],"summary":"Listar editores del curso","description":"Personas que editan el curso sin ser su autora (ADR 0028). La lista la ve y la cambia el admin de la organización (en un curso global, el admin de plataforma).","responses":{"200":{"description":"Editores del curso.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseEditorList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}},"post":{"operationId":"orgEditorAddCourseEditor","tags":["editor"],"summary":"Añadir editor al curso","description":"Idempotente. La persona tiene que ser miembro de la organización del curso con rol ≥ teacher (si no es miembro, 404; si lo es con otro rol, 422 `invalid_editor_role`). Queda en la auditoría (`content.editor_added`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseEditorAdd"}}}},"responses":{"200":{"description":"Editores del curso tras el alta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseEditorList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Rol insuficiente (`invalid_editor_role`) o cuerpo inválido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/editors/{editorId}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}},{"name":"editorId","in":"path","required":true,"description":"ID (UUID) de la persona editora.","schema":{"type":"string","format":"uuid"}}],"delete":{"operationId":"orgEditorRemoveCourseEditor","tags":["editor"],"summary":"Quitar editor del curso","description":"Deja de poder editar el curso (auditoría `content.editor_removed`). Si no figuraba, 404.","responses":{"200":{"description":"Editores del curso tras la baja.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseEditorList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/translations":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgEditorListCourseTranslations","tags":["editor"],"summary":"Traducciones del curso, lado a lado","description":"Cada campo traducible del curso, sus lecciones, quizzes y preguntas, con su texto base, su traducción y su estado (ADR 0027). Producción solo sirve lo `reviewed`.","parameters":[{"name":"lang","in":"query","required":false,"description":"Idioma de la traducción; por defecto, el que no es el base del curso.","schema":{"type":["string","null"],"enum":["es","en",null]}}],"responses":{"200":{"description":"Campos traducibles con su estado y el resumen.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseTranslations"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"El idioma pedido es el base del curso (`invalid_translation`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"orgEditorSaveCourseTranslation","tags":["editor"],"summary":"Guardar una traducción","description":"Escribe (o borra, con valor vacío) la traducción de un campo. Toda escritura la deja en `draft` y sella el texto base del que se hizo; hasta que alguien la marque revisada no se sirve en producción. Un campo que no es de este curso → 404.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranslationSave"}}}},"responses":{"200":{"description":"El campo, con su nuevo estado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranslationField"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Idioma base u opciones que no casan con las del texto base (`invalid_translation`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/translations/review":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgEditorReviewCourseTranslations","tags":["editor"],"summary":"Marcar traducciones revisadas","description":"Marca `reviewed` (con quién y cuándo) los campos pedidos, o todos los `draft` del curso si no se pide ninguno. Es la puerta del contenido traducido a producción (ADR 0027). Queda en la auditoría (`content.translation_reviewed`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranslationReview"}}}},"responses":{"200":{"description":"Cuántos campos se marcaron y el resumen nuevo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranslationReviewResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"El idioma pedido es el base del curso (`invalid_translation`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/publish":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgEditorSetPublished","tags":["editor"],"summary":"Publicar o despublicar curso","description":"Fija el estado de publicación del curso.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["published"],"properties":{"published":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Estado de publicación actualizado.","content":{"application/json":{"schema":{"type":"object","required":["id","published"],"properties":{"status":{"type":"string","enum":["draft","published","archived"],"description":"Estado del ciclo de vida (borrador → publicado → archivado)."},"version":{"type":"integer","description":"Versión publicada; sube en cada publicación (la primera es la 1)."},"published_at":{"type":["string","null"],"format":"date-time"},"id":{"type":"string","format":"uuid"},"published":{"type":"boolean"}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Transición no permitida (`invalid_transition`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"El curso no se puede publicar (`course_not_publishable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/publish-seo":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgEditorPublishSeo","tags":["editor"],"summary":"Publicar página SEO del curso","description":"Genera y publica la página SEO estática del curso.","responses":{"200":{"description":"Página SEO publicada.","content":{"application/json":{"schema":{"type":"object","required":["published","slug"],"properties":{"published":{"type":"boolean"},"slug":{"type":"string"}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Curso no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/quizzes":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgEditorListQuizzes","tags":["editor"],"summary":"Listar quizzes (editor)","description":"Devuelve los quizzes del curso con sus preguntas y respuestas correctas.","responses":{"200":{"description":"Quizzes del curso.","content":{"application/json":{"schema":{"type":"object","required":["quizzes"],"properties":{"quizzes":{"type":"array","items":{"type":"object","required":["id","lesson_id","title","passing_score","questions"],"properties":{"id":{"type":"string","format":"uuid"},"lesson_id":{"type":"integer"},"title":{"type":"string"},"passing_score":{"type":"integer"},"questions":{"type":"array","items":{"type":"object","required":["question_number","question","options","correct_answer"],"properties":{"question_number":{"type":"integer"},"question":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"correct_answer":{"type":"integer"},"explanation":{"type":["string","null"]}}}}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}},"post":{"operationId":"orgEditorUpsertQuiz","tags":["editor"],"summary":"Crear o actualizar quiz","description":"Crea o actualiza un quiz del curso.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","lesson_id","title"],"properties":{"id":{"type":"string"},"lesson_id":{"type":"integer","minimum":1},"title":{"type":"string"},"passing_score":{"type":"integer","default":60}}}}}},"responses":{"200":{"description":"Quiz guardado.","content":{"application/json":{"schema":{"type":"object","required":["quiz"],"properties":{"quiz":{"type":"object","required":["id","lesson_id","title","passing_score"],"properties":{"id":{"type":"string","format":"uuid"},"lesson_id":{"type":"integer"},"title":{"type":"string"},"passing_score":{"type":"integer"}}}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Curso o lección no encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/quizzes/{quizId}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}},{"name":"quizId","in":"path","required":true,"description":"ID (UUID) del quiz.","schema":{"type":"string","format":"uuid"}}],"delete":{"operationId":"orgEditorDeleteQuiz","tags":["editor"],"summary":"Eliminar quiz","description":"Elimina el quiz indicado del curso.","responses":{"200":{"description":"Quiz eliminado.","content":{"application/json":{"schema":{"type":"object","required":["deleted","quiz_id"],"properties":{"deleted":{"type":"boolean"},"quiz_id":{"type":"string","format":"uuid"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Quiz no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/quizzes/{quizId}/questions":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}},{"name":"quizId","in":"path","required":true,"description":"ID (UUID) del quiz.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgEditorSetQuizQuestions","tags":["editor"],"summary":"Fijar preguntas del quiz","description":"Reemplaza el conjunto de preguntas del quiz.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["questions"],"properties":{"questions":{"type":"array","items":{"type":"object","required":["question","options","correct_answer"],"properties":{"question":{"type":"string"},"options":{"type":"array","minItems":1,"items":{"type":"string"}},"correct_answer":{"type":"integer","minimum":0},"explanation":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Preguntas guardadas.","content":{"application/json":{"schema":{"type":"object","required":["saved","count"],"properties":{"saved":{"type":"boolean"},"count":{"type":"integer"}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Quiz no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/courses/{id}/tags":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del curso.","schema":{"type":"string","format":"uuid"}}],"put":{"operationId":"orgEditorSetTags","tags":["editor"],"summary":"Fijar tags del curso","description":"Reemplaza el conjunto de tags del curso.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tags"],"properties":{"tags":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Tags actualizados.","content":{"application/json":{"schema":{"type":"object","required":["tags"],"properties":{"tags":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/course-clones":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"orgEditorCloneGlobalCourse","tags":["editor"],"summary":"Clonar un curso del catálogo global como curso privado","description":"Docente o más de la organización. Solo un curso GLOBAL publicado que la organización ve (si no, 404). La copia nace `draft`, privada de la organización y con `cloned_from_course_id`: lecciones y módulos, quizzes, examen final con preguntas y reglas de sorteo, etiquetas, traducciones (con su revisión), portada e imágenes del contenido (copiadas bajo `orgs/{org_id}/…`, con las URLs reescritas). Editarla no toca el global. Cuenta como un curso privado más (403 `plan_limit_reached`) y sus ficheros, en la cuota de almacenamiento. Con más de 30 lecciones la hace un job y se responde 202 con el id que tendrá. Auditado (`content.course_cloned`). EDAPI-110.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseCloneInput"}}}},"responses":{"201":{"description":"Copia hecha.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseCloneResult"}}}},"202":{"description":"Curso grande; la copia la hace un job.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseCloneResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/programs":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgEditorListPrograms","tags":["editor"],"summary":"Listar programas (editor)","description":"Programas que la persona puede editar (EDFRO-229): los privados de la organización (admin u owner) y, si es autora o admin de plataforma, los del catálogo global.","responses":{"200":{"description":"Programas editables.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditorProgramList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"orgEditorCreateProgram","tags":["editor"],"summary":"Crear un programa de la organización","description":"Admin u owner de la organización. El programa nace privado de ELLA, inactivo y sin cursos. Slug repetido → 409 `slug_taken`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditorProgramCreate"}}}},"responses":{"201":{"description":"Programa creado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditorProgram"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Slug repetido (`slug_taken`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/programs/{pathId}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"pathId","in":"path","required":true,"description":"Id del programa.","schema":{"type":"string"}}],"get":{"operationId":"orgEditorGetProgram","tags":["editor"],"summary":"Ver un programa (editor)","description":"Con sus cursos en orden. Programa de otra organización → 404.","responses":{"200":{"description":"El programa.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditorProgram"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"orgEditorUpdateProgram","tags":["editor"],"summary":"Cambiar los datos de un programa","description":"Solo los campos que vienen. Slug repetido → 409 `slug_taken`. Publicar (`active: true`) exige al menos un curso requerido (422 `no_required_courses`) y que todos sus cursos estén publicados (422 `course_not_published`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditorProgramUpdate"}}}},"responses":{"200":{"description":"El programa.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditorProgram"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Slug repetido (`slug_taken`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"operationId":"orgEditorDeleteProgram","tags":["editor"],"summary":"Borrar un programa (editor)","description":"Solo un programa SIN publicar (`active: false`), sin nadie inscrito y sin certificados emitidos; si no, 409 `program_in_use` (despublícalo y consérvalo). Su capstone, si lo tiene, queda desactivado y suelto. Quien puede editarlo; otra organización → 404. Queda en la auditoría (`content.program_deleted`). EDAPI-114.","responses":{"204":{"description":"Programa borrado."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Publicado, con inscritos o con certificados (`program_in_use`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/editor/programs/{pathId}/courses":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"pathId","in":"path","required":true,"description":"Id del programa.","schema":{"type":"string"}}],"put":{"operationId":"orgEditorSetProgramCourses","tags":["editor"],"summary":"Fijar los cursos de un programa, en orden","description":"Sustituye la lista entera (añadir, quitar y reordenar son lo mismo). Cada curso tiene que ser visible para la organización del programa; si no, 404 (no existe para ella). Repetidos → 422. En un programa ya publicado valen las reglas de publicar: 422 `no_required_courses` o `course_not_published`. Queda en la auditoría (`content.program_courses_set`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditorProgramCourses"}}}},"responses":{"200":{"description":"El programa con sus cursos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditorProgram"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/programs/{pathId}/capstone":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"pathId","in":"path","required":true,"description":"ID (UUID) del programa (ruta).","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgEditorGetCapstone","tags":["editor"],"summary":"Obtener capstone del programa (editor)","description":"Devuelve el examen capstone del programa con sus preguntas, o `null` si no existe.","responses":{"200":{"description":"Capstone del programa.","content":{"application/json":{"schema":{"type":"object","required":["exam"],"properties":{"exam":{"oneOf":[{"type":"object","required":["id","title","passing_score","time_limit_minutes","questions_count","max_attempts","active","questions"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["string","null"]},"passing_score":{"type":"integer"},"time_limit_minutes":{"type":"integer"},"questions_count":{"type":"integer"},"max_attempts":{"type":"integer"},"active":{"type":"boolean"},"questions":{"type":"array","items":{"type":"object","required":["question","options","correct_answer","difficulty"],"properties":{"question":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"correct_answer":{"type":"integer"},"difficulty":{"type":"string","enum":["easy","medium","hard"]},"topic_tag":{"type":["string","null"]}}}}}},{"type":"null"}]}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}},"put":{"operationId":"orgEditorUpsertCapstone","tags":["editor"],"summary":"Crear o actualizar capstone del programa","description":"Crea o actualiza la configuración del examen capstone del programa.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","title"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"passing_score":{"type":"integer","minimum":1,"maximum":100,"default":80},"time_limit_minutes":{"type":"integer","minimum":1,"maximum":600,"default":60},"questions_count":{"type":"integer"},"max_attempts":{"type":"integer","minimum":1,"maximum":100,"default":3},"active":{"type":"boolean","default":true}}}}}},"responses":{"200":{"description":"Capstone guardado.","content":{"application/json":{"schema":{"type":"object","required":["exam"],"properties":{"exam":{"type":"object","required":["id","title","passing_score","time_limit_minutes","questions_count","max_attempts","active"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["string","null"]},"passing_score":{"type":"integer"},"time_limit_minutes":{"type":"integer"},"questions_count":{"type":"integer"},"max_attempts":{"type":"integer"},"active":{"type":"boolean"}}}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Programa no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/programs/{pathId}/capstone/questions":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"pathId","in":"path","required":true,"description":"ID (UUID) del programa (ruta).","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgEditorSetCapstoneQuestions","tags":["editor"],"summary":"Fijar preguntas del capstone","description":"Reemplaza el conjunto de preguntas del examen capstone del programa.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["questions"],"properties":{"questions":{"type":"array","items":{"type":"object","required":["question","options","correct_answer"],"properties":{"question":{"type":"string"},"options":{"type":"array","minItems":1,"items":{"type":"string"}},"correct_answer":{"type":"integer","minimum":0},"difficulty":{"type":"string","enum":["easy","medium","hard"],"default":"medium"},"topic_tag":{"type":["string","null"]}}}}}}}}},"responses":{"200":{"description":"Preguntas guardadas.","content":{"application/json":{"schema":{"type":"object","required":["saved","count"],"properties":{"saved":{"type":"boolean"},"count":{"type":"integer"}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Programa no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/programs/{pathId}/certificate":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"pathId","in":"path","required":true,"description":"ID (UUID) del programa (ruta).","schema":{"type":"string","format":"uuid"}}],"put":{"operationId":"orgEditorSetProgramCertificate","tags":["editor"],"summary":"Configurar certificado del programa","description":"Configura si el programa emite certificado y su título de credencial.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cert_enabled":{"type":"boolean"},"credential_title":{"type":["string","null"],"maxLength":255}}}}}},"responses":{"200":{"description":"Certificado del programa actualizado.","content":{"application/json":{"schema":{"type":"object","required":["program"],"properties":{"program":{"type":"object","required":["id","cert_enabled","credential_title"],"properties":{"id":{"type":"string","format":"uuid"},"cert_enabled":{"type":"boolean"},"credential_title":{"type":["string","null"]}}}}}}}},"400":{"description":"Datos inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permiso (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Programa no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/exams/sessions/{sessionId}/answer":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"sessionId","in":"path","required":true,"description":"ID de la sesión de examen.","schema":{"type":"string"}}],"post":{"operationId":"orgSaveExamAnswer","tags":["exams"],"summary":"Guardar respuesta","description":"Guarda la opción elegida para una pregunta en el servidor.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["question_id","option_index"],"properties":{"question_id":{"type":"string","format":"uuid"},"option_index":{"type":"integer"}}}}}},"responses":{"200":{"description":"Respuesta guardada.","content":{"application/json":{"schema":{"type":"object","required":["saved","answered_count"],"properties":{"saved":{"type":"boolean"},"answered_count":{"type":"integer"}}}}}},"400":{"description":"Cuerpo inválido (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Sesión o pregunta no encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/exams/sessions/{sessionId}/heartbeat":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"sessionId","in":"path","required":true,"description":"ID de la sesión de examen.","schema":{"type":"string"}}],"post":{"operationId":"orgExamHeartbeat","tags":["exams"],"summary":"Latido de sesión","description":"Mantiene viva la sesión y registra cambios de pestaña (anti-cheat).","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"tab_switch":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Estado actual de la sesión.","content":{"application/json":{"schema":{"type":"object","required":["status","active","tab_switches","max_tab_switches","time_remaining","message"],"properties":{"status":{"type":"string","enum":["active","expired","invalidated"]},"active":{"type":"boolean"},"tab_switches":{"type":"integer"},"max_tab_switches":{"type":"integer"},"time_remaining":{"type":"integer","description":"Segundos restantes."},"message":{"type":["string","null"]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Sesión no encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/exams/sessions/{sessionId}/question/{index}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"sessionId","in":"path","required":true,"description":"ID de la sesión de examen.","schema":{"type":"string"}},{"name":"index","in":"path","required":true,"description":"Índice de la pregunta (base 0).","schema":{"type":"integer","minimum":0}}],"get":{"operationId":"orgGetExamQuestion","tags":["exams"],"summary":"Obtener pregunta","description":"Devuelve la pregunta por índice con su respuesta guardada si existe.","responses":{"200":{"description":"Pregunta del examen.","content":{"application/json":{"schema":{"type":"object","required":["id","question","options","difficulty","topic_tag","index","total","saved_answer","answered_count"],"properties":{"id":{"type":"string","format":"uuid"},"question":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"difficulty":{"type":["string","null"]},"topic_tag":{"type":["string","null"]},"index":{"type":"integer"},"total":{"type":"integer"},"saved_answer":{"type":["integer","null"],"description":"Índice de opción guardada, o null."},"answered_count":{"type":"integer"}}}}}},"400":{"description":"Índice inválido (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Sesión o pregunta no encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/exams/sessions/{sessionId}/submit":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"sessionId","in":"path","required":true,"description":"ID de la sesión de examen.","schema":{"type":"string"}}],"post":{"operationId":"orgSubmitExam","tags":["exams"],"summary":"Enviar examen","description":"Corrige la sesión, emite certificado y badges si aprueba.","responses":{"200":{"description":"Resultado del examen con certificado y badges si aprobó.","content":{"application/json":{"schema":{"type":"object","required":["result","certificate","new_badges","xp_earned","email_sent"],"properties":{"result":{"type":"object","required":["result_id","score","correct","total","passed","attempt_number","attempts_remaining","status"],"properties":{"result_id":{"type":"string","format":"uuid"},"score":{"type":"integer"},"correct":{"type":"integer"},"total":{"type":"integer"},"passed":{"type":"boolean"},"attempt_number":{"type":"integer"},"attempts_remaining":{"type":"integer"},"status":{"type":"string","enum":["completed","expired"],"description":"`expired` si se envió pasada la gracia (cuenta como intento)."}}},"certificate":{"description":"Certificado emitido si aprobó, o null.","oneOf":[{"$ref":"#/components/schemas/Certificate"},{"type":"null"}]},"new_badges":{"type":"array","items":{"$ref":"#/components/schemas/Badge"}},"xp_earned":{"type":["integer","null"]},"email_sent":{"type":["boolean","null"]}}}}}},"400":{"description":"Sesión no enviable (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sesión invalidada o expirada (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Sesión no encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto (`conflict`): la sesión ya se envió o ya existe una activa.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/exams/{courseId}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"courseId","in":"path","required":true,"description":"ID del curso.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgGetExam","tags":["exams"],"summary":"Estado del examen","description":"Devuelve elegibilidad, intentos, metadatos del examen y resultados previos.","responses":{"200":{"description":"Estado y elegibilidad del examen para el usuario.","content":{"application/json":{"schema":{"type":"object","required":["can_take","reason","already_passed","attempts_used","attempts_max","exam","previous_results","has_certificate","max_tab_switches","active_session","retry_at"],"properties":{"can_take":{"type":"boolean"},"reason":{"type":["string","null"],"description":"Motivo si no puede rendir, o null."},"already_passed":{"type":"boolean"},"attempts_used":{"type":"integer"},"attempts_max":{"type":"integer"},"exam":{"type":"object","required":["id","course_id","kind","path_id","title","description","passing_score","time_limit_minutes","questions_count","max_attempts","cooldown_minutes","show_review","active"],"properties":{"id":{"type":"string","format":"uuid"},"course_id":{"type":["string","null"],"format":"uuid","description":"Curso asociado, o null para capstones."},"kind":{"type":"string","enum":["course","capstone"]},"path_id":{"type":["string","null"],"format":"uuid","description":"Ruta asociada (capstones), o null."},"title":{"type":"string"},"description":{"type":["string","null"]},"passing_score":{"type":"integer"},"time_limit_minutes":{"type":"integer"},"questions_count":{"type":"integer"},"max_attempts":{"type":"integer"},"active":{"type":"boolean"},"cooldown_minutes":{"type":"integer","description":"Minutos de espera entre intentos (EDAPI-144)."},"show_review":{"type":"string","enum":["never","after_submit","after_close"],"description":"Cuándo ve el alumno la corrección."}}},"previous_results":{"type":"array","items":{"type":"object","required":["score","passed","attempt_number","time_taken","date"],"properties":{"score":{"type":"integer"},"passed":{"type":"boolean"},"attempt_number":{"type":"integer"},"time_taken":{"type":"integer","description":"Tiempo empleado en segundos."},"date":{"type":"string","format":"date-time"}}}},"has_certificate":{"type":"boolean"},"max_tab_switches":{"type":"integer"},"active_session":{"type":["object","null"],"description":"Sesión activa reanudable o null."},"retry_at":{"type":["string","null"],"format":"date-time","description":"Si hay que esperar para el siguiente intento, desde cuándo se puede (EDAPI-144)."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Examen no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/exams/{courseId}/start":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"courseId","in":"path","required":true,"description":"ID del curso.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgStartExam","tags":["exams"],"summary":"Iniciar o reanudar examen","description":"Crea una sesión nueva (201) o reanuda la sesión activa existente (200).","responses":{"200":{"description":"Sesión activa reanudada.","content":{"application/json":{"schema":{"type":"object","required":["session_id","questions_count","expires_at","time_limit_minutes","passing_score","tab_switches","max_tab_switches","question_ids","answered_ids","resumed"],"properties":{"session_id":{"type":"string"},"questions_count":{"type":"integer"},"expires_at":{"type":"string","format":"date-time"},"time_limit_minutes":{"type":"integer"},"passing_score":{"type":"integer"},"tab_switches":{"type":"integer"},"max_tab_switches":{"type":"integer"},"question_ids":{"description":"Ids (UUID) de las preguntas de la sesión, en el orden en que se presentan.","type":"array","items":{"type":"string","format":"uuid"}},"answered_ids":{"description":"Ids (UUID) de las preguntas ya respondidas, subconjunto de `question_ids` (no posiciones).","type":"array","items":{"type":"string","format":"uuid"}},"resumed":{"type":"boolean"}}}}}},"201":{"description":"Sesión nueva creada.","content":{"application/json":{"schema":{"type":"object","required":["session_id","questions_count","expires_at","time_limit_minutes","passing_score","tab_switches","max_tab_switches"],"properties":{"session_id":{"type":"string"},"questions_count":{"type":"integer"},"expires_at":{"type":"string","format":"date-time"},"time_limit_minutes":{"type":"integer"},"passing_score":{"type":"integer"},"tab_switches":{"type":"integer"},"max_tab_switches":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin intentos disponibles o no elegible (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Conflicto (`conflict`): la sesión ya se envió o ya existe una activa.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/feed":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetActivityFeed","tags":["social"],"summary":"Feed de actividad","description":"Devuelve el feed de actividad reciente de la plataforma, paginado.","parameters":[{"name":"page","in":"query","required":false,"description":"Número de página.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Actividades del feed con paginación.","content":{"application/json":{"schema":{"type":"object","required":["activities","pagination"],"properties":{"activities":{"type":"array","items":{"type":"object","required":["id","user_name","user_avatar","action_type","action_text","metadata","created_at"],"properties":{"id":{"type":"integer"},"user_name":{"type":["string","null"]},"user_avatar":{"type":["string","null"],"format":"uri"},"action_type":{"type":"string","enum":["course_completed","course_started","level_up","challenge_completed"]},"action_text":{"type":"string"},"metadata":{"type":"object","additionalProperties":true},"created_at":{"type":"string","format":"date-time"}}}},"pagination":{"type":"object","required":["page","per_page","total","has_more"],"properties":{"page":{"type":"integer"},"per_page":{"type":"integer"},"total":{"type":"integer"},"has_more":{"type":"boolean"}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/flashcards/courses/{courseId}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"courseId","in":"path","required":true,"description":"ID del curso.","schema":{"type":"string"}}],"get":{"operationId":"orgListCourseFlashcards","tags":["flashcards"],"summary":"Flashcards de un curso","description":"Devuelve todas las flashcards de un curso (modo explorar, sin auth).","responses":{"200":{"description":"Flashcards del curso.","content":{"application/json":{"schema":{"type":"object","required":["cards","count"],"properties":{"cards":{"type":"array","items":{"type":"object","required":["id","course_id","lesson_id","lesson_title","front","back","tags","difficulty","created_at"],"properties":{"id":{"type":"integer"},"course_id":{"type":"string","format":"uuid"},"lesson_id":{"type":["string","null"],"format":"uuid"},"lesson_title":{"type":["string","null"]},"front":{"type":"string"},"back":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"difficulty":{"type":["string","null"],"enum":["easy","medium","hard",null]},"created_at":{"type":"string","format":"date-time"}}}},"count":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/flashcards/courses/{courseId}/due":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"courseId","in":"path","required":true,"description":"ID del curso.","schema":{"type":"string"}}],"get":{"operationId":"orgListDueFlashcards","x-tool":{"name":"list_due_flashcards","domain":"learning","profile":"all","sensitive":false,"superficies":["mcp","tutor"]},"tags":["flashcards"],"summary":"Flashcards pendientes de repaso","description":"Devuelve las flashcards pendientes de repaso hoy para el usuario en el curso.","responses":{"200":{"description":"Flashcards pendientes de repaso con el progreso del usuario.","content":{"application/json":{"schema":{"type":"object","required":["cards","count"],"properties":{"cards":{"type":"array","items":{"type":"object","required":["id","course_id","lesson_id","lesson_title","front","back","tags","difficulty","created_at","ease_factor","interval_days","repetitions","next_review","last_reviewed","progress_status"],"properties":{"id":{"type":"integer"},"course_id":{"type":"string","format":"uuid"},"lesson_id":{"type":["string","null"],"format":"uuid"},"lesson_title":{"type":["string","null"]},"front":{"type":"string"},"back":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"difficulty":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"ease_factor":{"type":"number"},"interval_days":{"type":"integer"},"repetitions":{"type":"integer"},"next_review":{"type":["string","null"],"format":"date"},"last_reviewed":{"type":["string","null"],"format":"date-time"},"progress_status":{"type":"string","enum":["new","learning","review","mastered"]}}}},"count":{"type":"integer"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/flashcards/review":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"orgReviewFlashcard","tags":["flashcards"],"summary":"Registrar repaso de flashcard","description":"Envía la calidad del recuerdo y recalcula la programación SM-2 de la tarjeta.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["flashcard_id","quality"],"properties":{"flashcard_id":{"type":"integer"},"quality":{"type":"integer","minimum":0,"maximum":5,"description":"Calidad del recuerdo (0=fallo total, 5=perfecto)."}}}}}},"responses":{"200":{"description":"Programación actualizada de la tarjeta.","content":{"application/json":{"schema":{"type":"object","required":["flashcard_id","ease_factor","interval_days","repetitions","next_review","status"],"properties":{"flashcard_id":{"type":"integer"},"ease_factor":{"type":"number"},"interval_days":{"type":"integer"},"repetitions":{"type":"integer"},"next_review":{"type":"string","format":"date"},"status":{"type":"string","enum":["learning","review","mastered"]}}}}}},"400":{"description":"Petición inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/organizations/{org_id}/flashcards/stats":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgFlashcardStats","tags":["flashcards"],"summary":"Estadísticas de flashcards","description":"Devuelve el resumen de repaso del usuario autenticado y el desglose por curso.","responses":{"200":{"description":"Resumen agregado y desglose por curso.","content":{"application/json":{"schema":{"type":"object","required":["total","due_today","mastered","learning","review","per_course"],"properties":{"total":{"type":"integer"},"due_today":{"type":"integer"},"mastered":{"type":"integer"},"learning":{"type":"integer"},"review":{"type":"integer"},"per_course":{"type":"array","items":{"type":"object","required":["course_id","course_title","total_cards","due_cards","mastered_cards"],"properties":{"course_id":{"type":"string","format":"uuid"},"course_title":{"type":"string"},"total_cards":{"type":"integer"},"due_cards":{"type":"integer"},"mastered_cards":{"type":"integer"}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/games/leaderboard":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetGameLeaderboard","tags":["games"],"summary":"Ranking de juego","description":"Ranking por modo; con `mode=duel` devuelve la forma con estadísticas de duelos.","parameters":[{"name":"mode","in":"query","required":false,"description":"Modo del ranking.","schema":{"type":"string","default":"all","enum":["all","survival","speed","risk","duel"]}},{"name":"limit","in":"query","required":false,"description":"Máximo de entradas.","schema":{"type":"integer","minimum":1,"maximum":50,"default":10}},{"name":"cohort_id","in":"query","required":false,"description":"Solo el alumnado de esta cohorte de la organización (EDAPI-202); de otra organización → 404.","schema":{"type":["string","null"],"maxLength":36}}],"responses":{"200":{"description":"Ranking de jugadores (la forma con wins/losses aplica a `mode=duel`).","content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["leaderboard"],"properties":{"leaderboard":{"type":"array","items":{"type":"object","required":["rank","user_id","username","name","avatar","level","best_score","best_streak","total_sessions","avg_accuracy","total_xp"],"properties":{"rank":{"type":"integer"},"user_id":{"type":"string","format":"uuid"},"username":{"type":["string","null"]},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"},"best_score":{"type":"integer"},"best_streak":{"type":"integer"},"total_sessions":{"type":"integer"},"avg_accuracy":{"type":"number"},"total_xp":{"type":"integer"}}}}}},{"type":"object","description":"Forma para `mode=duel`.","required":["leaderboard"],"properties":{"leaderboard":{"type":"array","items":{"type":"object","required":["rank","user_id","username","name","avatar","level","wins","losses","draws","total_duels","win_rate","total_xp"],"properties":{"rank":{"type":"integer"},"user_id":{"type":"string","format":"uuid"},"username":{"type":["string","null"]},"name":{"type":"string"},"avatar":{"type":["string","null"]},"level":{"type":"integer"},"wins":{"type":"integer"},"losses":{"type":"integer"},"draws":{"type":"integer"},"total_duels":{"type":"integer"},"win_rate":{"type":"integer","minimum":0,"maximum":100},"total_xp":{"type":"integer"}}}}}}]}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/games/sessions/{sessionId}/answer":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"sessionId","in":"path","required":true,"description":"ID de la sesión de juego.","schema":{"type":"string"}}],"post":{"operationId":"orgSubmitGameAnswer","tags":["games"],"summary":"Responder pregunta","description":"Corrige la respuesta y devuelve el estado actualizado de la partida.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["question_id","answer"],"properties":{"question_id":{"type":"integer"},"answer":{"type":"integer","description":"Índice 0-based."},"bet_size":{"type":"string","description":"Requerido en modo risk.","enum":["small","medium","large"]}}}}}},"responses":{"200":{"description":"Estado de la partida tras la respuesta.","content":{"application/json":{"schema":{"type":"object","required":["correct","correct_answer","xp_change","streak","best_streak","score","lives","time_remaining","xp_pool","xp_earned","total_questions","game_over","game_over_reason","xp_awarded","new_badges","daily_xp_remaining"],"properties":{"correct":{"type":"boolean"},"correct_answer":{"type":"integer"},"xp_change":{"type":"integer"},"streak":{"type":"integer"},"best_streak":{"type":"integer"},"score":{"type":"integer"},"lives":{"type":["integer","null"]},"time_remaining":{"type":["integer","null"]},"xp_pool":{"type":"integer"},"xp_earned":{"type":"integer"},"total_questions":{"type":"integer"},"game_over":{"type":"boolean"},"game_over_reason":{"type":["string","null"],"enum":["no_lives","time_up","bankrupt",null]},"xp_awarded":{"type":"integer"},"new_badges":{"type":"array","items":{"$ref":"#/components/schemas/Badge"}},"daily_xp_remaining":{"type":"integer"}}}}}},"400":{"description":"Cuerpo inválido o falta bet_size en modo risk (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sesión no pertenece al usuario o ya finalizada (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/games/sessions/{sessionId}/cashout":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"sessionId","in":"path","required":true,"description":"ID de la sesión de juego.","schema":{"type":"string"}}],"post":{"operationId":"orgCashOutGame","tags":["games"],"summary":"Retirarse con las ganancias","description":"Retira el XP acumulado (modo riesgo) y cierra la sesión con el resumen final.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Resumen final de la partida.","content":{"application/json":{"schema":{"type":"object","required":["xp_earned","xp_awarded","daily_xp_remaining","new_badges","score","total_questions","streak","best_streak","duration"],"properties":{"xp_earned":{"type":"integer"},"xp_awarded":{"type":"integer"},"daily_xp_remaining":{"type":"integer"},"new_badges":{"type":"array","items":{"$ref":"#/components/schemas/Badge"}},"score":{"type":"integer"},"total_questions":{"type":"integer"},"streak":{"type":"integer"},"best_streak":{"type":"integer"},"duration":{"type":"integer"}}}}}},"400":{"description":"Sesión no retirable (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sesión no pertenece al usuario (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"}}}},"/api/v1/organizations/{org_id}/games/sessions/{sessionId}/complete":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"sessionId","in":"path","required":true,"description":"ID de la sesión de juego.","schema":{"type":"string"}}],"post":{"operationId":"orgCompleteGame","tags":["games"],"summary":"Finalizar partida","description":"Cierra la sesión, otorga XP y badges, y devuelve el resumen final.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"duration_seconds":{"type":"integer","minimum":0}}}}}},"responses":{"200":{"description":"Resumen final de la partida.","content":{"application/json":{"schema":{"type":"object","required":["xp_earned","xp_awarded","daily_xp_remaining","new_badges","score","total_questions","streak","best_streak","duration"],"properties":{"xp_earned":{"type":"integer"},"xp_awarded":{"type":"integer"},"daily_xp_remaining":{"type":"integer"},"new_badges":{"type":"array","items":{"$ref":"#/components/schemas/Badge"}},"score":{"type":"integer"},"total_questions":{"type":"integer"},"streak":{"type":"integer"},"best_streak":{"type":"integer"},"duration":{"type":"integer"}}}}}},"400":{"description":"Sesión no finalizable (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sesión no pertenece al usuario (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/games/sessions/{sessionId}/time":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"sessionId","in":"path","required":true,"description":"ID de la sesión de juego.","schema":{"type":"string"}}],"get":{"operationId":"orgGetGameTime","tags":["games"],"summary":"Tiempo restante autoritativo","description":"Tiempo restante autoritativo del servidor (solo modo velocidad); incluye resultados si la partida terminó.","responses":{"200":{"description":"Tiempo restante y, si terminó, el resumen final.","content":{"application/json":{"schema":{"type":"object","required":["time_remaining","game_over","results"],"properties":{"time_remaining":{"type":["integer","null"]},"game_over":{"type":"boolean"},"results":{"type":["object","null"],"description":"Resumen final si la partida terminó, o null.","properties":{"xp_earned":{"type":"integer"},"xp_awarded":{"type":"integer"},"daily_xp_remaining":{"type":"integer"},"new_badges":{"type":"array","items":{"$ref":"#/components/schemas/Badge"}},"score":{"type":"integer"},"total_questions":{"type":"integer"},"streak":{"type":"integer"},"best_streak":{"type":"integer"},"duration":{"type":"integer"}}}}}}}},"400":{"description":"Sesión no válida para consultar tiempo (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sesión no pertenece al usuario (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"}}}},"/api/v1/organizations/{org_id}/games/start":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"orgStartGame","tags":["games"],"summary":"Iniciar partida","description":"Crea una sesión de juego y devuelve las preguntas del modo elegido.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["game_mode"],"properties":{"game_mode":{"type":"string","enum":["survival","speed","risk"]}}}}}},"responses":{"201":{"description":"Sesión de juego creada.","content":{"application/json":{"schema":{"type":"object","required":["session_id","game_mode","questions","lives","time","xp_pool","questions_count"],"properties":{"session_id":{"type":"string"},"game_mode":{"type":"string","enum":["survival","speed","risk"]},"questions":{"type":"array","items":{"type":"object","required":["id","question","options","difficulty","topic_tag"],"properties":{"id":{"type":"integer"},"question":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"difficulty":{"type":"string","enum":["easy","medium","hard"]},"topic_tag":{"type":["string","null"]}}}},"lives":{"type":["integer","null"],"description":"Vidas iniciales (modo supervivencia), o null."},"time":{"type":["integer","null"],"description":"Segundos iniciales (modo velocidad), o null."},"xp_pool":{"type":"integer"},"questions_count":{"type":"integer"}}}}}},"400":{"description":"Modo inválido o cuerpo mal formado (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"409":{"description":"No hay preguntas suficientes en los cursos de la organización (`game_questions_unavailable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno al crear la sesión (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/games/stats":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetGameStats","tags":["games"],"summary":"Estadísticas de juego","description":"Devuelve las estadísticas por modo y el consumo de XP diario del usuario.","responses":{"200":{"description":"Estadísticas por modo y XP diario.","content":{"application/json":{"schema":{"type":"object","required":["survival","speed","risk","all","daily_xp","daily_xp_remaining","daily_xp_cap"],"properties":{"survival":{"type":"object","required":["best_score","best_streak","best_xp_session","total_sessions","total_questions_answered","total_correct","total_xp_earned","avg_score","avg_accuracy"],"properties":{"best_score":{"type":"integer"},"best_streak":{"type":"integer"},"best_xp_session":{"type":"integer"},"total_sessions":{"type":"integer"},"total_questions_answered":{"type":"integer"},"total_correct":{"type":"integer"},"total_xp_earned":{"type":"integer"},"avg_score":{"type":"number"},"avg_accuracy":{"type":"number"}}},"speed":{"type":"object","required":["best_score","best_streak","best_xp_session","total_sessions","total_questions_answered","total_correct","total_xp_earned","avg_score","avg_accuracy"],"properties":{"best_score":{"type":"integer"},"best_streak":{"type":"integer"},"best_xp_session":{"type":"integer"},"total_sessions":{"type":"integer"},"total_questions_answered":{"type":"integer"},"total_correct":{"type":"integer"},"total_xp_earned":{"type":"integer"},"avg_score":{"type":"number"},"avg_accuracy":{"type":"number"}}},"risk":{"type":"object","required":["best_score","best_streak","best_xp_session","total_sessions","total_questions_answered","total_correct","total_xp_earned","avg_score","avg_accuracy"],"properties":{"best_score":{"type":"integer"},"best_streak":{"type":"integer"},"best_xp_session":{"type":"integer"},"total_sessions":{"type":"integer"},"total_questions_answered":{"type":"integer"},"total_correct":{"type":"integer"},"total_xp_earned":{"type":"integer"},"avg_score":{"type":"number"},"avg_accuracy":{"type":"number"}}},"all":{"type":"object","required":["best_score","best_streak","best_xp_session","total_sessions","total_questions_answered","total_correct","total_xp_earned","avg_score","avg_accuracy"],"properties":{"best_score":{"type":"integer"},"best_streak":{"type":"integer"},"best_xp_session":{"type":"integer"},"total_sessions":{"type":"integer"},"total_questions_answered":{"type":"integer"},"total_correct":{"type":"integer"},"total_xp_earned":{"type":"integer"},"avg_score":{"type":"number"},"avg_accuracy":{"type":"number"}}},"daily_xp":{"type":"integer"},"daily_xp_remaining":{"type":"integer"},"daily_xp_cap":{"type":"integer"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"}}}},"/api/v1/organizations/{org_id}/goals":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgListGoals","tags":["gamification"],"summary":"Listar metas","description":"Devuelve las metas activas del usuario autenticado y los tipos disponibles.","responses":{"200":{"description":"Metas del usuario y tipos disponibles.","content":{"application/json":{"schema":{"type":"object","required":["goals","goal_types"],"properties":{"goals":{"type":"array","items":{"type":"object","required":["id","type","type_label","type_icon","target_value","current_value","percentage","period","period_label","description","started_at","deadline","completed_at","active","is_completed"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["courses_complete","lessons_complete","xp_earn","streak_days","games_played","exams_passed","flashcards_review"]},"type_label":{"type":"string"},"type_icon":{"type":["string","null"]},"target_value":{"type":"integer"},"current_value":{"type":"integer"},"percentage":{"type":"integer","minimum":0,"maximum":100},"period":{"type":"string","enum":["daily","weekly","monthly"]},"period_label":{"type":"string"},"description":{"type":"string"},"started_at":{"type":"string","format":"date"},"deadline":{"type":"string","format":"date"},"completed_at":{"type":["string","null"],"format":"date-time"},"active":{"type":"boolean"},"is_completed":{"type":"boolean"}}}},"goal_types":{"type":"array","items":{"type":"object","required":["id","label","icon"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"icon":{"type":["string","null"]}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"orgCreateGoal","tags":["gamification"],"summary":"Crear meta","description":"Crea una nueva meta para el usuario autenticado en la organización. Como mucho 5 metas activas a la vez: la sexta es 422 `goal_limit_reached`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","target"],"properties":{"type":{"type":"string","enum":["courses_complete","lessons_complete","xp_earn","streak_days","games_played","exams_passed","flashcards_review"]},"target":{"type":"integer","minimum":1},"period":{"type":"string","enum":["daily","weekly","monthly"],"default":"weekly"}}}}}},"responses":{"201":{"description":"Meta creada.","content":{"application/json":{"schema":{"type":"object","required":["goal"],"properties":{"goal":{"type":"object","required":["id","type","type_label","type_icon","target_value","current_value","percentage","period","period_label","description","started_at","deadline","completed_at","active","is_completed"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["courses_complete","lessons_complete","xp_earn","streak_days","games_played","exams_passed","flashcards_review"]},"type_label":{"type":"string"},"type_icon":{"type":["string","null"]},"target_value":{"type":"integer"},"current_value":{"type":"integer"},"percentage":{"type":"integer","minimum":0,"maximum":100},"period":{"type":"string","enum":["daily","weekly","monthly"]},"period_label":{"type":"string"},"description":{"type":"string"},"started_at":{"type":"string","format":"date"},"deadline":{"type":"string","format":"date"},"completed_at":{"type":["string","null"],"format":"date-time"},"active":{"type":"boolean"},"is_completed":{"type":"boolean"}}}}}}}},"400":{"description":"Datos inválidos (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/goals/history":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetGoalsHistory","tags":["gamification"],"summary":"Historial de metas","description":"Devuelve las metas completadas y expiradas del usuario autenticado.","responses":{"200":{"description":"Historial de metas.","content":{"application/json":{"schema":{"type":"object","required":["completed","expired"],"properties":{"completed":{"type":"array","items":{"type":"object","required":["id","type","type_label","type_icon","target_value","current_value","percentage","period","period_label","description","started_at","deadline","completed_at","active","is_completed"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["courses_complete","lessons_complete","xp_earn","streak_days","games_played","exams_passed","flashcards_review"]},"type_label":{"type":"string"},"type_icon":{"type":["string","null"]},"target_value":{"type":"integer"},"current_value":{"type":"integer"},"percentage":{"type":"integer","minimum":0,"maximum":100},"period":{"type":"string","enum":["daily","weekly","monthly"]},"period_label":{"type":"string"},"description":{"type":"string"},"started_at":{"type":"string","format":"date"},"deadline":{"type":"string","format":"date"},"completed_at":{"type":["string","null"],"format":"date-time"},"active":{"type":"boolean"},"is_completed":{"type":"boolean"}}}},"expired":{"type":"array","items":{"type":"object","required":["id","type","type_label","type_icon","target_value","current_value","percentage","period","period_label","description","started_at","deadline","completed_at","active","is_completed"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["courses_complete","lessons_complete","xp_earn","streak_days","games_played","exams_passed","flashcards_review"]},"type_label":{"type":"string"},"type_icon":{"type":["string","null"]},"target_value":{"type":"integer"},"current_value":{"type":"integer"},"percentage":{"type":"integer","minimum":0,"maximum":100},"period":{"type":"string","enum":["daily","weekly","monthly"]},"period_label":{"type":"string"},"description":{"type":"string"},"started_at":{"type":"string","format":"date"},"deadline":{"type":"string","format":"date"},"completed_at":{"type":["string","null"],"format":"date-time"},"active":{"type":"boolean"},"is_completed":{"type":"boolean"}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/goals/{id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la meta.","schema":{"type":"string"}}],"delete":{"operationId":"orgDeleteGoal","tags":["gamification"],"summary":"Eliminar meta","description":"Elimina una meta del usuario autenticado.","responses":{"200":{"description":"Meta eliminada.","content":{"application/json":{"schema":{"type":"object","required":["deleted","id"],"properties":{"deleted":{"type":"boolean"},"id":{"type":"string"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/leaderboard":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetLeaderboard","tags":["users"],"summary":"Ranking de XP de la organización","description":"Ranking de la organización (EDAPI-213): miembros activos sin opt-out, por XP total o de esta semana, con su nombre, username y pepo (el alias de menores se retiró con el ADR 0036). Con sesión añade la posición de quien llama (aunque tenga opt-out). Caché de 60 s por organización.","parameters":[{"name":"limit","in":"query","required":false,"description":"Número máximo de posiciones.","schema":{"type":"integer","default":10,"minimum":1,"maximum":200}},{"name":"period","in":"query","required":false,"description":"`all`: XP total; `week`: XP ganada esta semana EN la organización (desde el lunes de su zona horaria).","schema":{"type":"string","enum":["all","week"],"default":"all"}},{"name":"offset","in":"query","required":false,"description":"Posiciones que se saltan (paginación en servidor, EDFRO-123).","schema":{"type":"integer","default":0,"minimum":0,"maximum":10000}},{"name":"q","in":"query","required":false,"description":"Busca por nombre dentro del ranking (EDFRO-123). Quien sale con alias no aparece en una búsqueda. Las posiciones (`rank`) son las del ranking entero.","schema":{"type":["string","null"],"maxLength":100}},{"name":"X-Lang","in":"header","required":false,"description":"Sin efecto desde el ADR 0036 (era el idioma de los alias de menores); se acepta y se ignora (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Ranking global y, con sesión, la posición del usuario.","content":{"application/json":{"schema":{"type":"object","required":["leaderboard","user_position","total"],"properties":{"total":{"type":"integer","description":"Personas en el ranking (con `q`, las que casan). EDFRO-123."},"leaderboard":{"type":"array","items":{"type":"object","required":["rank","username","name","avatar","level","xp","badges"],"properties":{"rank":{"type":"integer"},"username":{"type":["string","null"]},"name":{"type":"string"},"avatar":{"type":["string","null"]},"user_id":{"type":["string","null"],"description":"Id de la persona para pintar su pepo (`/users/{id}/avatar.svg`). Desde el ADR 0036 siempre presente (el `null` de los alias de menores ya no se da)."},"level":{"type":"integer"},"xp":{"type":"integer"},"badges":{"type":"integer"}}}},"period":{"type":"string","enum":["all","week"],"description":"Qué XP ordena el ranking (`all` total, `week` de esta semana en la organización)."},"user_position":{"type":"object","description":"Posición del usuario autenticado; `null` si no hay sesión.","required":["rank","username","name","avatar","level","xp","badges"],"properties":{"rank":{"type":"integer"},"username":{"type":["string","null"]},"name":{"type":"string"},"avatar":{"type":["string","null"]},"user_id":{"type":["string","null"],"description":"Id de la persona para pintar su pepo (`/users/{id}/avatar.svg`). Desde el ADR 0036 siempre presente (el `null` de los alias de menores ya no se da)."},"level":{"type":"integer"},"xp":{"type":"integer"},"badges":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/learning-paths":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgListLearningPaths","x-tool":{"name":"list_learning_paths","domain":"learning","profile":"all","sensitive":false,"superficies":["mcp","tutor"]},"tags":["paths"],"summary":"Listar rutas de aprendizaje","description":"Devuelve las rutas de aprendizaje publicadas.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Lista de rutas de aprendizaje.","content":{"application/json":{"schema":{"type":"object","required":["paths"],"properties":{"paths":{"type":"array","items":{"type":"object","required":["id","title","description","slug","icon","difficulty","estimated_hours","order_index","active","course_count","total_xp","awards_certificate","credential_title","badge_id","created_at","enrolled","progress"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["string","null"]},"slug":{"type":"string"},"icon":{"type":["string","null"]},"difficulty":{"type":"string","enum":["beginner","intermediate","advanced"]},"estimated_hours":{"type":"integer"},"order_index":{"type":"integer"},"active":{"type":"boolean"},"course_count":{"type":"integer"},"total_xp":{"type":"integer"},"awards_certificate":{"type":"boolean"},"is_featured":{"type":"boolean","description":"Certificado Profesional insignia (destacado/recomendado)."},"credential_title":{"type":["string","null"]},"badge_id":{"type":["string","null"],"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"enrolled":{"type":"boolean"},"progress":{"type":["object","null"],"properties":{"percentage":{"type":"integer"},"completed_courses":{"type":"integer"},"total_courses":{"type":"integer"}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/learning-paths/{id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID o slug de la ruta de aprendizaje.","schema":{"type":"string"}}],"get":{"operationId":"orgGetLearningPath","tags":["paths"],"summary":"Obtener ruta de aprendizaje","description":"Devuelve una ruta de aprendizaje por ID o slug, con sus cursos.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Ruta de aprendizaje con sus cursos.","content":{"application/json":{"schema":{"type":"object","required":["id","title","description","slug","icon","difficulty","estimated_hours","order_index","active","course_count","total_xp","awards_certificate","credential_title","badge_id","created_at","enrolled","progress","courses","capstone_status"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["string","null"]},"slug":{"type":"string"},"icon":{"type":["string","null"]},"difficulty":{"type":"string","enum":["beginner","intermediate","advanced"]},"estimated_hours":{"type":"integer"},"order_index":{"type":"integer"},"active":{"type":"boolean"},"course_count":{"type":"integer"},"total_xp":{"type":"integer"},"awards_certificate":{"type":"boolean"},"is_featured":{"type":"boolean","description":"Certificado Profesional insignia (destacado/recomendado)."},"credential_title":{"type":["string","null"]},"badge_id":{"type":["string","null"],"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"enrolled":{"type":"boolean"},"progress":{"type":["object","null"],"properties":{"percentage":{"type":"integer"},"completed_courses":{"type":"integer"},"total_courses":{"type":"integer"}}},"courses":{"type":"array","description":"Cursos de la ruta, en orden.","items":{"type":"object","required":["id","title","slug","level","duration","xp","order_index","is_required","lessons_count","user_progress","user_completed_at","completed_lessons"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"level":{"type":"string"},"duration":{"type":["string","null"]},"xp":{"type":"integer"},"order_index":{"type":"integer"},"is_required":{"type":"boolean"},"lessons_count":{"type":"integer"},"user_progress":{"type":["integer","null"]},"user_completed_at":{"type":["string","null"],"format":"date-time"},"completed_lessons":{"type":["integer","null"]}}}},"capstone_status":{"type":["object","null"],"description":"Estado del capstone si `awards_certificate` y hay sesión."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/learning-paths/{id}/capstone":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la ruta de aprendizaje.","schema":{"type":"string"}}],"get":{"operationId":"orgGetCapstoneStatus","tags":["paths"],"summary":"Estado del capstone","description":"Devuelve el estado del examen capstone de la ruta para el usuario autenticado.","responses":{"200":{"description":"Estado del capstone y prerrequisitos.","content":{"application/json":{"schema":{"type":"object","required":["state","prereq","has_capstone","capstone","passed","has_certificate","credential_title"],"properties":{"state":{"type":"string","enum":["locked","unlocked","passed","earned"]},"prereq":{"type":"object","required":["met","have","total","missing"],"properties":{"met":{"type":"boolean"},"have":{"type":"integer"},"total":{"type":"integer"},"missing":{"type":"array","items":{"type":"string"}}}},"has_capstone":{"type":"boolean"},"capstone":{"type":["object","null"],"properties":{"exam_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"passing_score":{"type":"integer"},"time_limit_minutes":{"type":"integer"},"questions_count":{"type":"integer"},"max_attempts":{"type":"integer"},"attempts_used":{"type":"integer"}}},"passed":{"type":"boolean"},"has_certificate":{"type":"boolean"},"credential_title":{"type":["string","null"]}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/learning-paths/{id}/capstone/start":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la ruta de aprendizaje.","schema":{"type":"string"}}],"post":{"operationId":"orgStartCapstoneExam","tags":["paths"],"summary":"Iniciar examen capstone","description":"Inicia o reanuda la sesión del examen capstone de la ruta.","responses":{"200":{"description":"Sesión de examen reanudada.","content":{"application/json":{"schema":{"type":"object","required":["session_id","questions_count","expires_at","time_limit_minutes","passing_score","tab_switches","max_tab_switches","resumed","question_ids","answered_ids"],"properties":{"session_id":{"type":"string"},"questions_count":{"type":"integer"},"expires_at":{"type":"string","format":"date-time"},"time_limit_minutes":{"type":"integer"},"passing_score":{"type":"integer"},"tab_switches":{"type":"integer"},"max_tab_switches":{"type":"integer"},"resumed":{"type":"boolean"},"question_ids":{"description":"Ids (UUID) de las preguntas de la sesión, en el orden en que se presentan.","type":"array","items":{"type":"string","format":"uuid"}},"answered_ids":{"description":"Ids (UUID) de las preguntas ya respondidas, subconjunto de `question_ids` (no posiciones).","type":"array","items":{"type":"string","format":"uuid"}}}}}}},"201":{"description":"Nueva sesión de examen iniciada.","content":{"application/json":{"schema":{"type":"object","required":["session_id","questions_count","expires_at","time_limit_minutes","passing_score","tab_switches","max_tab_switches","resumed"],"properties":{"session_id":{"type":"string"},"questions_count":{"type":"integer"},"expires_at":{"type":"string","format":"date-time"},"time_limit_minutes":{"type":"integer"},"passing_score":{"type":"integer"},"tab_switches":{"type":"integer"},"max_tab_switches":{"type":"integer"},"resumed":{"type":"boolean"}}}}}},"400":{"description":"Solicitud inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Prohibido (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/learning-paths/{id}/enroll":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la ruta de aprendizaje.","schema":{"type":"string"}}],"post":{"operationId":"orgEnrollLearningPath","tags":["paths"],"summary":"Inscribirse en una ruta","description":"Inscribe al usuario autenticado en la ruta de aprendizaje y lo matricula en cascada en sus cursos REQUERIDOS (origen `program`; los opcionales no). Idempotente: repetir da 200 y repasa la cascada. Con la automatrícula apagada en la organización → 403 `self_enroll_disabled`; sin plaza de aprendiz en el plan → 403 `plan_limit_reached`. En los dos casos no queda nada a medias (EDAPI-100).","responses":{"200":{"description":"El usuario ya estaba inscrito.","content":{"application/json":{"schema":{"type":"object","required":["enrolled","message"],"properties":{"enrolled":{"type":"boolean"},"message":{"type":"string"}}}}}},"201":{"description":"Nueva inscripción creada.","content":{"application/json":{"schema":{"type":"object","required":["enrolled","path_id","started_at"],"properties":{"enrolled":{"type":"boolean"},"path_id":{"type":"string","format":"uuid"},"started_at":{"type":"string","format":"date-time"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/me/activity":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"orgRegisterActivity","tags":["users"],"summary":"Registrar actividad","description":"Registra actividad del día y actualiza la racha del usuario.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Racha actualizada.","content":{"application/json":{"schema":{"type":"object","required":["streak","last_activity"],"properties":{"streak":{"type":"integer"},"last_activity":{"type":"string","format":"date-time"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/me/badges":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgMyBadges","x-tool":{"name":"list_my_badges","domain":"learning","profile":"core","sensitive":false,"superficies":["mcp","tutor"]},"tags":["gamification"],"summary":"Mis insignias","description":"Devuelve las insignias obtenidas por el usuario autenticado.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Insignias del usuario.","content":{"application/json":{"schema":{"type":"object","required":["badges","total"],"properties":{"badges":{"type":"array","items":{"$ref":"#/components/schemas/Badge"}},"total":{"type":"integer"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/me/challenges":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgMyChallenges","tags":["gamification"],"summary":"Mis retos","description":"Devuelve los retos del usuario autenticado con su progreso.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Retos del usuario con progreso.","content":{"application/json":{"schema":{"type":"object","required":["challenges"],"properties":{"challenges":{"type":"array","items":{"type":"object","required":["challenge","progress"],"properties":{"challenge":{"type":"object","required":["id","title","description","type","target","min_score","target_course_id","xp_reward","badge_reward","start_date","end_date","active"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"]},"type":{"type":"string","enum":["lessons","quizzes","streak","course","xp","courses_completed","quiz_perfect","level","lessons_week"]},"target":{"type":"integer"},"min_score":{"type":["integer","null"]},"target_course_id":{"type":["string","null"],"format":"uuid"},"xp_reward":{"type":"integer"},"badge_reward":{"type":["string","null"]},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"active":{"type":"boolean"},"user_progress":{"type":["object","null"],"properties":{"current":{"type":"integer"},"target":{"type":"integer"},"percentage":{"type":"integer"},"completed":{"type":"boolean"},"claimed":{"type":"boolean"}}}}},"progress":{"type":"object","required":["current","target","percentage","completed"],"properties":{"current":{"type":"integer"},"target":{"type":"integer"},"percentage":{"type":"integer"},"completed":{"type":"boolean"}}}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/me/challenges/{id}/complete":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del reto.","schema":{"type":"string"}}],"post":{"operationId":"orgCompleteChallenge","tags":["gamification"],"summary":"Completar reto","description":"Reclama las recompensas de un reto completado por el usuario. Una sola vez: la segunda reclamación, también si llega a la vez, es 409 `challenge_already_completed` y no suma XP.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Reto completado con recompensas otorgadas.","content":{"application/json":{"schema":{"type":"object","required":["message","rewards","user"],"properties":{"message":{"type":"string"},"rewards":{"type":"object","required":["xp","badge"],"properties":{"xp":{"type":"integer"},"badge":{"description":"Insignia otorgada, o null.","oneOf":[{"$ref":"#/components/schemas/Badge"},{"type":"null"}]}}},"user":{"type":"object","required":["xp","level","leveled_up"],"properties":{"xp":{"type":"integer"},"level":{"type":"integer"},"leveled_up":{"type":"boolean"}}}}}}}},"400":{"description":"Reto no completable (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"El reto ya estaba reclamado; no suma XP otra vez (`challenge_already_completed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/me/cohort":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetMyCohort","tags":["cohorts"],"summary":"Mi cohorte","description":"Devuelve la cohorte del usuario autenticado con su top-5, o `null`.","responses":{"200":{"description":"Cohorte del usuario.","content":{"application/json":{"schema":{"type":"object","required":["cohort"],"properties":{"cohort":{"type":["object","null"],"description":"Cohorte del usuario con top-5, o `null` si ninguna.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"institute_id":{"type":"string","format":"uuid"},"institute_name":{"type":["string","null"]},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"active":{"type":"boolean"},"students_count":{"type":"integer"},"leaderboard":{"type":"array","items":{"type":"object","required":["rank","username","name","avatar","level","xp","badges"],"properties":{"rank":{"type":"integer"},"username":{"type":["string","null"]},"name":{"type":"string"},"avatar":{"type":["string","null"]},"user_id":{"type":["string","null"],"description":"Id de la persona para pintar su pepo (`/users/{id}/avatar.svg`). Desde el ADR 0036 siempre presente (el `null` de los alias de menores ya no se da)."},"level":{"type":"integer"},"xp":{"type":"integer"},"badges":{"type":"integer"}}}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/me/gamification":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getMyGamification","x-tool":{"name":"get_my_gamification","domain":"learning","profile":"core","sensitive":false,"superficies":["mcp","tutor"]},"tags":["gamification"],"summary":"Mi XP, nivel y racha en la organización","description":"Resumen de gamificación de la persona autenticada en esta organización: XP y nivel con sus umbrales (250 XP por nivel, ADR 0030), racha vigente en el día de la organización e interruptores que le aplican. Nace con organización (no tiene ruta heredada). Responde aunque la gamificación esté apagada (`gamification_enabled: false`) para que el cliente sepa qué ocultar.","responses":{"200":{"description":"Resumen de gamificación.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyGamification"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"operationId":"updateMyGamification","tags":["gamification"],"summary":"Cambiar mis preferencias de gamificación","description":"Preferencias de gamificación de la persona autenticada (EDAPI-213): `leaderboard_opt_out` la saca de los rankings ante los demás (sigue viendo su posición). Vale en todas sus organizaciones. Devuelve el resumen actualizado. Responde aunque la gamificación esté apagada.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyGamificationPreferences"}}}},"responses":{"200":{"description":"Resumen de gamificación actualizado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyGamification"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/me/courses":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetMyCourses","x-tool":{"name":"list_my_courses","domain":"learning","profile":"core","sensitive":false,"superficies":["mcp","tutor"]},"tags":["users"],"summary":"Mis cursos","description":"Devuelve los cursos en progreso y completados del usuario autenticado.","responses":{"200":{"description":"Cursos del usuario.","content":{"application/json":{"schema":{"type":"object","required":["in_progress","completed"],"properties":{"in_progress":{"type":"array","items":{"type":"object","required":["course_id","course","progress","completed_lessons","total_lessons","started_at","completed_at"],"properties":{"course_id":{"type":"string","format":"uuid"},"course":{"$ref":"#/components/schemas/Course"},"progress":{"type":"integer","minimum":0,"maximum":100},"completed_lessons":{"type":"integer"},"total_lessons":{"type":"integer"},"started_at":{"type":["string","null"],"format":"date"},"completed_at":{"type":["string","null"],"format":"date"}}}},"completed":{"type":"array","items":{"type":"object","required":["course_id","course","progress","completed_lessons","total_lessons","started_at","completed_at"],"properties":{"course_id":{"type":"string","format":"uuid"},"course":{"$ref":"#/components/schemas/Course"},"progress":{"type":"integer","minimum":0,"maximum":100},"completed_lessons":{"type":"integer"},"total_lessons":{"type":"integer"},"started_at":{"type":["string","null"],"format":"date"},"completed_at":{"type":["string","null"],"format":"date"}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/me/learning-paths":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetMyLearningPaths","tags":["paths"],"summary":"Mis rutas de aprendizaje","description":"Devuelve las rutas en las que el usuario autenticado está inscrito.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Rutas inscritas del usuario con su progreso.","content":{"application/json":{"schema":{"type":"object","required":["paths"],"properties":{"paths":{"type":"array","items":{"type":"object","required":["id","title","description","slug","icon","difficulty","estimated_hours","order_index","active","course_count","total_xp","awards_certificate","credential_title","badge_id","created_at","enrolled","progress","enrolled_at","completed_at"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["string","null"]},"slug":{"type":"string"},"icon":{"type":["string","null"]},"difficulty":{"type":"string","enum":["beginner","intermediate","advanced"]},"estimated_hours":{"type":"integer"},"order_index":{"type":"integer"},"active":{"type":"boolean"},"course_count":{"type":"integer"},"total_xp":{"type":"integer"},"awards_certificate":{"type":"boolean"},"is_featured":{"type":"boolean","description":"Certificado Profesional insignia (destacado/recomendado)."},"credential_title":{"type":["string","null"]},"badge_id":{"type":["string","null"],"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"enrolled":{"type":"boolean"},"enrolled_at":{"type":"string","format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"progress":{"type":"object","required":["percentage","completed_courses","total_courses"],"properties":{"percentage":{"type":"integer"},"completed_courses":{"type":"integer"},"total_courses":{"type":"integer"}}}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/me/progress":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetMyProgress","x-tool":{"name":"get_my_progress","domain":"learning","profile":"core","sensitive":false,"superficies":["mcp","tutor"]},"tags":["progress"],"summary":"Mi progreso global","description":"Devuelve el progreso del usuario autenticado en todos sus cursos y estadísticas agregadas.","responses":{"200":{"description":"Progreso por curso y estadísticas del usuario.","content":{"application/json":{"schema":{"type":"object","required":["progress","stats"],"properties":{"progress":{"type":"array","items":{"type":"object","required":["user_id","course_id","course_title","completed_lessons","current_lesson","progress","total_lessons","started_at","completed_at","quiz_results"],"properties":{"user_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"course_title":{"type":"string"},"completed_lessons":{"type":"array","description":"Números de lección completados.","items":{"type":"integer"}},"current_lesson":{"type":["integer","null"]},"progress":{"type":"integer","minimum":0,"maximum":100},"total_lessons":{"type":"integer"},"started_at":{"type":["string","null"],"format":"date"},"completed_at":{"type":["string","null"],"format":"date"},"quiz_results":{"type":"object","additionalProperties":true,"description":"Mapa quiz_id -> {score,passed,completed_at,attempts}."}}}},"stats":{"type":"object","required":["total_courses","completed_courses","in_progress_courses","total_lessons_completed","total_quizzes_passed","average_progress","average_quiz_score"],"properties":{"total_courses":{"type":"integer"},"completed_courses":{"type":"integer"},"in_progress_courses":{"type":"integer"},"total_lessons_completed":{"type":"integer"},"total_quizzes_passed":{"type":"integer"},"average_progress":{"type":"integer","minimum":0,"maximum":100},"average_quiz_score":{"type":"integer","minimum":0,"maximum":100}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/me/progress/{courseId}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"courseId","in":"path","required":true,"description":"Slug o ID del curso.","schema":{"type":"string"}}],"get":{"operationId":"orgGetProgressByCourse","x-tool":{"name":"get_my_course_progress","domain":"learning","profile":"core","sensitive":false,"superficies":["mcp","tutor"]},"tags":["progress"],"summary":"Progreso en un curso","description":"Devuelve el progreso del usuario autenticado en un curso concreto.","responses":{"200":{"description":"Progreso del usuario en el curso.","content":{"application/json":{"schema":{"type":"object","required":["course_id","course_title","progress","completed_lessons","total_lessons","current_lesson","started_at","completed_at","quiz_results"],"properties":{"course_id":{"type":"string"},"course_title":{"type":"string"},"progress":{"type":"integer","minimum":0,"maximum":100},"completed_lessons":{"type":"array","description":"Números de lección completados.","items":{"type":"integer"}},"total_lessons":{"type":"integer"},"current_lesson":{"type":["integer","null"]},"started_at":{"type":["string","null"],"format":"date"},"completed_at":{"type":["string","null"],"format":"date"},"quiz_results":{"type":"object","additionalProperties":true,"description":"Mapa quiz_id -> resultado del quiz."}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Curso no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/me/progress/{courseId}/lessons/{lessonId}/complete":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"courseId","in":"path","required":true,"description":"Slug o ID del curso.","schema":{"type":"string"}},{"name":"lessonId","in":"path","required":true,"description":"Número de la lección.","schema":{"type":"integer","minimum":1}}],"post":{"operationId":"orgCompleteLesson","x-tool":{"name":"complete_lesson","domain":"learning","profile":"all","sensitive":false},"tags":["progress"],"summary":"Completar lección","description":"Marca una lección como completada, actualiza el progreso y otorga XP/insignias.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["time_spent"],"properties":{"time_spent":{"type":"integer","minimum":30,"description":"Tiempo dedicado en segundos."},"scroll_depth":{"type":"integer","minimum":0,"maximum":100,"description":"Porcentaje de scroll de la lección."}}}}}},"responses":{"200":{"description":"Lección completada; progreso y recompensas actualizados.","content":{"application/json":{"schema":{"type":"object","required":["message","progress","xp_earned","new_badges","course_completed"],"properties":{"message":{"type":"string"},"progress":{"type":"object","required":["user_id","course_id","completed_lessons","current_lesson","progress","started_at","completed_at","quiz_results"],"properties":{"user_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"completed_lessons":{"type":"array","description":"Números de lección completados.","items":{"type":"integer"}},"current_lesson":{"type":["integer","null"]},"progress":{"type":"integer","minimum":0,"maximum":100},"started_at":{"type":["string","null"],"format":"date"},"completed_at":{"type":["string","null"],"format":"date"},"quiz_results":{"type":"object","additionalProperties":true,"description":"Mapa quiz_id -> {score,passed,completed_at,attempts}."}}},"xp_earned":{"type":"integer","minimum":0},"new_badges":{"type":"array","items":{"$ref":"#/components/schemas/Badge"}},"course_completed":{"type":"boolean"}}}}}},"400":{"description":"Cuerpo inválido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Curso o lección no encontrados (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"description":"Demasiadas peticiones (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/me/progress/{courseId}/quiz/{quizId}/submit":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"courseId","in":"path","required":true,"description":"Slug o ID del curso.","schema":{"type":"string"}},{"name":"quizId","in":"path","required":true,"description":"UUID o ID del quiz.","schema":{"type":"string"}}],"post":{"operationId":"orgSubmitQuiz","tags":["progress"],"summary":"Enviar quiz","description":"Corrige las respuestas del quiz, registra el resultado y otorga XP/insignias.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["answers"],"properties":{"answers":{"type":"array","minItems":1,"description":"Índices de respuesta (base 0), uno por pregunta; -1 = sin responder (cuenta como fallo).","items":{"type":["integer","string"]}}}}}}},"responses":{"200":{"description":"Quiz corregido; resultado y recompensas.","content":{"application/json":{"schema":{"type":"object","required":["result","xp_earned","new_badges"],"properties":{"result":{"type":"object","required":["score","passed","correct_answers","total_questions"],"properties":{"score":{"type":"integer","minimum":0,"maximum":100},"passed":{"type":"boolean"},"correct_answers":{"type":["integer","null"]},"total_questions":{"type":["integer","null"]}}},"xp_earned":{"type":"integer","minimum":0},"new_badges":{"type":"array","items":{"$ref":"#/components/schemas/Badge"}}}}}}},"400":{"description":"Cuerpo inválido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Curso o quiz no encontrados (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"description":"Demasiadas peticiones (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/me/progress/{courseId}/start":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"courseId","in":"path","required":true,"description":"Slug o ID del curso.","schema":{"type":"string"}}],"post":{"operationId":"orgStartCourse","x-tool":{"name":"start_course","domain":"learning","profile":"all","sensitive":false,"superficies":["mcp","tutor"]},"tags":["progress"],"summary":"Iniciar curso","description":"Inscribe al usuario en el curso y crea su registro de progreso (idempotente).","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"El curso ya estaba iniciado; se devuelve el progreso existente.","content":{"application/json":{"schema":{"type":"object","required":["message","progress"],"properties":{"message":{"type":"string"},"progress":{"type":"object","required":["user_id","course_id","completed_lessons","current_lesson","progress","started_at","completed_at","quiz_results"],"properties":{"user_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"completed_lessons":{"type":"array","description":"Números de lección completados.","items":{"type":"integer"}},"current_lesson":{"type":["integer","null"]},"progress":{"type":"integer","minimum":0,"maximum":100},"started_at":{"type":["string","null"],"format":"date"},"completed_at":{"type":["string","null"],"format":"date"},"quiz_results":{"type":"object","additionalProperties":true,"description":"Mapa quiz_id -> {score,passed,completed_at,attempts}."}}}}}}}},"201":{"description":"Nueva inscripción creada.","content":{"application/json":{"schema":{"type":"object","required":["message","progress"],"properties":{"message":{"type":"string"},"progress":{"type":"object","required":["user_id","course_id","completed_lessons","current_lesson","progress","started_at","completed_at","quiz_results"],"properties":{"user_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"completed_lessons":{"type":"array","description":"Números de lección completados.","items":{"type":"integer"}},"current_lesson":{"type":["integer","null"]},"progress":{"type":"integer","minimum":0,"maximum":100},"started_at":{"type":["string","null"],"format":"date"},"completed_at":{"type":["string","null"],"format":"date"},"quiz_results":{"type":"object","additionalProperties":true,"description":"Mapa quiz_id -> {score,passed,completed_at,attempts}."}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Curso no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/me/stats/advanced":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetUserMeStatsAdvanced","tags":["stats"],"summary":"Estadísticas avanzadas del usuario","description":"Devuelve mapa de calor, tiempo de estudio, progreso por curso e histórico del usuario autenticado.","responses":{"200":{"description":"Estadísticas avanzadas del usuario autenticado.","content":{"application/json":{"schema":{"type":"object","required":["activity_heatmap","weekly_study_time","course_progress","streak_history","top_categories","rank_history","summary"],"properties":{"activity_heatmap":{"type":"array","items":{"type":"object","required":["date","count"],"properties":{"date":{"type":"string","format":"date"},"count":{"type":"integer"}}}},"weekly_study_time":{"type":"array","items":{"type":"object","required":["week","minutes"],"properties":{"week":{"type":"string"},"minutes":{"type":"integer"}}}},"course_progress":{"type":"array","items":{"type":"object","required":["course_id","title","progress_pct","lessons_done","total_lessons"],"properties":{"course_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"progress_pct":{"type":"integer"},"lessons_done":{"type":"integer"},"total_lessons":{"type":"integer"}}}},"streak_history":{"type":"object","required":["current","longest","total_active_days"],"properties":{"current":{"type":"integer"},"longest":{"type":"integer"},"total_active_days":{"type":"integer"}}},"top_categories":{"type":"array","items":{"type":"object","required":["category","hours","percentage"],"properties":{"category":{"type":"string"},"hours":{"type":"number"},"percentage":{"type":"integer"}}}},"rank_history":{"type":"array","items":{"type":"object","required":["date","rank"],"properties":{"date":{"type":"string","format":"date"},"rank":{"type":"integer"}}}},"summary":{"type":"object","required":["total_study_minutes","total_lessons","avg_daily_xp","courses_completed","current_rank"],"properties":{"total_study_minutes":{"type":"integer"},"total_lessons":{"type":"integer"},"avg_daily_xp":{"type":"integer"},"courses_completed":{"type":"integer"},"current_rank":{"type":["integer","null"]}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/me/streak":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetStreak","tags":["users"],"summary":"Racha propia","description":"Devuelve la racha de días activos del usuario autenticado.","responses":{"200":{"description":"Racha del usuario.","content":{"application/json":{"schema":{"type":"object","required":["streak","last_activity"],"properties":{"streak":{"type":"integer"},"last_activity":{"type":["string","null"],"format":"date-time"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/missions/history":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetMissionsHistory","tags":["gamification"],"summary":"Historial de misiones","description":"Devuelve el historial de misiones semanales del usuario autenticado.","parameters":[{"name":"limit","in":"query","required":false,"description":"Número máximo de semanas a devolver.","schema":{"type":"integer","minimum":1,"maximum":50,"default":20}},{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Historial de misiones por semana.","content":{"application/json":{"schema":{"type":"object","required":["weeks"],"properties":{"weeks":{"type":"array","items":{"type":"object","required":["week_start","missions"],"properties":{"week_start":{"type":"string","format":"date"},"missions":{"type":"array","items":{"type":"object","required":["id","mission_id","title","description","type","icon","target_value","current_value","progress","xp_reward","completed","claimed","completed_at","claimed_at"],"properties":{"id":{"type":"string"},"mission_id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"type":{"type":"string"},"icon":{"type":["string","null"]},"target_value":{"type":"integer"},"current_value":{"type":"integer"},"progress":{"type":"integer","minimum":0,"maximum":100},"xp_reward":{"type":"integer"},"completed":{"type":"boolean"},"claimed":{"type":"boolean"},"completed_at":{"type":["string","null"],"format":"date-time"},"claimed_at":{"type":["string","null"],"format":"date-time"}}}}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/missions/weekly":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetWeeklyMissions","tags":["gamification"],"summary":"Misiones semanales","description":"Devuelve las misiones de la semana actual del usuario autenticado.","responses":{"200":{"description":"Misiones semanales.","content":{"application/json":{"schema":{"type":"object","required":["week_start","week_end","missions","completed_count","claimed_count","total_xp_available"],"properties":{"week_start":{"type":"string","format":"date"},"week_end":{"type":"string","format":"date"},"missions":{"type":"array","items":{"type":"object","required":["id","mission_id","title","description","type","icon","target_value","current_value","progress","xp_reward","completed","claimed","completed_at","claimed_at"],"properties":{"id":{"type":"string"},"mission_id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"type":{"type":"string"},"icon":{"type":["string","null"]},"target_value":{"type":"integer"},"current_value":{"type":"integer"},"progress":{"type":"integer","minimum":0,"maximum":100},"xp_reward":{"type":"integer"},"completed":{"type":"boolean"},"claimed":{"type":"boolean"},"completed_at":{"type":["string","null"],"format":"date-time"},"claimed_at":{"type":["string","null"],"format":"date-time"}}}},"completed_count":{"type":"integer"},"claimed_count":{"type":"integer"},"total_xp_available":{"type":"integer"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}]}},"/api/v1/organizations/{org_id}/missions/{id}/claim":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la misión.","schema":{"type":"string"}}],"post":{"operationId":"orgClaimMission","tags":["gamification"],"summary":"Reclamar misión","description":"Reclama la recompensa de una misión semanal completada.","responses":{"201":{"description":"Recompensa de misión reclamada.","content":{"application/json":{"schema":{"type":"object","required":["reward","user"],"properties":{"reward":{"type":"object","required":["mission_id","title","xp_reward","claimed_at"],"properties":{"mission_id":{"type":"string"},"title":{"type":"string"},"xp_reward":{"type":"integer"},"claimed_at":{"type":"string","format":"date-time"}}},"user":{"type":"object","required":["xp","level","leveled_up"],"properties":{"xp":{"type":"integer"},"level":{"type":"integer"},"leveled_up":{"type":"boolean"}}}}}}}},"400":{"description":"Misión no reclamable (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"409":{"description":"La misión ya estaba reclamada; no suma XP otra vez (`mission_already_claimed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}]}},"/api/v1/organizations/{org_id}/notes":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgListNotes","x-tool":{"name":"list_my_notes","domain":"learning","profile":"core","sensitive":false,"superficies":["mcp","tutor"]},"tags":["notes"],"summary":"Listar notas","description":"Devuelve las notas del usuario autenticado, con filtro opcional por curso.","parameters":[{"name":"course","in":"query","required":false,"description":"Filtra las notas por ID de curso.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Número máximo de notas a devolver.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"offset","in":"query","required":false,"description":"Desplazamiento para paginación.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Lista de notas del usuario.","content":{"application/json":{"schema":{"type":"object","required":["notes"],"properties":{"notes":{"type":"array","items":{"type":"object","required":["id","user_id","course_id","lesson_id","content","created_at","updated_at"],"properties":{"id":{"type":"integer"},"user_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"lesson_id":{"type":"string","format":"uuid"},"content":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/notes/{courseId}/export":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"courseId","in":"path","required":true,"description":"ID del curso.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgExportNotes","tags":["notes"],"summary":"Exportar notas de un curso","description":"Exporta las notas de un curso para su descarga en markdown.","responses":{"200":{"description":"Notas del curso listas para exportar.","content":{"application/json":{"schema":{"type":"object","required":["course_id","notes","count"],"properties":{"course_id":{"type":"string","format":"uuid"},"notes":{"type":"array","items":{"type":"object","required":["lesson_id","content","updated_at"],"properties":{"lesson_id":{"type":"string","format":"uuid"},"content":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}}}},"count":{"type":"integer"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/notes/{courseId}/{lessonId}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"courseId","in":"path","required":true,"description":"ID del curso.","schema":{"type":"string","format":"uuid"}},{"name":"lessonId","in":"path","required":true,"description":"ID de la lección.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgGetNote","tags":["notes"],"summary":"Obtener nota de una lección","description":"Devuelve la nota del usuario para la lección indicada, o null si no existe.","responses":{"200":{"description":"Nota de la lección (o null si no hay).","content":{"application/json":{"schema":{"type":"object","required":["note","content"],"properties":{"note":{"oneOf":[{"type":"object","required":["id","user_id","course_id","lesson_id","content","created_at","updated_at"],"properties":{"id":{"type":"integer"},"user_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"lesson_id":{"type":"string","format":"uuid"},"content":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},{"type":"null"}]},"content":{"type":["string","null"]}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"orgSaveNote","x-tool":{"name":"save_note","domain":"learning","profile":"all","sensitive":false,"superficies":["mcp","tutor"]},"tags":["notes"],"summary":"Guardar nota de una lección","description":"Crea o actualiza la nota del usuario para la lección indicada.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","minLength":1}}}}}},"responses":{"200":{"description":"Nota guardada.","content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"type":"object","required":["id","user_id","course_id","lesson_id","content","created_at","updated_at"],"properties":{"id":{"type":"integer"},"user_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"lesson_id":{"type":"string","format":"uuid"},"content":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Petición inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"operationId":"orgDeleteNote","tags":["notes"],"summary":"Eliminar nota de una lección","description":"Elimina la nota del usuario para la lección indicada.","responses":{"200":{"description":"Nota eliminada.","content":{"application/json":{"schema":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"boolean"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/profiles/{username}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"username","in":"path","required":true,"description":"Nombre de usuario (`users.username`, slug a-z 0-9 y guiones).","schema":{"type":"string","minLength":1,"maxLength":50}}],"get":{"operationId":"orgGetPublicProfile","tags":["users"],"summary":"Perfil de un compañero por username","description":"Perfil de una persona de LA MISMA organización (EDAPI-92, EDFRO-182). Solo existe si la persona lo activó (`public_profile`, apagado de fábrica) y la organización tiene lo social encendido (si no, 404 `feature_disabled`). Una persona de otra organización, una cuenta desactivada o con la supresión pedida, un perfil sin activar o un bloqueo entre las dos personas responden 404 `not_found` igual que un username que no existe: no se confirma nada. La propia persona ve siempre el suyo (`is_self`), activado o no. Fuera de la organización no hay perfil en v1 (regla 26: nada público sin decisión de la institución). Solo datos que la persona ya enseña en la organización: nombre, username, titular, nivel y XP (null si la gamificación está apagada o si se sacó de los rankings), insignias, certificados emitidos en ESTA organización y LinkedIn solo si es `https://` de linkedin.com.","responses":{"200":{"description":"Perfil visible.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicProfile"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/programs":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgListPrograms","tags":["paths"],"summary":"Listar programas","description":"Rutas que otorgan Certificado Profesional.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Lista de programas con Certificado Profesional.","content":{"application/json":{"schema":{"type":"object","required":["programs"],"properties":{"programs":{"type":"array","items":{"type":"object","required":["id","title","description","slug","icon","difficulty","estimated_hours","order_index","active","course_count","total_xp","awards_certificate","credential_title","badge_id","created_at","enrolled","progress"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["string","null"]},"slug":{"type":"string"},"icon":{"type":["string","null"]},"difficulty":{"type":"string","enum":["beginner","intermediate","advanced"]},"estimated_hours":{"type":"integer"},"order_index":{"type":"integer"},"active":{"type":"boolean"},"course_count":{"type":"integer"},"total_xp":{"type":"integer"},"awards_certificate":{"type":"boolean"},"is_featured":{"type":"boolean","description":"Certificado Profesional insignia (destacado/recomendado)."},"credential_title":{"type":["string","null"]},"badge_id":{"type":["string","null"],"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"enrolled":{"type":"boolean"},"progress":{"type":["object","null"],"properties":{"percentage":{"type":"integer"},"completed_courses":{"type":"integer"},"total_courses":{"type":"integer"}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/quizzes/{id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID (UUID) del quiz.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgGetQuiz","tags":["courses"],"summary":"Obtener quiz","description":"Devuelve el quiz con sus preguntas y opciones. Las respuestas correctas y explicaciones se omiten deliberadamente.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Quiz con preguntas (sin respuestas correctas).","content":{"application/json":{"schema":{"type":"object","required":["id","course_id","lesson_id","title","passing_score","questions"],"properties":{"lang":{"type":"string","enum":["es","en"],"description":"Idioma en el que sale el contenido (X-Lang > Accept-Language > idioma de la persona > es)."},"fallback":{"type":"boolean","description":"Algún campo no tenía traducción servible y salió en el idioma base del curso."},"id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"lesson_id":{"type":"integer"},"title":{"type":"string"},"passing_score":{"type":"integer"},"questions":{"type":"array","items":{"type":"object","required":["id","question","options"],"properties":{"id":{"type":"integer","description":"Número de pregunta (`question_number`)."},"question":{"type":"string"},"options":{"type":"array","items":{"type":"string"}}}}},"previous_attempt":{"type":["object","null"],"properties":{"score":{"type":"integer"},"passed":{"type":"boolean"},"completed_at":{"type":["string","null"],"format":"date-time"},"attempts":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Quiz no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/rewards/daily/claim":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"orgClaimDailyReward","tags":["gamification"],"summary":"Reclamar recompensa diaria","description":"Reclama la recompensa diaria del usuario autenticado.","responses":{"201":{"description":"Recompensa diaria reclamada.","content":{"application/json":{"schema":{"type":"object","required":["reward","user"],"properties":{"reward":{"type":"object","required":["day","base_xp","xp_awarded","bonus_type","weekend_bonus","multiplier","badge_awarded","multiplier_awarded"],"properties":{"day":{"type":"integer","minimum":1,"maximum":7},"base_xp":{"type":"integer"},"xp_awarded":{"type":"integer"},"bonus_type":{"type":"string"},"weekend_bonus":{"type":"boolean"},"multiplier":{"type":"number"},"badge_awarded":{"type":["string","null"]},"multiplier_awarded":{"type":["object","null"],"properties":{"type":{"type":"string"},"value":{"type":"number"},"expires":{"type":"string","format":"date-time"}}}}},"user":{"type":"object","required":["xp","level","leveled_up"],"properties":{"xp":{"type":"integer"},"level":{"type":"integer"},"leveled_up":{"type":"boolean"}}}}}}}},"400":{"description":"Recompensa no reclamable (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"409":{"description":"La recompensa de hoy ya estaba reclamada (`daily_reward_already_claimed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/rewards/daily/status":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetDailyRewardStatus","tags":["gamification"],"summary":"Estado de recompensa diaria","description":"Devuelve el estado de la recompensa diaria del usuario autenticado.","responses":{"200":{"description":"Estado de la recompensa diaria.","content":{"application/json":{"schema":{"type":"object","required":["can_claim","current_day","streak_count","today_reward","is_weekend","week_history","active_multipliers","next_claim_at"],"properties":{"can_claim":{"type":"boolean"},"current_day":{"type":"integer","minimum":1,"maximum":7},"streak_count":{"type":"integer"},"today_reward":{"type":"object","required":["xp","bonus_type"],"properties":{"xp":{"type":"integer"},"bonus_type":{"type":"string","enum":["xp","multiplier","badge"]}}},"next_claim_at":{"type":"string","format":"date-time","description":"Cuándo se abre la siguiente recompensa: la próxima medianoche del día de la organización (`organizations.timezone`), en UTC. EDFRO-153."},"is_weekend":{"type":"boolean"},"week_history":{"type":"array","items":{"type":"object","required":["day","claimed","xp","bonus_type","current"],"properties":{"day":{"type":"integer"},"claimed":{"type":"boolean"},"xp":{"type":"integer"},"bonus_type":{"type":"string"},"current":{"type":"boolean"}}}},"active_multipliers":{"type":"array","items":{"type":"object","required":["id","type","value","expires"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["streak","weekend","event"]},"value":{"type":"number"},"expires":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/social/feed":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetSocialFeed","tags":["social"],"summary":"Feed social","description":"Devuelve el feed social con reacciones agregadas y la reacción del usuario.","parameters":[{"name":"page","in":"query","required":false,"description":"Número de página.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"description":"Número máximo de eventos por página.","schema":{"type":"integer","minimum":1,"maximum":50,"default":20}},{"name":"before","in":"query","required":false,"description":"Cursor: id de la última entrada ya vista (`pagination.next_cursor`). Con él se ignora `page` y no hay duplicados al paginar.","schema":{"type":["integer","null"],"minimum":1}},{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Eventos del feed social con paginación.","content":{"application/json":{"schema":{"type":"object","required":["feed","pagination"],"properties":{"feed":{"type":"array","items":{"type":"object","required":["id","user_id","user_name","user_avatar","event_type","event_text","event_data","visibility","created_at","reactions","reactions_total","my_reaction","is_mine"],"properties":{"id":{"type":"integer"},"user_id":{"type":"string","format":"uuid"},"user_name":{"type":["string","null"]},"user_avatar":{"type":["string","null"],"format":"uri"},"event_type":{"type":"string"},"event_text":{"type":"string"},"event_data":{"type":"object","additionalProperties":true},"visibility":{"type":"string","enum":["public","friends","private"]},"created_at":{"type":"string","format":"date-time"},"reactions":{"type":"object","required":["fire","clap","star","heart"],"properties":{"fire":{"type":"integer"},"clap":{"type":"integer"},"star":{"type":"integer"},"heart":{"type":"integer"}}},"reactions_total":{"type":"integer"},"my_reaction":{"type":["string","null"]},"is_mine":{"type":"boolean","description":"La entrada es de quien pregunta: puede ocultarla y no denunciarla."}}}},"pagination":{"type":"object","required":["page","limit","total","has_more","next_cursor"],"properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"has_more":{"type":"boolean"},"next_cursor":{"type":["integer","null"],"description":"Cursor de la página siguiente (`before`); null si no hay más."}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/social/feed/user/{userId}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"userId","in":"path","required":true,"description":"ID del usuario.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgGetUserSocialFeed","tags":["social"],"summary":"Feed social de un usuario","description":"Devuelve los eventos públicos del feed social de un usuario concreto.","parameters":[{"name":"limit","in":"query","required":false,"description":"Número máximo de eventos a devolver.","schema":{"type":"integer","minimum":1,"maximum":50,"default":20}},{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Eventos del feed social del usuario.","content":{"application/json":{"schema":{"type":"object","required":["feed"],"properties":{"feed":{"type":"array","items":{"type":"object","required":["id","user_id","user_name","user_avatar","event_type","event_text","event_data","visibility","created_at"],"properties":{"id":{"type":"integer"},"user_id":{"type":"string","format":"uuid"},"user_name":{"type":["string","null"]},"user_avatar":{"type":["string","null"],"format":"uri"},"event_type":{"type":"string"},"event_text":{"type":"string"},"event_data":{"type":"object","additionalProperties":true},"visibility":{"type":"string","enum":["public","friends","private"]},"created_at":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/social/feed/{id}/react":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del evento del feed social.","schema":{"type":"integer"}}],"post":{"operationId":"orgReactToSocialFeedItem","tags":["social"],"summary":"Reaccionar a un evento del feed","description":"Añade, cambia o quita la reacción del usuario sobre un evento del feed social.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reaction":{"type":"string","enum":["fire","clap","star","heart"],"default":"fire"}}}}}},"responses":{"200":{"description":"Resultado de la reacción con el recuento actualizado.","content":{"application/json":{"schema":{"type":"object","required":["action","reaction","reactions","total"],"properties":{"action":{"type":"string","enum":["added","changed","removed"]},"reaction":{"type":"string","enum":["fire","clap","star","heart"]},"reactions":{"type":"object","required":["fire","clap","star","heart"],"properties":{"fire":{"type":"integer"},"clap":{"type":"integer"},"star":{"type":"integer"},"heart":{"type":"integer"}}},"total":{"type":"integer"}}}}}},"400":{"description":"Petición inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}]}},"/api/v1/organizations/{org_id}/social/feed/{id}/hide":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"Id público (entero) de la entrada del feed.","schema":{"type":"integer"}}],"post":{"operationId":"orgHideSocialFeedItem","tags":["social"],"summary":"Ocultar una entrada propia del feed","description":"Quien protagoniza la entrada la retira del feed de su organización. No se deshace desde aquí. Una entrada ajena → 403; de otra organización, oculta o de alguien bloqueado → 404. Función `social` (A9, EDFRO-143).","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Entrada oculta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialFeedHide"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"}}}},"/api/v1/organizations/{org_id}/stats/activity":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetStatsActivity","tags":["stats"],"summary":"Estadísticas de actividad","description":"Devuelve la actividad de estudiantes y la distribución por nivel.","responses":{"200":{"description":"Métricas de actividad de la plataforma.","content":{"application/json":{"schema":{"type":"object","required":["active_today","active_this_week","total_students","level_distribution"],"properties":{"active_today":{"type":"integer"},"active_this_week":{"type":"integer"},"total_students":{"type":"integer"},"level_distribution":{"type":"object","description":"nivel -> nº de usuarios","additionalProperties":{"type":"integer"}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/stats/courses":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetStatsCourses","tags":["stats"],"summary":"Estadísticas por curso","description":"Devuelve las métricas de matriculación y finalización por curso.","responses":{"200":{"description":"Métricas por curso.","content":{"application/json":{"schema":{"type":"object","required":["courses"],"properties":{"courses":{"type":"array","items":{"type":"object","required":["course_id","title","enrolled","completed","average_progress","completion_rate"],"properties":{"course_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"enrolled":{"type":"integer"},"completed":{"type":"integer"},"average_progress":{"type":"integer"},"completion_rate":{"type":"integer"}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/stats/user/me":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgGetStatsUserMe","tags":["stats"],"summary":"Estadísticas del usuario","description":"Devuelve las estadísticas de progreso, rankings, insignias y retos del usuario autenticado.","responses":{"200":{"description":"Estadísticas del usuario autenticado.","content":{"application/json":{"schema":{"type":"object","required":["user","progress","rankings","badges","challenges"],"properties":{"user":{"type":"object","required":["id","name","level","xp","xp_for_next_level","xp_progress","streak"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"level":{"type":"integer"},"xp":{"type":"integer"},"xp_for_next_level":{"type":"integer"},"xp_progress":{"type":"integer"},"streak":{"type":"integer"}}},"progress":{"type":"object","required":["total_courses","completed_courses","in_progress_courses","total_lessons_completed","total_quizzes_passed","average_progress","average_quiz_score"],"properties":{"total_courses":{"type":"integer"},"completed_courses":{"type":"integer"},"in_progress_courses":{"type":"integer"},"total_lessons_completed":{"type":"integer"},"total_quizzes_passed":{"type":"integer"},"average_progress":{"type":"integer"},"average_quiz_score":{"type":"integer"}}},"rankings":{"type":"object","required":["global","cohort","cohort_name"],"properties":{"global":{"type":["integer","null"]},"cohort":{"type":["integer","null"]},"cohort_name":{"type":["string","null"]}}},"badges":{"type":"object","required":["earned","total","percentage"],"properties":{"earned":{"type":"integer"},"total":{"type":"integer"},"percentage":{"type":"integer"}}},"challenges":{"type":"object","required":["active","completed"],"properties":{"active":{"type":"integer"},"completed":{"type":"integer"}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/teacher/cohorts":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgTeacherListCohorts","x-tool":{"name":"list_teacher_cohorts","domain":"teaching","profile":"core","sensitive":false,"roles":["owner","admin","teacher"]},"tags":["teacher"],"summary":"Mis cohortes","description":"Devuelve las cohortes asignadas al profesor autenticado.","responses":{"200":{"description":"Cohortes del profesor.","content":{"application/json":{"schema":{"type":"object","required":["cohorts"],"properties":{"cohorts":{"type":"array","items":{"$ref":"#/components/schemas/Cohort"}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/teacher/cohorts/{id}/progress":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la cohorte.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgTeacherGetCohortProgress","x-tool":{"name":"get_cohort_progress","domain":"teaching","profile":"core","sensitive":false,"roles":["owner","admin","teacher"]},"tags":["teacher"],"summary":"Progreso de la cohorte","description":"Devuelve el progreso detallado por alumno y curso de una cohorte.","responses":{"200":{"description":"Progreso por alumno y curso.","content":{"application/json":{"schema":{"type":"object","required":["cohort_id","progress"],"properties":{"cohort_id":{"type":"string","format":"uuid"},"progress":{"type":"array","items":{"type":"object","required":["student_id","student_name","email","level","xp","last_activity","courses"],"properties":{"student_id":{"type":"string","format":"uuid"},"student_name":{"type":"string"},"email":{"type":"string","format":"email"},"level":{"type":"integer"},"xp":{"type":"integer"},"last_activity":{"type":["string","null"],"format":"date-time"},"courses":{"type":"array","items":{"type":"object","required":["course_id","course_title","progress","completed_lessons","total_lessons","started_at","completed_at","quizzes_passed","total_quizzes","exam"],"properties":{"course_id":{"type":"string","format":"uuid"},"course_title":{"type":"string"},"progress":{"type":"integer","minimum":0,"maximum":100},"completed_lessons":{"type":"integer"},"total_lessons":{"type":"integer"},"started_at":{"type":["string","null"],"format":"date"},"completed_at":{"type":["string","null"],"format":"date"},"quizzes_passed":{"type":"integer"},"total_quizzes":{"type":"integer"},"exam":{"type":["object","null"],"properties":{"score":{"type":"integer"},"passed":{"type":"boolean"},"completed_at":{"type":["string","null"],"format":"date-time"}}}}}}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/teacher/cohorts/{id}/stats":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la cohorte.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgTeacherGetCohortStats","tags":["teacher"],"summary":"Estadísticas de la cohorte","description":"Devuelve métricas agregadas de una cohorte y su mejor alumno.","responses":{"200":{"description":"Estadísticas agregadas de la cohorte.","content":{"application/json":{"schema":{"type":"object","required":["cohort_id","name","total_students","total_xp","average_xp","average_level","total_courses_completed","total_lessons_completed","top_performer"],"properties":{"cohort_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"total_students":{"type":"integer"},"total_xp":{"type":"integer"},"average_xp":{"type":"number"},"average_level":{"type":"number"},"total_courses_completed":{"type":"integer"},"total_lessons_completed":{"type":"integer"},"top_performer":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"xp":{"type":"integer"}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cohorte no encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/teacher/cohorts/{id}/students":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de la cohorte.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgTeacherListCohortStudents","x-tool":{"name":"list_cohort_students","domain":"teaching","profile":"all","sensitive":true,"roles":["owner","admin","teacher"]},"tags":["teacher"],"summary":"Alumnos de la cohorte","description":"Lista los alumnos de una cohorte con sus estadísticas de progreso.","responses":{"200":{"description":"Alumnos y estadísticas de la cohorte.","content":{"application/json":{"schema":{"type":"object","required":["cohort_id","cohort_name","students"],"properties":{"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":["string","null"]},"students":{"type":"array","items":{"type":"object","required":["student","stats","membership_status"],"properties":{"student":{"$ref":"#/components/schemas/User"},"membership_status":{"type":"string","enum":["active","suspended"],"description":"Estado de la membresía EN esta organización (alta/baja del alumno aquí)."},"stats":{"type":"object","required":["total_courses","completed_courses","in_progress_courses","total_lessons_completed","total_quizzes_passed","average_progress","average_quiz_score"],"properties":{"total_courses":{"type":"integer"},"completed_courses":{"type":"integer"},"in_progress_courses":{"type":"integer"},"total_lessons_completed":{"type":"integer"},"total_quizzes_passed":{"type":"integer"},"average_progress":{"type":"integer","minimum":0,"maximum":100},"average_quiz_score":{"type":"integer","minimum":0,"maximum":100}}}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cohorte no encontrada (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/teacher/students/{id}/assign-course":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de alumno.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgTeacherAssignCourse","x-tool":{"name":"assign_course_to_student","domain":"teaching","profile":"core","sensitive":false,"roles":["owner","admin","teacher"]},"tags":["teacher"],"summary":"Asignar curso a alumno","description":"Inscribe al alumno en un curso (idempotente).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["course_id"],"properties":{"course_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Alumno inscrito en el curso; se devuelve su progreso.","content":{"application/json":{"schema":{"type":"object","required":["student_id","course_id","course_title","progress"],"properties":{"student_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"course_title":{"type":"string"},"progress":{"type":"object","required":["user_id","course_id","completed_lessons","current_lesson","progress","started_at","completed_at","quiz_results"],"properties":{"user_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"completed_lessons":{"type":"array","description":"Números de lección completados.","items":{"type":"integer"}},"current_lesson":{"type":["integer","null"]},"progress":{"type":"integer","minimum":0,"maximum":100},"started_at":{"type":["string","null"],"format":"date"},"completed_at":{"type":["string","null"],"format":"date"},"quiz_results":{"type":"object","additionalProperties":true,"description":"Mapa quiz_id -> resultado del quiz."}}}}}}}},"400":{"description":"Cuerpo inválido (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Alumno o curso no encontrados (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/teacher/students/{id}/detail":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de alumno.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgTeacherGetStudentDetail","x-tool":{"name":"get_student_detail","domain":"teaching","profile":"all","sensitive":true,"roles":["owner","admin","teacher"]},"tags":["teacher"],"summary":"Detalle del alumno","description":"Devuelve el perfil completo del alumno con cursos, insignias, exámenes y certificados.","responses":{"200":{"description":"Detalle completo del alumno.","content":{"application/json":{"schema":{"type":"object","required":["student","stats","courses","badges","exams","certificates","membership_status"],"properties":{"student":{"$ref":"#/components/schemas/User"},"membership_status":{"type":"string","enum":["active","suspended"],"description":"Estado de la membresía EN esta organización."},"stats":{"type":"object","required":["total_courses","completed_courses","in_progress_courses","total_lessons_completed","total_quizzes_passed","average_progress","average_quiz_score"],"properties":{"total_courses":{"type":"integer"},"completed_courses":{"type":"integer"},"in_progress_courses":{"type":"integer"},"total_lessons_completed":{"type":"integer"},"total_quizzes_passed":{"type":"integer"},"average_progress":{"type":"integer","minimum":0,"maximum":100},"average_quiz_score":{"type":"integer","minimum":0,"maximum":100}}},"courses":{"type":"array","items":{"type":"object","required":["course_id","course_title","progress","completed_lessons","total_lessons","started_at","completed_at","quiz_results"],"properties":{"course_id":{"type":"string","format":"uuid"},"course_title":{"type":"string"},"progress":{"type":"integer","minimum":0,"maximum":100},"completed_lessons":{"type":"integer"},"total_lessons":{"type":"integer"},"started_at":{"type":["string","null"],"format":"date"},"completed_at":{"type":["string","null"],"format":"date"},"quiz_results":{"type":"object","additionalProperties":true,"description":"Mapa quiz_id -> resultado del quiz."}}}},"badges":{"type":"array","items":{"type":"string"}},"exams":{"type":"array","items":{"type":"object","required":["course_id","course_title","score","passed","completed_at"],"properties":{"course_id":{"type":"string","format":"uuid"},"course_title":{"type":"string"},"score":{"type":"integer"},"passed":{"type":"boolean"},"completed_at":{"type":["string","null"],"format":"date-time"}}}},"certificates":{"type":"array","items":{"type":"object","required":["id","course_title","verification_code","issued_at"],"properties":{"id":{"type":"string","format":"uuid"},"course_title":{"type":"string"},"verification_code":{"type":"string"},"issued_at":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Alumno no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/teacher/students/{id}/toggle-active":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID de alumno.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgTeacherToggleStudentActive","tags":["teacher"],"summary":"Activar/desactivar alumno","description":"Alterna el estado activo de un alumno de la cohorte del profesor.","responses":{"200":{"description":"Estado activo actualizado.","content":{"application/json":{"schema":{"type":"object","required":["id","active"],"properties":{"id":{"type":"string","format":"uuid"},"active":{"type":"boolean"}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Alumno no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`server_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/teachers/{id}/assign-cohort":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del profesor.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgAdminAssignCohortToTeacher","tags":["cohorts"],"summary":"Asignar cohorte a profesor","description":"Asigna una cohorte a un profesor; operación idempotente (administración).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["cohort_id"],"properties":{"cohort_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Cohorte asignada.","content":{"application/json":{"schema":{"type":"object","required":["teacher_id","cohort_id","assigned"],"properties":{"teacher_id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"assigned":{"type":"boolean"}}}}}},"400":{"description":"Solicitud inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Profesor o cohorte no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/teachers/{id}/cohorts":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del profesor.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgAdminGetTeacherCohorts","tags":["cohorts"],"summary":"Cohortes del profesor","description":"Devuelve las cohortes asignadas a un profesor (administración).","responses":{"200":{"description":"Cohortes del profesor.","content":{"application/json":{"schema":{"type":"object","required":["teacher_id","cohorts"],"properties":{"teacher_id":{"type":"string","format":"uuid"},"cohorts":{"type":"array","items":{"$ref":"#/components/schemas/Cohort"}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Profesor no encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/teachers/{id}/remove-cohort":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del profesor.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgAdminRemoveCohortFromTeacher","tags":["cohorts"],"summary":"Quitar cohorte a profesor","description":"Quita una cohorte asignada a un profesor (administración).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["cohort_id"],"properties":{"cohort_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Cohorte quitada.","content":{"application/json":{"schema":{"type":"object","required":["teacher_id","cohort_id","removed"],"properties":{"teacher_id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"removed":{"type":"boolean"}}}}}},"400":{"description":"Solicitud inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/titles":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgListTitles","tags":["gamification"],"summary":"Listar títulos","description":"Devuelve el catálogo de títulos disponibles.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Lista de títulos.","content":{"application/json":{"schema":{"type":"object","required":["titles"],"properties":{"titles":{"type":"array","items":{"type":"object","required":["id","name","description","requirement_type","requirement_value","icon","rarity","unlocked","active","unlocked_at"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"requirement_type":{"type":"string","enum":["xp","level","badges","courses","streak","special"]},"requirement_value":{"type":"integer"},"icon":{"type":["string","null"]},"rarity":{"type":"string","enum":["common","uncommon","rare","epic","legendary"]},"unlocked":{"type":"boolean"},"active":{"type":"boolean"},"unlocked_at":{"type":["string","null"],"format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/titles/active":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"put":{"operationId":"orgSetActiveTitle","tags":["gamification"],"summary":"Fijar título activo","description":"Establece el título activo del usuario autenticado. Un título que aún no ha desbloqueado es 403 `title_locked`; uno que no existe en la organización (global o propio), 404.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title_id"],"properties":{"title_id":{"type":"string"}}}}}},"responses":{"200":{"description":"Título activo actualizado.","content":{"application/json":{"schema":{"type":"object","required":["message","active_title"],"properties":{"message":{"type":"string"},"active_title":{"type":"object","required":["id","name","description","requirement_type","requirement_value","icon","rarity","unlocked","active","unlocked_at"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"requirement_type":{"type":"string","enum":["xp","level","badges","courses","streak","special"]},"requirement_value":{"type":"integer"},"icon":{"type":["string","null"]},"rarity":{"type":"string","enum":["common","uncommon","rare","epic","legendary"]},"unlocked":{"type":"boolean"},"active":{"type":"boolean"},"unlocked_at":{"type":["string","null"],"format":"date-time"}}}}}}}},"400":{"description":"Título no válido (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/titles/mine":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgMyTitles","tags":["gamification"],"summary":"Mis títulos","description":"Devuelve los títulos del usuario autenticado y el título activo.","parameters":[{"name":"X-Lang","in":"header","required":false,"description":"Idioma preferido para contenido traducible (`en`|`es`).","schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Títulos del usuario.","content":{"application/json":{"schema":{"type":"object","required":["titles","total","active_title"],"properties":{"titles":{"type":"array","items":{"type":"object","required":["id","name","description","requirement_type","requirement_value","icon","rarity","unlocked","active","unlocked_at"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"requirement_type":{"type":"string","enum":["xp","level","badges","courses","streak","special"]},"requirement_value":{"type":"integer"},"icon":{"type":["string","null"]},"rarity":{"type":"string","enum":["common","uncommon","rare","epic","legendary"]},"unlocked":{"type":"boolean"},"active":{"type":"boolean"},"unlocked_at":{"type":["string","null"],"format":"date-time"}}}},"total":{"type":"integer"},"active_title":{"description":"Título activo, o null.","oneOf":[{"type":"object","required":["id","name","description","requirement_type","requirement_value","icon","rarity","unlocked","active","unlocked_at"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"requirement_type":{"type":"string","enum":["xp","level","badges","courses","streak","special"]},"requirement_value":{"type":"integer"},"icon":{"type":["string","null"]},"rarity":{"type":"string","enum":["common","uncommon","rare","epic","legendary"]},"unlocked":{"type":"boolean"},"active":{"type":"boolean"},"unlocked_at":{"type":["string","null"],"format":"date-time"}}},{"type":"null"}]}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"description":"Error interno (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/users":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"orgListUsers","tags":["users"],"summary":"Listar usuarios","description":"Lista de usuarios para administración.","parameters":[{"name":"role","in":"query","required":false,"description":"Filtra por rol.","schema":{"type":"string"}},{"name":"cohort","in":"query","required":false,"description":"Filtra por cohorte.","schema":{"type":"string"}}],"responses":{"200":{"description":"Lista de usuarios.","content":{"application/json":{"schema":{"type":"object","required":["users"],"properties":{"users":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/users/{id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del usuario.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgGetUserById","tags":["users"],"summary":"Obtener usuario por ID","description":"Devuelve un usuario con sus estadísticas de progreso.","responses":{"200":{"description":"Usuario con estadísticas de progreso.","content":{"application/json":{"schema":{"type":"object","required":["user","progress_stats"],"properties":{"user":{"$ref":"#/components/schemas/User"},"progress_stats":{"type":"object","required":["total_courses","completed_courses","in_progress_courses","total_lessons_completed","total_quizzes_passed","average_progress","average_quiz_score"],"properties":{"total_courses":{"type":"integer"},"completed_courses":{"type":"integer"},"in_progress_courses":{"type":"integer"},"total_lessons_completed":{"type":"integer"},"total_quizzes_passed":{"type":"integer"},"average_progress":{"type":"integer"},"average_quiz_score":{"type":"integer"}}}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No encontrado (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/organizations/{org_id}/users/{id}/rate":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del usuario.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"orgRateUser","tags":["users"],"summary":"Valorar usuario","description":"Registra o actualiza la valoración del usuario autenticado sobre otro.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rating"],"properties":{"rating":{"type":"integer","minimum":1,"maximum":5}}}}}},"responses":{"200":{"description":"Valoración registrada.","content":{"application/json":{"schema":{"type":"object","required":["average","count","user_rating"],"properties":{"average":{"type":["number","null"]},"count":{"type":"integer"},"user_rating":{"type":["integer","null"],"minimum":1,"maximum":5}}}}}},"400":{"description":"Petición inválida (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Sin permisos (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"operationId":"orgDeleteUserRating","tags":["users"],"summary":"Eliminar valoración","description":"Elimina la valoración del usuario autenticado sobre otro.","responses":{"200":{"description":"Valoración eliminada.","content":{"application/json":{"schema":{"type":"object","required":["average","count","user_rating"],"properties":{"average":{"type":["number","null"]},"count":{"type":"integer"},"user_rating":{"type":"null","description":"Siempre `null` tras eliminar la valoración."}}}}}},"401":{"description":"No autenticado (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/users/{id}/rating":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"id","in":"path","required":true,"description":"ID del usuario.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"orgGetUserRating","tags":["users"],"summary":"Valoración de un usuario","description":"Devuelve la valoración media de un usuario y, con sesión, la del que llama.","responses":{"200":{"description":"Valoración del usuario.","content":{"application/json":{"schema":{"type":"object","required":["average","count","user_rating"],"properties":{"average":{"type":["number","null"]},"count":{"type":"integer","minimum":0},"user_rating":{"type":["integer","null"],"minimum":1,"maximum":5}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/teacher/cohorts/{cohort_id}/insights":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"cohort_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"getTeacherCohortInsights","x-tool":{"name":"get_cohort_insights","domain":"teaching","profile":"all","sensitive":true,"roles":["owner","admin","teacher"]},"tags":["teacher"],"summary":"Estadísticas de una cohorte: finalización, exámenes, actividad y alumnado en riesgo","description":"Estadísticas ampliadas de una cohorte (EDAPI-206). Docente: la cohorte que gestiona (otra → 403); admin: cualquiera de su organización; otra organización → 404. Solo cuenta lo que el alumnado hizo EN esta organización.","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortInsights"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/teacher/cohorts/{cohort_id}/export":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"cohort_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"exportTeacherCohortCsv","tags":["teacher"],"summary":"Exportar el alumnado, el progreso o los exámenes de una cohorte (CSV)","description":"CSV síncrono (EDAPI-219): UTF-8 con BOM, cabeceras en el idioma de `lang` o de `Accept-Language` (es por defecto), celdas neutralizadas contra fórmulas. Por encima del tope de filas responde 413 `export_too_large`: se pide la exportación asíncrona del informe de la organización. Cada exportación queda auditada.","parameters":[{"name":"kind","in":"query","required":true,"schema":{"type":"string","enum":["students","progress","exams"]}},{"name":"sep","in":"query","required":false,"schema":{"type":["string","null"],"enum":["comma","semicolon",null]}},{"name":"lang","in":"query","required":false,"schema":{"type":["string","null"],"pattern":"^(es|en)$"}}],"responses":{"200":{"description":"CSV con cabeceras en el idioma pedido.","content":{"text/csv":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"description":"Demasiadas filas para una exportación síncrona (`export_too_large`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/teacher/quizzes/generate":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"generateTeacherQuizDraft","tags":["teacher"],"summary":"Generar con IA el borrador de un quiz (contrato; sin motor todavía)","description":"Contrato del generador de quizzes con IA del panel docente (EDFRO-175/178). El borrador NUNCA se publica solo: el docente lo revisa y lo guarda en el editor. Respeta la función `ai_tutor` que permite plataforma y el interruptor de IA de la organización (apagada → 403 `ai_tutor_disabled`). Hasta que exista el tutor IA, con la IA encendida responde 503 `ai_quiz_generation_unavailable`. Sin `x-tool` desde EDMCP-43: la tool `draft_quiz` se retiró porque siempre respondía 503; un agente propone quizzes con `propose_quiz` y `confirm_quiz_proposal`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizDraftRequest"}}}},"responses":{"200":{"description":"Borrador para revisar.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizDraft"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/api/v1/organizations/{org_id}/reports/overview":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getOrgReportOverview","x-tool":{"name":"get_org_report_overview","domain":"teaching","profile":"all","sensitive":false,"roles":["owner","admin"]},"tags":["reports"],"summary":"Informe de la organización: actividad, matrículas, finalizaciones, certificados y exámenes","description":"Cifras agregadas de la organización en un rango de fechas (EDAPI-214). Solo admin u owner (docente → 403). `from` y `to` son días (UTC) incluidos; sin ellos, los últimos 30 días. Un rango al revés o de más de 366 días → 422.","parameters":[{"name":"from","in":"query","required":false,"description":"Primer día incluido (UTC). Sin él, hace 29 días.","schema":{"type":["string","null"],"format":"date"}},{"name":"to","in":"query","required":false,"description":"Último día incluido (UTC). Sin él, hoy.","schema":{"type":["string","null"],"format":"date"}}],"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrgReportOverview"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/reports/courses":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getOrgReportCourses","x-tool":{"name":"get_org_report_courses","domain":"teaching","profile":"all","sensitive":false,"roles":["owner","admin"]},"tags":["reports"],"summary":"Uso por curso y por cohorte de la organización","description":"Desglose por curso (global o privado de la organización) y, dentro, por cohorte (EDAPI-214). Solo admin u owner. Mismo rango que el resumen.","parameters":[{"name":"from","in":"query","required":false,"description":"Primer día incluido (UTC). Sin él, hace 29 días.","schema":{"type":["string","null"],"format":"date"}},{"name":"to","in":"query","required":false,"description":"Último día incluido (UTC). Sin él, hoy.","schema":{"type":["string","null"],"format":"date"}}],"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrgReportCourses"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/reports/at-risk-rule":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getOrgAtRiskRule","tags":["reports"],"summary":"Regla de alumnado en riesgo de la organización","description":"Días sin actividad y progreso mínimo que marcan a alguien «en riesgo» en las estadísticas de cohorte (EDAPI-206). La ve el docente; la cambia el admin.","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AtRiskRule"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"updateOrgAtRiskRule","tags":["reports"],"summary":"Cambiar la regla de alumnado en riesgo","description":"Solo admin u owner. Queda en el registro de auditoría.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AtRiskRule"}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AtRiskRule"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/reports/exports":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"listOrgReportExports","tags":["reports"],"summary":"Mis últimas exportaciones de informes de la organización","description":"Las 10 más recientes que pidió la persona (EDAPI-224). Solo admin u owner.","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrgReportExportList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"createOrgReportExport","tags":["reports"],"summary":"Pedir una exportación asíncrona (CSV) de un informe de la organización","description":"Encola la exportación en el worker (cola `exports`) y responde 202 (EDAPI-224). Cuando está lista llega un correo que lleva al panel; la descarga exige la sesión de quien la pidió y un enlace firmado que caduca a las 24 horas y vale una vez. Una exportación por persona cada 5 minutos (429 `export_rate_limited`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrgReportExportCreate"}}}},"responses":{"202":{"description":"Encolada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrgReportExport"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/organizations/{org_id}/reports/exports/{export_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"export_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"getOrgReportExport","tags":["reports"],"summary":"Estado de una exportación de informe","description":"Con `download_url` firmado solo si está lista, sin descargar y sin caducar.","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrgReportExport"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/reports/exports/{export_id}/download":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"export_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"downloadOrgReportExport","tags":["reports"],"summary":"Descargar una exportación de informe (una vez, antes de caducar)","description":"Exige la sesión de quien la pidió y el `token` firmado del enlace. Otra organización o una firma que no casa → 404; caducada o ya descargada → 410.","parameters":[{"name":"token","in":"query","required":false,"description":"Firma del enlace. Sin ella, o si no casa, 404.","schema":{"type":["string","null"],"minLength":10,"maxLength":64,"pattern":"^[0-9]+-[0-9a-f]+$"}}],"responses":{"200":{"description":"CSV del informe.","content":{"text/csv":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"description":"Caducada o ya descargada (`export_expired`, `export_already_downloaded`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/cohorts/{cohort_id}/assignments":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"cohort_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"listCohortAssignments","tags":["progress"],"summary":"Cursos y programas asignados a una cohorte","description":"Cursos y programas asignados a una cohorte (EDAPI-122).","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortAssignmentList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"createCohortAssignment","x-tool":{"name":"assign_course","domain":"teaching","profile":"core","sensitive":false,"roles":["owner","admin","teacher"]},"tags":["progress"],"summary":"Asignar un curso o un programa a una cohorte","description":"Asignar un curso o un programa a una cohorte (EDAPI-122).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortAssignmentCreate"}}}},"responses":{"201":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortAssignment"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"La cohorte ya tiene asignado ese curso o programa (`conflict`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/cohorts/{cohort_id}/assignments/{assignment_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"cohort_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"assignment_id","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"operationId":"deleteCohortAssignment","tags":["progress"],"summary":"Retirar una asignación de una cohorte","description":"Retirar una asignación de una cohorte (EDAPI-122).","responses":{"204":{"description":"Sin contenido."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/cohorts/{cohort_id}/progress":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"cohort_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"getCohortProgress","x-tool":{"name":"get_cohort_report","domain":"teaching","profile":"core","sensitive":false,"roles":["owner","admin","teacher"]},"tags":["progress"],"summary":"Progreso de una cohorte: alumno × asignación","description":"Progreso de una cohorte: alumno × asignación (EDAPI-157).","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortProgress"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/cohorts/{cohort_id}/progress/export":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"cohort_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"exportCohortProgress","tags":["progress"],"summary":"Exportar el progreso de una cohorte (CSV)","description":"Exportar el progreso de una cohorte (CSV) (EDAPI-157).","parameters":[{"name":"lang","in":"query","required":false,"schema":{"type":["string","null"],"pattern":"^(es|en)$"}}],"responses":{"200":{"description":"CSV con cabeceras en el idioma pedido.","content":{"text/csv":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/courses/{course_id}/enrollment":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"course_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"getMyEnrollment","tags":["progress"],"summary":"Mi matrícula en un curso","description":"Mi matrícula en un curso (EDAPI-133).","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Enrollment"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"enrollInCourse","tags":["progress"],"summary":"Matricularme en un curso","description":"Matricularme en un curso (EDAPI-133).","responses":{"200":{"description":"Ya estaba matriculada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Enrollment"}}}},"201":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Enrollment"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"operationId":"leaveCourse","tags":["progress"],"summary":"Dejar un curso","description":"Dejar un curso (EDAPI-133).","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Enrollment"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/courses/{course_id}/notes/export":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"course_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"exportCourseNotesMarkdown","tags":["notes"],"summary":"Exportar mis notas de un curso (Markdown)","description":"Exportar mis notas de un curso (Markdown) (EDAPI-151).","parameters":[{"name":"X-Lang","in":"header","required":false,"schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Mis notas en Markdown.","content":{"text/markdown":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/courses/{course_id}/quizzes/{quiz_id}/submit":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"course_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"quiz_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"operationId":"submitCourseQuiz","tags":["progress"],"summary":"Enviar un quiz","description":"Enviar un quiz (EDAPI-131).","requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"answers":{"items":{"anyOf":[{"type":"integer"},{"type":"string"}]},"type":"array","minItems":1,"description":"Índices de respuesta (base 0), uno por pregunta; -1 = sin responder (cuenta como fallo)."}},"type":"object","required":["answers"],"description":"Cuerpo de POST submitQuiz."}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"properties":{"result":{"properties":{"score":{"type":"integer","maximum":100,"minimum":0},"passed":{"type":"boolean"},"correct_answers":{"type":["integer","null"]},"total_questions":{"type":["integer","null"]}},"type":"object","required":["score","passed"],"description":"Resultado del quiz (anidado en SubmitQuizOut)."},"xp_earned":{"type":"integer","minimum":0},"new_badges":{"items":{"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":["string","null"]},"icon":{"type":["string","null"]},"color":{"type":"string","default":"#6366f1"},"rarity":{"type":"string"},"requirement":{"type":["string","null"]},"hidden":{"type":"boolean","default":false}},"type":"object","required":["id","name","rarity"],"description":"Insignia del contrato OpenAPI (schema Badge)."},"type":"array"}},"type":"object","required":["result","xp_earned"],"description":"Respuesta de POST submitQuiz."}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/banks":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"listQuestionBanks","tags":["exams"],"summary":"Bancos de preguntas (globales y de la organización)","description":"Bancos de preguntas (globales y de la organización) (EDAPI-173).","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionBankList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"createQuestionBank","tags":["exams"],"summary":"Crear un banco de preguntas de la organización","description":"Crear un banco de preguntas de la organización (EDAPI-173).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionBankCreate"}}}},"responses":{"201":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionBank"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/banks/{bank_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"bank_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"getQuestionBank","tags":["exams"],"summary":"Un banco de preguntas con sus preguntas","description":"Un banco de preguntas con sus preguntas (EDAPI-173).","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionBankDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/editor/banks/{bank_id}/questions/import":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"bank_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"operationId":"importBankQuestions","tags":["exams"],"summary":"Importar preguntas a un banco (todo o nada)","description":"Importar preguntas a un banco (todo o nada) (EDAPI-173).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankImport"}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankImportResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/editor/banks/{bank_id}/questions/{question_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"bank_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"question_id","in":"path","required":true,"schema":{"type":"string"}}],"patch":{"operationId":"setBankQuestionStatus","tags":["exams"],"summary":"Retirar o reactivar una pregunta de un banco","description":"Retirar o reactivar una pregunta de un banco (EDAPI-173).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankQuestionStatusUpdate"}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankQuestion"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/enrollments/{enrollment_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"enrollment_id","in":"path","required":true,"schema":{"type":"string"}}],"patch":{"operationId":"updateEnrollment","tags":["progress"],"summary":"Dar de baja, reactivar o fijar el vencimiento de una matrícula","description":"Dar de baja, reactivar o fijar el vencimiento de una matrícula (EDAPI-162).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrollmentUpdate"}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Enrollment"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/exams/{exam_id}/draw-rules":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"exam_id","in":"path","required":true,"schema":{"type":"string"}}],"put":{"operationId":"setExamDrawRules","tags":["exams"],"summary":"Fijar las reglas de sorteo de un examen","description":"Fijar las reglas de sorteo de un examen (EDAPI-137).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExamDrawRulesUpdate"}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExamDrawRules"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/exams/{exam_id}/results":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"exam_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"listExamResults","tags":["exams"],"summary":"Resultados de un examen (docente: su cohorte; admin: toda la organización)","description":"Resultados de un examen (docente: su cohorte; admin: toda la organización) (EDAPI-176).","parameters":[{"name":"cohort_id","in":"query","required":false,"schema":{"type":["string","null"]}}],"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExamResultList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/exams/{exam_id}/results/export":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"exam_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"exportExamResults","tags":["exams"],"summary":"Exportar los resultados de un examen (CSV)","description":"Exportar los resultados de un examen (CSV) (EDAPI-176).","parameters":[{"name":"cohort_id","in":"query","required":false,"schema":{"type":["string","null"]}},{"name":"lang","in":"query","required":false,"schema":{"type":["string","null"],"pattern":"^(es|en)$"}}],"responses":{"200":{"description":"CSV con cabeceras en el idioma pedido.","content":{"text/csv":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/exams/{exam_id}/results/{result_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"exam_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"result_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"getExamResultDetail","tags":["exams"],"summary":"Detalle de un intento: respuestas, corrección e incidencias","description":"Detalle de un intento: respuestas, corrección e incidencias (EDAPI-176).","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExamResultDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/exams/{exam_id}/sessions/{session_id}/void":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"exam_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"session_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"operationId":"voidExamSession","tags":["exams"],"summary":"Anular una sesión de examen (no cuenta como intento)","description":"Anular una sesión de examen (no cuenta como intento) (EDAPI-160).","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExamSessionVoided"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/exams/{exam_id}/students/{user_id}/accommodations":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"exam_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"user_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"operationId":"grantExamAccommodation","tags":["exams"],"summary":"Conceder intentos extra o más tiempo a una persona","description":"Conceder intentos extra o más tiempo a una persona (EDAPI-160).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExamAccommodationUpdate"}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExamAccommodation"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/progress/courses/{course_id}/lessons/{lesson_id}/complete":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"course_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"lesson_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"operationId":"completeLessonById","tags":["progress"],"summary":"Completar una lección (por su UUID)","description":"Completar una lección (por su UUID) (EDAPI-143).","requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"time_spent":{"type":"integer","minimum":30,"description":"Tiempo dedicado en segundos."},"scroll_depth":{"description":"Porcentaje de scroll de la lección.","type":["integer","null"],"maximum":100,"minimum":0}},"type":"object","required":["time_spent"],"description":"Cuerpo de POST completeLesson."}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"properties":{"message":{"type":"string"},"progress":{"properties":{"user_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"completed_lessons":{"items":{"type":"integer"},"type":"array","description":"Números de lección completados."},"current_lesson":{"type":["integer","null"]},"progress":{"type":"integer","maximum":100,"minimum":0},"started_at":{"type":["string","null"],"format":"date"},"completed_at":{"type":["string","null"],"format":"date"},"quiz_results":{"additionalProperties":true,"type":"object","description":"Mapa quiz_id -> {score,passed,completed_at,attempts}."}},"type":"object","required":["user_id","course_id","progress"],"description":"Progreso actualizado tras completar una lección (completeLesson)."},"xp_earned":{"type":"integer","minimum":0},"new_badges":{"items":{"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":["string","null"]},"icon":{"type":["string","null"]},"color":{"type":"string","default":"#6366f1"},"rarity":{"type":"string"},"requirement":{"type":["string","null"]},"hidden":{"type":"boolean","default":false}},"type":"object","required":["id","name","rarity"],"description":"Insignia del contrato OpenAPI (schema Badge)."},"type":"array"},"course_completed":{"type":"boolean"}},"type":"object","required":["message","progress","xp_earned","course_completed"],"description":"Respuesta de POST completeLesson."}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/organizations/{org_id}/api-keys":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"listApiKeys","tags":["api-keys"],"summary":"Tokens de acceso de la organización","description":"admin u owner ven todos los de la organización; el resto, solo los suyos. Incluye los revocados y caducados (con su `status`), nunca el secreto.","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"}}},"post":{"operationId":"createApiKey","tags":["api-keys"],"summary":"Crear un token de acceso personal","description":"Exige step-up reciente (403 `step_up_required`). `write` solo para owner, admin y teacher (403 `api_key_write_not_allowed`). (El 403 `bearer_not_allowed_for_minors` se retiró con el ADR 0036.) Máximo 10 vivos por persona y organización (409 `api_key_limit_reached`). El secreto se devuelve UNA vez.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}}},"responses":{"201":{"description":"Token creado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"409":{"description":"Límite de tokens vivos alcanzado (`api_key_limit_reached`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/api-keys/{key_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"},{"name":"key_id","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"operationId":"revokeApiKey","tags":["api-keys"],"summary":"Revocar un token de acceso","description":"admin u owner, cualquiera de la organización; el resto, solo los suyos. Idempotente. Deja de autenticar en la siguiente petición.","responses":{"204":{"description":"Revocado."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"}}}},"/.well-known/oauth-authorization-server":{"get":{"operationId":"getOauthAsMetadata","tags":["oauth"],"summary":"Metadata del Authorization Server (RFC 8414)","security":[],"responses":{"200":{"description":"Documento de discovery.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthMetadata"}}}}}}},"/.well-known/oauth-protected-resource":{"get":{"operationId":"getOauthProtectedResourceMetadata","tags":["oauth"],"summary":"Metadata del recurso protegido /mcp (RFC 9728)","security":[],"responses":{"200":{"description":"Documento de discovery.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthMetadata"}}}}}}},"/.well-known/oauth-protected-resource/mcp":{"get":{"operationId":"getOauthProtectedResourceMetadataMcp","tags":["oauth"],"summary":"Metadata del recurso /mcp en su ruta derivada (RFC 9728 §3.1)","security":[],"responses":{"200":{"description":"Documento de discovery.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthMetadata"}}}}}}},"/api/v1/oauth/register":{"post":{"operationId":"registerOauthClient","tags":["oauth"],"summary":"Registro dinámico de cliente público (RFC 7591)","description":"Solo clientes públicos (`token_endpoint_auth_method: none`) con PKCE. 10 por minuto e IP.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthClientRegistration"}}}},"responses":{"201":{"description":"Cliente registrado (la metadata que MANDA).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthClientRegistered"}}}},"400":{"description":"Metadata inválida (`invalid_client_metadata` / `invalid_redirect_uri`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthError"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/oauth/authorize":{"get":{"operationId":"oauthAuthorize","tags":["oauth"],"summary":"Inicio de la autorización (redirige al consentimiento)","description":"Valida cliente, `redirect_uri` exacta y PKCE S256, y redirige (302) a `/oauth/consent?rid=…` de la web. Con cliente o URI inválidos NO redirige (400 `OauthError`); el resto de errores vuelven al `redirect_uri` con `?error=`. 30 por minuto e IP.","security":[],"parameters":[{"name":"response_type","in":"query","required":false,"schema":{"type":"string","enum":["code"],"default":""}},{"name":"client_id","in":"query","required":false,"schema":{"type":"string","default":""}},{"name":"redirect_uri","in":"query","required":false,"schema":{"type":"string","default":""}},{"name":"code_challenge","in":"query","required":false,"schema":{"type":["string","null"]}},{"name":"code_challenge_method","in":"query","required":false,"schema":{"anyOf":[{"type":"string","enum":["S256"]},{"type":"null"}]}},{"name":"scope","in":"query","required":false,"schema":{"type":["string","null"]}},{"name":"state","in":"query","required":false,"schema":{"type":["string","null"]}},{"name":"resource","in":"query","required":false,"schema":{"type":["string","null"]}}],"responses":{"302":{"description":"Al consentimiento, o al redirect_uri con `?error=`."},"400":{"description":"Cliente o `redirect_uri` inválidos (sin redirección).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthError"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/oauth/consent/{request_id}":{"parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"getOauthConsentInfo","tags":["oauth"],"summary":"Qué cliente pide acceso y en qué organizaciones puede concederse","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthConsentInfo"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"decideOauthConsent","tags":["oauth"],"summary":"Aprobar o denegar el acceso de un cliente","description":"Al aprobar, la persona elige UNA organización (404 si no es de las ofrecidas) y el acceso; `write` solo si su rol ahí es teacher o superior (403 `oauth_write_not_allowed`). Devuelve la URL de vuelta al cliente con el code.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthConsentDecisionRequest"}}}},"responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthConsentDecision"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/oauth/token":{"post":{"operationId":"oauthToken","tags":["oauth"],"summary":"Canje del code o rotación del refresh (RFC 6749)","description":"Form-encoded. 60 por minuto e IP.","security":[],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"grant_type":{"type":"string","default":""},"code":{"type":["string","null"]},"redirect_uri":{"type":["string","null"]},"client_id":{"type":["string","null"]},"code_verifier":{"type":["string","null"]},"refresh_token":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"Par de tokens.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthTokenResponse"}}}},"400":{"description":"`invalid_grant`, `invalid_request` o `unsupported_grant_type`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthError"}}}},"401":{"description":"Cliente desconocido (`invalid_client`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthError"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/oauth/authorizations":{"get":{"operationId":"listMyOauthAuthorizations","tags":["oauth"],"summary":"Mis apps conectadas (autorizaciones OAuth vivas)","responses":{"200":{"description":"Respuesta correcta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthAuthorizationList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/v1/oauth/authorizations/{authorization_id}":{"parameters":[{"name":"authorization_id","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"operationId":"revokeMyOauthAuthorization","tags":["oauth"],"summary":"Desconectar una app (revoca su autorización y todos sus tokens)","responses":{"204":{"description":"Revocada."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/organizations/{org_id}/tutor/status":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getTutorStatus","tags":["tutor"],"summary":"Estado del tutor IA para quien pregunta","description":"Responde SIEMPRE a un miembro (también con la función apagada): el web decide con esto si pinta el panel del tutor. `reason`: `ok`, `notice_required` (falta aceptar el aviso del tutor: consentimiento `ai_tutor`), `unconfigured` (sin clave del proveedor o tutor global apagado) o `feature_disabled` (ops o la institución lo apagaron). Incluye el perfil de guardarraíles que se aplicaría (`menor`, el estricto de contenido, si la institución lo fuerza) y el cupo.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorStatus"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/tutor/chat":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"tutorChat","tags":["tutor"],"summary":"Preguntar al tutor IA (respuesta entera)","description":"Variante sin streaming de `tutorChatStream`, con las mismas puertas y el mismo resultado. Responde SOLO con el material de los cursos a los que la persona tiene acceso; las citas `[n]` las resuelve el servidor contra los trozos que puso delante del modelo. 403 `enrollment_required` sin matrícula (salvo instructor o editor del curso); 409 `ai_tutor_notice_required` sin aceptar el aviso; 409 `exam_in_progress` con un examen activo del curso; 429 `too_many_requests` (ráfaga) o `ai_quota_exceeded` (cupo; cabeceras `Retry-After` y `X-Quota-Scope: user|organization`); 503 `tutor_unavailable` sin proveedor, o `provider_timeout`/`provider_overloaded`/`provider_error` si el proveedor falla (el turno queda guardado como fallido).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorChatIn"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorChatResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"409":{"description":"Conflicto con el estado actual (código estable en el envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/api/v1/organizations/{org_id}/tutor/chat/stream":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"tutorChatStream","tags":["tutor"],"summary":"Preguntar al tutor IA en streaming (SSE)","description":"Server-Sent Events del turno (ADR 0035). Las puertas se resuelven ANTES de abrir el stream (mismos errores HTTP que `tutorChat`). Eventos, en este orden: `delta` (texto incremental con las citas ya numeradas) … → `citas` (las que resolvió el servidor) → `resultado` (mensaje final); o `error` (`provider_timeout`, `provider_overloaded`, `provider_error`, `internal_error`) en lugar de `resultado`. `ping` cada ~15 s sin nada que contar. El turno corre aparte: si el cliente corta, termina y queda guardado. Cabeceras `Cache-Control: no-cache` y `X-Accel-Buffering: no`. Cliente: `fetch` con `credentials: 'include'` y lector de stream (un `EventSource` no hace POST).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorChatIn"}}}},"responses":{"200":{"description":"Stream `text/event-stream` con los eventos del turno.","content":{"text/event-stream":{"schema":{"type":"string"}}},"x-eventos":{"delta":{"type":"object","description":"Evento `delta` del stream del tutor (texto incremental, citas ya numeradas).","properties":{"text":{"type":"string"}},"required":["text"],"title":"TutorStreamDelta"},"citas":{"type":"object","description":"Evento `citas`: las citas que resolvió el servidor y cuántas marcas descartó.","properties":{"citations":{"type":"array","items":{"$ref":"#/components/schemas/TutorCitation"}},"discarded":{"type":"integer"}},"required":["citations","discarded"],"title":"TutorStreamCitations"},"resultado":{"properties":{"conversation_id":{"type":"string"},"message_id":{"type":"string"},"user_message_id":{"type":"string"},"content":{"type":"string"},"citations":{"items":{"$ref":"#/components/schemas/TutorCitation"},"type":"array"},"discarded_citations":{"type":"integer"},"status":{"type":"string","enum":["ok","moderated","blocked","failed"]},"guardrail_profile":{"type":"string","enum":["adulto","menor"]},"model":{"type":["string","null"]},"usage":{"$ref":"#/components/schemas/TutorUsage"},"quota":{"$ref":"#/components/schemas/TutorQuota"}},"type":"object","required":["conversation_id","message_id","user_message_id","content","citations","discarded_citations","status","guardrail_profile","model","usage","quota"],"title":"TutorChatResult"},"error":{"type":"object","description":"Evento `error`: código estable (`provider_timeout`, `provider_overloaded`, `provider_error`, `internal_error`) y un mensaje sin texto del proveedor.","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"title":"TutorStreamError"},"ping":{"type":"object"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"409":{"description":"Conflicto con el estado actual (código estable en el envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/api/v1/organizations/{org_id}/tutor/conversations":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"listTutorConversations","tags":["tutor"],"summary":"Mis conversaciones con el tutor","description":"Solo las propias, de esta organización, sin las borradas; página estable por `updated_at` descendente con `cursor` opaco.","parameters":[{"name":"course_id","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":36}},{"name":"cursor","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":200}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":20}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorConversationList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/tutor/conversations/{conversation_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getTutorConversation","tags":["tutor"],"summary":"Una conversación propia con sus mensajes y citas","description":"Otra persona (de esta organización o de otra) recibe 404: no se confirma que exista. Tampoco la lee el admin de la organización.","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorConversationDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"operationId":"deleteTutorConversation","tags":["tutor"],"summary":"Borrar una conversación propia","description":"Borrado lógico inmediato (deja de listarse y no se puede continuar: el chat responde 404); la purga física la hace la retención al día siguiente.","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"OK"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/tutor/messages/{message_id}/feedback":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"put":{"operationId":"setTutorMessageFeedback","tags":["tutor"],"summary":"Valorar una respuesta del tutor","description":"Pulgar arriba o abajo (`rating: null` lo quita) y, con abajo, un motivo de una lista cerrada (sin texto libre). Solo sobre respuestas propias.","parameters":[{"name":"message_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorFeedbackIn"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorFeedbackOut"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/tutor/settings":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getTutorSettings","tags":["tutor"],"summary":"Ajustes del tutor de la institución","description":"Admin u owner.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorSettings"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}},"put":{"operationId":"updateTutorSettings","tags":["tutor"],"summary":"Cambiar los ajustes del tutor","description":"Admin u owner, auditado. `guardrail_profile`: `auto` (el perfil de siempre) o `minor` (perfil estricto de contenido para todo el alumnado; la clave se conserva, ADR 0036). No existe un valor menos restrictivo que `auto`: cualquier otro es 422.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorSettings"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorSettings"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/tutor/safety-flags":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"listTutorSafetyFlags","tags":["tutor"],"summary":"Señales de seguridad del tutor","description":"Para el responsable designado (admin u owner): quién, qué categoría y cuándo, SIN el mensaje. Se registran solo con el perfil estricto (`minor`). Existe aunque el tutor esté apagado.","parameters":[{"name":"pending","in":"query","required":false,"schema":{"type":"boolean","description":"Solo las no revisadas.","default":false},"description":"Solo las no revisadas."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorSafetyFlagList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/tutor/safety-flags/{flag_id}/review":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"reviewTutorSafetyFlag","tags":["tutor"],"summary":"Marcar revisada una señal de seguridad","description":"Admin u owner; idempotente y auditado.","parameters":[{"name":"flag_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TutorSafetyFlag"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/quiz-proposals":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"createQuizProposal","x-tool":{"name":"propose_quiz","domain":"teaching","profile":"all","sensitive":false,"superficies":["mcp"],"roles":["owner","admin","teacher"]},"tags":["tutor"],"summary":"Proponer un quiz con IA para una lección (A12b)","description":"Crea un BORRADOR (`pending`, 202) que un job genera en segundo plano a partir del material de la lección; nada se escribe en los quizzes hasta que una persona lo confirma (`confirmQuizProposal`). Exige poder editar el curso (docente que lo firma o figura como editor, o admin). 503 `tutor_unavailable` sin proveedor; 429 `too_many_requests` o `ai_quota_exceeded`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizProposalCreate"}}}},"responses":{"202":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizProposal"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"get":{"operationId":"listQuizProposals","tags":["tutor"],"summary":"Propuestas de quiz de los cursos que puedo editar","description":"Sin las descartadas; filtro opcional por curso.","parameters":[{"name":"course_id","in":"query","required":false,"schema":{"type":["string","null"],"maxLength":36}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizProposalList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/quiz-proposals/{proposal_id}":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getQuizProposal","tags":["tutor"],"summary":"Una propuesta de quiz","description":"`status`: `pending` → `ready` | `failed` (`error_code`: `provider_error`, `invalid_output`…) → `confirmed` | `discarded`.","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizProposal"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}},"patch":{"operationId":"updateQuizProposal","tags":["tutor"],"summary":"Editar las preguntas de una propuesta","description":"Solo en `ready` (409 `invalid_transition` si no). Cada pregunta: 2-6 opciones y el índice de la correcta.","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizProposalUpdate"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizProposal"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"409":{"description":"Conflicto con el estado actual (código estable en el envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"operationId":"discardQuizProposal","tags":["tutor"],"summary":"Descartar una propuesta","description":"Una confirmada no se descarta (409).","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"OK"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"409":{"description":"Conflicto con el estado actual (código estable en el envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/quiz-proposals/{proposal_id}/confirm":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"post":{"operationId":"confirmQuizProposal","x-tool":{"name":"confirm_quiz_proposal","domain":"teaching","profile":"all","sensitive":false,"superficies":["mcp"],"roles":["owner","admin","teacher"]},"tags":["tutor"],"summary":"Confirmar una propuesta: escribe el quiz en la lección","description":"La ÚNICA operación que escribe en los quizzes, por el repositorio del editor. Idempotente: confirmar dos veces devuelve el mismo quiz (`already_confirmed: true`) sin duplicarlo. 409 `not_ready` si la propuesta no está lista.","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizProposalConfirmResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"409":{"description":"Conflicto con el estado actual (código estable en el envelope).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/ai-usage/me":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getMyAiUsage","tags":["ai-usage"],"summary":"Mi consumo del tutor IA y lo que me queda","description":"Mensajes de hoy, tokens del mes y el libro del mes (sin contenido).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyAiUsage"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/organizations/{org_id}/ai-usage":{"parameters":[{"$ref":"#/components/parameters/OrgId"}],"get":{"operationId":"getOrgAiUsage","tags":["ai-usage"],"summary":"Consumo de IA de la organización","description":"Admin u owner. Totales y reparto por persona, curso y recurso en un rango de días (por defecto, el mes en curso); sin contenido de conversaciones. Las cifras salen del libro de tokens.","parameters":[{"name":"from","in":"query","required":false,"schema":{"type":["string","null"],"format":"date","description":"Primer día (UTC)."},"description":"Primer día (UTC)."},{"name":"to","in":"query","required":false,"schema":{"type":["string","null"],"format":"date","description":"Último día (UTC)."},"description":"Último día (UTC)."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrgAiUsage"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFoundOrFeatureDisabled"},"422":{"$ref":"#/components/responses/ValidationError"}}}}},"components":{"parameters":{"OrgId":{"name":"org_id","in":"path","required":true,"description":"Id de la organización (resuelta contra la membresía de la sesión: 404 a no-miembros).","schema":{"type":"string"}}},"responses":{"BadRequest":{"description":"Petición incorrecta para el estado actual del recurso (`bad_request` u otro código de dominio).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Sin sesión válida (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"Sesión válida sin permiso para esta operación (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"El recurso no existe o no es visible para quien pregunta (`not_found`). Un recurso de otra organización responde 404, nunca 403: no se confirma que exista.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFoundOrFeatureDisabled":{"description":"No existe o no es visible para quien pregunta (`not_found`), o la función de la organización está apagada (`feature_disabled`: `social`, `gamification` o `ai_tutor`, que enciende la institución y permite ops; A9/A12). Nunca 403: no se confirma nada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ValidationError":{"description":"Cuerpo o parámetros inválidos (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooManyRequests":{"description":"Cupo de peticiones superado (`too_many_requests`). Reintenta tras `Retry-After` segundos; no es un bloqueo de la cuenta.","headers":{"Retry-After":{"description":"Segundos hasta que el cupo se libera.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServiceUnavailable":{"description":"Avería de infraestructura reintentable (`database_unavailable`, `database_busy`, `database_locked`, `cache_unavailable`) o función no configurada. El trabajo NO se guardó: reintenta tras `Retry-After`.","headers":{"Retry-After":{"description":"Segundos recomendados antes de reintentar.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"securitySchemes":{"cookieAuth":{"type":"apiKey","in":"cookie","name":"access_token","description":"JWT de acceso (15 min) en cookie HttpOnly `access_token`.\nSe emite/rota al canjear el código por email, en la callback OAuth y en refresh,\njunto con la cookie HttpOnly `refresh_token` (token opaco rotativo, hasheado en\nDB), que solo usa `POST /api/v1/auth/refresh`, y con la cookie de PRESENCIA\n`edr_session=1` (no HttpOnly, sin nada secreto) para que el front sepa si merece\nla pena preguntar por la sesión. `SameSite=Lax`; `Secure` según `COOKIE_SECURE`.\nGlobal, como alternativa a `bearerAuth`. Las operaciones públicas\n(health/ready/version, verificación de certificado, perfil público, plantilla\npública de certificado, código por email, proveedores y OAuth) declaran `security: []`.\n"},"bearerAuth":{"type":"http","scheme":"bearer","description":"Credencial DELEGADA (A2c): un token de acceso personal `edr_live_…` (Ajustes\n→ Tokens) o un access token OAuth `edr_oat_…` del Authorization Server del\nMCP. Autentica como su dueña, SOLO dentro de su organización (cualquier otra\n→ 404), con acceso `read` o `write` (403 `credential_read_only`), con la\nfunción `mcp` activa (404 `feature_disabled`) y 120 peticiones por minuto.\nNunca abre ops, la gestión de la cuenta ni la de credenciales (403\n`bearer_not_allowed`), ni sirve al alumnado tratado como menor (403\n`bearer_not_allowed_for_minors`).\n"}},"schemas":{"BankImport":{"properties":{"questions":{"items":{"$ref":"#/components/schemas/BankQuestionCreate"},"type":"array","maxItems":1000,"minItems":1}},"additionalProperties":false,"type":"object","required":["questions"],"title":"BankImport"},"BankImportError":{"properties":{"row":{"type":"integer"},"reason":{"type":"string"}},"type":"object","required":["row","reason"],"title":"BankImportError"},"BankImportResult":{"properties":{"imported":{"type":"integer"},"errors":{"items":{"$ref":"#/components/schemas/BankImportError"},"type":"array"}},"type":"object","required":["imported","errors"],"description":"Todo o nada: si alguna fila falla, no se importa ninguna.","title":"BankImportResult"},"BankQuestion":{"properties":{"id":{"type":"string","format":"uuid"},"question":{"type":"string"},"options":{"items":{"type":"string"},"type":"array"},"correct_answer":{"type":"integer"},"explanation":{"type":["string","null"]},"difficulty":{"type":"string","enum":["easy","medium","hard"]},"topic_tag":{"type":["string","null"]},"status":{"type":"string","enum":["active","retired"]},"version":{"type":"integer"}},"type":"object","required":["id","question","options","correct_answer","explanation","difficulty","topic_tag","status","version"],"description":"Vista de EDITOR (docente): incluye la respuesta correcta.","title":"BankQuestion"},"BankQuestionCreate":{"properties":{"question":{"type":"string"},"options":{"items":{"type":"string"},"type":"array"},"correct_answer":{"type":"integer"},"explanation":{"type":["string","null"]},"difficulty":{"type":"string","enum":["easy","medium","hard"],"default":"medium"},"topic_tag":{"type":["string","null"],"maxLength":100}},"type":"object","required":["question","options","correct_answer"],"description":"Una pregunta a importar (formato de `seed_data/exams.json`).","title":"BankQuestionCreate"},"BankQuestionStatusUpdate":{"properties":{"status":{"type":"string","enum":["active","retired"]}},"additionalProperties":false,"type":"object","required":["status"],"title":"BankQuestionStatusUpdate"},"CohortAssignment":{"properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"entity_type":{"type":"string","enum":["course","program"]},"entity_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"starts_at":{"type":["string","null"],"format":"date-time"},"due_at":{"type":["string","null"],"format":"date-time"},"mandatory":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}},"type":"object","required":["id","cohort_id","entity_type","entity_id","title","starts_at","due_at","mandatory","created_at"],"title":"CohortAssignment"},"CohortAssignmentCreate":{"properties":{"entity_type":{"type":"string","enum":["course","program"]},"entity_id":{"type":"string","format":"uuid"},"starts_at":{"type":["string","null"],"format":"date-time"},"due_at":{"type":["string","null"],"format":"date-time"},"mandatory":{"type":"boolean","default":true}},"additionalProperties":false,"type":"object","required":["entity_type","entity_id"],"title":"CohortAssignmentCreate"},"CohortAssignmentList":{"properties":{"assignments":{"items":{"$ref":"#/components/schemas/CohortAssignment"},"type":"array"}},"type":"object","required":["assignments"],"title":"CohortAssignmentList"},"CohortProgress":{"properties":{"cohort_id":{"type":"string","format":"uuid"},"assignments":{"items":{"$ref":"#/components/schemas/CohortAssignment"},"type":"array"},"learners":{"items":{"$ref":"#/components/schemas/CohortProgressLearner"},"type":"array"}},"type":"object","required":["cohort_id","assignments","learners"],"title":"CohortProgress"},"CohortProgressCell":{"properties":{"assignment_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["not_enrolled","active","completed","dropped"]},"progress":{"type":"integer","maximum":100,"minimum":0},"last_activity_at":{"type":["string","null"],"format":"date-time"},"exam_score":{"type":["integer","null"]},"overdue":{"type":"boolean"}},"type":"object","required":["assignment_id","status","progress","last_activity_at","exam_score","overdue"],"title":"CohortProgressCell"},"CohortProgressLearner":{"properties":{"user_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"cells":{"items":{"$ref":"#/components/schemas/CohortProgressCell"},"type":"array"}},"type":"object","required":["user_id","name","cells"],"title":"CohortProgressLearner"},"Enrollment":{"properties":{"id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"course_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["active","completed","dropped"]},"source":{"type":"string","enum":["self","cohort","admin","program","legacy"]},"cohort_id":{"type":["string","null"],"format":"uuid"},"enrolled_at":{"type":"string","format":"date-time"},"due_at":{"type":["string","null"],"format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"dropped_at":{"type":["string","null"],"format":"date-time"}},"type":"object","required":["id","user_id","course_id","status","source","cohort_id","enrolled_at","due_at","completed_at","dropped_at"],"description":"Una matrícula en la organización de la ruta.","title":"Enrollment"},"EnrollmentUpdate":{"properties":{"status":{"type":["string","null"],"enum":["active","dropped"]},"due_at":{"type":["string","null"],"format":"date-time"}},"additionalProperties":false,"type":"object","description":"Admin de la organización: baja, reactivación o vencimiento (EDAPI-162).","title":"EnrollmentUpdate"},"ExamAccommodation":{"properties":{"id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"exam_id":{"type":["string","null"],"format":"uuid"},"extra_attempts":{"type":"integer"},"time_multiplier":{"type":"number"},"reason":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"}},"type":"object","required":["id","user_id","exam_id","extra_attempts","time_multiplier","reason","created_at"],"title":"ExamAccommodation"},"ExamAccommodationUpdate":{"properties":{"extra_attempts":{"type":"integer","maximum":10,"minimum":0,"default":0},"time_multiplier":{"type":"number","maximum":2,"minimum":1,"default":1},"reason":{"type":["string","null"],"maxLength":255},"all_exams":{"type":"boolean","default":false}},"additionalProperties":false,"type":"object","title":"ExamAccommodationUpdate"},"ExamAnswerReview":{"properties":{"question_id":{"type":"string"},"question":{"type":"string"},"options":{"items":{"type":"string"},"type":"array"},"chosen_option":{"type":["integer","null"]},"correct_option":{"type":"integer"},"is_correct":{"type":"boolean"},"explanation":{"type":["string","null"]}},"type":"object","required":["question_id","question","options","chosen_option","correct_option","is_correct","explanation"],"title":"ExamAnswerReview"},"ExamDrawRule":{"properties":{"bank_id":{"type":"string","format":"uuid"},"count":{"type":"integer"},"difficulty":{"type":["string","null"],"enum":["easy","medium","hard"]},"topic":{"type":["string","null"]}},"type":"object","required":["bank_id","count","difficulty","topic"],"title":"ExamDrawRule"},"ExamDrawRuleInput":{"properties":{"bank_id":{"type":"string","format":"uuid"},"count":{"type":"integer","maximum":500,"minimum":1},"difficulty":{"type":["string","null"],"enum":["easy","medium","hard"]},"topic":{"type":["string","null"],"maxLength":100}},"additionalProperties":false,"type":"object","required":["bank_id","count"],"title":"ExamDrawRuleInput"},"ExamDrawRules":{"properties":{"exam_id":{"type":"string","format":"uuid"},"rules":{"items":{"$ref":"#/components/schemas/ExamDrawRule"},"type":"array"},"questions_per_session":{"type":"integer"}},"type":"object","required":["exam_id","rules","questions_per_session"],"title":"ExamDrawRules"},"ExamDrawRulesUpdate":{"properties":{"rules":{"items":{"$ref":"#/components/schemas/ExamDrawRuleInput"},"type":"array","maxItems":50}},"additionalProperties":false,"type":"object","required":["rules"],"title":"ExamDrawRulesUpdate"},"ExamIncident":{"properties":{"type":{"type":"string"},"occurred_at":{"type":"string","format":"date-time"}},"type":"object","required":["type","occurred_at"],"title":"ExamIncident"},"ExamIncidents":{"properties":{"tab_switch":{"type":"integer","default":0},"focus_lost":{"type":"integer","default":0},"resume":{"type":"integer","default":0},"ip_change":{"type":"integer","default":0},"ua_change":{"type":"integer","default":0},"stalled":{"type":"integer","default":0},"invalidated":{"type":"integer","default":0}},"type":"object","title":"ExamIncidents"},"ExamResultDetail":{"properties":{"result":{"$ref":"#/components/schemas/ExamResultRow"},"answers":{"items":{"$ref":"#/components/schemas/ExamAnswerReview"},"type":"array"},"incidents":{"items":{"$ref":"#/components/schemas/ExamIncident"},"type":"array"}},"type":"object","required":["result","answers","incidents"],"title":"ExamResultDetail"},"ExamResultList":{"properties":{"exam_id":{"type":"string","format":"uuid"},"results":{"items":{"$ref":"#/components/schemas/ExamResultRow"},"type":"array"}},"type":"object","required":["exam_id","results"],"title":"ExamResultList"},"ExamResultRow":{"properties":{"result_id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"attempt_number":{"type":"integer"},"score":{"type":"integer"},"passed":{"type":"boolean"},"time_taken_seconds":{"type":["integer","null"]},"finish_reason":{"type":["string","null"]},"voided":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"incidents":{"$ref":"#/components/schemas/ExamIncidents"}},"type":"object","required":["result_id","user_id","name","attempt_number","score","passed","time_taken_seconds","finish_reason","voided","created_at","incidents"],"title":"ExamResultRow"},"ExamSessionVoided":{"properties":{"session_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["voided","completed","expired","invalidated"]},"result_voided":{"type":"boolean"}},"type":"object","required":["session_id","status","result_voided"],"title":"ExamSessionVoided"},"CourseEditor":{"type":"object","required":["user_id","name","assigned_at"],"properties":{"user_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"assigned_at":{"type":"string","format":"date-time"}}},"CourseEditorList":{"type":"object","required":["editors"],"properties":{"editors":{"type":"array","items":{"$ref":"#/components/schemas/CourseEditor"}}}},"CourseEditorAdd":{"type":"object","required":["user_id"],"properties":{"user_id":{"type":"string","format":"uuid","description":"Miembro de la organización del curso con rol ≥ teacher (en un curso global, autor o admin de plataforma)."}}},"EditorProgramCourse":{"type":"object","required":["course_id","title","slug","status","scope","is_required"],"properties":{"course_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string","enum":["draft","published","archived"]},"scope":{"type":"string","enum":["global","organization"]},"is_required":{"type":"boolean"}}},"EditorProgram":{"type":"object","description":"Un programa (ruta de aprendizaje) visto desde el editor (EDFRO-229).","required":["id","title","slug","difficulty","estimated_hours","active","scope","awards_certificate","courses"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":["string","null"]},"difficulty":{"type":"string","enum":["beginner","intermediate","advanced"]},"estimated_hours":{"type":"integer"},"active":{"type":"boolean"},"scope":{"type":"string","enum":["global","organization"]},"awards_certificate":{"type":"boolean"},"credential_title":{"type":["string","null"]},"courses":{"type":"array","description":"Cursos del programa, en orden.","items":{"$ref":"#/components/schemas/EditorProgramCourse"}}}},"EditorProgramSummary":{"type":"object","required":["id","title","slug","difficulty","active","scope","course_count"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"difficulty":{"type":"string","enum":["beginner","intermediate","advanced"]},"active":{"type":"boolean"},"scope":{"type":"string","enum":["global","organization"]},"course_count":{"type":"integer"}}},"EditorProgramList":{"type":"object","required":["programs"],"properties":{"programs":{"type":"array","items":{"$ref":"#/components/schemas/EditorProgramSummary"}}}},"EditorProgramCreate":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":255},"slug":{"type":["string","null"],"maxLength":100,"description":"Vacío = se deriva del título."},"description":{"type":["string","null"],"maxLength":5000},"difficulty":{"type":"string","enum":["beginner","intermediate","advanced"],"default":"beginner"},"estimated_hours":{"type":"integer","minimum":0,"maximum":10000,"default":0}}},"EditorProgramUpdate":{"type":"object","properties":{"title":{"type":["string","null"],"minLength":1,"maxLength":255},"slug":{"type":["string","null"],"maxLength":100},"description":{"type":["string","null"],"maxLength":5000},"difficulty":{"type":["string","null"],"enum":["beginner","intermediate","advanced",null]},"estimated_hours":{"type":["integer","null"],"minimum":0,"maximum":10000},"active":{"type":["boolean","null"]}}},"EditorProgramCourseRef":{"type":"object","required":["course_id"],"properties":{"course_id":{"type":"string","format":"uuid"},"is_required":{"type":"boolean","default":true}}},"CourseCloneInput":{"type":"object","title":"CourseCloneInput","additionalProperties":false,"required":["course_id"],"properties":{"course_id":{"type":"string","format":"uuid","description":"Curso GLOBAL que se clona."}}},"CourseCloneResult":{"type":"object","title":"CourseCloneResult","required":["course_id","cloned_from_course_id","status","slug"],"properties":{"course_id":{"type":"string","format":"uuid","description":"Id de la copia privada (también cuando la hace un job)."},"cloned_from_course_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["done","queued"],"description":"`done`: la copia ya existe; `queued`: la está haciendo un job."},"slug":{"type":["string","null"],"description":"Slug de la copia (null mientras la hace el job)."}}},"EditorProgramCourses":{"type":"object","required":["courses"],"properties":{"courses":{"type":"array","maxItems":100,"description":"Cursos del programa en el orden nuevo (sustituye a los de antes). Cada uno tiene que ser visible para la organización del programa (privado de ELLA o del catálogo global); en un programa global, solo globales. Otro → 404 (no existe para esta organización). Sin repetidos (422).","items":{"$ref":"#/components/schemas/EditorProgramCourseRef"}}}},"LessonVersionSummary":{"type":"object","description":"Una versión guardada de una lección (EDFRO-236), sin su contenido.","required":["id","revision","title","saved_at","length"],"properties":{"id":{"type":"string","format":"uuid"},"revision":{"type":"integer"},"title":{"type":"string"},"saved_at":{"type":"string","format":"date-time"},"saved_by_name":{"type":["string","null"]},"restored_from":{"type":["string","null"],"format":"uuid","description":"Si es una restauración, la versión de la que salió."},"length":{"type":"integer","description":"Caracteres del Markdown guardado."}}},"LessonVersionList":{"type":"object","required":["versions"],"properties":{"versions":{"type":"array","description":"De la más reciente a la más antigua (las últimas 50).","items":{"$ref":"#/components/schemas/LessonVersionSummary"}}}},"LessonVersion":{"type":"object","description":"Una versión con su contenido (para compararla y restaurarla).","required":["id","revision","title","saved_at","length"],"properties":{"id":{"type":"string","format":"uuid"},"revision":{"type":"integer"},"title":{"type":"string"},"saved_at":{"type":"string","format":"date-time"},"saved_by_name":{"type":["string","null"]},"restored_from":{"type":["string","null"],"format":"uuid"},"length":{"type":"integer"},"content":{"type":["string","null"]}}},"CourseStructureModule":{"type":"object","required":["lesson_numbers"],"properties":{"name":{"type":["string","null"],"maxLength":100,"description":"Nombre del módulo (vacío o null = sin nombre)."},"lesson_numbers":{"type":"array","minItems":1,"description":"`lesson_number` ACTUAL de cada lección del módulo, en el orden nuevo (la clave estable de la lección en el editor, como en el reordenado).","items":{"type":"integer"}}}},"CourseStructure":{"type":"object","required":["modules"],"properties":{"modules":{"type":"array","maxItems":500,"description":"Módulos en orden. Entre todos tienen que nombrar CADA lección del curso exactamente una vez; un módulo vacío no existe (ADR 0016, módulo como columnas de la lección).","items":{"$ref":"#/components/schemas/CourseStructureModule"}}}},"CourseStructureResult":{"type":"object","required":["modules","updated"],"properties":{"modules":{"type":"array","items":{"$ref":"#/components/schemas/CourseStructureModule"}},"updated":{"type":"integer","description":"Lecciones que cambiaron de número, de módulo o de nombre de módulo."}}},"TranslationField":{"type":"object","required":["entity_type","entity_id","field","source","status"],"properties":{"entity_type":{"type":"string","enum":["course","lesson","quiz","quiz_question"]},"entity_id":{"type":"string"},"field":{"type":"string"},"position":{"type":["integer","null"],"description":"Número de la lección o de la pregunta; null en el curso."},"source":{"type":"string","description":"Valor en el idioma base (las opciones de una pregunta, como lista JSON)."},"value":{"type":["string","null"]},"status":{"type":"string","enum":["missing","draft","reviewed","stale"],"description":"`missing` sin traducir; `draft` sin revisar (no se sirve en producción); `reviewed` revisada; `stale` hecha de un texto base que ya cambió (no se sirve)."},"reviewed_at":{"type":["string","null"],"format":"date-time"}}},"TranslationSummary":{"type":"object","required":["total","missing","draft","reviewed","stale"],"properties":{"total":{"type":"integer"},"missing":{"type":"integer"},"draft":{"type":"integer"},"reviewed":{"type":"integer"},"stale":{"type":"integer"}}},"CourseTranslations":{"type":"object","required":["course_id","base_lang","lang","fields","summary"],"properties":{"course_id":{"type":"string","format":"uuid"},"base_lang":{"type":"string","enum":["es","en"]},"lang":{"type":"string","enum":["es","en"]},"fields":{"type":"array","items":{"$ref":"#/components/schemas/TranslationField"}},"summary":{"$ref":"#/components/schemas/TranslationSummary"}}},"TranslationSave":{"type":"object","required":["entity_type","entity_id","field","lang","value"],"properties":{"entity_type":{"type":"string","enum":["course","lesson","quiz","quiz_question"]},"entity_id":{"type":"string","minLength":1,"maxLength":50},"field":{"type":"string","minLength":1,"maxLength":50},"lang":{"type":"string","enum":["es","en"]},"value":{"type":"string","maxLength":200000,"description":"Vacío borra la traducción. El contenido de una lección se sanea (sin HTML crudo ni enlaces peligrosos)."}}},"TranslationRef":{"type":"object","required":["entity_type","entity_id","field"],"properties":{"entity_type":{"type":"string","enum":["course","lesson","quiz","quiz_question"]},"entity_id":{"type":"string","minLength":1,"maxLength":50},"field":{"type":"string","minLength":1,"maxLength":50}}},"TranslationReview":{"type":"object","required":["lang"],"properties":{"lang":{"type":"string","enum":["es","en"]},"fields":{"type":["array","null"],"maxItems":2000,"description":"Campos a marcar revisados (también un `stale`, que así se da por bueno). Sin lista, todos los `draft` del curso.","items":{"$ref":"#/components/schemas/TranslationRef"}}}},"TranslationReviewResult":{"type":"object","required":["reviewed","summary"],"properties":{"reviewed":{"type":"integer"},"summary":{"$ref":"#/components/schemas/TranslationSummary"}}},"QuestionBank":{"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"course_id":{"type":["string","null"],"format":"uuid"},"is_global":{"type":"boolean"},"active_questions":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}},"type":"object","required":["id","title","course_id","is_global","active_questions","created_at"],"title":"QuestionBank"},"QuestionBankCreate":{"properties":{"title":{"type":"string","maxLength":255,"minLength":1},"course_id":{"type":["string","null"],"format":"uuid"}},"additionalProperties":false,"type":"object","required":["title"],"title":"QuestionBankCreate"},"QuestionBankDetail":{"properties":{"bank":{"$ref":"#/components/schemas/QuestionBank"},"questions":{"items":{"$ref":"#/components/schemas/BankQuestion"},"type":"array"}},"type":"object","required":["bank","questions"],"title":"QuestionBankDetail"},"QuestionBankList":{"properties":{"banks":{"items":{"$ref":"#/components/schemas/QuestionBank"},"type":"array"}},"type":"object","required":["banks"],"title":"QuestionBankList"},"User":{"type":"object","title":"User","description":"Usuario de la plataforma (tabla `users`).","required":["id","email","name","created_at"],"properties":{"id":{"type":"string","format":"uuid","description":"Identificador UUID.","examples":["6f1f9c3e-8f2a-4b6e-9a3d-2b1c0d4e5f60"]},"email":{"type":"string","format":"email","examples":["nick@3xa.es"]},"name":{"type":"string","examples":["Nick Valdivia"]},"username":{"type":["string","null"],"description":"Handle público único; `null` si no lo ha reclamado.","examples":["nickv"]},"role":{"type":"string","description":"Rol del usuario.","enum":["student","instructor","admin","teacher"],"examples":["student"]},"level":{"type":"integer","description":"Nivel derivado del XP acumulado.","examples":[7]},"xp":{"type":"integer","description":"Puntos de experiencia acumulados.","examples":[5400]},"avatar":{"type":["string","null"],"description":"URL del avatar; `null` si no tiene.","examples":["https://cdn.edurails.com/avatars/nick.png"]},"headline":{"type":["string","null"],"description":"Titular/rol corto bajo el nombre en su perfil; `null` si vacío.","examples":["Fundador y CTO en 3XA"]},"bio":{"type":["string","null"],"description":"Biografía libre para su perfil; `null` si vacía.","examples":["Construyendo Learn. Café, código y kanban."]},"location":{"type":["string","null"],"description":"Ubicación (ciudad/país); `null` si no la ha puesto.","examples":["Madrid, España"]},"public_profile":{"type":"boolean","description":"Si el perfil es visible públicamente.","examples":[true]},"linkedin_url":{"type":["string","null"],"format":"uri","description":"Perfil de LinkedIn; `null` si vacío."},"github_url":{"type":["string","null"],"format":"uri","description":"Perfil de GitHub; `null` si vacío."},"cohort_id":{"type":["string","null"],"format":"uuid","description":"Cohorte a la que pertenece; `null` si ninguna."},"streak":{"type":"integer","description":"Racha de días de actividad consecutivos.","examples":[12]},"last_activity":{"type":["string","null"],"format":"date-time","description":"Última actividad registrada; `null` si nunca."},"active":{"type":"boolean","description":"Si la cuenta está activa (los inactivos no pueden entrar).","examples":[true]},"active_title_id":{"type":["string","null"],"format":"uuid","description":"Título de gamificación mostrado activo; `null` si ninguno."},"created_at":{"type":"string","format":"date-time","examples":["2026-07-08T12:00:00Z"]}}},"PublicProfile":{"type":"object","title":"PublicProfile","additionalProperties":false,"required":["user_id","username","name","headline","level","xp","badges","certificates","linkedin_url","public_profile","is_self"],"properties":{"user_id":{"type":"string","format":"uuid","description":"Id de la persona (para pintar su pepo; nunca una foto)."},"username":{"type":"string"},"name":{"type":"string"},"headline":{"type":["string","null"],"description":"Titular que la persona escribió en su perfil."},"level":{"type":["integer","null"],"description":"Nivel; `null` si la gamificación está apagada o la persona se sacó de los rankings."},"xp":{"type":["integer","null"],"description":"XP total; `null` en los mismos casos que `level`."},"badges":{"type":"integer","description":"Insignias conseguidas."},"certificates":{"type":"integer","description":"Certificados emitidos en ESTA organización."},"linkedin_url":{"type":["string","null"],"description":"LinkedIn saneado (solo `https://` de linkedin.com); `null` si no hay o no es válido."},"public_profile":{"type":"boolean","description":"Si la persona tiene el perfil activado. A terceros siempre `true`; `false` solo lo ve ella."},"is_self":{"type":"boolean","description":"`true` si es el perfil de quien pregunta."}}},"Session":{"type":"object","title":"Session","description":"Una sesión abierta en un dispositivo (una familia de refresh tokens). `device` es una etiqueta gruesa («Chrome · macOS»); ni User-Agent completo ni IP.","required":["id","started_at","expires_at","current"],"properties":{"id":{"type":"string","description":"Id de la sesión (la familia de refresh). Es lo que se pasa a `DELETE /me/sessions/{session_id}`."},"device":{"type":["string","null"],"description":"Navegador y sistema, si se reconocen.","examples":["Chrome · macOS"]},"started_at":{"type":"string","format":"date-time","description":"Cuándo se entró en ese dispositivo."},"last_used_at":{"type":["string","null"],"format":"date-time","description":"Última renovación de la sesión."},"expires_at":{"type":"string","format":"date-time","description":"Cuándo caduca si no se usa."},"current":{"type":"boolean","description":"Es la sesión desde la que se pregunta."}}},"SessionList":{"type":"object","title":"SessionList","required":["sessions"],"properties":{"sessions":{"type":"array","description":"Sesiones vivas; la actual primero, luego de la más reciente a la más antigua.","items":{"$ref":"#/components/schemas/Session"}}}},"MyIntegration":{"type":"object","title":"MyIntegration","description":"Una integración que comparte datos míos (EDAPI-343).","required":["kind","organization_id","organization_name","recipient","shared_data","status","linked_since","shared_since","last_shared_at","publishing_enabled"],"properties":{"kind":{"type":"string","enum":["step_projekt"]},"organization_id":{"type":"string"},"organization_name":{"type":"string"},"recipient":{"type":"string","enum":["internship_tutor"],"description":"A quién llega lo compartido."},"shared_data":{"type":"array","items":{"type":"string","enum":["course_progress","certificates"]}},"status":{"type":"string","enum":["active","removed"]},"linked_since":{"type":"string","format":"date-time"},"shared_since":{"type":["string","null"],"format":"date-time"},"last_shared_at":{"type":["string","null"],"format":"date-time"},"publishing_enabled":{"type":"boolean"}}},"MyIntegrationList":{"type":"object","title":"MyIntegrationList","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/MyIntegration"}}}},"SessionsRevoked":{"type":"object","title":"SessionsRevoked","required":["revoked"],"properties":{"revoked":{"type":"integer","description":"Cuántas sesiones se cerraron."}}},"TwoFactorStatus":{"type":"object","title":"TwoFactorStatus","required":["enabled","recovery_codes_remaining","required"],"properties":{"enabled":{"type":"boolean","description":"TOTP confirmado."},"enabled_at":{"type":["string","null"],"format":"date-time"},"recovery_codes_remaining":{"type":"integer","description":"Códigos de recuperación sin gastar (0 si no hay 2FA)."},"required":{"type":"boolean","description":"La política se lo exige: admin de plataforma con `PLATFORM_REQUIRE_TWO_FACTOR` (sin él no escribe en ops) u owner/admin activo de una institución con `require_admin_two_factor` (EDFRO-84; sin él no administra esa institución). Para el resto es opcional."}}},"TwoFactorSetup":{"type":"object","title":"TwoFactorSetup","description":"Alta empezada: el secreto (para teclearlo), la URI `otpauth://` (un toque en el móvil) y su QR en SVG. Se confirma con `POST /me/two-factor/enable`. Solo se enseña aquí.","required":["secret","otpauth_uri","qr_svg"],"properties":{"secret":{"type":"string","description":"Secreto TOTP en base32."},"otpauth_uri":{"type":"string"},"qr_svg":{"type":"string","description":"El QR de `otpauth_uri` como documento SVG."}}},"TwoFactorCode":{"type":"object","title":"TwoFactorCode","required":["code"],"properties":{"code":{"type":"string","minLength":6,"maxLength":32,"description":"Código TOTP de 6 dígitos o, donde se admita, un código de recuperación (`xxxxx-xxxxx`)."}}},"RecoveryCodes":{"type":"object","title":"RecoveryCodes","description":"Códigos de recuperación en claro. Solo se enseñan en esta respuesta; en la base, su SHA-256.","required":["codes"],"properties":{"codes":{"type":"array","items":{"type":"string"}}}},"StepUpChallenge":{"type":"object","title":"StepUpChallenge","required":["method"],"properties":{"method":{"type":"string","enum":["totp","email_code"],"description":"`totp` si la cuenta tiene 2FA; si no, `email_code` (acaba de salir un código a su email)."}}},"StepUpVerify":{"type":"object","title":"StepUpVerify","required":["code"],"properties":{"code":{"type":"string","minLength":6,"maxLength":32}}},"EmailChangeRequest":{"type":"object","title":"EmailChangeRequest","additionalProperties":false,"required":["new_email"],"properties":{"new_email":{"type":"string","minLength":3,"maxLength":255}}},"EmailChangeRequested":{"type":"object","title":"EmailChangeRequested","required":["status"],"properties":{"status":{"type":"string","enum":["sent"]}}},"EmailChangeVerify":{"type":"object","title":"EmailChangeVerify","additionalProperties":false,"required":["new_email","code"],"properties":{"new_email":{"type":"string","minLength":3,"maxLength":255},"code":{"type":"string","pattern":"^[0-9]{6}$"}}},"EmailChanged":{"type":"object","title":"EmailChanged","required":["email","revoked_sessions"],"properties":{"email":{"type":"string"},"revoked_sessions":{"type":"integer","description":"Sesiones cerradas en los demás aparatos."}}},"Onboarding":{"type":"object","title":"Onboarding","required":["completed","name","terms_version","privacy_version"],"properties":{"completed":{"type":"boolean","description":"Onboarding hecho Y «Soy mayor de 18 años» declarado (ADR 0036). Si es `false`, la web lleva a `/onboarding`."},"adult_declared":{"type":"boolean","default":false,"description":"¿Ha declarado la persona que es mayor de 18 años? (ADR 0036). Sin ello `completed` es false y la web la lleva al onboarding."},"terms_version":{"type":"string","description":"Versión vigente de los términos con la que se registra la declaración."},"privacy_version":{"type":"string","description":"Versión vigente de la política de privacidad. Terminar el onboarding registra la aceptación de esta versión y de `terms_version` (PRIV-04); la web las enseña junto al botón de continuar."},"completed_at":{"type":["string","null"],"format":"date-time"},"name":{"type":"string"},"locale":{"type":["string","null"],"enum":["es","en",null],"description":"Idioma elegido; `null` si nunca lo eligió."},"first_steps_done":{"type":"array","description":"Primeros pasos por rol que la persona ya hizo o marcó (EDFRO-69).","items":{"type":"string","enum":["perfil","primer_curso","cohortes","invitar_alumnos","datos_institucion","marca","invitar_docentes"]}},"first_steps_dismissed":{"type":"boolean","default":false,"description":"¿Ocultó la lista de primeros pasos del panel?"}}},"OnboardingComplete":{"type":"object","title":"OnboardingComplete","description":"Lo que pide el primer acceso: nombre, idioma y «Soy mayor de 18 años» (ADR 0036). `adult_confirmed` es opcional en el schema (compatible en v1), pero sin `true` la respuesta es `422 adult_declaration_required`.","required":["name","locale"],"properties":{"name":{"type":"string","minLength":1},"locale":{"type":"string","enum":["es","en"]},"adult_confirmed":{"type":"boolean","default":false,"description":"Casilla obligatoria «Soy mayor de 18 años». Se registra como consentimiento `adult_declaration` con la versión vigente de los términos."}}},"FirstStepsUpdate":{"type":"object","title":"FirstStepsUpdate","description":"Marca pasos hechos (se suman a los que había), devuelve pasos a pendientes y oculta o vuelve a enseñar la lista. Lo que no viene no cambia.","properties":{"done":{"type":["array","null"],"maxItems":20,"description":"Pasos que pasan a hechos (se suman).","items":{"type":"string","enum":["perfil","primer_curso","cohortes","invitar_alumnos","datos_institucion","marca","invitar_docentes"]}},"undone":{"type":["array","null"],"maxItems":20,"description":"Pasos que vuelven a pendientes.","items":{"type":"string","enum":["perfil","primer_curso","cohortes","invitar_alumnos","datos_institucion","marca","invitar_docentes"]}},"dismissed":{"type":["boolean","null"],"description":"true oculta la lista del panel; false la vuelve a enseñar."}}},"InvitationToken":{"type":"object","title":"InvitationToken","additionalProperties":false,"required":["token"],"properties":{"token":{"type":"string","minLength":16,"maxLength":128,"description":"Token del enlace del correo (va en el CUERPO, nunca en una URL del API)."}}},"InvitationCodeRequest":{"type":"object","title":"InvitationCodeRequest","additionalProperties":false,"required":["token"],"properties":{"token":{"type":"string","minLength":16,"maxLength":128},"locale":{"type":["string","null"],"enum":["es","en",null],"description":"Idioma del correo; por defecto, el de la cuenta o el de la organización."}}},"InvitationSignup":{"type":"object","title":"InvitationSignup","additionalProperties":false,"required":["token","code"],"properties":{"token":{"type":"string","minLength":16,"maxLength":128},"code":{"type":"string","pattern":"^[0-9]{6}$","description":"Código de 6 dígitos que llegó al email invitado."},"name":{"type":["string","null"],"description":"Nombre para la cuenta nueva (si ya existe, se ignora)."}}},"InvitationOrganization":{"type":"object","title":"InvitationOrganization","description":"Lo único que se enseña de la organización antes de entrar.","required":["name"],"properties":{"name":{"type":"string"},"logo_url":{"type":["string","null"]},"brand_color":{"type":["string","null"]}}},"InvitationPreview":{"type":"object","title":"InvitationPreview","required":["organization","role","email_hint","expires_at","has_account"],"properties":{"organization":{"$ref":"#/components/schemas/InvitationOrganization"},"role":{"type":"string","enum":["owner","admin","teacher","student"]},"email_hint":{"type":"string","description":"Email invitado enmascarado (`a***@colegio.es`)."},"expires_at":{"type":"string","format":"date-time"},"has_account":{"type":"boolean","description":"El email invitado ya tiene cuenta (no hace falta pedir nombre)."},"cohort_name":{"type":["string","null"],"description":"Nombre de la cohorte a la que se une al aceptar, si la invitación la trae (y la cohorte sigue siendo de esta organización). EDFRO-57."}}},"ImpersonationStatus":{"type":"object","title":"ImpersonationStatus","description":"Para el banner de la web. Nunca lleva el motivo.","required":["active"],"properties":{"active":{"type":"boolean"},"impersonator_name":{"type":["string","null"]},"expires_at":{"type":["string","null"],"format":"date-time"}}},"Institute":{"type":"object","title":"Institute","description":"Institución educativa propietaria de cohortes.","required":["id","name","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","examples":["IES Ramón y Cajal"]},"city":{"type":["string","null"],"examples":["Madrid"]},"contact_email":{"type":["string","null"],"format":"email"},"website":{"type":["string","null"],"format":"uri"},"cohorts_count":{"type":"integer","description":"Número de cohortes de la institución.","examples":[4]},"created_at":{"type":"string","format":"date-time"}}},"Cohort":{"type":"object","title":"Cohort","description":"Grupo-clase de una institución, con ventana de fechas.","required":["id","name","institute_id","start_date","end_date","active"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","examples":["Promoción 2026 — DAW"]},"institute_id":{"type":"string","format":"uuid","description":"Instituto heredado de la cohorte. Desde EDAPI-21 la cohorte es de su organización y el instituto es opcional: si no tiene, aquí va el id de la organización (y en `institute_name`, su nombre)."},"institute_name":{"type":["string","null"],"description":"Nombre de la institución (desnormalizado para listados).","examples":["IES Ramón y Cajal"]},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"active":{"type":"boolean"},"students_count":{"type":"integer","description":"Número de estudiantes asignados.","examples":[28]},"created_at":{"type":"string","format":"date-time"}}},"Course":{"type":"object","title":"Course","description":"Curso publicable del catálogo.","required":["id","title","slug","level","xp","published"],"properties":{"id":{"type":"string","format":"uuid"},"legacy_id":{"type":["string","null"],"description":"Identificador legacy (slug/entero) preservado en migración."},"title":{"type":"string","examples":["Introducción a Python"]},"slug":{"type":"string","examples":["intro-to-python"]},"description":{"type":["string","null"]},"level":{"type":"string","description":"Nivel del curso.","enum":["Principiante","Intermedio","Avanzado"],"examples":["Principiante"]},"duration":{"type":["string","null"],"description":"Duración legible (p. ej. \"6 horas\")."},"xp":{"type":"integer","examples":[500]},"instructor_id":{"type":["string","null"],"format":"uuid"},"published":{"type":"boolean"},"visibility":{"type":"string","enum":["global","org_private"],"description":"Origen del curso: `global` = catálogo global de Valfiguer (lo ve cualquier organización); `org_private` = curso privado de la organización de la ruta. Opcional por compatibilidad (las rutas heredadas lo pueden omitir).","examples":["global"]},"cover_image_url":{"type":["string","null"],"format":"uri"},"cert_enabled":{"type":"boolean","description":"Si el curso emite certificado al aprobar su examen."},"cert_title":{"type":["string","null"],"description":"Título del certificado (si difiere del título del curso)."},"tags":{"type":"array","description":"Etiquetas del curso.","items":{"type":"string"},"examples":[["python","backend"]]},"lessons_count":{"type":"integer","examples":[12]},"created_at":{"type":"string","format":"date-time"},"base_lang":{"type":"string","description":"Idioma en el que está escrito el curso (`es`|`en`)."},"lang":{"type":"string","description":"Idioma en el que sale el contenido (X-Lang > Accept-Language > idioma de la persona > es), `es`|`en`."},"fallback":{"type":"boolean","description":"Algún campo no tenía traducción servible y salió en el idioma base del curso."},"content_lang":{"type":"string","description":"Idioma en el que sale el contenido de las lecciones (`es`|`en`) — el pedido si todas están traducidas; si no, el base."},"cover_image_alt":{"type":["string","null"],"description":"Texto alternativo de la portada."}}},"Badge":{"type":"object","title":"Badge","description":"Insignia otorgable por logros.","required":["id","name","rarity"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","examples":["Primeros pasos"]},"description":{"type":["string","null"]},"icon":{"type":["string","null"],"description":"Nombre del icono de lucide (p. ej. `trophy`)."},"color":{"type":"string","description":"Color hex de la insignia.","examples":["#6366f1"]},"rarity":{"type":"string","enum":["common","uncommon","rare","epic","legendary"],"examples":["rare"]},"requirement":{"type":["string","null"],"description":"Descripción legible del requisito para obtenerla."},"hidden":{"type":"boolean","description":"Insignia secreta (no se muestra hasta obtenerla)."},"earned":{"type":"boolean","description":"Si el usuario autenticado la ha obtenido (solo con sesión)."},"awarded_at":{"type":["string","null"],"format":"date-time","description":"Cuándo la obtuvo el usuario autenticado; `null` si no."},"scope":{"type":"string","description":"`global` (catálogo de Valfiguer) u `organization` (propia de la organización, EDAPI-178).","examples":["global"]}}},"Certificate":{"type":"object","title":"Certificate","description":"Certificado emitido a un usuario por aprobar un curso o programa.","required":["id","user_id","verification_code","student_name","course_title","score","type","issued_at"],"properties":{"id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"course_id":{"type":["string","null"],"format":"uuid","description":"Curso certificado; `null` para certificados de programa."},"path_id":{"type":["string","null"],"format":"uuid","description":"Ruta/programa certificado; `null` para certificados de curso."},"type":{"type":"string","enum":["course","program"],"examples":["course"]},"verification_code":{"type":"string","description":"Código público de verificación (`EDU-XXXX-XXXX`; los `3XA-` legados se conservan, ADR 0014).","examples":["3XA-7K2M-9QWE"]},"student_name":{"type":"string"},"course_title":{"type":"string"},"score":{"type":"integer","description":"Puntuación del examen (0-100).","examples":[92]},"verify_url":{"type":["string","null"],"format":"uri","description":"URL pública de verificación del certificado."},"issued_at":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["active","revoked","replaced"],"default":"active","description":"Estado del certificado (revocación, docs/specs/credenciales.md §2)."},"credential_title":{"type":["string","null"],"description":"Título de la credencial tal como se emitió (snapshot)."},"revoked_at":{"type":["string","null"],"format":"date-time"}}},"PublicCertificateVerification":{"type":"object","title":"PublicCertificateVerification","description":"Lo mínimo para comprobar un certificado sin sesión. Nunca lleva email, ids internos ni nota. `holder_name` son las iniciales si el titular se trata como menor, y `null` si el certificado está revocado.","required":["status","verification_code","type","credential_title","issuer_name","issued_at","legacy"],"properties":{"status":{"type":"string","enum":["valid","revoked","replaced"]},"verification_code":{"type":"string","examples":["EDU-7K2M-9QWE"]},"type":{"type":"string","enum":["course","program"]},"holder_name":{"type":["string","null"]},"credential_title":{"type":"string"},"issuer_name":{"type":"string","description":"Emisor tal como quedó en el certificado («3XA Learn» en los legados sin emisor)."},"organization_name":{"type":["string","null"]},"issued_at":{"type":"string","format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"},"replaced_by_code":{"type":["string","null"],"description":"Código del certificado que lo sustituye (reemisión)."},"legacy":{"type":"boolean","description":"Emitido por 3XA (learn.3xa.es o EduRails antes del ADR 0014)."}}},"IssuedCertificate":{"type":"object","title":"IssuedCertificate","description":"Certificado emitido por la organización, visto por su owner/admin.","required":["id","user_id","type","verification_code","student_name","credential_title","status","issued_at"],"properties":{"id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["course","program"]},"course_id":{"type":["string","null"],"format":"uuid"},"path_id":{"type":["string","null"],"format":"uuid"},"verification_code":{"type":"string"},"student_name":{"type":"string"},"credential_title":{"type":"string"},"status":{"type":"string","enum":["active","revoked","replaced"]},"issued_at":{"type":"string","format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"}}},"IssuedCertificateList":{"type":"object","title":"IssuedCertificateList","required":["items","total"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/IssuedCertificate"}},"total":{"type":"integer"}}},"MyCertificateEverywhere":{"type":"object","title":"MyCertificateEverywhere","description":"Un certificado propio, de cualquiera de las organizaciones que lo emitieron (EDAPI-185).","required":["id","type","verification_code","student_name","credential_title","score","status","issued_at","organization_id","organization_name"],"properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["course","program"]},"course_id":{"type":["string","null"],"format":"uuid"},"path_id":{"type":["string","null"],"format":"uuid"},"verification_code":{"type":"string"},"student_name":{"type":"string"},"credential_title":{"type":"string"},"score":{"type":"integer"},"status":{"type":"string","enum":["active","revoked","replaced"]},"issued_at":{"type":"string","format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"},"organization_id":{"type":["string","null"],"description":"Organización que lo emitió (`null` en los migrados sin organización)."},"organization_name":{"type":["string","null"]}}},"MyCertificatesEverywhere":{"type":"object","title":"MyCertificatesEverywhere","required":["certificates"],"properties":{"certificates":{"type":"array","items":{"$ref":"#/components/schemas/MyCertificateEverywhere"}}}},"RevokeCertificateInput":{"type":"object","title":"RevokeCertificateInput","additionalProperties":false,"required":["reason"],"properties":{"reason":{"type":"string","minLength":3,"maxLength":500,"description":"Motivo (obligatorio). Queda en la auditoría y en el certificado; no se publica."}}},"ReissueCertificateInput":{"type":"object","title":"ReissueCertificateInput","additionalProperties":false,"required":["student_name","reason"],"properties":{"student_name":{"type":"string","minLength":1,"maxLength":255,"description":"Nombre corregido, tal como debe salir en el certificado nuevo."},"reason":{"type":"string","minLength":3,"maxLength":500,"description":"Motivo (queda en la auditoría)."}}},"ProgramCourseProgress":{"type":"object","title":"ProgramCourseProgress","required":["course_id","title","required","completed"],"properties":{"course_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"required":{"type":"boolean"},"completed":{"type":"boolean"}}},"ProgramCredentialRef":{"type":"object","title":"ProgramCredentialRef","required":["id","verification_code","status","issued_at"],"properties":{"id":{"type":"string","format":"uuid"},"verification_code":{"type":"string"},"status":{"type":"string","enum":["active","revoked","replaced"]},"issued_at":{"type":"string","format":"date-time"}}},"ProgramProgress":{"type":"object","title":"ProgramProgress","description":"Progreso de quien pregunta en un programa. Un curso cuenta como completado con la matrícula completada, el progreso al 100 % o un certificado de curso activo.","required":["path_id","title","awards_certificate","enrolled","courses","required_completed","required_total","has_capstone","capstone_passed","completed","status"],"properties":{"status":{"type":"string","enum":["not_started","in_progress","capstone_available","completed"],"description":"Estado (EDAPI-95): `completed` si cumple HOY la regla (un requerido añadido después lo reabre a `in_progress` sin tocar el certificado emitido); `capstone_available` con los requeridos hechos y el capstone pendiente; `in_progress` con matrícula o algún curso hecho."},"path_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"credential_title":{"type":["string","null"]},"awards_certificate":{"type":"boolean"},"enrolled":{"type":"boolean"},"courses":{"type":"array","items":{"$ref":"#/components/schemas/ProgramCourseProgress"}},"required_completed":{"type":"integer"},"required_total":{"type":"integer"},"has_capstone":{"type":"boolean"},"capstone_passed":{"type":"boolean"},"completed":{"type":"boolean"},"completed_at":{"type":["string","null"],"format":"date-time"},"certificate":{"oneOf":[{"$ref":"#/components/schemas/ProgramCredentialRef"},{"type":"null"}]}}},"OtpRequestResult":{"type":"object","title":"OtpRequestResult","description":"Respuesta genérica a la solicitud de código. Es idéntica exista o no el email (anti-enumeración): no permite descubrir qué correos están registrados.","required":["message"],"properties":{"message":{"type":"string","description":"Mensaje neutro para mostrar al usuario.","examples":["If that email is registered, a sign-in code has been sent."]}}},"AuthProviders":{"type":"object","title":"AuthProviders","description":"Métodos de acceso disponibles. `email_code` es siempre true; `google` y `github` dependen de que el proveedor OAuth tenga credenciales configuradas.","required":["email_code","google","github","sso"],"properties":{"email_code":{"type":"boolean","description":"Código de 6 dígitos por email (siempre disponible)."},"google":{"type":"boolean","description":"OAuth con Google configurado."},"github":{"type":"boolean","description":"OAuth con GitHub configurado."},"sso":{"type":"boolean","description":"SSO institucional (Google Workspace / Microsoft Entra) disponible en este despliegue (`SSO_ENABLED`). Qué organización lo usa se sabe con `POST /auth/sso/discover`."}}},"ResendWebhookAck":{"type":"object","title":"ResendWebhookAck","description":"El evento se aceptó. `duplicate` es true si esa entrega (`svix-id`) ya se había procesado.","required":["received"],"properties":{"received":{"type":"boolean"},"duplicate":{"type":"boolean","default":false}}},"Organization":{"type":"object","title":"Organization","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"kind":{"enum":["institute","company","community"],"type":"string"},"locale":{"enum":["es","en"],"type":"string"},"timezone":{"type":"string"},"country":{"type":["string","null"]},"city":{"type":["string","null"]},"contact_email":{"type":["string","null"]},"website":{"type":["string","null"]},"logo_url":{"type":["string","null"]},"brand_color":{"type":["string","null"]},"accent_color":{"type":["string","null"]},"plan":{"enum":["free","institution","custom"],"type":"string"},"suspended":{"type":"boolean"},"require_admin_two_factor":{"type":"boolean","description":"EDFRO-84. Owners y admins necesitan la verificación en dos pasos para administrar. Apagado de fábrica."},"created_at":{"format":"date-time","type":"string"}},"required":["id","name","slug","kind","locale","timezone","country","city","contact_email","website","logo_url","brand_color","accent_color","plan","suspended","require_admin_two_factor","created_at"]},"MyOrganization":{"type":"object","title":"MyOrganization","properties":{"organization":{"$ref":"#/components/schemas/Organization"},"role":{"enum":["owner","admin","teacher","student"],"type":"string"}},"required":["organization","role"]},"MyOrganizations":{"type":"object","title":"MyOrganizations","properties":{"organizations":{"items":{"$ref":"#/components/schemas/MyOrganization"},"type":"array"},"active_organization_id":{"type":["string","null"]},"platform_role":{"description":"Rol de plataforma de Valfiguer (`platform_admins`) o `null`. Presentación para el shell del producto (qué enlaces pintar); no autoriza nada por sí mismo.","enum":["admin","author","staff"],"type":["string","null"]}},"required":["organizations","active_organization_id","platform_role"]},"Member":{"type":"object","title":"Member","properties":{"user_id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"role":{"enum":["owner","admin","teacher","student"],"type":"string"},"status":{"enum":["active","suspended"],"type":"string"},"joined_at":{"format":"date-time","type":"string"}},"required":["user_id","name","email","role","status","joined_at"]},"OrganizationOverview":{"type":"object","title":"OrganizationOverview","properties":{"members":{"type":"integer","description":"Miembros activos (todos los roles)."},"learners":{"type":"integer","description":"Miembros activos con rol student."},"active_learners_7d":{"type":"integer","description":"Personas distintas que completaron al menos una lección en los últimos 7 días."},"completed_courses":{"type":"integer","description":"Cursos terminados en esta organización."},"certificates_issued":{"type":"integer","description":"Certificados emitidos por la organización y no revocados."},"active_cohorts":{"type":"integer"},"pending_invites":{"type":"integer"},"weekly_activity":{"type":"array","description":"Las últimas 8 semanas, de la más antigua a la actual (lunes UTC).","items":{"$ref":"#/components/schemas/OverviewWeek"}}},"required":["members","learners","active_learners_7d","completed_courses","certificates_issued","active_cohorts","pending_invites","weekly_activity"]},"OverviewWeek":{"type":"object","title":"OverviewWeek","properties":{"week_start":{"type":"string","format":"date"},"lessons_completed":{"type":"integer"},"active_learners":{"type":"integer"}},"required":["week_start","lessons_completed","active_learners"]},"MemberStatusUpdate":{"type":"object","title":"MemberStatusUpdate","additionalProperties":false,"properties":{"status":{"enum":["active","suspended"],"type":"string"}},"required":["status"]},"OrganizationFeatureState":{"type":"object","title":"OrganizationFeatureState","properties":{"key":{"enum":["social","ai_tutor","gamification","mcp"],"type":"string","description":"`mcp`: tokens de acceso personales, OAuth y el servidor MCP (A2c). Apagada de fábrica; con ella apagada ningún Bearer autentica en la organización."},"enabled":{"type":"boolean","description":"Interruptor de la organización."},"allowed":{"type":"boolean","description":"Plataforma (ops) la permite en esta organización."},"active":{"type":"boolean","description":"allowed Y enabled."}},"required":["key","enabled","allowed","active"]},"MyGamification":{"type":"object","title":"MyGamification","description":"XP, nivel (250 XP por nivel), umbrales, racha vigente en el día de la organización e interruptores que aplican a la persona en esta organización. La XP es global por persona (ADR 0030): es la misma en todas sus organizaciones.","required":["xp","level","xp_per_level","level_start_xp","next_level_xp","xp_into_level","xp_to_next_level","streak","streak_active_today","streak_at_risk","last_activity","timezone","gamification_enabled","social_enabled","leaderboard_identity"],"properties":{"xp":{"type":"integer","minimum":0,"description":"XP acumulada."},"level":{"type":"integer","minimum":1,"description":"Nivel (`1 + xp // 250`)."},"xp_per_level":{"type":"integer","description":"XP que hace falta para subir un nivel (250)."},"level_start_xp":{"type":"integer","description":"XP con la que empieza el nivel actual."},"next_level_xp":{"type":"integer","description":"XP con la que empieza el siguiente nivel."},"xp_into_level":{"type":"integer","minimum":0,"description":"XP ganada dentro del nivel actual."},"xp_to_next_level":{"type":"integer","minimum":1,"description":"XP que falta para el siguiente nivel."},"streak":{"type":"integer","minimum":0,"description":"Racha vigente: días seguidos con actividad contando hoy o ayer en la zona de la organización; 0 si se rompió."},"streak_active_today":{"type":"boolean","description":"Ya hubo actividad hoy."},"streak_at_risk":{"type":"boolean","description":"Racha viva sin actividad hoy (se pierde a medianoche)."},"last_activity":{"type":["string","null"],"format":"date-time"},"timezone":{"type":"string","description":"Zona IANA con la que se cuenta el día (la de la organización).","examples":["Europe/Madrid"]},"gamification_enabled":{"type":"boolean","description":"La gamificación está activa en la organización (interruptor de la institución Y permiso de ops). Apagada, sus rutas responden 404 `feature_disabled`; la XP se sigue acumulando."},"social_enabled":{"type":"boolean","description":"Funciones sociales (feed, duelos, valoraciones) disponibles para ESTA persona: activas en la organización y la persona no se trata como menor."},"leaderboard_identity":{"type":"string","enum":["name","alias"],"description":"Cómo aparece en los rankings (`alias` si se trata como menor, sin foto)."},"leaderboard_opt_out":{"type":"boolean","description":"La persona no sale en los rankings ante los demás (EDAPI-213); sigue viendo su posición. Vale en todas sus organizaciones."}}},"MyGamificationPreferences":{"type":"object","title":"MyGamificationPreferences","description":"Preferencias de gamificación de la persona. Los campos ausentes no cambian.","additionalProperties":false,"properties":{"leaderboard_opt_out":{"type":["boolean","null"],"description":"`true`: no salir en los rankings ante los demás (se sigue viendo la posición propia)."}}},"OrganizationFeatureStates":{"type":"object","title":"OrganizationFeatureStates","properties":{"features":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationFeatureState"}}},"required":["features"]},"OrganizationFeaturesUpdate":{"type":"object","title":"OrganizationFeaturesUpdate","additionalProperties":false,"properties":{"social":{"type":["boolean","null"]},"ai_tutor":{"type":["boolean","null"]},"gamification":{"type":["boolean","null"]},"mcp":{"type":["boolean","null"]}}},"CatalogToggle":{"type":"object","title":"CatalogToggle","additionalProperties":false,"properties":{"enabled":{"type":"boolean"}},"required":["enabled"]},"SsoDiscoverRequest":{"type":"object","title":"SsoDiscoverRequest","additionalProperties":false,"required":["email"],"properties":{"email":{"type":"string","minLength":3,"maxLength":255,"description":"Email que se va a usar para entrar. Solo se mira su dominio."}}},"SsoDiscovery":{"type":"object","title":"SsoDiscovery","description":"¿Entra este email por el SSO de su institución? Se decide por DOMINIO verificado, nunca por la cuenta: no revela si el email tiene cuenta.","required":["sso","enforced"],"properties":{"sso":{"type":"boolean"},"enforced":{"type":"boolean","description":"Con `true`, el código por email y Google/GitHub no valen para este dominio."},"connection_id":{"type":["string","null"]},"provider":{"type":["string","null"],"enum":["google","microsoft",null]},"organization_name":{"type":["string","null"]},"start_url":{"type":["string","null"],"description":"Ruta (del API) que lleva al IdP."}}},"SsoDomainCreate":{"type":"object","title":"SsoDomainCreate","additionalProperties":false,"required":["domain"],"properties":{"domain":{"type":"string","minLength":3,"maxLength":253,"description":"Dominio de email de la institución (`colegio.es`). Los de correo público se rechazan (`domain_not_allowed`)."}}},"SsoDomainUpdate":{"type":"object","title":"SsoDomainUpdate","additionalProperties":false,"required":["auto_join"],"properties":{"auto_join":{"type":"boolean","description":"Encender exige el dominio verificado (409 `domain_not_verified`)."}}},"SsoDomain":{"type":"object","title":"SsoDomain","required":["id","domain","verified","verified_at","last_checked_at","txt_record_name","txt_record_value","auto_join"],"properties":{"id":{"type":"string"},"domain":{"type":"string"},"verified":{"type":"boolean"},"auto_join":{"type":"boolean","description":"Unión automática (EDAPI-41): con el dominio verificado, quien entra con código, Google o GitHub con un email de él queda unido a la organización como `student`. Apagada de fábrica."},"verified_at":{"type":["string","null"],"format":"date-time"},"last_checked_at":{"type":["string","null"],"format":"date-time"},"txt_record_name":{"type":"string","description":"Nombre del registro TXT que publica el centro (`_edurails-verification.<dominio>`)."},"txt_record_value":{"type":"string","description":"Valor del registro TXT (`edurails-verification=<token>`)."}}},"SsoConnectionUpsert":{"type":"object","title":"SsoConnectionUpsert","additionalProperties":false,"required":["provider","tenant","client_id"],"properties":{"provider":{"type":"string","enum":["google","microsoft"]},"tenant":{"type":"string","minLength":3,"maxLength":255,"description":"Google: dominio de Workspace (claim `hd`). Microsoft: id (GUID) del tenant de Entra (claim `tid`)."},"client_id":{"type":"string","minLength":3,"maxLength":255},"client_secret":{"type":["string","null"],"minLength":3,"maxLength":2048,"description":"Solo escritura (se guarda cifrado). Obligatorio al crear; si no llega, se conserva el guardado."},"default_role":{"type":"string","enum":["student","teacher"],"default":"student","description":"Rol con el que entra quien llega por primera vez (JIT)."},"enabled":{"type":"boolean","default":false}}},"SsoConnection":{"type":"object","title":"SsoConnection","required":["id","provider","tenant","client_id","has_client_secret","default_role","enabled","enforced","tested_at","last_login_at","updated_at"],"properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["google","microsoft"]},"tenant":{"type":"string"},"client_id":{"type":"string"},"has_client_secret":{"type":"boolean","description":"El secreto nunca vuelve; solo si hay uno guardado."},"default_role":{"type":"string","enum":["student","teacher"]},"enabled":{"type":"boolean"},"enforced":{"type":"boolean","description":"SSO obligatorio para los dominios verificados (salvo los owners, cuenta de emergencia)."},"tested_at":{"type":["string","null"],"format":"date-time"},"last_login_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"SsoEnforcementUpdate":{"type":"object","title":"SsoEnforcementUpdate","additionalProperties":false,"required":["enforced"],"properties":{"enforced":{"type":"boolean"}}},"SsoConfig":{"type":"object","title":"SsoConfig","required":["available","redirect_uri","connection","domains"],"properties":{"available":{"type":"boolean","description":"SSO disponible en este despliegue (`SSO_ENABLED`)."},"redirect_uri":{"type":"string","description":"URL de callback que se registra en el IdP (la misma para todas las organizaciones)."},"connection":{"oneOf":[{"$ref":"#/components/schemas/SsoConnection"},{"type":"null"}]},"domains":{"type":"array","items":{"$ref":"#/components/schemas/SsoDomain"}}}},"SsoTestStart":{"type":"object","title":"SsoTestStart","required":["authorize_url"],"properties":{"authorize_url":{"type":"string","description":"URL del IdP para la prueba; al volver, `/org/sso?test=ok` o `/login?error=<code>`."}}},"OrganizationCatalogItem":{"type":"object","title":"OrganizationCatalogItem","properties":{"id":{"type":"string"},"kind":{"enum":["course","program"],"type":"string"},"title":{"type":"string"},"slug":{"type":"string"},"level":{"type":["string","null"]},"lang":{"type":["string","null"]},"enabled":{"type":"boolean"},"enrolled":{"type":"integer","description":"Matrículas (curso) o inscripciones (programa) activas de ESTA organización."}},"required":["id","kind","title","slug","level","lang","enabled","enrolled"]},"OrganizationCatalog":{"type":"object","title":"OrganizationCatalog","properties":{"global_catalog_enabled":{"type":"boolean","description":"Interruptor de ops; apagado, ningún curso global existe para la organización."},"courses":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationCatalogItem"}},"programs":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationCatalogItem"}},"private_courses":{"type":"integer"},"private_courses_limit":{"type":"integer"}},"required":["global_catalog_enabled","courses","programs","private_courses","private_courses_limit"]},"ModerationReportCreate":{"type":"object","title":"ModerationReportCreate","additionalProperties":false,"description":"`target_type=feed_item` exige `feed_item_id`; `target_type=user` exige `user_id`. `inappropriate_avatar` solo vale para personas y retira su avatar hasta que se revise.","properties":{"target_type":{"$ref":"#/components/schemas/ModerationTargetType"},"feed_item_id":{"type":["integer","null"],"description":"Id de la entrada del feed tal como la devuelve `…/social/feed`."},"user_id":{"type":["string","null"],"minLength":1,"maxLength":36,"description":"Persona denunciada (miembro de la organización)."},"reason":{"$ref":"#/components/schemas/ModerationReason"},"context":{"description":"Dónde lo vio quien denuncia (contexto para la cola).","oneOf":[{"$ref":"#/components/schemas/ModerationContext"},{"type":"null"}]}},"required":["reason"]},"ModerationReport":{"type":"object","title":"ModerationReport","properties":{"id":{"type":"string"},"target_type":{"$ref":"#/components/schemas/ModerationTargetType"},"feed_item_id":{"type":["integer","null"],"description":"Entrada denunciada (NULL en una denuncia de persona)."},"target_user_id":{"type":["string","null"],"description":"Persona afectada (autor de la entrada o persona denunciada)."},"reason":{"$ref":"#/components/schemas/ModerationReason"},"context":{"oneOf":[{"$ref":"#/components/schemas/ModerationContext"},{"type":"null"}]},"status":{"$ref":"#/components/schemas/ModerationStatus"},"decision":{"description":"Qué se hizo al resolverla (NULL mientras está abierta).","oneOf":[{"$ref":"#/components/schemas/ModerationAction"},{"type":"null"}]},"event_type":{"type":["string","null"],"description":"Tipo del evento denunciado (qué se ve en el feed); NULL en una denuncia de persona."},"author_name":{"type":"string","description":"Persona afectada (miembro de la organización). Quién denuncia no viaja nunca."},"reports_count":{"type":"integer","description":"Denuncias del mismo objetivo en la organización."},"avatar_withheld":{"type":"boolean","description":"Si esta denuncia retiró el avatar de la persona hasta revisarla."},"escalated_at":{"format":"date-time","type":["string","null"]},"escalation_reason":{"oneOf":[{"$ref":"#/components/schemas/EscalationReason"},{"type":"null"}]},"resolved_by_platform":{"type":"boolean"},"created_at":{"format":"date-time","type":"string"},"resolved_at":{"format":"date-time","type":["string","null"]}},"required":["id","target_type","feed_item_id","target_user_id","reason","context","status","decision","event_type","author_name","reports_count","avatar_withheld","escalated_at","escalation_reason","resolved_by_platform","created_at","resolved_at"]},"ModerationReportList":{"type":"object","title":"ModerationReportList","properties":{"reports":{"type":"array","items":{"$ref":"#/components/schemas/ModerationReport"}},"total":{"type":"integer"}},"required":["reports","total"]},"ModerationResolve":{"type":"object","title":"ModerationResolve","additionalProperties":false,"description":"`days` (1-90) es obligatorio con `suspend_social` y no vale con otra acción.","properties":{"action":{"$ref":"#/components/schemas/ModerationAction"},"days":{"type":["integer","null"],"minimum":1,"maximum":90,"description":"Días de suspensión (solo `suspend_social`)."}},"required":["action"]},"ModerationEscalate":{"type":"object","title":"ModerationEscalate","additionalProperties":false,"properties":{"reason":{"$ref":"#/components/schemas/EscalationReason"}},"required":["reason"]},"ModerationTargetType":{"type":"string","title":"ModerationTargetType","enum":["feed_item","user"]},"ModerationReason":{"type":"string","title":"ModerationReason","enum":["inappropriate","bullying","personal_data","spam","impersonation","inappropriate_avatar","other"]},"ModerationStatus":{"type":"string","title":"ModerationStatus","description":"`open` pendiente; `escalated` pasada a plataforma (sigue en la cola de la org); `hidden`, `dismissed`, `actioned` y `restored` resueltas.","enum":["open","escalated","hidden","dismissed","actioned","restored"]},"ModerationAction":{"type":"string","title":"ModerationAction","enum":["hide","dismiss","restore","warn","suspend_social","reset_avatar","suspend_account"]},"ModerationContext":{"type":"string","title":"ModerationContext","enum":["feed","duel","profile","ranking","directory"]},"EscalationReason":{"type":"string","title":"EscalationReason","enum":["minor_safety","illegal_content","cross_organization","other"]},"MyModerationReport":{"type":"object","title":"MyModerationReport","properties":{"id":{"type":"string"},"target_type":{"$ref":"#/components/schemas/ModerationTargetType"},"reason":{"$ref":"#/components/schemas/ModerationReason"},"outcome":{"type":"string","enum":["pending","reviewed"],"description":"Si ya se revisó; nunca qué se decidió."},"created_at":{"type":"string","format":"date-time"},"reviewed_at":{"type":["string","null"],"format":"date-time"}},"required":["id","target_type","reason","outcome","created_at","reviewed_at"]},"MyModerationReportList":{"type":"object","title":"MyModerationReportList","properties":{"reports":{"type":"array","items":{"$ref":"#/components/schemas/MyModerationReport"}},"total":{"type":"integer"}},"required":["reports","total"]},"UserBlock":{"type":"object","title":"UserBlock","properties":{"user_id":{"type":"string"},"display_name":{"type":"string","description":"Nombre, o alias `#XXXX` si la persona es menor."},"created_at":{"type":"string","format":"date-time"}},"required":["user_id","display_name","created_at"]},"ModerationSanction":{"type":"object","title":"ModerationSanction","description":"Aviso o suspensión de lo social de una persona en la organización (E0.5b).","properties":{"id":{"type":"string"},"user_id":{"type":"string"},"display_name":{"type":"string","description":"Nombre, o alias `#XXXX` si la persona es menor."},"kind":{"type":"string","enum":["social_suspension","warning"]},"ends_at":{"type":["string","null"],"format":"date-time","description":"Fin de la suspensión; `null` en un aviso."},"active":{"type":"boolean","description":"Suspensión en vigor ahora mismo."},"report_id":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"}},"required":["id","user_id","display_name","kind","ends_at","active","report_id","created_at"]},"ModerationSanctionList":{"type":"object","title":"ModerationSanctionList","properties":{"sanctions":{"type":"array","items":{"$ref":"#/components/schemas/ModerationSanction"}},"total":{"type":"integer"}},"required":["sanctions","total"]},"UserBlockList":{"type":"object","title":"UserBlockList","properties":{"blocks":{"type":"array","items":{"$ref":"#/components/schemas/UserBlock"}},"total":{"type":"integer"}},"required":["blocks","total"]},"MemberList":{"type":"object","title":"MemberList","properties":{"members":{"items":{"$ref":"#/components/schemas/Member"},"type":"array"},"total":{"type":"integer"}},"required":["members","total"]},"OrganizationRole":{"type":"object","title":"OrganizationRole","properties":{"role":{"enum":["owner","admin","teacher","student"],"type":"string"},"rank":{"type":"integer"}},"required":["role","rank"]},"OrganizationRoles":{"type":"object","title":"OrganizationRoles","properties":{"roles":{"items":{"$ref":"#/components/schemas/OrganizationRole"},"type":"array"}},"required":["roles"]},"Invite":{"type":"object","title":"Invite","properties":{"id":{"type":"string"},"email":{"type":"string"},"role":{"enum":["owner","admin","teacher","student"],"type":"string"},"cohort_id":{"type":["string","null"]},"expires_at":{"format":"date-time","type":"string"},"sent_at":{"format":"date-time","type":["string","null"]},"created_at":{"format":"date-time","type":"string"}},"required":["id","email","role","cohort_id","expires_at","sent_at","created_at"]},"InviteList":{"type":"object","title":"InviteList","properties":{"invites":{"items":{"$ref":"#/components/schemas/Invite"},"type":"array"}},"required":["invites"]},"MyInvitation":{"type":"object","title":"MyInvitation","properties":{"id":{"type":"string"},"organization_id":{"type":"string"},"organization_name":{"type":"string"},"role":{"enum":["owner","admin","teacher","student"],"type":"string"},"expires_at":{"format":"date-time","type":"string"}},"required":["id","organization_id","organization_name","role","expires_at"]},"MyInvitations":{"type":"object","title":"MyInvitations","properties":{"invitations":{"items":{"$ref":"#/components/schemas/MyInvitation"},"type":"array"}},"required":["invitations"]},"MemberImportRow":{"type":"object","title":"MemberImportRow","properties":{"line":{"type":"integer"},"email":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"},"cohort":{"type":"string"},"status":{"enum":["ok","skipped","error"],"type":"string"},"reason":{"type":["string","null"]}},"required":["line","email","name","role","cohort","status","reason"]},"MemberImport":{"type":"object","title":"MemberImport","properties":{"id":{"type":["string","null"]},"dry_run":{"type":"boolean"},"status":{"enum":["queued","running","done","failed"],"type":["string","null"]},"total_rows":{"type":"integer"},"valid_rows":{"type":"integer"},"processed_rows":{"type":"integer"},"invited_count":{"type":"integer"},"skipped_count":{"type":"integer"},"rows":{"items":{"$ref":"#/components/schemas/MemberImportRow"},"type":"array"}},"required":["id","dry_run","status","total_rows","valid_rows","processed_rows","invited_count","skipped_count","rows"]},"PlanLimits":{"type":"object","title":"PlanLimits","properties":{"members":{"type":"integer"},"private_courses":{"type":"integer"},"storage_mb":{"type":"integer"}},"required":["members","private_courses","storage_mb"]},"PlanUsage":{"type":"object","title":"PlanUsage","properties":{"plan":{"enum":["free","institution","custom"],"type":"string"},"limits":{"$ref":"#/components/schemas/PlanLimits"},"members":{"type":"integer"},"pending_invites":{"type":"integer"},"private_courses":{"type":"integer"},"storage_mb":{"type":"integer","description":"MB subidos a los cursos privados de la organización (redondeado arriba), desde que existe el contador (EDAPI-276, `0053`)."},"override_expires_at":{"format":"date-time","type":["string","null"]}},"required":["plan","limits","members","pending_invites","private_courses","storage_mb","override_expires_at"]},"ActiveAnnouncement":{"type":"object","title":"ActiveAnnouncement","properties":{"id":{"type":"string"},"severity":{"type":"string","enum":["info","warning","critical"]},"title":{"type":"string"},"body":{"type":"string"},"starts_at":{"type":"string","format":"date-time"},"ends_at":{"type":["string","null"],"format":"date-time"}},"required":["id","severity","title","body","starts_at","ends_at"]},"ActiveAnnouncementList":{"type":"object","title":"ActiveAnnouncementList","properties":{"announcements":{"type":"array","items":{"$ref":"#/components/schemas/ActiveAnnouncement"}}},"required":["announcements"]},"OrganizationUpdate":{"type":"object","title":"OrganizationUpdate","additionalProperties":false,"properties":{"name":{"maxLength":255,"minLength":1,"type":["string","null"],"default":null},"locale":{"enum":["es","en"],"type":["string","null"],"default":null},"timezone":{"maxLength":64,"type":["string","null"],"default":null},"country":{"maxLength":2,"minLength":2,"type":["string","null"],"default":null},"city":{"maxLength":100,"type":["string","null"],"default":null},"contact_email":{"maxLength":255,"type":["string","null"],"default":null},"website":{"maxLength":255,"type":["string","null"],"default":null},"logo_url":{"maxLength":512,"type":["string","null"],"default":null},"brand_color":{"pattern":"^#[0-9A-Fa-f]{6}$","type":["string","null"],"default":null},"accent_color":{"pattern":"^#[0-9A-Fa-f]{6}$","type":["string","null"],"default":null},"require_admin_two_factor":{"type":["boolean","null"],"default":null}}},"ActiveOrganizationUpdate":{"type":"object","title":"ActiveOrganizationUpdate","additionalProperties":false,"properties":{"organization_id":{"maxLength":36,"minLength":1,"type":"string"}},"required":["organization_id"]},"MemberUpdate":{"type":"object","title":"MemberUpdate","additionalProperties":false,"properties":{"role":{"enum":["owner","admin","teacher","student"],"type":"string"}},"required":["role"]},"InviteCreate":{"type":"object","title":"InviteCreate","additionalProperties":false,"properties":{"email":{"maxLength":255,"type":"string"},"role":{"default":"student","enum":["owner","admin","teacher","student"],"type":"string"},"cohort_id":{"maxLength":36,"type":["string","null"],"default":null}},"required":["email"]},"AcceptInvitation":{"type":"object","title":"AcceptInvitation","additionalProperties":false,"properties":{"token":{"maxLength":128,"minLength":16,"type":"string"}},"required":["token"]},"ContentSearch":{"type":"object","title":"ContentSearch","properties":{"q":{"type":"string","description":"El texto buscado, normalizado (espacios de sobra fuera, máx. 100)."},"lang":{"type":"string","enum":["es","en"]},"courses":{"type":"array","items":{"$ref":"#/components/schemas/SearchCourseHit"}},"lessons":{"type":"array","items":{"$ref":"#/components/schemas/SearchLessonHit"}}},"required":["q","lang","courses","lessons"]},"SearchCourseHit":{"type":"object","title":"SearchCourseHit","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"],"default":null},"level":{"type":"string"},"scope":{"type":"string","enum":["global","org"],"description":"`org` = curso privado de la organización; `global` = catálogo Valfiguer."}},"required":["id","slug","title","level","scope"]},"SearchLessonHit":{"type":"object","title":"SearchLessonHit","properties":{"course_id":{"type":"string","format":"uuid"},"course_slug":{"type":"string"},"course_title":{"type":"string"},"lesson_id":{"type":"string","format":"uuid"},"lesson_number":{"type":"integer"},"title":{"type":"string"},"match":{"type":"string","enum":["title","content"],"description":"Dónde coincidió (el contenido solo se busca en cursos empezados o matriculados)."},"snippet":{"type":["string","null"],"default":null,"description":"Fragmento alrededor de la coincidencia (solo si fue en el contenido)."}},"required":["course_id","course_slug","course_title","lesson_id","lesson_number","title","match"]},"AvatarOptions":{"type":"object","title":"AvatarOptions","additionalProperties":false,"description":"El pepo de una persona: semilla + rasgos. `null` en un rasgo = lo decide la semilla (salvo `mask`, que sin elegir es «sin mascarilla»); `none` apaga un rasgo opcional (`facial_hair`, `accessories`, `mask`). Los valores válidos salen de `GET /api/v1/avatars/options`; cualquier otro es 422.","properties":{"seed":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,64}$","description":"Semilla aleatoria (nunca el nombre ni el email)."},"head":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$","description":"Cabeza y pelo (variante `head` del estilo)."},"expression":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$","description":"Expresión de la cara."},"facial_hair":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$","description":"Barba o bigote, o `none`."},"accessories":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$","description":"Gafas o parche, o `none`."},"mask":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$","description":"Mascarilla, o `none`."},"skin_color":{"type":["string","null"],"pattern":"^#?[0-9A-Fa-f]{6}$","description":"Tono de piel (de la paleta del estilo)."},"hair_color":{"type":["string","null"],"pattern":"^#?[0-9A-Fa-f]{6}$","description":"Color del pelo (de la paleta del estilo)."},"clothing_color":{"type":["string","null"],"pattern":"^#?[0-9A-Fa-f]{6}$","description":"Color de la ropa (de la paleta del estilo)."},"background":{"type":["string","null"],"maxLength":24,"pattern":"^[a-z]+-[0-9]{2,3}$","description":"Fondo, por nombre de token de `@edurails/design-tokens` (p. ej. `orange-100`)."}},"required":["seed"]},"MyAvatar":{"type":"object","title":"MyAvatar","additionalProperties":false,"description":"Mi pepo, con la ruta de su SVG y si hoy se muestra una foto en su lugar.","properties":{"seed":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,64}$"},"head":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$"},"expression":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$"},"facial_hair":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$"},"accessories":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$"},"mask":{"type":["string","null"],"maxLength":32,"pattern":"^[a-z][A-Za-z0-9]*$"},"skin_color":{"type":["string","null"],"pattern":"^#?[0-9A-Fa-f]{6}$"},"hair_color":{"type":["string","null"],"pattern":"^#?[0-9A-Fa-f]{6}$"},"clothing_color":{"type":["string","null"],"pattern":"^#?[0-9A-Fa-f]{6}$"},"background":{"type":["string","null"],"maxLength":24,"pattern":"^[a-z]+-[0-9]{2,3}$"},"svg_url":{"type":"string","description":"Ruta (relativa al host del API) del SVG, con la versión en la query."},"etag":{"type":"string","description":"ETag del SVG actual."},"photo_in_use":{"type":"boolean","description":"`true` si hoy se muestra una foto propia en lugar del pepo. Guardar el pepo (`PUT`) la deja de mostrar."},"updated_at":{"type":["string","null"],"format":"date-time"}},"required":["seed","svg_url","etag","photo_in_use"]},"AvatarBackground":{"type":"object","title":"AvatarBackground","properties":{"token":{"type":"string","description":"Nombre del token de color (`orange-100`…)."},"hex":{"type":"string","description":"Su valor, sin `#`."}},"required":["token","hex"]},"AvatarLicense":{"type":"object","title":"AvatarLicense","properties":{"name":{"type":"string"},"url":{"type":"string"},"creator":{"type":"string"},"source":{"type":"string"}},"required":["name","url","creator","source"]},"AvatarCatalog":{"type":"object","title":"AvatarCatalog","description":"Las opciones que existen en el estilo Open Peeps instalado en el servidor.","properties":{"style":{"type":"string","description":"Estilo (`open-peeps`)."},"engine_version":{"type":"string","description":"Versiones de `dicebear-core` y `dicebear-styles` que fijan el dibujo."},"variants":{"type":"object","description":"Rasgo → variantes (`head`, `expression`, `facial_hair`, `accessories`, `mask`).","additionalProperties":{"type":"array","items":{"type":"string"}}},"optional":{"type":"array","description":"Rasgos que admiten `none`.","items":{"type":"string"}},"colors":{"type":"object","description":"Color → valores hex sin `#` (`skin_color`, `hair_color`, `clothing_color`).","additionalProperties":{"type":"array","items":{"type":"string"}}},"backgrounds":{"type":"array","items":{"$ref":"#/components/schemas/AvatarBackground"}},"license":{"$ref":"#/components/schemas/AvatarLicense"}},"required":["style","engine_version","variants","optional","colors","backgrounds","license"]},"SupportRequestCreate":{"type":"object","title":"SupportRequestCreate","additionalProperties":false,"properties":{"category":{"type":"string","enum":["problem","question","account","feedback"],"description":"Algo no funciona, duda de uso, acceso y cuenta, o sugerencia."},"message":{"type":"string","minLength":10,"maxLength":4000,"description":"Lo que pasa, con las palabras de la persona (sin caracteres de control)."},"page_path":{"type":["string","null"],"maxLength":300,"description":"Ruta de la pantalla desde la que escribe. Query y fragmento se descartan."},"client_request_id":{"type":["string","null"],"maxLength":64,"description":"request_id de un error que la persona vio, si lo hay."},"web_version":{"type":["string","null"],"maxLength":64,"description":"Versión (SHA) de la web que usa."},"locale":{"type":"string","enum":["es","en"],"default":"es"}},"required":["category","message"]},"SupportRequestAccepted":{"type":"object","title":"SupportRequestAccepted","properties":{"reference":{"type":"string","description":"Referencia de la solicitud, para citarla en una respuesta."}},"required":["reference"]},"DemoRequestCreate":{"type":"object","title":"DemoRequestCreate","description":"Solicitud de demo de una institución.","additionalProperties":false,"required":["name","email","organization","kind"],"properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"Nombre de la persona de contacto."},"email":{"type":"string","format":"email","maxLength":254,"description":"Correo de contacto (se normaliza a minúsculas)."},"organization":{"type":"string","minLength":2,"maxLength":120,"description":"Nombre del centro, academia o empresa."},"kind":{"type":"string","enum":["fp","academy","company","other"],"description":"Tipo de institución."},"size":{"type":["string","null"],"maxLength":12,"description":"Alumnado aproximado, texto libre corto (opcional)."},"message":{"type":["string","null"],"maxLength":1500,"description":"Qué necesita (opcional)."},"locale":{"type":"string","enum":["es","en"],"default":"es","description":"Idioma en el que se rellenó el formulario."},"website":{"type":["string","null"],"maxLength":200,"description":"Campo trampa anti-spam: oculto para personas, debe llegar vacío u omitido. Relleno → 202 sin guardar nada."}}},"DemoRequestAccepted":{"type":"object","title":"DemoRequestAccepted","description":"Solicitud de demo aceptada.","required":["message"],"properties":{"message":{"type":"string","description":"Mensaje neutro de confirmación.","examples":["Demo request received."]}}},"PublicCourseSummary":{"type":"object","title":"PublicCourseSummary","description":"Curso del catálogo global, tal como se enseña en la web pública.","required":["slug","title","description","level","duration","xp","cover_image_url","tags","lessons_count","cert_enabled","updated_at"],"properties":{"slug":{"type":"string","examples":["claude-code"]},"title":{"type":"string"},"description":{"type":["string","null"]},"level":{"type":"string","enum":["Principiante","Intermedio","Avanzado"]},"duration":{"type":["string","null"],"description":"Duración declarada por el autor (texto libre del legado, p. ej. «2h 30min»)."},"xp":{"type":"integer"},"cover_image_url":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}},"lessons_count":{"type":"integer"},"cert_enabled":{"type":"boolean","description":"El curso emite certificado verificable al completarse."},"updated_at":{"type":"string","format":"date-time","description":"Última modificación (o creación); es el `lastModified` del sitemap."}}},"PublicLesson":{"type":"object","title":"PublicLesson","description":"Una lección del temario público (sin contenido).","required":["number","title","duration","module","module_name"],"properties":{"number":{"type":"integer"},"title":{"type":"string"},"duration":{"type":["string","null"]},"module":{"type":"integer"},"module_name":{"type":["string","null"]}}},"PublicCourseDetail":{"type":"object","title":"PublicCourseDetail","description":"Ficha pública de un curso global publicado, con su temario.","required":["course","lessons"],"properties":{"lang":{"type":"string","enum":["es","en"],"description":"Idioma en el que sale el contenido (X-Lang > Accept-Language > es)."},"fallback":{"type":"boolean","description":"Algún campo no tenía traducción servible y salió en el idioma base."},"course":{"$ref":"#/components/schemas/PublicCourseSummary"},"lessons":{"type":"array","items":{"$ref":"#/components/schemas/PublicLesson"}}}},"PublicCourseFilters":{"type":"object","title":"PublicCourseFilters","description":"Facetas del catálogo público (niveles y etiquetas presentes).","required":["levels","tags"],"properties":{"levels":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}}}},"PublicCourseList":{"type":"object","title":"PublicCourseList","description":"Cursos del catálogo global publicados, del más reciente al más antiguo.","required":["items","filters"],"properties":{"lang":{"type":"string","enum":["es","en"],"description":"Idioma en el que sale el contenido (X-Lang > Accept-Language > es)."},"fallback":{"type":"boolean","description":"Algún campo no tenía traducción servible y salió en el idioma base."},"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicCourseSummary"}},"filters":{"$ref":"#/components/schemas/PublicCourseFilters"}}},"PublicProgramSummary":{"type":"object","title":"PublicProgramSummary","description":"Programa (ruta de aprendizaje) global activo.","required":["slug","title","credential_title","description","icon","difficulty","estimated_hours","course_count","total_xp","awards_certificate","updated_at"],"properties":{"slug":{"type":"string","examples":["ia-aplicada"]},"title":{"type":"string"},"credential_title":{"type":["string","null"]},"description":{"type":["string","null"]},"icon":{"type":["string","null"]},"difficulty":{"type":"string","enum":["beginner","intermediate","advanced"]},"estimated_hours":{"type":"integer"},"course_count":{"type":"integer","description":"Cursos globales publicados del programa."},"total_xp":{"type":"integer"},"awards_certificate":{"type":"boolean"},"updated_at":{"type":"string","format":"date-time"}}},"PublicProgramDetail":{"type":"object","title":"PublicProgramDetail","description":"Ficha pública de un programa global activo, con sus cursos en orden.","required":["program","courses","has_capstone"],"properties":{"lang":{"type":"string","enum":["es","en"],"description":"Idioma en el que sale el contenido (X-Lang > Accept-Language > es)."},"fallback":{"type":"boolean","description":"Algún campo no tenía traducción servible y salió en el idioma base."},"program":{"$ref":"#/components/schemas/PublicProgramSummary"},"courses":{"type":"array","items":{"$ref":"#/components/schemas/PublicCourseSummary"}},"has_capstone":{"type":"boolean","description":"El programa cierra con un examen final (capstone)."}}},"PublicProgramList":{"type":"object","title":"PublicProgramList","description":"Programas globales activos, en su orden editorial.","required":["items"],"properties":{"lang":{"type":"string","enum":["es","en"],"description":"Idioma en el que sale el contenido (X-Lang > Accept-Language > es)."},"fallback":{"type":"boolean","description":"Algún campo no tenía traducción servible y salió en el idioma base."},"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicProgramSummary"}}}},"WebTelemetryIn":{"type":"object","title":"WebTelemetryIn","description":"Web Vitals y errores de una vista, con la ruta ya en plantilla.","additionalProperties":false,"required":["app","route"],"properties":{"app":{"type":"string","enum":["marketing","app","movil"],"description":"Superficie que mide (landing, producto o web móvil `/m`)."},"route":{"type":"string","minLength":1,"maxLength":120,"pattern":"^/[A-Za-z0-9/_\\-\\[\\]]*$","description":"Ruta con los identificadores sustituidos por su plantilla (`/curso/[slug]/leccion/[id]`). Sin query, sin fragmento y sin ids."},"vitals":{"type":"array","maxItems":8,"default":[],"items":{"$ref":"#/components/schemas/WebVitalIn"}},"errors":{"type":"array","maxItems":5,"default":[],"items":{"$ref":"#/components/schemas/WebClientErrorIn"}}}},"WebVitalIn":{"type":"object","title":"WebVitalIn","additionalProperties":false,"required":["name","value","rating"],"properties":{"name":{"type":"string","enum":["LCP","INP","CLS","FCP","TTFB"]},"value":{"type":"number","minimum":0,"maximum":3600000,"description":"Milisegundos (CLS sin unidad). Tope de una hora."},"rating":{"type":"string","enum":["good","needs-improvement","poor"]}}},"WebClientErrorIn":{"type":"object","title":"WebClientErrorIn","additionalProperties":false,"required":["kind","message"],"properties":{"kind":{"type":"string","enum":["error","unhandledrejection","console","boundary"],"description":"`error` (window.onerror), `unhandledrejection`, `console` (console.error) o `boundary` (error.tsx / global-error.tsx de Next)."},"message":{"type":"string","minLength":1,"maxLength":200},"request_id":{"type":["string","null"],"maxLength":64,"pattern":"^[A-Za-z0-9\\-]+$","description":"`request_id` del envelope de error del API cuando el error viene de una respuesta suya, o el `digest` de Next en un error de servidor."}}},"WebEventIn":{"type":"object","title":"WebEventIn","description":"Un evento de la web pública, con la ruta ya en plantilla.","additionalProperties":false,"required":["route","event","locale"],"properties":{"route":{"type":"string","minLength":1,"maxLength":120,"pattern":"^/[A-Za-z0-9/_\\-\\[\\]]*$","description":"Ruta INTERNA de la página (sin prefijo de idioma) con los identificadores sustituidos por su plantilla (`/cursos/[slug]`)."},"event":{"type":"string","enum":["page_view","generate_lead","view_course","cta_login"],"description":"Evento del catálogo cerrado (`apps/web/src/lib/analytics/eventos.ts`)."},"locale":{"type":"string","enum":["es","en"],"description":"Idioma de la página."},"label":{"type":["string","null"],"pattern":"^[a-z][a-z0-9-]{0,63}$","description":"Solo en `view_course`: el slug PÚBLICO del curso o programa del catálogo global. En cualquier otro evento, 422."}}},"LegalDocument":{"type":"object","title":"LegalDocument","properties":{"type":{"type":"string","enum":["terms","privacy","cookies","legal"]},"version":{"type":"string"},"published_at":{"type":"string","format":"date"},"requires_acceptance":{"type":"boolean"}},"required":["type","version","published_at","requires_acceptance"]},"LegalDocuments":{"type":"object","title":"LegalDocuments","properties":{"documents":{"items":{"$ref":"#/components/schemas/LegalDocument"},"type":"array"}},"required":["documents"]},"Consent":{"type":"object","title":"Consent","properties":{"type":{"type":"string","enum":["terms","privacy","ai_tutor","step_progress_sharing","marketing_email","adult_declaration"]},"version":{"type":"string"},"granted":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}},"required":["type","version","granted","created_at"]},"PendingAcceptance":{"type":"object","title":"PendingAcceptance","properties":{"type":{"type":"string","enum":["terms","privacy","cookies","legal"]},"version":{"type":"string"}},"required":["type","version"]},"MyConsents":{"type":"object","title":"MyConsents","properties":{"current":{"items":{"$ref":"#/components/schemas/Consent"},"type":"array","description":"Última decisión por tipo."},"pending":{"items":{"$ref":"#/components/schemas/PendingAcceptance"},"type":"array","description":"Documentos que exigen aceptación y cuya versión vigente no está aceptada."}},"required":["current","pending"]},"ConsentCreate":{"type":"object","title":"ConsentCreate","properties":{"type":{"type":"string","enum":["terms","privacy","ai_tutor","step_progress_sharing","marketing_email"]},"version":{"type":"string","maxLength":20,"pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"granted":{"type":"boolean"},"source":{"type":"string","enum":["signup","login_gate","settings","invitation"],"default":"settings"}},"additionalProperties":false,"required":["type","version","granted"],"description":"Una decisión sobre un tipo. El sujeto es SIEMPRE la sesión: un `user_id` en el cuerpo es un campo extra → 422."},"EffectivePrivacy":{"type":"object","title":"EffectivePrivacy","properties":{"public_profile_allowed":{"type":"boolean"},"leaderboard_identity":{"type":"string","enum":["name","alias"]},"external_links_allowed":{"type":"boolean"},"social_features":{"type":"boolean"}},"required":["public_profile_allowed","leaderboard_identity","external_links_allowed","social_features"]},"MyPrivacy":{"type":"object","title":"MyPrivacy","properties":{"age_band":{"type":"string","enum":["under_14","14_17","adult","unknown"]},"treated_as_minor":{"type":"boolean","deprecated":true,"description":"Deprecado (ADR 0036, solo mayores de 18): siempre `false`. Se retirará en v2."},"effective":{"$ref":"#/components/schemas/EffectivePrivacy"},"deletion_requested_at":{"type":["string","null"],"format":"date-time"},"deletion_scheduled_for":{"type":["string","null"],"format":"date-time"}},"required":["age_band","treated_as_minor","effective","deletion_requested_at","deletion_scheduled_for"]},"DataExport":{"type":"object","title":"DataExport","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["pending","processing","ready","failed","expired"]},"created_at":{"type":"string","format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":["string","null"],"format":"date-time"},"downloaded_at":{"type":["string","null"],"format":"date-time"},"size_bytes":{"type":["integer","null"]},"download_url":{"type":["string","null"],"description":"Enlace firmado, de un solo uso, mientras está lista y sin descargar."}},"required":["id","status","created_at","finished_at","expires_at","downloaded_at","size_bytes","download_url"]},"DataExportList":{"type":"object","title":"DataExportList","properties":{"exports":{"items":{"$ref":"#/components/schemas/DataExport"},"type":"array","description":"Las 10 más recientes."}},"required":["exports"]},"DeletionRequest":{"type":"object","title":"DeletionRequest","properties":{"code":{"type":"string","pattern":"^\\d{6}$"},"keep_certificate_name":{"type":"boolean","default":false}},"additionalProperties":false,"required":["code"],"description":"`code`: código de acceso recién pedido a su propio email (re-autenticación). `keep_certificate_name`: sus certificados seguirán verificables; con `false` (por defecto) solo con sus iniciales."},"DeletionStatus":{"type":"object","title":"DeletionStatus","properties":{"deletion_requested_at":{"type":["string","null"],"format":"date-time"},"deletion_scheduled_for":{"type":["string","null"],"format":"date-time"},"keep_certificate_name":{"type":"boolean"}},"required":["deletion_requested_at","deletion_scheduled_for","keep_certificate_name"]},"MemberAgeBandUpdate":{"type":"object","title":"MemberAgeBandUpdate","properties":{"age_band":{"type":"string","enum":["under_14","14_17","adult","unknown"]}},"additionalProperties":false,"required":["age_band"]},"MemberAgeBand":{"type":"object","title":"MemberAgeBand","properties":{"user_id":{"type":"string"},"age_band":{"type":"string","enum":["under_14","14_17","adult","unknown"]},"age_band_source":{"type":"string","enum":["self_declared","organization","migration"]},"effective_age_band":{"type":"string","description":"Tramo que se aplica (el más protector de todas sus organizaciones y el propio).","enum":["under_14","14_17","adult","unknown"]},"treated_as_minor":{"type":"boolean","deprecated":true,"description":"Deprecado (ADR 0036, solo mayores de 18): siempre `false`. Se retirará en v2."}},"required":["user_id","age_band","age_band_source","effective_age_band","treated_as_minor"]},"OrganizationPrivacy":{"type":"object","title":"OrganizationPrivacy","properties":{"ai_tutor_enabled":{"type":"boolean"},"has_minor_learners":{"type":"boolean","deprecated":true,"description":"Deprecado (ADR 0036, solo mayores de 18): siempre `false`; `true` en una petición es `422 minors_not_allowed`. Se retirará en v2."},"dpa_signed":{"type":"boolean"},"dpa_version":{"type":["string","null"]}},"required":["ai_tutor_enabled","has_minor_learners","dpa_signed","dpa_version"]},"OrganizationPrivacyUpdate":{"type":"object","title":"OrganizationPrivacyUpdate","properties":{"ai_tutor_enabled":{"type":"boolean"}},"additionalProperties":false,"required":["ai_tutor_enabled"]},"AuditLogEntry":{"type":"object","title":"AuditLogEntry","description":"Evento de la organización. `payload` lleva ids, códigos y valores cortos; nunca un email.","properties":{"id":{"type":"string"},"action":{"type":"string"},"actor_user_id":{"type":["string","null"]},"target_type":{"type":["string","null"]},"target_id":{"type":["string","null"]},"payload":{"type":["object","null"],"additionalProperties":true},"created_at":{"type":"string","format":"date-time"}},"required":["id","action","actor_user_id","target_type","target_id","payload","created_at"]},"AuditLogList":{"type":"object","title":"AuditLogList","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AuditLogEntry"}},"total":{"type":"integer"}},"required":["items","total"]},"AtRiskLearner":{"properties":{"user_id":{"format":"uuid","type":"string"},"name":{"type":"string"},"average_progress":{"maximum":100,"minimum":0,"type":"integer"},"last_activity_at":{"format":"date-time","type":["string","null"]},"days_inactive":{"type":["integer","null"],"description":"Días desde la última actividad; null si nunca."}},"type":"object","required":["user_id","name","average_progress","last_activity_at","days_inactive"],"title":"AtRiskLearner"},"AtRiskRule":{"properties":{"inactive_days":{"maximum":90,"minimum":1,"type":"integer"},"min_progress":{"maximum":100,"minimum":0,"type":"integer"}},"type":"object","required":["inactive_days","min_progress"],"additionalProperties":false,"description":"Regla de alumnado en riesgo: sin actividad en `inactive_days` días Y con\nun progreso medio por debajo de `min_progress` (%).","title":"AtRiskRule"},"NotificationCategory":{"type":"string","enum":["learning","streak","social","summary","catalog"],"description":"Categoría de preferencias de un aviso (catálogo en `docs/notificaciones.md`).","title":"NotificationCategory"},"NotificationChannel":{"type":"string","enum":["in_app","email","push"],"title":"NotificationChannel"},"NotificationDigest":{"type":"string","enum":["instant","daily","weekly"],"description":"Cadencia del correo de la categoría.","title":"NotificationDigest"},"Notification":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","description":"Evento del catálogo (`course_assigned`, `streak_at_risk`…)."},"category":{"$ref":"#/components/schemas/NotificationCategory"},"title":{"type":"string"},"body":{"anyOf":[{"type":"string"},{"type":"null"}]},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Ruta del producto (`/curso/x`), nunca una URL absoluta; `/m` le pone su prefijo."},"read_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time"}},"required":["id","type","category","title","body","link","read_at","created_at"],"title":"Notification"},"NotificationList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Notification"}},"unread_count":{"type":"integer"},"has_more":{"type":"boolean"}},"required":["items","unread_count","has_more"],"title":"NotificationList"},"NotificationUnreadCount":{"type":"object","properties":{"unread_count":{"type":"integer"}},"required":["unread_count"],"title":"NotificationUnreadCount"},"NotificationPreference":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/NotificationCategory"},"in_app":{"type":"boolean"},"email":{"type":"boolean"},"push":{"type":"boolean"},"digest":{"$ref":"#/components/schemas/NotificationDigest"},"available_channels":{"type":"array","items":{"$ref":"#/components/schemas/NotificationChannel"},"description":"Canales por los que puede salir algún aviso de la categoría para esta persona; la pantalla solo enseña estos interruptores."},"customized":{"type":"boolean","description":"false = valor de fábrica (incluido el mapa de las preferencias de correo heredadas)."}},"required":["category","in_app","email","push","digest","available_channels","customized"],"title":"NotificationPreference"},"NotificationPreferenceList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/NotificationPreference"}},"quiet_hours_start":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Ventana de silencio que se aplica a esta persona en la organización (la de la organización o, a un menor, la de fábrica 21-8)."},"quiet_hours_end":{"anyOf":[{"type":"integer"},{"type":"null"}]},"timezone":{"type":"string"}},"required":["items","quiet_hours_start","quiet_hours_end","timezone"],"title":"NotificationPreferenceList"},"NotificationPreferenceUpdate":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/NotificationCategory"},"in_app":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"email":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"push":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"digest":{"anyOf":[{"$ref":"#/components/schemas/NotificationDigest"},{"type":"null"}]}},"required":["category"],"additionalProperties":false,"title":"NotificationPreferenceUpdate"},"OrgNotificationSettings":{"type":"object","properties":{"quiet_hours_start":{"anyOf":[{"type":"integer","minimum":0,"maximum":23},{"type":"null"}]},"quiet_hours_end":{"anyOf":[{"type":"integer","minimum":0,"maximum":23},{"type":"null"}]},"timezone":{"type":"string","description":"Zona de la organización en la que se leen las horas (solo lectura aquí)."},"minor_default_quiet_hours":{"type":"array","items":{"type":"integer"},"deprecated":true,"description":"Deprecado (ADR 0036): la ventana de fábrica para alumnado menor ya no se aplica; devuelve el valor histórico `[21, 8]`. Se retirará en v2."}},"required":["quiet_hours_start","quiet_hours_end","timezone","minor_default_quiet_hours"],"title":"OrgNotificationSettings"},"OrgNotificationSettingsUpdate":{"type":"object","properties":{"quiet_hours_start":{"anyOf":[{"type":"integer","minimum":0,"maximum":23},{"type":"null"}]},"quiet_hours_end":{"anyOf":[{"type":"integer","minimum":0,"maximum":23},{"type":"null"}]}},"required":["quiet_hours_start","quiet_hours_end"],"additionalProperties":false,"description":"Las dos horas o ninguna (null y null = sin ventana propia).","title":"OrgNotificationSettingsUpdate"},"DeviceTokenIn":{"type":"object","properties":{"platform":{"type":"string","enum":["apns","fcm"]},"token":{"type":"string","minLength":1,"maxLength":255,"pattern":"^\\S+$"}},"required":["platform","token"],"additionalProperties":false,"title":"DeviceTokenIn"},"DeviceTokenRemoveIn":{"type":"object","properties":{"token":{"type":"string","minLength":1,"maxLength":255,"pattern":"^\\S+$"}},"required":["token"],"additionalProperties":false,"title":"DeviceTokenRemoveIn"},"DeviceTokenResult":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"],"title":"DeviceTokenResult"},"NotificationCapabilities":{"type":"object","properties":{"web_push":{"type":"boolean","description":"Hay claves VAPID en el servidor (sin ellas la web no ofrece avisos del navegador)."},"native_push":{"type":"boolean","description":"Hay credenciales de APNs o FCM."},"vapid_public_key":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["web_push","native_push","vapid_public_key"],"title":"NotificationCapabilities"},"CohortCourseCompletion":{"properties":{"course_id":{"format":"uuid","type":"string"},"title":{"type":"string"},"enrolled":{"type":"integer"},"completed":{"type":"integer"},"completion_rate":{"description":"% de matrículas completadas.","maximum":100,"minimum":0,"type":"integer"},"average_progress":{"maximum":100,"minimum":0,"type":"integer"}},"type":"object","required":["course_id","title","enrolled","completed","completion_rate","average_progress"],"title":"CohortCourseCompletion"},"CohortExamSummary":{"properties":{"exam_id":{"format":"uuid","type":"string"},"course_id":{"format":"uuid","type":["string","null"]},"title":{"type":"string"},"learners_attempted":{"type":"integer"},"attempts":{"type":"integer"},"average_score":{"type":["integer","null"],"description":"Media de la mejor nota de cada persona."},"pass_rate":{"type":["integer","null"],"description":"% de personas que lo aprobaron alguna vez."}},"type":"object","required":["exam_id","course_id","title","learners_attempted","attempts","average_score","pass_rate"],"title":"CohortExamSummary"},"CohortInsights":{"properties":{"cohort_id":{"format":"uuid","type":"string"},"name":{"type":"string"},"generated_at":{"format":"date-time","type":"string"},"learners":{"type":"integer"},"active_7d":{"type":"integer"},"active_30d":{"type":"integer"},"average_progress":{"maximum":100,"minimum":0,"type":"integer"},"average_streak":{"type":"number"},"weekly_game_xp":{"type":"integer"},"weekly_xp":{"type":"integer","description":"XP de TODAS las fuentes (lecciones, quizzes, exámenes, juegos, retos…) ganada por la cohorte en esta organización en los últimos 7 días (EDAPI-206). `weekly_game_xp` es solo la de los juegos."},"courses":{"items":{"$ref":"#/components/schemas/CohortCourseCompletion"},"type":"array"},"exams":{"items":{"$ref":"#/components/schemas/CohortExamSummary"},"type":"array"},"at_risk":{"items":{"$ref":"#/components/schemas/AtRiskLearner"},"type":"array"},"at_risk_rule":{"$ref":"#/components/schemas/AtRiskRule"}},"type":"object","required":["cohort_id","name","generated_at","learners","active_7d","active_30d","average_progress","average_streak","weekly_game_xp","weekly_xp","courses","exams","at_risk","at_risk_rule"],"title":"CohortInsights"},"OrgReportCohortRow":{"properties":{"cohort_id":{"format":"uuid","type":"string"},"name":{"type":"string"},"enrolled":{"type":"integer"},"completed":{"type":"integer"}},"type":"object","required":["cohort_id","name","enrolled","completed"],"title":"OrgReportCohortRow"},"OrgReportCourseRow":{"properties":{"course_id":{"format":"uuid","type":"string"},"title":{"type":"string"},"scope":{"enum":["global","org"],"type":"string"},"enrolled":{"type":"integer"},"started_in_range":{"type":"integer"},"completed_in_range":{"type":"integer"},"certificates_in_range":{"type":"integer"},"cohorts":{"items":{"$ref":"#/components/schemas/OrgReportCohortRow"},"type":"array"}},"type":"object","required":["course_id","title","scope","enrolled","started_in_range","completed_in_range","certificates_in_range","cohorts"],"title":"OrgReportCourseRow"},"OrgReportCourses":{"properties":{"from_date":{"format":"date","type":"string"},"to_date":{"format":"date","type":"string"},"courses":{"items":{"$ref":"#/components/schemas/OrgReportCourseRow"},"type":"array"}},"type":"object","required":["from_date","to_date","courses"],"title":"OrgReportCourses"},"OrgReportExport":{"properties":{"id":{"format":"uuid","type":"string"},"kind":{"enum":["courses","learners"],"type":"string"},"status":{"enum":["pending","processing","ready","failed","expired"],"type":"string"},"lang":{"enum":["es","en"],"type":"string"},"from_date":{"format":"date","type":"string"},"to_date":{"format":"date","type":"string"},"row_count":{"type":["integer","null"]},"created_at":{"format":"date-time","type":"string"},"finished_at":{"format":"date-time","type":["string","null"]},"expires_at":{"format":"date-time","type":["string","null"]},"downloaded_at":{"format":"date-time","type":["string","null"]},"download_url":{"type":["string","null"],"description":"Enlace firmado de un solo uso; solo mientras está lista y sin descargar."}},"type":"object","required":["id","kind","status","lang","from_date","to_date","row_count","created_at","finished_at","expires_at","downloaded_at","download_url"],"title":"OrgReportExport"},"OrgReportExportCreate":{"properties":{"kind":{"enum":["courses","learners"],"type":"string"},"from_date":{"format":"date","type":["string","null"]},"to_date":{"format":"date","type":["string","null"]},"lang":{"enum":["es","en",null],"type":["string","null"]}},"type":"object","required":["kind"],"additionalProperties":false,"title":"OrgReportExportCreate"},"OrgReportExportList":{"properties":{"exports":{"items":{"$ref":"#/components/schemas/OrgReportExport"},"type":"array"}},"type":"object","required":["exports"],"title":"OrgReportExportList"},"OrgReportOverview":{"properties":{"from_date":{"format":"date","type":"string"},"to_date":{"format":"date","type":"string"},"learners_total":{"description":"Alumnado con membresía activa hoy.","type":"integer"},"learners_active":{"description":"Alumnado con actividad en el rango.","type":"integer"},"enrollments_started":{"type":"integer"},"enrollments_completed":{"type":"integer"},"lessons_completed":{"type":"integer"},"certificates_issued":{"type":"integer"},"exam_attempts":{"type":"integer"},"exams_passed":{"type":"integer"}},"type":"object","required":["from_date","to_date","learners_total","learners_active","enrollments_started","enrollments_completed","lessons_completed","certificates_issued","exam_attempts","exams_passed"],"title":"OrgReportOverview"},"QuizDraft":{"properties":{"status":{"const":"draft","type":"string"},"course_id":{"format":"uuid","type":"string"},"lesson_id":{"format":"uuid","type":["string","null"]},"lang":{"enum":["es","en"],"type":"string"},"questions":{"items":{"$ref":"#/components/schemas/QuizDraftQuestion"},"type":"array"}},"type":"object","required":["status","course_id","lesson_id","lang","questions"],"title":"QuizDraft"},"QuizDraftQuestion":{"properties":{"question":{"type":"string"},"options":{"items":{"type":"string"},"maxItems":6,"minItems":2,"type":"array"},"correct_index":{"minimum":0,"type":"integer"},"explanation":{"type":["string","null"]}},"type":"object","required":["question","options","correct_index","explanation"],"title":"QuizDraftQuestion"},"QuizDraftRequest":{"properties":{"course_id":{"format":"uuid","type":"string"},"lesson_id":{"format":"uuid","type":["string","null"]},"topic":{"maxLength":200,"type":["string","null"]},"questions":{"default":5,"maximum":20,"minimum":1,"type":"integer"},"difficulty":{"default":"medium","enum":["easy","medium","hard"],"type":"string"},"lang":{"default":"es","enum":["es","en"],"type":"string"}},"type":"object","required":["course_id"],"additionalProperties":false,"title":"QuizDraftRequest"},"SocialFeedHide":{"type":"object","required":["id","hidden"],"properties":{"id":{"type":"integer"},"hidden":{"type":"boolean"}}},"ApiKeyAccess":{"type":"string","enum":["read","write"],"description":"`read`: solo GET/HEAD/OPTIONS (un intento de escribir es 403 `credential_read_only`). `write`: lo que el rol de su dueña permita."},"ApiKey":{"type":"object","title":"ApiKey","description":"Un token de acceso personal de UNA persona en UNA organización. Nunca lleva el secreto: solo su prefijo (`edr_live_ab12`) para reconocerlo.","required":["id","name","token_prefix","access","status","expires_at","last_used_at","revoked_at","created_at","owner_name","is_mine"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"token_prefix":{"type":"string"},"access":{"$ref":"#/components/schemas/ApiKeyAccess"},"status":{"type":"string","enum":["active","expired","revoked"]},"expires_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"owner_name":{"type":["string","null"]},"is_mine":{"type":"boolean"}}},"ApiKeyList":{"type":"object","title":"ApiKeyList","required":["keys"],"properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}},"ApiKeyCreate":{"type":"object","title":"ApiKeyCreate","additionalProperties":false,"required":["name","expires_in_days"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120},"access":{"$ref":"#/components/schemas/ApiKeyAccess","default":"read"},"expires_in_days":{"type":"integer","minimum":1,"maximum":90,"description":"Caducidad obligatoria, en días (máximo 90)."}}},"ApiKeyCreated":{"type":"object","title":"ApiKeyCreated","description":"La key recién acuñada y su secreto `edr_live_…`, que NO se vuelve a enseñar: guárdalo en el cliente MCP o en el gestor de secretos al momento.","required":["key","token"],"properties":{"key":{"$ref":"#/components/schemas/ApiKey"},"token":{"type":"string"}}},"CurrentCredential":{"type":"object","title":"CurrentCredential","description":"Qué credencial trae la petición. Con cookie, `session` (sin organización fija). Con `Authorization: Bearer`, su organización, el rol ahí y su acceso: el MCP lo usa para ofrecer solo las tools que ese rol alcanza (C2b).","required":["kind","organization_id","role","access","expires_at","treated_as_minor"],"properties":{"kind":{"type":"string","enum":["session","pat","oauth"]},"organization_id":{"type":["string","null"],"format":"uuid"},"role":{"anyOf":[{"type":"string","enum":["owner","admin","teacher","student"]},{"type":"null"}]},"access":{"$ref":"#/components/schemas/ApiKeyAccess"},"expires_at":{"type":["string","null"],"format":"date-time"},"treated_as_minor":{"type":"boolean","deprecated":true,"description":"Deprecado (ADR 0036, solo mayores de 18): siempre `false`. Se retirará en v2."}}},"OauthAccess":{"type":"string","enum":["read","write"]},"OauthClientRegistration":{"type":"object","title":"OauthClientRegistration","description":"Metadata de registro dinámico de cliente público (RFC 7591).","required":["redirect_uris"],"properties":{"redirect_uris":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"string"},"description":"https obligatorio (http solo para localhost), sin fragmento ni userinfo."},"client_name":{"type":["string","null"]},"token_endpoint_auth_method":{"anyOf":[{"type":"string","enum":["none"]},{"type":"null"}]},"grant_types":{"anyOf":[{"type":"array","items":{"type":"string","enum":["authorization_code","refresh_token"]}},{"type":"null"}]},"response_types":{"anyOf":[{"type":"array","items":{"type":"string","enum":["code"]}},{"type":"null"}]},"scope":{"type":["string","null"],"maxLength":255}}},"OauthClientRegistered":{"type":"object","title":"OauthClientRegistered","required":["client_id","client_id_issued_at","client_name","redirect_uris","token_endpoint_auth_method","grant_types","response_types","scope"],"properties":{"client_id":{"type":"string","format":"uuid"},"client_id_issued_at":{"type":"integer"},"client_name":{"type":["string","null"]},"redirect_uris":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string","enum":["none"]},"grant_types":{"type":"array","items":{"type":"string","enum":["authorization_code","refresh_token"]}},"response_types":{"type":"array","items":{"type":"string","enum":["code"]}},"scope":{"type":"string"}}},"OauthConsentOrganization":{"type":"object","title":"OauthConsentOrganization","description":"Una organización que la persona puede elegir al consentir: miembro activo, con la función `mcp` activa y, si es alumnado, no tratada como menor.","required":["id","name","role","max_access"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"role":{"type":"string","enum":["owner","admin","teacher","student"]},"max_access":{"$ref":"#/components/schemas/OauthAccess"}}},"OauthConsentInfo":{"type":"object","title":"OauthConsentInfo","required":["request_id","client_name","redirect_host","scope","expires_at","organizations"],"properties":{"request_id":{"type":"string","format":"uuid"},"client_name":{"type":"string"},"redirect_host":{"type":"string"},"scope":{"type":"string"},"expires_at":{"type":"string","format":"date-time"},"organizations":{"type":"array","items":{"$ref":"#/components/schemas/OauthConsentOrganization"}}}},"OauthConsentDecisionRequest":{"type":"object","title":"OauthConsentDecisionRequest","additionalProperties":false,"required":["approve"],"properties":{"approve":{"type":"boolean"},"organization_id":{"type":["string","null"],"format":"uuid","description":"Obligatoria al aprobar; una de `organizations` del consentimiento."},"access":{"$ref":"#/components/schemas/OauthAccess","default":"read"}}},"OauthConsentDecision":{"type":"object","title":"OauthConsentDecision","required":["redirect_to"],"properties":{"redirect_to":{"type":"string"}}},"OauthTokenResponse":{"type":"object","title":"OauthTokenResponse","required":["access_token","expires_in","refresh_token","scope"],"properties":{"access_token":{"type":"string","description":"`edr_oat_…`, 1 h. Bearer solo dentro de la organización elegida."},"token_type":{"type":"string","enum":["Bearer"],"default":"Bearer"},"expires_in":{"type":"integer"},"refresh_token":{"type":"string","description":"`edr_ort_…`, 30 días, rotativo (reusar uno rotado revoca la familia)."},"scope":{"type":"string"}}},"OauthAuthorization":{"type":"object","title":"OauthAuthorization","required":["id","client_id","client_name","organization_id","organization_name","access","authorized_at","last_used_at"],"properties":{"id":{"type":"string","format":"uuid"},"client_id":{"type":"string","format":"uuid"},"client_name":{"type":["string","null"]},"organization_id":{"type":"string","format":"uuid"},"organization_name":{"type":"string"},"access":{"$ref":"#/components/schemas/OauthAccess"},"authorized_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"}}},"OauthAuthorizationList":{"type":"object","title":"OauthAuthorizationList","required":["authorizations"],"properties":{"authorizations":{"type":"array","items":{"$ref":"#/components/schemas/OauthAuthorization"}}}},"OauthError":{"type":"object","description":"Error de protocolo en formato RFC 6749 §5.2 / RFC 7591 §3.2.2 (sin envelope).","required":["error"],"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}},"OauthMetadata":{"type":"object","description":"Documento de discovery (RFC 8414 o RFC 9728).","additionalProperties":true},"Error":{"type":"object","title":"Error","description":"Envelope de error estándar: `{\"error\":{\"code\",\"message\",\"request_id?\"}}`.","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Código estable machine-readable; el catálogo completo es `ErrorCode` (p. ej. `invalid_credentials`, `email_taken`, `not_found`, `validation_error`, `unauthorized`, `too_many_requests`, `database_unavailable`). Abierto a propósito: un cliente debe tolerar un código que no conoce.","examples":["not_found"]},"message":{"type":"string","description":"Mensaje legible para humanos.","examples":["Organization not found"]},"request_id":{"type":"string","description":"Identificador de la petición para correlación con logs (opcional).","examples":["req_01J9ZK3M8QW2"]}}}}},"VersionOut":{"type":"object","title":"VersionOut","description":"Qué código está corriendo.","required":["app_version","git_sha","build_time","schema_revision"],"properties":{"app_version":{"type":"string","description":"Versión de la aplicación (`APP_VERSION` en el build; \"0.0.0\" si no se fijó).","examples":["0.1.0"]},"git_sha":{"type":"string","description":"Commit del que se construyó la imagen (`APP_GIT_SHA`); \"unknown\" si no se inyectó.","examples":["ee4ce4c"]},"build_time":{"type":"string","description":"Hora del build en ISO 8601 UTC (`APP_BUILD_TIME`); \"unknown\" si no se inyectó.","examples":["2026-09-25T10:00:00Z"]},"schema_revision":{"type":["string","null"],"description":"Revisión de Alembic que espera este código (cabeza de la cadena de migraciones). No es la de la base: para comparar las dos está `python -m app.core.schema_version`. null si no se pudo leer.","examples":["8254eee45243"]}}},"TutorChatIn":{"properties":{"conversation_id":{"type":["string","null"],"description":"Seguir una conversación propia."},"course_id":{"type":"string","maxLength":100,"minLength":1,"description":"Curso (id o slug)."},"lesson_id":{"type":["string","null"],"maxLength":36},"message":{"type":"string","maxLength":4000,"minLength":1},"lang":{"type":["string","null"],"enum":["es","en",null]},"mode":{"type":"string","enum":["free","explain","example","quiz_me"],"default":"free"}},"additionalProperties":false,"type":"object","required":["course_id","message"],"title":"TutorChatIn"},"TutorCitation":{"properties":{"n":{"type":"integer"},"chunk_id":{"type":"string"},"course_id":{"type":"string"},"course_slug":{"type":"string"},"lesson_id":{"type":"string"},"lesson_number":{"type":"integer"},"lesson_title":{"type":"string"},"heading":{"type":"string"},"anchor":{"type":["string","null"]},"lang":{"type":"string"}},"type":"object","required":["n","chunk_id","course_id","course_slug","lesson_id","lesson_number","lesson_title","heading","anchor","lang"],"title":"TutorCitation"},"TutorUsage":{"properties":{"input_tokens":{"type":"integer"},"output_tokens":{"type":"integer"},"cache_write_tokens":{"type":"integer"},"cache_read_tokens":{"type":"integer"},"iterations":{"type":"integer"}},"type":"object","required":["input_tokens","output_tokens","cache_write_tokens","cache_read_tokens","iterations"],"title":"TutorUsage"},"TutorQuota":{"properties":{"messages_today":{"type":"integer"},"messages_per_day":{"type":"integer"},"tokens_month":{"type":"integer"},"tokens_per_month":{"type":"integer"},"organization_exhausted":{"type":"boolean"},"resets_day":{"type":"string","format":"date-time"},"resets_month":{"type":"string","format":"date-time"}},"type":"object","required":["messages_today","messages_per_day","tokens_month","tokens_per_month","organization_exhausted","resets_day","resets_month"],"title":"TutorQuota"},"TutorChatResult":{"properties":{"conversation_id":{"type":"string"},"message_id":{"type":"string"},"user_message_id":{"type":"string"},"content":{"type":"string"},"citations":{"items":{"$ref":"#/components/schemas/TutorCitation"},"type":"array"},"discarded_citations":{"type":"integer"},"status":{"type":"string","enum":["ok","moderated","blocked","failed"]},"guardrail_profile":{"type":"string","enum":["adulto","menor"]},"model":{"type":["string","null"]},"usage":{"$ref":"#/components/schemas/TutorUsage"},"quota":{"$ref":"#/components/schemas/TutorQuota"}},"type":"object","required":["conversation_id","message_id","user_message_id","content","citations","discarded_citations","status","guardrail_profile","model","usage","quota"],"title":"TutorChatResult"},"TutorStatus":{"properties":{"enabled":{"type":"boolean"},"reason":{"type":"string","enum":["ok","unconfigured","feature_disabled","notice_required"]},"notice_required":{"type":"boolean"},"guardrail_profile":{"type":"string","enum":["adulto","menor"]},"modes":{"items":{"type":"string","enum":["free","explain","example","quiz_me"]},"type":"array"},"quota":{"oneOf":[{"$ref":"#/components/schemas/TutorQuota"},{"type":"null"}]}},"type":"object","required":["enabled","reason","notice_required","guardrail_profile","modes","quota"],"title":"TutorStatus"},"TutorConversationSummary":{"properties":{"id":{"type":"string"},"course_id":{"type":"string"},"lesson_id":{"type":["string","null"]},"title":{"type":"string"},"lang":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"type":"object","required":["id","course_id","lesson_id","title","lang","created_at","updated_at"],"title":"TutorConversationSummary"},"TutorConversationList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TutorConversationSummary"},"type":"array"},"next_cursor":{"type":["string","null"]}},"type":"object","required":["items","next_cursor"],"title":"TutorConversationList"},"TutorMessage":{"properties":{"id":{"type":"string"},"role":{"type":"string","enum":["user","assistant"]},"content":{"type":"string"},"citations":{"items":{"$ref":"#/components/schemas/TutorCitation"},"type":"array"},"status":{"type":"string","enum":["ok","failed","moderated","blocked"]},"feedback":{"type":["string","null"],"enum":["up","down",null]},"created_at":{"type":"string","format":"date-time"}},"type":"object","required":["id","role","content","citations","status","feedback","created_at"],"title":"TutorMessage"},"TutorConversationDetail":{"properties":{"id":{"type":"string"},"course_id":{"type":"string"},"lesson_id":{"type":["string","null"]},"title":{"type":"string"},"lang":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"messages":{"items":{"$ref":"#/components/schemas/TutorMessage"},"type":"array"}},"type":"object","required":["id","course_id","lesson_id","title","lang","created_at","updated_at","messages"],"title":"TutorConversationDetail"},"TutorFeedbackIn":{"properties":{"rating":{"type":["string","null"],"enum":["up","down",null]},"reason":{"type":["string","null"],"enum":["wrong","unclear","off_topic","unsafe","other",null]}},"additionalProperties":false,"type":"object","required":["rating"],"title":"TutorFeedbackIn"},"TutorFeedbackOut":{"properties":{"message_id":{"type":"string"},"rating":{"type":["string","null"],"enum":["up","down",null]},"reason":{"type":["string","null"],"enum":["wrong","unclear","off_topic","unsafe","other",null]}},"type":"object","required":["message_id","rating","reason"],"title":"TutorFeedbackOut"},"TutorSettings":{"properties":{"guardrail_profile":{"type":"string","enum":["auto","minor"],"description":"`auto`: según el tramo de edad de cada persona; `minor`: perfil de menor para todos."}},"additionalProperties":false,"type":"object","required":["guardrail_profile"],"title":"TutorSettings"},"TutorSafetyFlag":{"properties":{"id":{"type":"string"},"user_id":{"type":"string"},"category":{"type":"string","enum":["self_harm","abuse","sexual","violence","personal_data"]},"created_at":{"type":"string","format":"date-time"},"reviewed_at":{"type":["string","null"],"format":"date-time"}},"type":"object","required":["id","user_id","category","created_at","reviewed_at"],"title":"TutorSafetyFlag"},"TutorSafetyFlagList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TutorSafetyFlag"},"type":"array"}},"type":"object","required":["items"],"title":"TutorSafetyFlagList"},"QuizProposalCreate":{"properties":{"course_id":{"type":"string","maxLength":36,"minLength":1},"lesson_id":{"type":"string","maxLength":36,"minLength":1},"questions":{"type":"integer","maximum":20,"minimum":1,"default":5},"difficulty":{"type":"string","enum":["easy","medium","hard"],"default":"medium"},"lang":{"type":"string","enum":["es","en"],"default":"es"}},"additionalProperties":false,"type":"object","required":["course_id","lesson_id"],"title":"QuizProposalCreate"},"QuizProposalCitation":{"properties":{"chunk_id":{"type":"string"},"heading":{"type":"string"},"anchor":{"type":["string","null"]}},"type":"object","required":["chunk_id","heading","anchor"],"title":"QuizProposalCitation"},"QuizProposalQuestion":{"properties":{"question":{"type":"string","maxLength":500,"minLength":3},"options":{"items":{"type":"string"},"type":"array","maxItems":6,"minItems":2},"correct_index":{"type":"integer","maximum":5,"minimum":0},"explanation":{"type":["string","null"],"maxLength":1000},"citation":{"oneOf":[{"$ref":"#/components/schemas/QuizProposalCitation"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["question","options","correct_index"],"title":"QuizProposalQuestion"},"QuizProposalUpdate":{"properties":{"questions":{"items":{"$ref":"#/components/schemas/QuizProposalQuestion"},"type":"array","maxItems":20,"minItems":1}},"additionalProperties":false,"type":"object","required":["questions"],"title":"QuizProposalUpdate"},"QuizProposal":{"properties":{"id":{"type":"string"},"course_id":{"type":"string"},"lesson_id":{"type":"string"},"lang":{"type":"string"},"status":{"type":"string","enum":["pending","ready","failed","confirmed","discarded"]},"num_questions":{"type":"integer"},"difficulty":{"type":"string"},"questions":{"items":{"$ref":"#/components/schemas/QuizProposalQuestion"},"type":["array","null"]},"error_code":{"type":["string","null"]},"confirmed_quiz_id":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"type":"object","required":["id","course_id","lesson_id","lang","status","num_questions","difficulty","questions","error_code","confirmed_quiz_id","created_at","updated_at"],"title":"QuizProposal"},"QuizProposalList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/QuizProposal"},"type":"array"}},"type":"object","required":["items"],"title":"QuizProposalList"},"QuizProposalConfirmResult":{"properties":{"proposal_id":{"type":"string"},"quiz_id":{"type":"string"},"questions_count":{"type":"integer"},"already_confirmed":{"type":"boolean"}},"type":"object","required":["proposal_id","quiz_id","questions_count","already_confirmed"],"title":"QuizProposalConfirmResult"},"AiUsageTotals":{"properties":{"input_tokens":{"type":"integer"},"output_tokens":{"type":"integer"},"cache_write_tokens":{"type":"integer"},"cache_read_tokens":{"type":"integer"},"cost_micros":{"type":"integer"},"calls":{"type":"integer"}},"type":"object","required":["input_tokens","output_tokens","cache_write_tokens","cache_read_tokens","cost_micros","calls"],"title":"AiUsageTotals"},"MyAiUsage":{"properties":{"messages_today":{"type":"integer"},"messages_per_day":{"type":"integer"},"tokens_month":{"type":"integer"},"tokens_per_month":{"type":"integer"},"organization_exhausted":{"type":"boolean"},"resets_day":{"type":"string","format":"date-time"},"resets_month":{"type":"string","format":"date-time"},"month":{"$ref":"#/components/schemas/AiUsageTotals"}},"type":"object","required":["messages_today","messages_per_day","tokens_month","tokens_per_month","organization_exhausted","resets_day","resets_month","month"],"title":"MyAiUsage"},"AiUsageShare":{"properties":{"key":{"type":["string","null"],"description":"Persona, curso o recurso (None: suprimida o sin curso)."},"totals":{"$ref":"#/components/schemas/AiUsageTotals"}},"type":"object","required":["key","totals"],"title":"AiUsageShare"},"AiQuotaLimits":{"properties":{"tutor_tokens_org_month":{"type":"integer"},"tutor_tokens_user_month":{"type":"integer"},"tutor_messages_user_day":{"type":"integer"},"quiz_generations_month":{"type":"integer"}},"type":"object","required":["tutor_tokens_org_month","tutor_tokens_user_month","tutor_messages_user_day","quiz_generations_month"],"title":"AiQuotaLimits"},"OrgAiUsage":{"properties":{"from_date":{"type":"string","format":"date"},"to_date":{"type":"string","format":"date"},"totals":{"$ref":"#/components/schemas/AiUsageTotals"},"by_user":{"items":{"$ref":"#/components/schemas/AiUsageShare"},"type":"array"},"by_course":{"items":{"$ref":"#/components/schemas/AiUsageShare"},"type":"array"},"by_resource":{"items":{"$ref":"#/components/schemas/AiUsageShare"},"type":"array"},"limits":{"$ref":"#/components/schemas/AiQuotaLimits"},"org_tokens_month":{"type":"integer"}},"type":"object","required":["from_date","to_date","totals","by_user","by_course","by_resource","limits","org_tokens_month"],"title":"OrgAiUsage"}}}}