# Manual del Programador — Games Bakuryu

## Índice

1. [Arquitectura General](#1-arquitectura-general)
2. [Infraestructura Cloudflare](#2-infraestructura-cloudflare)
3. [API Reference](#3-api-reference)
4. [WebSocket Protocol](#4-websocket-protocol)
5. [Assets y CDN](#5-assets-y-cdn)
6. [Base de Datos](#6-base-de-datos)
7. [Despliegue Local](#7-despliegue-local)
8. [Vitácora de Errores y Soluciones](#8-vitácora-de-errores-y-soluciones)

---

## 1. Arquitectura General

```
Internet → Cloudflare (proxy)
    ├── domino.bakuryu.com → Nginx → Godot Web Export
    ├── cdn.bakuryu.com    → Worker → GCS direct (edge)
    ├── api.bakuryu.com    → Nginx → Docker (servicios Go)
    ├── web.bakuryu.com    → Nginx → Landing page static
    ├── d.bakuryu.com      → Nginx → Dev site
    └── assets.bakuryu.com → Nginx → GCS proxy (legacy)
```

### Stack

| Capa | Tecnología |
|------|-----------|
| Backend | Go 1.22 (microservicios) |
| Base de datos | PostgreSQL 16 (`games_bakuryu`) |
| Cache | Redis 7 |
| Cliente | Godot 4.4 (Web export) |
| Infra | Docker Compose + Nginx |
| CDN | Cloudflare Worker → GCS |
| Monitoreo | Prometheus + Grafana |

### Servicios

| Servicio | Puerto REST | Puerto gRPC | Propósito |
|----------|------------|-------------|-----------|
| auth-service | 4001 | 5001 | Login, JWT, KYC |
| wallet-service | 4002 | 5002 | Billetera, ledger |
| subscription-service | 4003 | - | Free/Premium |
| social-service | 4004 | - | Amigos, chat |
| shop-service | 4005 | - | Cosméticos |
| ranking-service | 4006 | 5003 | ELO global |
| audit-service | 4007 | 5004 | Auditoría |
| domino-service | 4010 | 5010 | Juego dominó |

---

## 2. Infraestructura Cloudflare

### 2.1 API Access

```bash
TOKEN="cfut_P9IgvmO0Xttio0t4nhZ4MdCpTVevmve6pnqjhTwO0ce5830d"
ZONE_ID="c7ab36c9b3e6eeadc68764c130522269"
ACCOUNT_ID="b5d3b02a58c9f4f09b207ba410218dc6"

# Verificar token
curl -s4 -H "Authorization: Bearer $TOKEN" \
  "https://api.cloudflare.com/client/v4/user/tokens/verify"

# Listar DNS
curl -s4 -H "Authorization: Bearer $TOKEN" \
  "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records"
```

> **Importante**: Usar `curl -4` (IPv4 forzado). El token no acepta IPv6.

### 2.2 CDN Worker (domino-cdn)

**Worker**: `domino-cdn` — proxy edge directo a GCS.

```
cdn.bakuryu.com/<path> → https://storage.googleapis.com/bakuryu/domino/<path>
```

**Deploy:**
```bash
# Service Worker format (API directa)
curl -s4 -X PUT \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/javascript" \
  --data-binary @src/cloudflare-worker-cdn/src/index-sw.js \
  "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/scripts/domino-cdn"

# Crear ruta
curl -s4 -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pattern": "cdn.bakuryu.com/*", "script": "domino-cdn"}' \
  "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/workers/routes"
```

### 2.3 CSP Worker (domino-csp)

**Worker**: `domino-csp` — parchea CSP en `domino.bakuryu.com`.
Agrega `api.bakuryu.com` a connect-src y `cdn.bakuryu.com` a img-src.

### 2.4 Cloudflare Tunnel

Tunnel `bakuryu-tunnel` (ID: `c934e13f-9958-42ea-a12f-1ff837763080`).

```bash
# Verificar
cloudflared tunnel list

# Iniciar
cloudflared tunnel run bakuryu-tunnel
```

### 2.5 DNS Records

| Tipo | Nombre | Proxy | Destino |
|------|--------|:-----:|---------|
| A | `bakuryu.com` | 🟠 | 46.250.236.252 |
| A | `domino.bakuryu.com` | 🟠 | 46.250.236.252 |
| A | `api.bakuryu.com` | 🟠 | 46.250.236.252 |
| CNAME | `cdn.bakuryu.com` | 🟠 | Worker domino-cdn |
| A | `web.bakuryu.com` | 🟠 | 46.250.236.252 |
| A | `d.bakuryu.com` | 🟠 | 46.250.236.252 |

---

## 3. API Reference

### 3.1 Auth Service (`:4001`)

#### Registro
```bash
curl -s4 -X POST https://api.bakuryu.com/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","username":"testuser","password":"SecurePass123!"}'
```

#### Login
```bash
curl -s4 -X POST https://api.bakuryu.com/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","password":"SecurePass123!","remember_me":true}'
# Response: JWT token + refresh token + user data
```

#### Perfil
```bash
curl -s4 https://api.bakuryu.com/auth/profile \
  -H "Authorization: Bearer <JWT_TOKEN>"
```

#### Health
```bash
curl -s4 https://api.bakuryu.com/auth/health
```

### 3.2 Domino Service (`:4010`)

#### Lobby (lista de partidas)
```bash
curl -s4 https://api.bakuryu.com/api/lobby \
  -H "Authorization: Bearer <JWT_TOKEN>"
```

#### Crear partida
```bash
curl -s4 -X POST https://api.bakuryu.com/api/games \
  -H "Authorization: Bearer <JWT_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"mode":"fvd","max_players":4,"bet":0,"is_ranked":true}'
```

#### Unirse a partida
```bash
curl -s4 -X POST https://api.bakuryu.com/api/games/join \
  -H "Authorization: Bearer <JWT_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"game_id": 1}'
```

#### WebSocket
```bash
wscat -c wss://api.bakuryu.com/ws -H "Authorization: Bearer <JWT_TOKEN>"
```

### 3.3 Wallet Service (`:4002`)

#### Balance
```bash
curl -s4 https://api.bakuryu.com/wallet/balance \
  -H "Authorization: Bearer <JWT_TOKEN>"
```

### 3.4 Ranking Service (`:4006`)

#### Leaderboard
```bash
curl -s4 https://api.bakuryu.com/ranking/leaderboard
```

---

## 4. WebSocket Protocol

### Formato de mensajes

```json
{
  "op": "move:play",
  "seq": 142,
  "ts": 1719012345,
  "payload": {"ficha": [6,4], "extremo": "der"}
}
```

### Opcodes

| Opcode | Dirección | Descripción |
|--------|-----------|-------------|
| `auth:login` | → Servidor | Handshake con JWT |
| `auth:login_ack` | ← Cliente | Confirmación + estado |
| `game:hand_deal` | ← Cliente | Repartición de fichas |
| `game:state` | ← Cliente | Estado del tablero |
| `move:play` | → Servidor | Jugar ficha |
| `move:ack` | ← Cliente | Jugada validada |
| `move:pass` | → Servidor | Pasar turno |
| `move:buy` | → Servidor | Comprar del pozo |
| `chat:msg` | ↔ Bidireccional | Mensaje de chat |
| `heartbeat` | ↔ Bidireccional | Keep-alive (15s) |

---

## 5. Assets y CDN

### Estructura en GCS

```
gs://bakuryu/domino/
├── 2d/
│   ├── tiles/         # 203 fichas 2D (7 temas × 29)
│   ├── board/         # 7 tableros
│   ├── ui/            # 34 assets UI
│   └── avatars/       # 24 avatares
├── 3d/
│   └── tiles/         # 203 fichas GLB (7 temas × 29)
├── audio/
│   ├── sfx/           # 13 efectos
│   ├── music/         # 7 pistas
│   └── voices/        # 416 voces
└── environments/
    ├── cantina_caracas/   # 56 archivos
    ├── vip_club/          # 56 archivos
    ├── playa_margarita/   # 56 archivos
    ├── taberna_andalucia/ # 56 archivos
    └── maracaibo/         # 56 archivos
```

**Total**: 1187 assets

### CDN URL Mapping

```
cdn.bakuryu.com/2d/tiles/classic/tile_0_0.png
cdn.bakuryu.com/3d/tiles/marble/tile_6_6.glb
cdn.bakuryu.com/environments/cantina_caracas/bg/scene_4k.png
cdn.bakuryu.com/audio/sfx/tile_place.mp3
```

### Subir nuevos assets

```bash
# Usar gsutil o el script Python
python3 src/scripts/upload_gcs.py
# Los assets deben estar en /workspace/output/bakuryu/domino/
```

---

## 6. Base de Datos

### Conexión

```bash
# Desde el servidor
PGPASSWORD="eq9YjDiCe4300+OzUs1omzbzUWdkrSX1hjvVxAxMVeI=" \
  psql -h localhost -U bakuryu -d games_bakuryu -p 6432
```

### Esquemas

| Esquema | Tablas | Propósito |
|---------|--------|-----------|
| `usuarios` | users, sessions, wallets, etc. | Datos compartidos |
| `domino` | games, game_players, rounds, moves | Juego dominó |

### Migraciones

```
src/db/migrations/
├── 001_usuarios_schema/    # 6 tablas base
├── 002_domino_schema/      # 8 tablas + 4 enums
├── 003_db_review_fixes/    # FK + índices
├── 011_terms_privacy/      # Términos y privacidad
├── 012_password_reset/     # Reset de password
└── 013_missing_schemas/    # 6 schemas faltantes
```

---

## 7. Despliegue Local

### Prerrequisitos

```bash
# Go 1.22+
go version
# Node 20+
node --version
# Docker + Compose
docker compose version
# Cloudflare CLI
which cloudflared
```

### Iniciar entorno

```bash
# 1. Clonar
git clone <repo>
cd src

# 2. Variables de entorno
cp .env.example .env

# 3. Iniciar infraestructura
docker compose -f infra/docker-compose.yml up -d db redis pgbouncer

# 4. Migraciones
docker compose -f infra/docker-compose.yml exec db \
  psql -U bakuryu -d games_bakuryu \
  -f /migrations/001_usuarios_schema/up.sql

# 5. Iniciar servicios
docker compose -f infra/docker-compose.yml up -d
```

### Compilar y testear

```bash
# Todos los servicios
go build ./...
go vet ./...
go test ./... -count=1

# Servicio específico
cd services/domino-service && go build ./...
```

---

## 8. Vitácora de Errores y Soluciones

### Sesión 1 — Infraestructura CDN + Cloudflare

| # | Error | Causa | Solución |
|---|-------|-------|----------|
| 1.1 | `wrangler deploy` → `Authentication error [code: 10000]` + `Cannot use access token from location: 2407:3640...` | El token CF solo acepta IPv4. Wrangler intentó IPv6. | Usar API directa con `curl -4` en lugar de wrangler. |
| 1.2 | API upload → `Uncaught SyntaxError: Unexpected token 'export'` | Cloudflare Workers API espera Service Worker format (`addEventListener`), no ES modules (`export default`). | Convertir a Service Worker format antes de upload. |
| 1.3 | `nginx: [error] invalid PID number "" in "/run/nginx.pid"` | Dos instancias de nginx: Docker (puertos 80/443) + host. La instancia host no escribió PID. | Enviar `kill -HUP` directo al PID del master process del host. |
| 1.4 | `cdn.bakuryu.com/environments/cantina_caracas/background.jpg` → 404 | La ruta GCS no coincide con la URL esperada. El archivo está en `environments/cantina_caracas/bg/scene_4k.png`. | Verificar estructura exacta en GCS antes de construir URLs. |
| 1.5 | `service nginx start` → falla | Docker nginx ya ocupa puertos 80/443. | La instancia host ya estaba corriendo (PID 1650574). No iniciar el servicio systemd. |

### Sesión 2 (Etapa 2) — Backend Wiring WS → Engine — RESUELTA

| # | Error | Causa | Solución |
|---|-------|-------|----------|
| 2.1 | WebSocket no procesa jugadas | `handleOnMessage` solo manejaba "join" y "chat". | `GameManager` creado en `internal/ws/game_manager.go` como bridge: `play_tile`, `pass_turn`, `buy_tile`, `start_game` enrutados al engine. Bot auto-fill + auto-play. |
| 2.2 | gRPC server vacío | domino-service registraba server sin handlers reales. | 3 endpoints implementados: `GetGameState`, `GetGameResult`, `ListActiveGames` via `proto/domino/domino.proto`. Reflection habilitado en no-prod. |
| 2.3 | Sin Redis para estado | El estado de partida no se persistía. | `internal/ws/redis_store.go`: snapshot JSON de ManagedGame, save tras cada broadcast, restore al arrancar, delete al finalizar. Key `domino:game:{gameID}`, TTL 72h. |

---

## 9. Arquitectura Backend Wiring (Etapa 2)

### GameManager (`internal/ws/game_manager.go`)

Bridge entre WebSocket y engine de juego. Gestiona ciclo de vida completo:

| Método | Descripción |
|--------|-------------|
| `CreateGame` | Crea ManagedGame con creator como North |
| `JoinGame` | Asigna posición libre (East/South/West) |
| `StartGame` | Llena slots vacíos con bots, inicia engine.Round |
| `PlayTile` | Valida + aplica jugada, log a DB, broadcast |
| `PassTurn` | Pasa turno, trigger bot si siguiente es IA |
| `BuyTile` | Compra del pozo, broadcast estado |

### Redis Persistence (`internal/ws/redis_store.go`)

- Snapshot JSON completo de `ManagedGame` (engine.Game + Round + player mapping)
- `saveToRedisLocked()` — tras cada broadcast de estado
- `LoadAllActive()` — restaura partidas activas al arrancar server
- `deleteFromRedisLocked()` — al finalizar partida
- Key pattern: `domino:game:{gameID}` con TTL 72h

### gRPC Handlers (`internal/server/grpc.go`)

```
proto/domino/domino.proto:
  GetGameState    → estado de partida activa (score, ronda, variante)
  GetGameResult   → resultado final si terminó
  ListActiveGames → lista resumen de partidas activas
```

### DB Persistence

- `LogMove()` — registra cada jugada en `domino.game_moves`
- `FinishGame()` — finaliza partida en `domino.games`
- Ambos llamados desde GameManager tras cada acción

### WebSocket Opcodes (actualizado)

| Opcode | Dirección | Descripción |
|--------|-----------|-------------|
| `auth:login` | → Servidor | Handshake con JWT |
| `auth:login_ack` | ← Cliente | Confirmación + estado |
| `game:hand_deal` | ← Cliente | Repartición de fichas |
| `game:state` | ← Cliente | Estado del tablero |
| `play_tile` | → Servidor | Jugar ficha `{"ficha": [6,4], "extremo": "izq"}` |
| `pass_turn` | → Servidor | Pasar turno |
| `buy_tile` | → Servidor | Comprar del pozo |
| `start_game` | → Servidor | Iniciar partida (REST) |
| `chat:msg` | ↔ Bidireccional | Mensaje de chat (max 500 chars) |
| `heartbeat` | ↔ Bidireccional | Keep-alive (15s) |

---

## 10. Comandos Rápidos

```bash
# Ver logs de servicio
docker logs domino-service
docker logs auth-service

# Test CDN
curl -sI "https://cdn.bakuryu.com/2d/tiles/tile_0_0.png"

# Ver estado servicios
ss -tlnp | grep -E ":(400[0-9]|4010|500[0-9]|5010)"

# DNS check
dig +short domino.bakuryu.com

# Recargar nginx
kill -HUP $(pgrep -f "nginx: master" | tail -1)

# Cloudflare API test
curl -s4 -H "Authorization: Bearer $TOKEN" \
  "https://api.cloudflare.com/client/v4/user/tokens/verify"

# Tests Go (todo el proyecto)
go test ./... -count=1

# Test E2E bot vs bot (domino-service)
go test ./services/domino-service/internal/ws/... -run TestBotVsBot -v -count=1

# Compilar + vet
go build ./... && go vet ./...
```
