Quickstart. Ponés esto en un index.html, lo zipeás, lo subís via /devs → tenés un juego con tracking + leaderboard + storage funcionando.
Esqueleto mínimo
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<meta name="theme-color" content="#0a0118">
<title>Mi juego</title>
<style>
html, body { margin: 0; padding: 0; height: 100%; background: #0a0118; color: #fff;
font-family: system-ui, sans-serif; overscroll-behavior: none; }
#stage { display: grid; place-items: center; height: 100vh; }
button { padding: 12px 24px; font-size: 18px; cursor: pointer; }
</style>
</head>
<body>
<div id="stage">
<div>
<h1 id="score">Score: 0</h1>
<button id="bump">+1</button>
<button id="finish">Game over</button>
</div>
</div>
<!-- 1. Slug del juego ANTES del SDK. -->
<script>window.NEON_GAME = "mi-juego";</script>
<!-- 2. Importar el SDK. -->
<script type="module">
import {
storage,
track,
submitScore,
ensurePlayerName,
} from "https://jugafy.com/sdk/v0.0.1-beta/jugafy.js";
// 3. Esperar al storage (importante en sandbox).
await storage.ready();
// 4. Cargar progreso si lo había.
let score = storage.get("highscore") ?? 0;
document.getElementById("score").textContent = `Score: ${score}`;
track("session_start"); // ya lo dispara el SDK al boot, pero podés re-emitir custom
// 5. Loop de juego (mock — un botón que suma).
document.getElementById("bump").addEventListener("click", () => {
score++;
document.getElementById("score").textContent = `Score: ${score}`;
track("score_bump", { score });
});
// 6. Game over: ensurar nick → submit al leaderboard → guardar highscore.
document.getElementById("finish").addEventListener("click", async () => {
const name = await ensurePlayerName(); // muestra modal si es la 1ra vez
try {
await submitScore(window.NEON_GAME, name, score);
track(`${window.NEON_GAME}_run_ended`, { score });
storage.set("highscore", Math.max(storage.get("highscore") ?? 0, score));
alert(`Score ${score} submited al leaderboard.`);
} catch (err) {
alert(`Error submit: ${err.code || err.message}`);
}
});
</script>
</body>
</html>
Qué hace este esqueleto
- Tracking dual —
session_start,score_bump,<slug>_run_endedvan a GA4 + tablaeventsdel backend. - Modal de nickname la 1ra vez que el user hace game over. Cross-game: si jugó otro juego de Jugafy antes, ya lo recordamos (no se repite el modal).
- Score submit al leaderboard global de tu juego. El backend dedupea por
name(1 row por player por juego, mejor score gana). - Storage del highscore — sobrevive reload, namespaced por slug (no choca con otros juegos).
- Auto-bridge al parent — la plataforma detecta
<slug>_run_endedy dispara el modal “no pierdas tu progreso” si el user no está logueado.
Workflow recomendado
1. Desarrollo local
# en tu carpeta del juego
python3 -m http.server 8000
# o vite, serve, busybox httpd, etc
Abrí http://localhost:8000. El SDK detecta same-origin → todo funciona normal con localStorage del browser.
2. Subir a /devs
-
Empacá el ZIP. Dos formatos aceptados (ambos andan):
# Formato A — archivos sueltos en root del ZIP mi-juego.zip ├── index.html ├── game.js └── assets/ └── sprite.png # Formato B — todo dentro de una carpeta única mi-juego.zip └── mi-juego/ ├── index.html ├── game.js └── assets/ └── sprite.pngEl formato B es lo que sale del right-click → “Comprimir” sobre la carpeta del juego en Mac/Windows. El sistema detecta el common root folder y lo strippea automáticamente. No tenés que pensarlo — comprimí como te resulte natural.
-
Logueate en
https://jugafy.com/devsy “Subir nuevo juego”. -
Slug, título, categoría, descripción. Thumbnail opcional (recomendado: 600×900 PNG/JPG).
3. Probar pre-approve
En /devs, fila del juego pending: botón “Probar” abre un modal con el iframe sandboxed real. Validá que el juego carga y que el progreso se persiste entre opens del modal.
4. Aprobar / re-publish
- Owner: aprueba → Vercel rebuilds → ruta pública
/<categoría>/<slug>viva en ~60-90s. - Subir nueva versión: en
/devs, fila del juego, botón “Editar” → file input “Nuevo bundle ZIP”. El status vuelve apending, hay que re-aprobar. La versión vieja queda viva en prod hasta que el rebuild ship la nueva.
Errores comunes
Jugafy.storage.set no persiste post-reload
- Confirmá
window.NEON_GAMEestá seteado antes del import. - En consola:
NeonArcade.storage.modedebería decirsame-origin(dev local) osandboxed-bridge(prod en Jugafy). - En prod, confirmá que
host.jscargó:window.JugafyHost?.versionen consola del parent (jugafy.com, no del iframe) debería decir"v0.0.1-beta".
submitScore 401 / 403
namerequerido —ensurePlayerName()antes resuelve esto.- Si tu user está en una sesión Supabase muy vieja, el backend puede rechazar el token. Logueate de nuevo en
jugafy.com.
track() parece no enviar nada
- Es fire-and-forget. No tira errores. Para verificar: abrí DevTools → Network → filtrá por
track-event. Si no ves requests, probablementewindow.NEON_GAMEno está seteado o un ad-blocker está cortando.
El juego anda local pero no en prod
Diferencia más común: localStorage directo en lugar de Jugafy.storage. En sandbox prod, localStorage tira SecurityError. Migrate: ver storage.
Próximos pasos
- Player identity — cómo manejar el nickname y la identidad anon entre runs.
- Tracking — eventos estándar (
session_start,*_run_started,*_run_ended) + el aggregator pattern. - Scores —
submitScorecon extras (level, duration_ms),getTopScorescon scope diario,persistRunpara URLs compartibles. - Storage — API completa + caveats del sandbox.