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.

14 KiB

Modelos de datos compartidos — Go, Svelte y Flutter

Estado: contrato base definido el 6 de octubre de 2026 por indicación del usuario. Hay una fuente JSON Schema, DTOs generados TypeScript/Go/Dart y fixtures comprobables. Esto no implementa sesiones, salas, WSS ni una app Flutter. Los adaptadores de error del runtime y los contratos completos de esos flujos siguen pendientes.

1. Una fuente para los tres ámbitos

Los datos que cruzan una frontera se definen en contracts/shared/schema.json, con JSON Schema 2020-12. De esa fuente se generan TypeScript, Go y Dart. No se mantienen tres definiciones manuales del mismo DTO. Los nombres JSON, tipos, campos obligatorios, ausencia/null, códigos, unidades y significado deben ser iguales.

Un DTO compartido no es una entidad de base de datos, un componente Svelte ni un widget Flutter. Go mantiene entidades y causas internas separadas y proyecta el DTO autorizado; web y app lo decodifican y lo representan. Ningún campo del DTO acredita actor, edad o permiso. La lectura con otro lenguaje no cambia la información que recibe un mismo destinatario autorizado.

Familia definida Modelos y reglas
Identidad y versión UUID: representación canónica minúscula; Revision: entero entre 0 y 2^53−1, sin pérdida de precisión JSON. Un UUID no concede autorización.
Tiempo e idioma Instant: RFC 3339 UTC con Z; Locale: etiqueta estructural BCP 47. El patrón de idioma no sustituye canonicalización, soporte real ni revisión de traducciones.
Presentación de mensajes LocalizedMessage { key, args }; argumentos escalares acotados, sin HTML, objetos anidados ni valores secretos. Misma clave/argumentos para cada SDK; traducción según preferencia.
Errores ErrorData, ErrorCategory, ErrorOrigin, OperationOutcome, Recovery, Violation; núcleo único en todos los transportes.
Correlación y transporte Correlation, HttpProblem, ErrorMessagePayload, CommandRejection; contexto separado del error duradero.
Catálogo GameCard, CatalogPage, CatalogResponse; nombres y valores compatibles con la base actual. Antes de adoptar el límite de 100 fichas se materializa paginación en servidor.

Los valores enteros deben seguir el rango seguro aunque Go/Dart dispongan de enteros más amplios. NaN/infinito no son datos JSON admitidos. Listas y mapas vacíos se emiten como [] y {}, no null; en Go deben inicializarse. Un campo opcional se omite; null solo se acepta donde el esquema lo declara, como nextCursor. Los instantes UTC son distintos de los intervalos monotónicos *Ms ligados a clockEpoch del perfil temporal.

Los modelos de cuentas, salas, admisión, operaciones, recibos completos y proyecciones se incorporarán por cortes, desde los documentos que definen su dominio. Una clase con nombre parecido no convierte esos flujos pendientes en implementados. Cada campo nuevo debe declarar destinatarios y tratamiento de privacidad además de su tipo.

2. Núcleo común del error

Todos los errores públicos usan ErrorData, sin un formato diferente para Svelte, Go o Flutter:

{
  "modelVersion": 1,
  "origin": "server",
  "code": "ROOM_FULL",
  "category": "conflict",
  "outcome": "rejected",
  "message": { "key": "errors.roomFull", "args": {} },
  "recovery": { "action": "change-selection" },
  "violations": []
}
Campo Información que comparte
modelVersion Versión del modelo, independiente de /api/v2, protocolo de juego y versión de renderer.
origin server, client o transport. Una pérdida de red normalizada por el SDK no se presenta como un rechazo del servidor.
code Identificador estable de causa pública. Nunca una frase, tipo de excepción Go/Dart/JS o código HTTP usado como sustituto de causa.
category Clasificación común para políticas de presentación/diagnóstico: validación, autenticación, autorización, conflicto, disponibilidad, etc. No concede permisos.
outcome Lo que se sabe sobre la operación, según la sección siguiente. Impide equiparar timeout con rechazo confirmado.
message Clave traducible y argumentos públicos tipados. No se programa comparando frases traducidas.
recovery Acción semántica de recuperación y retryAfterMs opcional. El SDK combina esa indicación con el estado local y la idempotencia.
violations Lista de campos inválidos: pointer JSON Pointer, código y mensaje estructurado. Vacía si no corresponde. No devuelve el valor recibido.

El esquema permite códigos futuros con el mismo formato; un código desconocido recibe mensaje genérico y conserva categoría/resultado válidos para diagnóstico. Una clave de traducción desconocida no rompe la pantalla. Una categoría, acción o versión desconocida requiere fallback seguro de protocolo, sin ejecutar automáticamente una recuperación nueva. Los escritores solo emiten campos publicados; los lectores pueden ignorar extensiones opcionales desconocidas. No interpretan esos campos como permisos ni almacenan/loguean todo el objeto recibido.

3. Resultado incierto y recuperación

outcome Significado
not-applicable Lectura, negociación o fallo sin una mutación de dominio asociada.
not-applied Existe evidencia de que este intento no aplicó la operación. No garantiza que otro envío del mismo comando esté sin resolver.
rejected Rechazo de negocio definitivo registrado en un recibo del servidor. Reenviar el mismo UUID devuelve ese recibo; no vuelve a ejecutar.
unknown No se puede demostrar aplicación o rechazo. Puede haber ocurrido un commit.

unknown exige recovery.action: resolve-pending y, en un sobre, referencia propia de comando u operación. El cliente consulta el recibo/estado pertinente y, cuando corresponda, reenvía el mismo sobre. No crea otro UUID, cambia precondición ni anuncia fracaso definitivo para desbloquear una acción duplicada. Un fallo local de decodificación después de enviar una mutación también conserva incertidumbre. Autenticación usa su registro de intento específico; no se inventa un recibo de juego para recuperar credenciales.

Las acciones publicadas son none, correct-input, authenticate, complete-requirements, resync, resolve-pending, retry-same-request, change-selection, update-client y contact-support. retry-same-request solo se utiliza con evidencia y una operación reintentable/idempotente. rejected no autoriza ese reintento automático. retryAfterMs es un entero no negativo, máximo 24 h en este perfil, y solo acompaña reintento/consulta pendiente; cuotas superiores necesitan otra política/versionado.

Una nueva intención después de rechazo/revisión obsoleta exige oferta actual y decisión del usuario. Los errores no contienen comandos ejecutables, destinos arbitrarios ni nuevas credenciales. Una recomendación de recuperación no elude condiciones, protección, membresía o generación de mando.

4. HTTP, mensajes y recibos

HTTP utiliza Problem Details, RFC 9457, con error y correlation como extensiones propias:

{
  "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": []
  }
}

Se emite application/problem+json, con status igual al HTTP real, caché no-store y título público localizado cuando proceda. type se deriva de code como urn:juegoland:problem: más código en minúsculas y guiones; cada código necesita documentación antes de publicarse. La URN identifica el tipo y no se descarga desde el cliente. title es fallback humano, sin ser la autoridad de causa; los SDK usan el mismo error.

Los mensajes v2 platform.error/game.error incorporan ErrorMessagePayload { correlation, error } dentro de su sobre negociado. No llevan un estado HTTP ficticio. Los rechazos de negocio incorporan el mismo error en su recibo, conservando recurso/revisiones/UUID según el contrato completo. CommandRejection valida solo ese fragmento; no constituye un recibo completo de sala o juego. Un rechazo necesita origen servidor y resultado rejected.

El error duradero de un recibo no cambia al reintentarlo ni al cambiar idioma/dispositivo. Correlation pertenece a la entrega: requestId de intento/traza, commandId estable, operationId o subscriptionId cuando corresponda y esté autorizado. No se guarda una nueva traza dentro del recibo para modificarlo en cada consulta. Si la recepción ya identifica el comando, un adaptador no elimina esa correlación al normalizar el fallo. Un ID de soporte no expone stack, hosts, SQL o estado privado.

Los fixtures comunes contienen el mismo núcleo ROOM_FULL en HTTP, mensaje y rechazo; el verificador compara igualdad. La nueva estructura reemplaza los errores planos del borrador de plataforma al adoptar este contrato. El protocolo de juego v1 conserva sus sobres/códigos; cualquier puente normaliza al modelo común en el SDK sin modificar silenciosamente v1.

5. Códigos, localización y privacidad

Cada código publicado registra significado, categoría, clave/argumentos, estados HTTP admitidos y recuperaciones válidas en su contrato de dominio. No se renombra o reutiliza para otra causa. Las categorías de API siguen siendo referencia de plataforma; los juegos registran sus causas al publicar el paquete. Los códigos son extensibles, pero los argumentos/recovery deben estar documentados y probados.

Ejemplo Categoría / recuperación
INVALID_REQUEST validation; corregir entrada; violations solo de campos públicos del formulario.
SESSION_EXPIRED authentication; reautenticar y eliminar vistas privadas.
FORBIDDEN authorization; mensaje genérico sin identificar a terceros.
ROOM_FULL conflict; cambiar selección; rechazo confirmado si ya tiene recibo.
STALE_CONTROL_GENERATION conflict; recuperar recibo/operación y sincronizar antes de una nueva intención.
RATE_LIMITED rate-limit; demora acotada; distinguir intento no aplicado de una intención anterior pendiente.
CONNECTION_LOST transport; resolver pendiente si se envió una mutación; lectura reintentable si no la hubo.
INVALID_RESPONSE protocol; fallback local; conservar incertidumbre de una mutación enviada.

Web y Flutter traducen las mismas claves/argumentos revisados de es/en; idioma de interfaz no cambia código, resultado ni recuperación. No se presentan mensajes crudos de excepciones. Las causas internas permanecen en diagnóstico restringido, fuera del DTO. No se incluyen teléfono/OTP, prueba de edad, tokens, datos de otra plaza ni payload rechazado. También violations evita enumerar recursos ajenos; un fallo de secreto/credencial conserva el mensaje genérico de su política.

Los fallos de proxy, HTML inesperado, bytes inválidos, cortes y timeout se normalizan localmente con esta estructura, manteniendo origin y el resultado que realmente se conoce. El SDK no inventa un requestId servidor ni una causa de negocio a partir de un 502 genérico.

6. Generación, conformidad y adopción

npm run contracts:generate
npm run contracts:check
go test ./contracts/shared/go
dart contracts/shared/dart/verify.dart
dart analyze contracts/shared/dart

El generador y el verificador funcionan desde la raíz. contracts:check detecta si un DTO generado se separó del esquema y verifica ejemplos positivos/negativos. El esquema es la fuente de estructura; las relaciones semánticas de las secciones 3/4 se comprueban también en validate.ts y en los fixtures compartidos. Esas relaciones no se deducen solo de los tipos generados. El perfil del generador cubre únicamente las construcciones usadas en esta fuente; ampliarlo exige fixtures y revisión de las tres salidas. No es un generador general de OpenAPI ni del SDK de reglas.

Los bindings Go/Dart son DTOs estructurales: sus tipos/decodificación no sustituyen rangos, enums, relaciones, límites ni autorización. La validación de esquema/semántica se verifica en las herramientas TypeScript; Go/Dart comprueban lectura/serialización de los mismos ejemplos válidos. Antes de usar los adaptadores en red deben superar también los casos negativos con validadores propios conformes. La recepción textual necesita además límites de bytes/profundidad, rechazo de claves duplicadas y Unicode inválido antes de parsear; JSON Schema sobre un objeto no detecta claves duplicadas.

Adopción por cortes: usar los tipos compartidos para el catálogo; integrar ErrorData en adaptadores HTTP y errores locales web; después binding/recibos v2 y futura app con su validador. Una capacidad sigue desactivada hasta tener su camino completo verificado. Las entidades privadas nunca se serializan directamente por haber generado una clase pública.

Comprobación inicial: 22 modelos generados sin deriva, 14 fixtures válidos y 25 rechazos de esquema/semántica; núcleo de error idéntico entre HTTP, mensaje y recibo. Go y Dart conservan los 14 fixtures al decodificar/serializar; análisis Dart y pruebas de API Go pasan. TypeScript del frontend y Svelte pasan; para Svelte se usó temporalmente configuración por defecto equivalente a las opciones de compilador actuales, porque cargar Vite intentaba escribir una caché bloqueada por el entorno. No se hizo un ensayo de binding WSS ni de una app Flutter.

Powered by TurnKey Linux.