---
type: tutorial
title: Construye un juego autoalojado
summary: Construye un bundle de juego en sandbox con el game SDK, alójalo tú mismo, apunta el widget a él con game-src, y produce la traza determinista que el servidor repite.
---

Al final de este tutorial tendrás un juego de Caputchin real: un bundle de JavaScript que corre en el iframe en sandbox del widget, habla con el anfitrión a través del game SDK, y reporta una **traza** que el servidor puede volver a correr. A diferencia del [modo manual](/docs/custom-game-development/manual-mode), un juego autoalojado puede [ejercer de gate de una clave de sitio](/docs/site-keys/games), porque su ronda es reproducible. Este tutorial cubre construir y alojar el juego; [repetición y gate](/docs/custom-game-development/replay-and-gating) cubre el artefacto que de verdad activa el gate.

Lee [corre tu propio juego](/docs/custom-game-development/overview) para saber dónde encaja esto. Necesitas un sitio donde alojar un archivo de JavaScript estático (cualquier CDN o host estático) y el widget en tu página.

## La forma de un juego autoalojado

Un juego autoalojado es un bundle de JavaScript autocontenido que:

1. Importa la función `register` del `@caputchin/game-sdk` y registra una única **factory de juego**.
2. Renderiza en el contenedor que el widget le da a la factory, usando el idioma, el skin y la configuración resueltos que se pasan junto a él.
3. Se desarrolla deterministamente bajo la semilla por ronda, registrando el juego como una **traza**.
4. Al ganar, llama a `bridge.pass({ trace })` con ese registro.

El widget carga tu bundle en su iframe e invoca tu factory; que la factory sea llamada *es* la señal de inicio (no hay un evento de inicio separado que esperar).

## 1. Registra una factory de juego

Instala el SDK y llama a `register` una vez con tu factory. **No** pasas el manifiesto; el servidor resuelve los presets y se los entrega a la factory.

```js
import { register } from "@caputchin/game-sdk";

register((container, bridge, ctx) => {
  // container — a DOM element inside the sandboxed iframe; render into it.
  // bridge    — push-only channel to the host (pass / error / setSize).
  // ctx       — the per-round context (seed + resolved locale/skin/config).

  const cleanup = startGame(container, bridge, ctx);

  // Return an optional cleanup function; the widget calls it on unmount.
  return cleanup;
});
```

```bash
npm install @caputchin/game-sdk
```

## 2. Lee el contexto por ronda

El tercer argumento de la factory, `ctx`, lleva todo lo que tu juego necesita para este visitante:

- `ctx.seed` — la semilla de repetición por ronda. **Deriva toda la aleatoriedad de esto.** Null cuando el juego corre fuera de una sesión verificada.
- `ctx.locale` — el objeto de idioma resuelto: `ctx.locale._lang` más tus claves de cadena traducidas.
- `ctx.skin` — el objeto de skin resuelto: `ctx.skin._theme` (siempre `light` o `dark`) más tus claves de color y de asset.
- `ctx.config` — la configuración de juego resuelta (o null).

Estos son los **presets resueltos** para este visitante, producidos por Caputchin a partir de tu [esquema y presets](/docs/custom-game-development/dashboard-schema) definidos en el dashboard. Lees tus propias claves de ellos; no resuelves nada tú mismo.

Si tu layout necesita un tamaño explícito, díselo al anfitrión una vez tras tu primer pintado:

```js
bridge.setSize(360, 480);
```

## 3. Haz el juego determinista

Esta es la regla que hace gateable un juego: **el juego debe ser determinista dados la semilla y las entradas del jugador.** No llames a `Math.random()` ni leas el reloj de pared para nada que afecte al desenlace; deriva cada elección aleatoria de `ctx.seed`. El servidor vuelve a correr tu lógica bajo la misma semilla y la misma traza registrada y debe llegar al mismo veredicto.

```js
// A tiny seeded PRNG; feed it ctx.seed so the server reproduces the run.
function makeRng(seed) {
  let s = hashToInt(seed);
  return () => (s = (s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
}
```

Registra las entradas que conducen el desenlace (qué objetivos golpeó el jugador, en orden, con temporización si importa) mientras juegas. Serializa ese registro a una cadena: esa cadena es tu traza.

## 4. Pasa con la traza

Cuando el visitante gana, entrégale al anfitrión la traza. `pass` toma un objeto con una única cadena `trace`:

```js
function onWin(traceString) {
  bridge.pass({ trace: traceString });
}
```

La traza es **opaca para la plataforma**: es cualquier cadena que solo tu juego define, tal que tu lógica, combinada con la semilla, reproduzca el resultado. El juego no reporta una puntuación aquí; la puntuación, si la hay, es asunto tuyo dentro del iframe.

Dos hechos más del bridge:

- El primer `pass` canjea la ronda; una ronda fallida o abandonada se señala simplemente **no llamando** a `pass` (no hay método `fail` en este bridge).
- `bridge.error({ code, message })` es para un fallo interno del juego (un asset falló al cargar, una excepción), no una derrota del jugador. Hace aflorar un evento `error` al anfitrión.

Después de que el anfitrión canjea el pass, libera el token del widget, y tu backend [verifica ese token](/docs/integration-guides/verify-on-your-backend) como siempre.

## 5. Empaqueta para el sandbox

El widget carga exactamente una URL de script, así que todo debe estar en ese único archivo. Configura tu bundler (esbuild, rollup, vite, webpack; cualquiera de ellos) para una sola salida autocontenida:

- inlinea los assets como data URLs (sprites, sonidos, fuentes);
- desactiva el code splitting;
- si usas WASM, embébelo como base64 e instáncialo desde bytes.

Patrones que **no** funcionan dentro del sandbox, porque el iframe es de origen opaco con una CSP estricta:

- `fetch('./sprite.png')` relativo a la ruta — no hay ruta desde la que descargar;
- `import('./chunk.js')` dinámico — la segunda URL está bloqueada;
- `new Worker('./worker.js')` — lanza workers desde URLs `Blob` inline en su lugar;
- descargas de CDN externo en tiempo de ejecución — `connect-src` las bloquea.

## 6. Aloja el bundle y apunta el widget a él

Sirve el archivo construido desde tu propio host estático sobre `https` (se permite `http` de loopback para dev local), luego apunta el widget a él:

```html
<caputchin-game
  sitekey="cpt_pub_..."
  game-src="https://cdn.example.com/my-game/game.js"
></caputchin-game>
```

El widget carga tu bundle en su iframe en sandbox, invoca tu factory con el contexto, y espera tu `pass`. En este punto el juego corre y verifica, pero todavía no está permitido **ejercer de gate** de una clave.

## 7. Hazlo gateable

Para usar tu juego autoalojado como gate de verificación, hacen falta dos cosas más, ambas cubiertas a continuación:

1. **Regístralo como un juego a medida** en el dashboard (un id que eliges) y define su [esquema y presets](/docs/custom-game-development/dashboard-schema) de campos para que el contexto lleve idioma/skin/config reales.
2. **Sube un artefacto de repetición**: un build headless de la lógica de tu juego que el servidor corre para volver a derivar el veredicto de la semilla y la traza. Mira [repetición y gate](/docs/custom-game-development/replay-and-gating).

Hasta que el artefacto de repetición se sube y pasa su autocomprobación, el juego a medida aparece como **No reproducible** y no puede ejercer de gate.

## Véase también

- [Repetición y gate](/docs/custom-game-development/replay-and-gating): el artefacto que activa el gate.
- [Referencia del esquema del dashboard](/docs/custom-game-development/dashboard-schema): definir los campos que el contexto resuelve.
- [Modo manual](/docs/custom-game-development/manual-mode): la alternativa más ligera y sin gate.
- [Verifica en tu backend](/docs/integration-guides/verify-on-your-backend): confirmar el token que produce un pass.
