You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

72 KiB

API y eventos de plataforma — propuesta v1 para Juegoland v2

Estado: borrador revisado el 6 de octubre de 2026; salvo las rutas iniciales de catálogo/bootstrap descritas en el handoff, no implementado. La revisión introduce cambios incompatibles respecto del borrador anterior; no implica un cambio de protocolo desplegado. Se completa con el modelo, protección del menor y registros de revisión del 5 y 6 de octubre. El protocolo game.* conserva su contrato v1; el perfil multidispositivo propone su versión 2.

1. Convenciones y autorización

La API y el servidor/motor de games2 usan Go por decisión del usuario. Los contratos de dominio no dependen del lenguaje. Arquitectura propone un backend modular; implementar reglas y cambiar el binding documentado Socket.IO por WebSocket son trabajos separados, aún pendientes. Este documento conserva el perfil Socket.IO explícito hasta versionar y probar su sustituto. Las políticas de producto concretan P1–P8 y son la referencia para plazos y admisión.

HTTP nuevo bajo /api/v2. /api anterior debe pasar por las mismas políticas durante la migración. JSON UTF-8, fechas UTC con Z, UUID para entidades de plataforma; gameId, versiones, digests y asientos siguen el formato del motor. Revisiones son enteros seguros no negativos. La excepción explícita son marcas temporales de ronda con sufijo Ms: intervalos monotónicos en milisegundos ligados a clockEpoch, según el perfil temporal, no fechas UTC.

Los modelos compartidos fijan primitivas, mensajes y ErrorData para Go, Svelte y Flutter desde una única fuente JSON Schema con DTOs generados. Hay verificación estructural de ese contrato base; OpenAPI y los esquemas completos de flujos/sobres v2 siguen pendientes. La adopción del nuevo error en runtime es un corte explícito, no una modificación silenciosa de los mensajes game.* v1.

Las lecturas con texto humano negocian idioma por Accept-Language y declaran Content-Language y Vary: Accept-Language; claves, códigos, permisos y revisiones no cambian por lengua. La selección explícita y el fallback se definen en localización. En el catálogo inicial, cada ficha declara su locale real para no ocultar una traducción ausente.

Las solicitudes tienen esquemas cerrados por versión. Se rechazan campos desconocidos, claves JSON duplicadas, Unicode inválido, números no finitos y exceso de bytes/profundidad antes de ejecutar. Claves duplicadas requieren validación en el decodificador de texto del transporte: Zod/JSON Schema sobre el objeto ya parseado no las detectan. Las respuestas permiten extensiones opcionales compatibles; tipos/códigos nuevos requieren una estrategia de fallback o una nueva versión.

Requisito de acceso Significado
Público seguro Proyección para anónimo/edad desconocida, sin información social no revisada.
Sesión Principal válido; no basta con que exista una cookie.
Juego Sesión, cuenta y condiciones habilitadas, membresía/rol y política de contenido/admisión.
Chat Juego + mayoría de edad acreditada + SMS vigente + sin restricción de comunicación/bloqueo; también para lectura e historial.
Propietario Principal dueño del recurso; nunca un userId autoritativo suministrado en el cuerpo.
Seguridad Reportes, bloqueo, logout y gestión de datos siguen accesibles sin chat/SMS; las condiciones nuevas no impiden reportar o salir.

Los menores y edades desconocidas no reciben chat por ninguna ruta, socket o adaptador. Ser anfitrión, tutor o tener un token no cambia esto. Los DTO no publican teléfono, prueba de edad, condición de menor ni causa privada de restricciones de terceros.

El perfil web HTTP usa cookie opaca; mutaciones y resolución de secretos exigen origen permitido y token CSRF ligado al contexto de navegador/sesión. Sus sockets exigen origen y CSRF en handshake además de autenticación. bootstrap emite un contexto anónimo aleatorio para vincular solicitudes iniciales; no acepta uno inventado por el cliente. Tras cambiar de identidad se rota ese contexto.

El perfil web/Flutter propuesto añade un adaptador de sesión nativa emitida/revocable por la misma plataforma, con credencial HTTPS y autenticación WSS mediante ticket breve de conexión. No aplica a una app una comprobación de cookie/CSRF de navegador como sustituto de identidad, ni acepta ausencia de Origin/etiqueta Flutter como autorización. Son extensiones futuras con esquemas y pruebas propios; el binding Socket.IO v1 y las rutas ejecutables actuales no adquieren soporte nativo por existir esta propuesta. Permisos, DTO y recibos de dominio son comunes a ambos clientes.

Éxito de lectura: { requestId, data }. Éxito de mutación: { requestId, commandId, duplicate, receipt, data? }; data es una proyección autorizada en ese momento, no contenido privado guardado para repetir sin reautorizar. requestId lo genera el servidor en HTTP; el requestId de consulta socket es UUID del cliente para correlación y no sustituye la traza interna.

2. Idempotencia y recibos

Mutaciones de dominio HTTP llevan Idempotency-Key UUID y issuedAt UTC en el cuerpo. Los comandos socket llevan commandId e issuedAt. Un registro común de plataforma se identifica por principal + commandId, independiente del transporte. La huella incluye operación, recurso, precondiciones, issuedAt y payload validado con JCS; excluye cookie, CSRF y datos de transporte. Los comandos game.* conservan su registro y forma propios.

No se conserva el JSON canónico en claro dentro del recibo. Si incluye OTP, teléfono o token, la huella se calcula con HMAC y clave separada para impedir comprobaciones por diccionario sobre secretos cortos. Los errores y trazas tampoco incorporan esos valores.

Orden obligatorio: autenticar, buscar recibo propio, comparar huella, comprobar autorización actual para datos de respuesta y, si es nuevo, autorizar la operación y validar precondiciones en la transacción. Una restricción de membresía posterior no impide recuperar el recibo mínimo de una salida propia. Nunca se devuelve una vieja instantánea privada al repetir un comando.

La ausencia de recibo no reserva la clave: solicitudes simultáneas se serializan mediante restricción única y la misma transacción del cambio. Repetición exacta devuelve recibo idéntico con duplicate: true; otro contenido da COMMAND_ID_REUSED sin sobrescribir el anterior. Rechazos de negocio definitivos se guardan; fallo de infraestructura, límite o autorización no consumen un comando como rechazo de negocio.

{
  "status": "applied",
  "resource": {
    "kind": "room",
    "id": "1093543c-d383-437c-a427-251d39d63e42"
  },
  "changed": true,
  "revisions": { "room": 6, "match": null }
}

Recibo rechazado al adoptar el perfil común v2: { status: "rejected", error: ErrorData, resource, observedRevisions }, con error.origin: server y error.outcome: rejected. El error es duradero y no se traduce ni cambia al repetir; la correlación del intento pertenece al sobre. Los contratos v1 que usaban code se normalizan mediante adaptador explícito. Chat aplicado añade messageId y chatSequence, sin texto. No-op devuelve changed: false, revisión actual y ningún incremento. Un ack de creación de desafío SMS confirma que se aceptó la tarea, no que se verificó el teléfono.

Para comandos nuevos, issuedAt no puede tener más de 24 h ni estar más de 5 min en el futuro; bootstrap.serverTime permite corregir reloj. Si existe recibo, se consulta antes de caducidad/precondiciones. Los recibos de sala/chat duran tanto como sus datos; el resto al menos 24 h. Tras purgarlos, el sobre original ya caducado se rechaza en vez de aplicarse de nuevo. Una intención revisada por el usuario puede crear otro UUID; el cliente nunca cambia UUID o fecha automáticamente para reintentar.

GET /api/v2/commands/{commandId} y platform.command-status devuelven found con recibo mínimo propio, o unknown. unknown no prueba que un envío retrasado no vaya a confirmarse. Se reenvía exactamente el mismo sobre mientras sea admisible. No se expone el resultado de otro principal.

Operaciones que emiten secretos

Login, alta y recuperación no reutilizan ciegamente el mecanismo de recibos de dominio ni guardan credenciales en su huella/log. P6 fija correo/contraseña, recuperación y registro de intento de autenticación propio: respuesta cifrada recuperable durante 5 min, ligada al contexto y prueba original, sin reemitir sesiones revocadas. La sección 9 concreta flujos; faltan esquemas ejecutables y pruebas de emisión concurrente.

Alta de invitado debe vincularse al contexto anónimo emitido en bootstrap y a su intento, con unicidad para no crear dos identidades al perder la respuesta. Repetir solo puede recuperar esa emisión dentro de su ventana, no una identidad convertida posteriormente en cuenta. Una emisión de sesión recuperable usa respuesta cifrada, vinculada al contexto original, durante un máximo de 5 min; luego exige el flujo de acceso correspondiente. El invitado queda limitado a juego individual hasta completar requisitos.

Creación/rotación de invitación almacena hash para validar el token y una copia cifrada de respuesta durante 24 h para el mismo principal/comando. La consulta de estado nunca devuelve el secreto. Un reintento autorizado durante la ventana puede recuperarlo; después devuelve recibo e invitationId con secretAvailable: false. Rotar después es una intención nueva y revoca el token anterior. No se prolonga caducidad al repetir.

3. HTTP

Las tablas indican operaciones mínimas y campos relevantes. Las políticas de credenciales, supervisión y plazos personales están desarrolladas en P6–P8; el proveedor de edad, los esquemas ejecutables y procedimientos de evidencia deben concretarse antes de habilitarlas.

Sesión, cuenta y protección

Método y ruta Solicitud / datos Acceso
GET /api/v2/bootstrap serverTime, versiones, defaultLocale, supportedLocales, límites, sesión propia mínima, condiciones y capacidades de protección Público seguro, no-store; CSRF propio fuera de vistas públicas.
POST /api/v2/terms/accept version, accepted: true → recibo Contexto propio o sesión; versión exacta.
POST /api/v2/terms/revoke Versión/referencia de aceptación → registro revocado Propietario; invalida capacidades protegidas de todas sus sesiones.
POST /api/v2/auth/guest Nombre seguro → identidad/cookie Contexto anónimo, aceptación; sin reservar asiento.
POST /api/v2/auth/register, /api/v2/auth/login Alta pendiente / credencial → sesión con capacidades reales P6 y sección 9; nunca se acepta edad acreditada del cliente.
POST /api/v2/auth/logout Revocar sesión actual Seguridad; puede repetirse sin sesión vigente.
GET /api/v2/me/sessions, POST /api/v2/me/sessions/{sessionId}/revoke Sesiones propias mínimas / revocación Propietario; no devuelve hashes/tokens.
GET /api/v2/me Perfil y preferencias propias con revisión Propietario; separado del perfil público.
PATCH /api/v2/me/profile expectedRevision, campos permitidos, avatarMediaId? Propietario y políticas de contenido.
GET /api/v2/me/safety Estados mínimos de edad/teléfono, capacidades y safetyRevision Propietario; no expone pruebas ni número completo.
POST /api/v2/me/age-checks Inicio de comprobación → referencia de proveedor Cuenta; umbral acreditado por proveedor evaluado, nunca prueba autodeclarada.
POST /api/v2/me/phone-challenges phoneNumber, finalidad → desafío y tiempos, HTTP 202 Cuenta adulta acreditada; acción explícita, límites SMS.
POST /api/v2/me/phone-challenges/{challengeId}/verify code → estado propio Principal/sesión del desafío, vigente e intentos disponibles; código nunca en URL.
POST /api/v2/me/phone/revoke Desvinculación → capacidades actualizadas Propietario con reautenticación; corta chat.
POST /api/v2/me/exports, POST /api/v2/me/deletion-requests Solicitud → trabajo y estado Propietario, comprobación reciente de titularidad y P8; no requieren chat/SMS.

Callbacks de edad/SMS son interfaces servidor a servidor autenticadas, con audiencia/nonce, firma, vencimiento y dedupe. No existe un endpoint público para establecer verified, adult o guardian. Los códigos fallidos tienen contador atómico; reenviar no reinicia la cuota acumulada. Un SMS entregado no cambia por sí mismo la elegibilidad.

Catálogo, salas, invitaciones y medios

Método y ruta Solicitud / datos Acceso y precondición
GET /api/v2/catalog Fichas aptas y paginadas Público seguro; una proyección personalizada no entra en caché público.
GET /api/v2/games/{gameId}/versions/{version} Distribución pública, digests, estado de publicación Política de contenido; retirar no convierte en secreto lo ya publicado.
GET /api/v2/rooms?scope=public|mine Resumen paginado public seguro; mine exige sesión. Sin chat, tokens, lista infantil o estado privado.
POST /api/v2/rooms game: { id, version, packageDigest, dependencyLockDigest }, capacidad, visibilidad, nombre, configuración; modeId y presentationProfile inicial al adoptar perfil v2; tvBoardSetupToken? para preparación desde TV Juego; fija la selección exacta o rechaza PACKAGE_NOT_CREATABLE. En preparación TV confirma sala, anfitrión, vínculo y QR de entrada en una unidad de trabajo.
GET /api/v2/rooms/{roomId} RoomView Solo miembros/participantes históricos autorizados, también en salas públicas.
POST /api/v2/rooms/{roomId}/memberships invitationToken? o screenEntryToken?, excluyentes según entryMethod → membresía Juego; validación atómica de capacidad, finalidad/generación del token, estado y protección. Un miembro actual no ocupa otra plaza.
POST /api/v2/invitations/resolve Token → ficha segura mínima y roomId Lectura sin consumo, CSRF y límites; no usa Idempotency-Key. Token ausente/inválido sin detalle.
GET /api/v2/rooms/{roomId}/invitations Metadatos/revisiones, nunca tokens Anfitrión autorizado.
POST /api/v2/rooms/{roomId}/invitations expectedRoomRevision, vencimiento/usos → ID/token Anfitrión en espera; valida cuotas y política.
POST /api/v2/invitations/{invitationId}/revoke expectedRevision → recibo Anfitrión actual; token deja de admitir entradas.
POST /api/v2/invitations/{invitationId}/rotate expectedRevision → nuevo secreto Anfitrión actual; revoca anterior en la misma transacción.
POST /api/v2/me/media Imagen acotada → mediaId, estado de validación Cuenta elegible; menores/edad desconocida solo presets.
GET /api/v2/me/media/{mediaId} Estado propio pending|ready|rejected Propietario; solo ready puede usarse como avatar.

La URL compartida propone /join#invite=TOKEN: el fragmento no se envía en la solicitud HTTP. El cliente lo lleva a memoria, limpia la barra mediante reemplazo del historial y lo resuelve por POST; la pantalla usa Referrer-Policy: no-referrer, Cache-Control: no-store y no carga terceros. El usuario debe reabrir su enlace si pierde el contexto antes de entrar. No se registran cuerpos con tokens.

Gestión de invitación tiene invitationRevision; no aumenta roomRevision salvo que cambie también el estado/configuración de sala. La comprobación de anfitrión se hace bajo bloqueo de sala, aunque la precondición sea sobre invitación.

Al adoptar el perfil v2, entryMethod: standard|screen-qr se deriva del modo publicado y queda fijado en la sala. Crear la mesa presencial TVBoard usa screen-qr; sus rutas de emisión, rotación, resolución o consumo de invitaciones se rechazan. Desktop/Mobil usan el flujo convencional, que también puede admitir TVBoard como presentación personal del jugador invitado si el paquete lo declara. Elegir una presentación no cambia el método de entrada existente.

Pantallas compartidas (extensión futura del perfil multidispositivo)

Estas rutas se reservan para salas cuyo modeId permita interaction.sharedDisplay en el perfil v2. Distinguen pantalla común displayScope: room y TV personal displayScope: participant; el servidor deriva propietario/alcance del flujo autorizado y del paquete, nunca de un campo que conceda permisos por sí solo. No forman parte de la base ejecutable actual ni conceden acceso a un espectador remoto. La pantalla obtiene un contexto de dispositivo anónimo y acotado por bootstrap, separado de cuentas y membresías; todas las respuestas llevan Cache-Control: private, no-store.

Método y ruta Solicitud / datos Acceso y efecto
POST /api/v2/displays/challenges issuedAt → challengeId, challengeToken opaco de al menos 128 bits, expiresAt, QR con ese token en fragmento y código manual opcional Contexto de pantalla emitido por servidor, origen y CSRF válidos; sin roomId ni datos de partida. Idempotency-Key liga un único desafío a ese contexto.
GET /api/v2/displays/current Estado propio waiting|paired|revoked|expired, caducidad y, solo si está vinculado, roomId, displayId y alcance; en pantalla común de mesa en espera, juego público apto y QR de entrada vigente Únicamente el contexto autorizado; no devuelve lista de sala, membresía propietaria de otra persona ni secretos de juego. El QR de altas nunca se entrega a una TV personal.
GET /api/v2/rooms/{roomId}/displays displayRevision, pantallas autorizadas, estado y caducidad, sin código de emparejamiento Gestión de pantalla común por permiso; miembro con TV personal solo obtiene su propio vínculo.
POST /api/v2/rooms/{roomId}/displays issuedAt, exactamente uno de challengeToken o challengeCode, alcance solicitado compatible, expectedDisplayRevision → recibo, displayId Consume el desafío y ambos alias; deriva el alcance: común exige gestión de sala; personal exige membresía propia. En protegidas interviene el responsable autorizado. No recibe un asiento elegido por cliente como autoridad.
POST /api/v2/rooms/{roomId}/displays/{displayId}/revoke issuedAt, expectedDisplayRevision → recibo Gestor autorizado de pantalla común, propietario de TV personal o responsable autorizado; invalida inmediatamente sesión y suscripción correspondientes.

El límite propuesto es 5 minutos y 5 intentos fallidos por desafío reconocido, además de cuotas por contexto, principal, IP y sala. Los códigos inexistentes también consumen cuota de búsqueda para impedir enumeración. El alias manual opcional usa 10 símbolos base32 aleatorios (50 bits); no satisface el requisito de 128 bits del token QR ni sirve de sesión. Ambos identifican el mismo desafío de un solo uso y se invalidan juntos. Colisiones de alias se rechazan/regeneran al crear; todos los límites se fijan en los futuros esquemas antes de habilitar el flujo.

Los secretos se generan criptográficamente; nunca se registran ni transmiten en URL de petición, analítica o referer. El QR usa el token en fragmento; la aplicación lo pasa a memoria, limpia el historial y lo entrega solo en el cuerpo del POST, sin cargar terceros. Repetir exactamente la creación con la misma clave recupera la respuesta cifrada únicamente durante su vigencia y desde el contexto original; fuera de ella devuelve recibo sin secreto. Emitir otro desafío revoca el anterior de ese contexto; no renueva intentos acumulados. No se reutilizan códigos de cuenta infantil ni invitaciones.

Código inválido, caducado, consumido o ajeno produce el mismo DISPLAY_CHALLENGE_UNAVAILABLE; no se revela qué salas o pantallas existen. Revisión de pantallas obsoleta produce STALE_DISPLAY_REVISION; modo incompatible, DISPLAY_MODE_UNAVAILABLE; cuota de una pantalla ocupada, DISPLAY_LIMIT_REACHED. Ninguno de estos rechazos devuelve el código ni la identidad de la pantalla ajena.

La vinculación toma el bloqueo de sala, verifica permiso actual, política de protección, modeId compatible, desafío vigente y expectedDisplayRevision. Incrementa displayRevision; inicio de partida y vinculación compiten bajo el mismo bloqueo para que required-at-start no observe una pantalla fantasma. La propuesta admite una pantalla común por sala y una personal por membresía humana, con cuotas técnicas globales; reemplazar una personal exige confirmación y revocación atómica de la anterior. El vínculo caduca al cerrar la sala o a las 8 horas, lo que ocurra antes, y se revoca si se pierde la autorización responsable en una sala protegida o la membresía propietaria de una TV personal. Renovarlo requiere un desafío nuevo. Revocar o caducar no adjudica el resultado del juego: se aplica onLoss del modo, subordinado a la política temporal durante una ronda sin resolver. El cambio de vista de una TV personal afecta solo a su jugador; neutralizar una comparación de reacción puede afectar a toda esa ronda.

Una sesión de pantalla solo puede negociar game.* v2 para surface: "shared-display" de la partida vinculada. No recibe RoomView, room.sync, presence.update, chat, avisos personales ni lista de miembros, y se rechazan room.command, game.command y consultas de recibos personales. El servidor deriva sala/partida del vínculo, revalida autorización en la entrega y cancela la suscripción al revocar. En espera, reconexión o error la pantalla borra la proyección anterior; no muestra identificadores de menores ni texto libre de participantes.

Preparación TVBoard, QR de entrada y enlace de mando (propuesta v2)

Son dos finalidades distintas: tvboard-entry admite personas en una mesa presencial sin invitaciones; controller-link continúa el acceso de un jugador invitado y enlaza exclusivamente su TV y móvil. Las rutas y nombres siguientes son diseño pendiente de OpenAPI, esquemas, implementación y fixtures:

Método y ruta propuestos Solicitud / datos Autoridad y efecto
POST /api/v2/tvboard/setups Juego/versiones públicos seleccionados y perfil TVBoard → preparación, QR y caducidad Contexto TV con origen/CSRF/límites; no crea sala, anfitrión ni membresía. Solo juegos aptos para contexto público.
POST /api/v2/tvboard/setups/resolve Token de preparación → selección pública apta No consume la preparación ni entrega datos privados. La confirmación usa POST /rooms con tvBoardSetupToken.
POST /api/v2/rooms/{roomId}/screen-entries issuedAt, expectedEntryRevision → acceso QR nuevo/rotado Anfitrión de mesa screen-qr en espera con TV vinculada; token cifrado recuperable por contexto autorizado durante vigencia. No crea invitación.
POST /api/v2/screen-entries/resolve Token de entrada QR → ficha de juego/mesa apta Sin consumo ni lista de participantes; la entrada usa POST /memberships con screenEntryToken y permisos actuales.
POST /api/v2/displays/controller-links Invitación introducida en Smart TV → desafío/QR personal y ficha apta Contexto TV; no consume usos ni crea otra sala/plaza. Conserva contexto pendiente cifrado con caducidad corta.
POST /api/v2/controller-links/resolve Desafío de enlace → flujo pendiente de juego y acceso No entrega el token de invitación en claro ni acceso a la partida. Finalidad/contexto acotados.
POST /api/v2/controller-links/{challengeId}/confirm issuedAt, token de desafío y confirmación desde móvil → recibo, membresía y vínculo de TV personal Sesión humana elegible; revalida/consume invitación, crea o recupera membresía y vincula pantalla atómicamente. En miembro actual no vuelve a consumir invitación. En protegidas requiere autorización responsable.
POST /api/v2/rooms/{roomId}/presentation-changes issuedAt, targetProfile, expectedPresentationRevision → recibo de solicitud y operationId Miembro humano solicita su propio cambio compatible; el servidor deriva membresía y vínculo. Para TV personal → Mobil, aplica barrera y revoca su TV al completar. Conserva plaza/recibos. El perfil temporal puede diferir la aplicación hasta entre rondas.
POST /api/v2/rooms/{roomId}/control-transfers issuedAt, matchId, expectedControlGeneration, targetConnectionId; si cambia perfil, targetProfile y expectedPresentationRevision → recibo de solicitud y operationId Adquisición/transferencia de mando propio a conexión autenticada del mismo principal; valida renderer y punto seguro. No acepta actor ni membresía ajena. Generación inicial 0 sin mando; adquirir incrementa bajo barrera.
GET /api/v2/rooms/{roomId}/operations/{operationId} Estado actual pending|applied|rejected|cancelled, revisión de operación y generaciones resultantes cuando corresponda Propietario de la solicitud; reautoriza datos. Si perdió acceso conserva solo resultado mínimo propio, sin snapshot privado.

Preparar el QR no otorga derechos ni reserva plaza. En la creación presencial, confirmación autorizada del anfitrión, sala waiting, membresía, vínculo común y acceso QR se confirman juntos. El QR de preparación se sustituye por el de entrada; ambos tienen finalidades/generaciones distintas. La mesa es privada y no emite ni consume invitaciones. El QR de altas sirve durante espera para varias personas, sujeto a capacidad descontando plazas virtuales, permisos, caducidad y límites; reintentar desde una membresía actual no consume otra plaza. Inicio, cierre, transferencia de anfitrión o revocación/expiración de TV invalidan ese acceso, bajo el mismo bloqueo que las altas.

Los tokens QR usan aleatoriedad criptográfica de al menos 128 bits, finalidad y hash/MAC de validación, recuperación cifrada acotada y rutas con token en fragmento seguido de POST. El alias manual de vinculación tiene el tratamiento específico de la sección anterior; no se rebaja la entropía del QR. No se registran cuerpos secretos, ni se cargan terceros/analítica; respuestas private, no-store y no-referrer. Se propone caducidad máxima de 5 minutos para QR de altas y desafíos, con cuotas/valores definitivos por fijar. La rotación conserva la sala y revoca generaciones anteriores; consultar/escaneo no renueva vigencia. Errores de token no revelan si hubo caducidad, consumo, otra sala o un usuario.

Cambios de presentación y transferencias de mando tienen un recibo inmutable de aceptación de solicitud, independiente de su operación duradera, que puede permanecer pending. applied en ese recibo acredita que se registró la solicitud, no que ya cambió la pantalla o el mando; el recurso identifica la operación y la UI consulta su estado hasta terminar. Reintentar el mismo UUID devuelve el mismo recibo/operación. La operación revalida permisos, precondiciones, conexión destino y generación antes de aplicar; no altera recibos previos. Al cambiar cliente y perfil a la vez se usa una sola transferencia con ambos campos, sin dos solicitudes que compitan. Las solicitudes propias se serializan y un conflicto de generación termina rechazado; la pérdida de respuesta no repite adquisición. Su barrera drena/rechaza ingresos antiguos y entrega snapshot antes de permitir nuevas acciones, según continuidad de mando.

La operación conserva expiresAt UTC y una política de espera acotada fijada al aceptarla; consultar o reintentar no amplía el plazo. Si desaparece la membresía, cierra la sala o vence la espera sin punto seguro, termina cancelled sin transferir; una precondición incompatible termina rejected. Un worker recuperado solo puede aplicar una operación pendiente, vigente y de la generación actual. Los límites y códigos de motivo se cierran en los esquemas v2; no se publica un flujo que pueda quedar pendiente indefinidamente.

El QR personal no puede utilizarse para altas screen-qr, ni el QR común para reclamar un mando personal. Confirmar una admisión nueva con la invitación revocada/agotada falla sin crear vínculo. Si el principal ya conserva una membresía autorizada, se valida ese acceso para vincular su propia TV y no se vuelve a consumir la invitación. Un timeout tras commit se resuelve consultando/reintentando el mismo comando, nunca admitiendo a otra persona o regenerando usos. Las tareas pendientes revalidan autorización. Tras confirmar, el móvil controla su plaza y la TV solo recibe la proyección pública; cerrar la suscripción visual no elimina la membresía ni reabre la invitación.

La distribución game.* v2 negocia perfil y contrato por suscripción: Desktop/Mobil reciben player-full, el mando TVBoard recibe player-controller y cada TV recibe public-board. El servidor deriva finalidad, alcance y destinatario del vínculo y la sesión; comandos y recibos pertenecen al principal autorizado, nunca al dispositivo elegido por el cliente. La sección de mensajes especifica tuplas, barreras, outbox, reautorización y cambio de presentación. No se añade un broadcast de estado privado ni se reutiliza v1 como si entendiera estos campos.

El recorrido de invitación y TV personal exige Mobil y la opción «Jugar solo en el móvil». La solicitud de cambio es idempotente y recuperable; no consume usos de invitación, abandona la sala ni revoca TVs ajenas. Un cambio pendiente conserva la fuente temporal de la ronda actual. Su aplicación revalida membresía/versión y publica la nueva generación; el móvil negocia el contrato completo y descarta entregas del anterior. Reabrir otra pestaña para leer no se interpreta por sí solo como petición de desvincular TV.

El perfil de sincronización v2 propone time.probe/time.sample y time.ready/time.readiness como control técnico. El estado temporal va en game.snapshot.context.timing y los eventos round.* en game.snapshot.events, ligados a época, generación, revisión y suscripción; no son sobres independientes. Los plazos y resultados se derivan en servidor. Respuestas privadas se registran con ingreso confiable duradero y se evalúan tras una recogida acotada; un timestamp cliente o el orden de commit no decide ganador. Los detalles y límites requieren esquemas/binding nuevos: no son mensajes admitidos hoy por v1.

La app Flutter futura es solo de controles: usa las mismas acciones, ofertas, snapshots player-controller y consultas de recibos que el mando web. Su runtime/renderer se declara en negociación, sin elegir actor o destino de TV; se rechazan player-full y public-board para Flutter. El paso a móvil completo abre Mobil web y coordina autenticación y transferencia de mando/presentación, conservando membresía y recibos. El perfil de comunicación describe la secuencia completa y propone POST /api/v2/realtime/tickets como emisión técnica ligada a desafío WSS, sesión/vínculo y generación. Esa ruta no es una mutación de juego, no sigue un recibo de jugada ni existe hoy. Se implementa antes de ofrecer la app, junto a su perfil de acceso nativo; ninguno reemplaza las validaciones de admisión/QR.

Chat, avisos y seguridad

Método y ruta Datos Acceso
GET /api/v2/rooms/{roomId}/messages Página histórica, historyEpoch, cursor Chat; se aplica corte por retirada y redacciones actuales.
GET /api/v2/me/matches Historial/resultados proyectados Propietario; no copia resultado privado del motor.
GET /api/v2/me/notifications Página, inboxRevision, no leídos Propietario; sin texto de chat ni datos ocultos.
POST /api/v2/me/notifications/{notificationId}/read Lectura monotónica → recibo Propietario; incrementa bandeja solo si cambia.
POST /api/v2/me/blocks, POST /api/v2/me/blocks/{blockId}/revoke Destinatario autorizado / desbloqueo Seguridad, sesión; no revela quién bloqueó al solicitante.
POST /api/v2/safety/reports Recurso/persona, categoría, nota opcional → referencia Seguridad; sin requisito de chat/SMS; contexto de reporte para no autenticados con límites.
GET /api/v2/me/reports/{reportId} Estado propio y respuesta mínima Reportante, con verificación de titularidad.
GET /api/v2/commands/{commandId} Recibo propio found|unknown Sesión; puede consultar tras salir o perder permiso de chat, sin recibir contenido.

La consola de moderación y los derechos de acceso a evidencia son una interfaz administrativa separada, con roles y objetivos definidos en políticas y operaciones en la sección 9. Debe implementarse antes de comunicación pública. Los reportes sin cuenta no se exponen por ID adivinable: requieren el contexto de reporte emitido, sin revelar datos a terceros.

Paginación y caché

Listas por cursor opaco ligado a principal, filtros, orden y versión de política; por defecto 20 y máximo 50 elementos. Orden total por fecha de creación + UUID; navegación histórica de chat por createdSequence. No offsets ilimitados. Cursor vencido, contexto cambiado o historial redactado da CURSOR_EXPIRED y obliga a reiniciar la lista. Un cursor no es autorización.

Las listas móviles como salas públicas no prometen un snapshot transaccional entre páginas: se pueden actualizar entre lecturas; cliente deduplica IDs y refresca la primera página. Chat usa el contrato específico de la sección 6. Las respuestas privadas llevan private, no-store; catálogo público puede usar ETag según idioma/proyección segura y assets públicos inmutables por digest. La concurrencia se controla con revisiones de dominio explícitas, no con un ETag que mezcle proyecciones de usuarios.

4. Conexión y sobres Socket.IO

Esta sección conserva el binding v1 previsto. El binding común futuro para web y Flutter separa transporte WSS de mensajes de aplicación, con autenticación inicial y renderer negociado; sustituirlo requiere contrato ejecutable y pruebas, no cambiar el nombre de una librería cliente.

Eventos en namespace principal: platform:message y game:message. El segundo mantiene protocolVersion: 1 y su propio sync en el contrato vigente. La pantalla compartida requiere negociación futura de game.* v2 y un contexto de dispositivo restringido; no se conecta al contrato v1 fingiendo ser observador. Los acuses de plataforma son mensajes de aplicación; el callback de Socket.IO no reemplaza un recibo persistido.

Antes de suscribirse se envía un mensaje de negociación independiente:

{
  "type": "platform.hello",
  "requestId": "bdf2ea3d-1473-4c75-a0f6-b2e56807bc99",
  "supportedVersions": [1]
}

Respuesta platform.welcome: requestId, selectedVersion: 1, connectionId UUID, serverTime, límites y safetyRevision propia. Sin versión compatible: platform.negotiation-error con código y versiones admitidas, sin datos privados, y cierre. No se interpreta room.sync para negociar chat o avisos.

Para una pantalla vinculada, el binding futuro negocia platform.hello con supportedVersions: [2] y el contexto de dispositivo. Su platform.welcome v2 identifica connectionKind: "shared-display", connectionId, serverTime, alcance/generación del vínculo derivados y límites; no incluye safetyRevision de una cuenta inexistente. Solo admite sincronización/cancelación de su partida por game.* v2 y control técnico de conexión/reloj negociado (time.* propuesto). Cualquier platform.* de sala, chat, presencia, avisos o comandos se rechaza sin datos. La sesión queda ligada al roomId autorizado en servidor, no a un identificador pedido por el cliente. Una única marca serverTime no sustituye los intercambios de sincronización y medición.

Después todos los sobres llevan platformProtocolVersion: 1 y type. Peticiones de lectura llevan requestId; mutaciones, commandId e issuedAt. Flujos llevan subscriptionId y recurso. Identificadores de suscripción son propios de conexión/principal y se invalidan al desconectar, cambiar identidad o revocar acceso.

Mensaje Dirección Campos específicos
room.sync C → S requestId, roomId. Sustituye suscripción anterior de esa sala/conexión.
room.snapshot S → C roomId, subscriptionId, revision, delivery, observedAt, room.
room.command C → S roomId, commandId, issuedAt, expectedRevision, expectedMatch, action.
room.ack S → C roomId, commandId, duplicate, receipt.
room.cursor S → C roomId, subscriptionId, revision.
room.unsubscribe C → S requestId, roomId, subscriptionId. Solo suscripción propia.
platform.unsubscribed S → C requestId, subscriptionId. Baja idempotente, sin alterar membresía.
platform.command-status C → S requestId, commandId. Sirve también para chat y mutaciones HTTP.
platform.command-result S → C requestId, commandId, status: found|unknown, receipt si encontrado.
chat.sync C → S requestId, roomId. Solo miembros actuales elegibles para chat.
chat.snapshot S → C roomId, subscriptionId, requestId, sequence, historyEpoch, messages, historyCursor. Cola reciente autorizada.
chat.send C → S roomId, commandId, issuedAt, text. Autor derivado del servidor.
chat.ack S → C roomId, commandId, duplicate, receipt.
chat.event S → C roomId, subscriptionId, eventId, fromSequence, sequence, historyEpoch, event.
chat.cursor S → C roomId, subscriptionId, sequence, historyEpoch.
chat.unsubscribe C → S requestId, roomId, subscriptionId; respuesta platform.unsubscribed.
presence.update S → C roomId, subscriptionId de sala, presenceSequence, observedAt, expiresAt, members.
lobby.changed S → C Señal sin IDs ni actividad de salas privadas; solo cambios del listado público.
notification.changed S → C inboxRevision propia; se consulta por HTTP.
platform.access-changed S → C eventId, safetyRevision propia, recursos/suscripciones invalidados y capacidades propias mínimas.
platform.error S → C code, messageKey, recovery, requestId? , commandId?, roomId?, subscriptionId?, retryAfterMs?.

El servidor no comparte suscripciones personales entre usuarios. Las invalidaciones de seguridad no dependen de recibir otro movimiento: la revocación corta directamente la entrega en servidor. Si el cliente pierde el aviso, el siguiente intento sigue rechazado.

platform.access-changed se deduplica por eventId, no solo por safetyRevision: perder membresía puede invalidar una suscripción sin cambiar la edad/teléfono de la cuenta. Toda invalidación de la suscripción actual se aplica aunque esa revisión de protección siga igual.

5. Sala, acciones y sincronización

RoomView contiene nombre/visibilidad aptos para destinatario, estado, capacidad, anfitrión, miembros visibles, configuración pública, paquete fijado, política/plazos, capacidades propias y matchRef nulo o con matchId, tupla del motor y minimumRevision. Miembros: membershipId, alias/avatar proyectados, estado, listo, plaza provisional y seatId nulo hasta inicio. No incluye token de invitación, historial de chat, teléfono/edad ni estado interno del motor. Crear fija admissionMode: individual|adult|protected y communicationMode: off|adult-verified: solo adult admite el segundo; individual tiene capacidad 1 y protected siempre es privado. En el perfil futuro incorpora modeId, entryMethod y perfiles disponibles; seleccionar presentación no cambia esos permisos. Los valores se validan contra políticas y paquete, no conceden permisos por sí mismos ni revelan la edad de un participante.

Las capacidades son indicaciones evaluadas en observedAt, pueden caducar por tiempo/permiso y siempre se revalidan. No todo cambio de permiso incrementa roomRevision: una invalidación de acceso exige nuevo sync aunque esa revisión sea igual.

{
  "platformProtocolVersion": 1,
  "type": "room.command",
  "roomId": "1093543c-d383-437c-a427-251d39d63e42",
  "commandId": "04de01b1-771f-4775-9db0-3137399c464b",
  "issuedAt": "2026-10-05T16:00:00Z",
  "expectedRevision": 5,
  "expectedMatch": null,
  "action": { "type": "set-ready", "ready": true }
}
Acción Payload y permiso
set-ready ready: boolean; propio miembro en espera. Inicio automático según política.
configure Capacidad/configuración permitida; anfitrión en espera. Invalida todos los listo; juego/digest no cambian.
choose-color colorId ofrecido por la política de preparación del juego; propio miembro en espera. El adaptador valida la configuración de inicio; no infiere posibilidades por nombre de juego.
set-invite-message Texto limitado y aprobado por política de contenido; anfitrión en espera. No se muestra libremente a menores.
transfer-host targetMembershipId activo; anfitrión en espera, con efectos P2.
cancel Anfitrión en espera; cancelación explícita.
leave Miembro; en espera sale, en partida retira mediante motor. El anfitrión responsable de una partida protegida activa provoca cancelación de seguridad sin resultado competitivo según P7.
close-inactive Participante activo elegible según política y flujo del motor; cancela sin resultado competitivo.

En espera expectedMatch = null. En retirada/cierre activo es obligatorio { matchId, revision } exacto. Revisión de sala correcta con partida obsoleta da STALE_MATCH_REVISION. El servicio bloquea sala y partida y confirma el enlace al motor y su recibo en una sola transacción. El controlador no llama al protocolo de red game.command contra sí mismo ni duplica la retirada en dos registros independientes.

Barrera de sync: instalar recepción y buffer acotado, leer revisión R/proyección confirmada, emitir snapshot con delivery: { kind: "sync", requestId }, descartar buffer ≤R y publicar R+1… con delivery: { kind: "live", fromRevision }. Duplicados se descartan; huecos o overflow exigen nuevo sync. Un snapshot completo leído en R+k no se etiqueta como R+1.

La respuesta de salida puede preceder a la revocación de suscripción o viceversa; el recibo se puede consultar por identidad. Un cliente conserva cada flujo separado. Si llega final de sala con minimumRevision = 13 y juego sigue en 12, solicita game.sync y espera la vista autorizada de al menos 13. Una instantánea de juego más reciente que sala tampoco implica corrupción.

minimumRevision registra la última revisión de partida coordinada con un cambio de sala, empezando en 0; no promete ser la última jugada existente ni obliga a aumentar la revisión de sala en cada jugada.

6. Chat, redacción y recuperación

ChatMessage: messageId, authorMembershipId, authorDisplay, createdAt, createdSequence, status: visible|redacted, text. En lápida text = null; no hay copia del original en payload público. Normalización NFC, texto no vacío de hasta 500 puntos de código y 2 KiB UTF-8, sin HTML ni adjuntos. Longitudes se verifican tras normalizar y la huella idempotente usa el texto validado. Cuerpos no conformes se rechazan, no se truncan.

sequence ordena eventos, no solo mensajes. message.created crea un mensaje; message.redacted apunta a su ID, añade secuencia nueva y aumenta historyEpoch. No se reemite texto anterior a la redacción en un replay. Un bloqueo personal genera invalidación de acceso y nuevos marcadores sin contenido donde proceda; no cambia el mensaje global ni revela el autor filtrado.

Cada chat.sync empieza de nuevo con los últimos 50 mensajes como máximo, limitados además por bytes. Se instala primero la suscripción, se lee snapshot R y se liberan después eventos >R. La navegación de mensajes anteriores se hace por HTTP con historyCursor; no retiene una conexión entera mientras pagina todo el historial. La reconexión muestra esa cola reciente, sin repetir sonidos; no promete reproducción exhaustiva de todos los eventos desconectados.

Un evento vivo tiene sequence = fromSequence + 1. Hueco, cursor adelantado o epoch superior provoca nuevo sync y descarta el historial cacheado. Una página HTTP incluye historyEpoch; el cliente no mezcla una página de epoch anterior al ya observado. En servidor se revalida permiso/epoch antes de publicar una página o outbox. Así una respuesta retrasada no restaura texto que ya fue moderado.

La membresía almacena chatJoinedAfterSequence al entrar y, al retirarse, chatLeftAtSequence. Solo mensajes con createdSequence posterior al primero y no superior al segundo son legibles; el segundo es nulo mientras continúa. Se sincronizan únicamente eventos que puedan proyectarse dentro de ese intervalo, sin revelar autores/texto fuera de él. El antiguo miembro retirado consulta ese historial por HTTP si conserva elegibilidad adulto/SMS y ve redacciones posteriores; no tiene suscripción viva. Al perder elegibilidad tampoco conserva historial. La paginación aplica políticas actuales, no las de cuando se creó el cursor.

Las salas protegidas/mixtas rechazan chat.sync, envío e historial incluso para adultos con SMS. El mensaje se purga a los 30 días desde creación; el borrado aumenta historyEpoch e invalida cursores/cachés igual que una redacción. No se conserva su texto en outbox/replay fuera del período; el caso de moderación, si existe, tiene almacenamiento y permisos distintos. Un mensaje purgado puede conservar un marcador mínimo mientras el flujo siga existiendo, sin texto ni datos del autor.

7. Límites, avisos y recuperación

Valores iniciales para conformidad, anunciados en bootstrap/welcome:

Límite Valor propuesto
JSON de entrada HTTP / plataforma socket 64 KiB / 16 KiB; profundidad 32.
Salida de plataforma / página 256 KiB; reducir elementos y devolver cursor si se alcanza.
Sobre de juego 64 KiB según protocolo del motor; se valida por separado.
Suscripciones por conexión Hasta 5 salas y 5 chats; total máximo 10.
Buffer por suscripción al sincronizar 256 eventos o 1 MiB, lo que ocurra antes.
Cursores sala/chat Cada 15 s; detectar incluso la última entrega perdida.
Presencia Latido observado cada 15 s, TTL 45 s, secuencia por suscripción.
Consulta de avisos mientras hay foco Al conectar, recuperar foco y cada 30 s; inboxRevision cambia también al leer.
Chat 15 envíos/10 s por principal y 100/min por sala como valores iniciales, además de límites globales.
Avatar Hasta 1 MiB de entrada y 1024×1024 píxeles decodificados; PNG/JPEG/WebP estáticos, nueva codificación antes de publicar.

Configurar el límite agregado de Socket.IO no sustituye los límites específicos de mensaje. El servidor rechaza entradas demasiado grandes antes de ejecutar y corta clientes lentos sin crecimiento ilimitado. Una presencia caducada pasa a desconocida, no provoca derrota. Lecturas periódicas se suspenden en segundo plano; al volver se resincroniza.

Al reconectar: obtener sesión/capacidades actuales, negociar plataforma, sincronizar salas permitidas, solo chats elegibles y partidas asociadas; consultar recibos inciertos y reintentar el mismo sobre. Las proyecciones privadas se aíslan por identidad y se eliminan al cambiarla. No se restaura acceso por el socket.id antiguo ni por la recuperación automática de Socket.IO.

8. Errores y compatibilidad

El modelo de errores compartido define ErrorData idéntico para Go, TypeScript/Svelte y Dart/Flutter. HTTP lo envuelve en Problem Details, RFC 9457: type, title, status, correlation y error. Los campos code, mensaje, resultado y recuperación están solo en error; no se duplican planos con valores discrepantes. La URN se deriva del código y debe documentarse antes de publicar. No hay mensajes de excepción, payloads privados ni identificadores de cuentas ajenas en errores.

HTTP Códigos y uso Recuperación
400 / 413 INVALID_REQUEST, PAYLOAD_TOO_LARGE Corregir intención; no reintento automático.
401 SESSION_REQUIRED, SESSION_EXPIRED Reautenticar y limpiar proyecciones.
403 TERMS_REQUIRED, CHAT_NOT_ELIGIBLE, ACCOUNT_RESTRICTED, FORBIDDEN Mostrar requisitos propios o retirar acceso.
404 RESOURCE_NOT_FOUND También para recursos privados cuya existencia no se autoriza.
409 STALE_ROOM_REVISION, STALE_MATCH_REVISION, STALE_RESOURCE_REVISION, ROOM_FULL, ROOM_NOT_WAITING, PACKAGE_NOT_CREATABLE Sincronizar y revisión humana de una nueva intención.
409 COMMAND_ID_REUSED, COMMAND_EXPIRED, CURSOR_EXPIRED, TERMS_VERSION_CHANGED No cambiar UUID en automático; reiniciar consulta/flujo pertinente.
404 / 409 INVITATION_UNAVAILABLE para resolver token; INVITATION_CONFLICT para propietario con revisión obsoleta Mensaje genérico al visitante; refrescar al propietario.
403 / 409 DISPLAY_MODE_UNAVAILABLE, DISPLAY_LIMIT_REACHED, STALE_DISPLAY_REVISION Elegir modo compatible o refrescar listado; sin datos ajenos.
409 STALE_CONTROL_GENERATION, STALE_PRESENTATION_REVISION Recuperar recibo/operación propia y snapshot; una nueva adquisición requiere intención explícita y generación vigente.
404 / 409 DISPLAY_CHALLENGE_UNAVAILABLE Crear otro desafío en la pantalla; no explica qué condición falló.
429 / 503 RATE_LIMITED, TEMPORARY_UNAVAILABLE Retry-After HTTP / retryAfterMs socket; mismo comando si sigue admisible.

La falta de revisión obligatoria es 400 INVALID_REQUEST en este diseño, porque las precondiciones son campos del dominio. No se usa 428 para aceptar condiciones: RFC 6585 lo define para exigir una petición condicional (sección 3). Si en el futuro se añade If-Match, deben definirse aparte 428 por ausencia y 412 por condición no satisfecha.

El binding v2 usa ErrorMessagePayload { correlation, error } en platform.error/game.error, sin fingir estado HTTP. La tabla de Socket.IO v1 mantiene su forma antigua hasta implementar/versionar el puente. Rechazos definitivos de sala/chat llevan el mismo error en recibo; fallos previos o transitorios usan mensaje de error. Una operación incierta usa error.outcome: unknown y recovery.action: resolve-pending, sin afirmar rechazo ni generar otro UUID. CHAT_NOT_ELIGIBLE no detalla estados de otros usuarios. Los errores de negociación usan el mismo núcleo cuando el binding admite el modelo; si no hay versión común, solo se entrega diagnóstico público mínimo y se cierra.

{
  "type": "urn:juegoland:problem:room-full",
  "title": "La sala está completa",
  "status": 409,
  "correlation": {
    "requestId": "f8aa2fa1-aec8-41c8-a27b-644e999df03a",
    "commandId": "30c474dd-030a-4e0e-883f-c8a6d636c8c9"
  },
  "error": {
    "modelVersion": 1,
    "origin": "server",
    "code": "ROOM_FULL",
    "category": "conflict",
    "outcome": "rejected",
    "message": { "key": "errors.roomFull", "args": {} },
    "recovery": { "action": "change-selection" },
    "violations": []
  }
}

Cambiar significado, tipo obligatorio o regla de autorización requiere revisar compatibilidad; un campo opcional de respuesta no autoriza campos nuevos de solicitud. Versiones de cliente incompatibles deben actualizarse antes de comunicar o ver un juego, incluida la API antigua. La fase siguiente produce OpenAPI, esquemas discriminados y pruebas desde una fuente común, con ejemplos de éxito, errores y carreras; este documento no declara que esos artefactos ya existan.

9. Flujos derivados de las políticas de producto

Operaciones de diseño que deben incorporarse a OpenAPI y pruebas. Los métodos/rutas siguientes sustituyen la ambigüedad del primer borrador; no se afirma que existan endpoints funcionando. Mutaciones de dominio mantienen recibos comunes; autenticación y tokens de credencial usan intentos específicos, vinculados al contexto anónimo/sesión y con respuesta secreta recuperable acotada según P6. No se incluye contraseña o token en una huella persistida de dominio.

Cuenta y recuperación

Operación Contrato mínimo
POST /api/v2/auth/register authAttemptId, correo, contraseña, versión de condiciones; crea cuenta pendiente o convierte invitado propio sin cambiar userId. Declarar mayoría no la acredita. El correo de respuesta no enumera cuentas existentes.
POST /api/v2/auth/email-verifications Solicitar envío/reenvío para contexto propio; 202 genérico, cuota y tarea deduplicada.
POST /api/v2/auth/email-verifications/complete Token del enlace + contexto; consumir atómicamente, verificar correo sin elevar edad/tutela/SMS.
POST /api/v2/auth/login authAttemptId, correo, contraseña, rememberMe; servidor decide si admite sesión larga. Credencial inválida con respuesta uniforme, sin distinguir correo inexistente.
POST /api/v2/auth/reauthenticate Prueba de credencial y segundo factor cuando corresponda; resultado ligado a sesión/finalidad, válido 5 min; no recibe reauthenticated: true del cliente.
POST /api/v2/auth/recovery-requests Correo y contexto; 202 genérico, sin iniciar sesión ni revelar estado.
POST /api/v2/auth/recoveries/complete Intento, token y nueva contraseña; consumo de un uso + credencial + revocación de sesiones/tokens + suspensión de privilegios sensibles en una transacción. Exige después login.
POST /api/v2/me/password Reautenticación, nueva contraseña; revocar otras sesiones y rotar la propia de forma atómica. No confundir cambio autenticado con recuperación por canal perdido.
POST /api/v2/me/email-changes, POST /api/v2/me/email-changes/{changeId}/complete Reautenticación, nuevo correo / token; unicidad y verificación antes de cambiar, aviso al anterior, sin transferir cuenta por correo escrito.
GET /api/v2/me/exports/{exportId} Estado propio; la descarga se emite solo tras comprobación vigente de titularidad, un uso, 24 h y sin URL/token en logs.
GET /api/v2/me/deletion-requests/{requestId} Estado propio y excepciones pertinentes; tras revocación, acceso con credencial de seguimiento específica, sin reactivar sesión social.

El contrato infantil de creación de credencial y emparejamiento requiere un esquema aparte; no se reutiliza el PIN actual. El desafío de emparejamiento se emite por responsable con vínculo activo, propósito/principal/dispositivo específicos, caduca en 5 min, admite 5 intentos y se consume una vez. Ningún dato de tutor o menor ajeno se revela antes de autorización. MFA administrativo es requisito de activación: endpoints WebAuthn/challenges, recuperación y validación de origen/RP deben especificarse antes de abrir la consola, no simularse con SMS.

Tutela, contactos y capacidades

Operación Contrato mínimo
POST /api/v2/me/supervision-requests Responsable adulto acreditado y reautenticado; referencia de proceso de evidencia y alcance. No enlaza un menor existente solo por correo/nick.
GET /api/v2/me/supervisions Vínculos propios y estado mínimo; sin consulta arbitraria por childUserId.
POST /api/v2/me/supervisions/{supervisionId}/children Vínculo de alta validado; crear cuenta tutelada, preset y políticas. La evidencia, edad exacta y credenciales no se publican en perfil.
POST /api/v2/me/supervisions/{supervisionId}/revoke Reautenticación, revisión del vínculo, motivo privado acotado; incrementar protección y revocar contactos/permisos dependientes.
POST /api/v2/me/supervisions/{supervisionId}/contact-authorizations Referencia de principal autorizado mediante intercambio privado, alcance y caducidad; no directorio público de menores. Para dos menores exige aprobación de ambos responsables.
POST /api/v2/me/contact-authorizations/{authorizationId}/revoke Titular responsable, revisión; corta nuevas admisiones y aplica cancelación de seguridad si comparten partida protegida activa.
POST /api/v2/me/supervision-disputes, POST /api/v2/me/supervision-release-requests Ayuda/corrección o emancipación; verificación adecuada y revisión, sin transferencia automática de control ni chat.

Evidencia y callbacks de tutela/edad son interfaces internas del adaptador elegido; no aceptan afirmaciones de estado del navegador. La revocación produce platform.access-changed, invalida suscripciones y vuelve a proyectar vistas propias; a los demás solo indisponibilidad genérica. La revisión del vínculo no sustituye safetyRevision ni viceversa. En una sala protegida la cancelación por seguridad usa el núcleo con su revisión actual, no una revisión aportada por el responsable sobre una partida a la que quizá no tiene acceso.

Moderación y publicación interna

Operación Contrato mínimo
GET /api/v2/me/restrictions Motivo notificable, alcance/vigencia y posibilidad de recurso; sin identidad del reportante ni reglas internas de detección.
POST /api/v2/me/appeals, GET /api/v2/me/appeals/{appealId} Restricción/caso propio, nota acotada, estado y decisión; identidad verificada aunque esté suspendida, sin exigir chat/SMS.
GET /internal/v2/moderation/cases, GET /internal/v2/moderation/cases/{caseId} MFA, rol, asignación y acceso auditado; paginación acotada, sin exportación masiva de evidencia por defecto.
POST /internal/v2/moderation/cases/{caseId}/decisions expectedRevision, acción, motivo, alcance y duración; recibo idempotente + restricción + auditoría + invalidaciones confirmados juntos.
POST /internal/v2/moderation/cases/{caseId}/reviews Revisión por otra persona, precondición de caso y resolución auditable; no sobreescribe la decisión previa.

Las interfaces internas no se hacen públicas por compartir un binario Go: ruta/red/roles separados y ningún acceso con cookie de jugador. Publicación de juegos se realiza solo por pipeline/consola interna del equipo con permiso de publicación; no existe POST /api/v2/games para usuarios. El identificador de build/artefacto debe corresponder a paquete validado y reglas disponibles, no a una URL ejecutable elegida por el solicitante.

Powered by TurnKey Linux.