# WebSocket no Genesis — Guia de Uso

> Como adicionar atualização em tempo real (server → navegador) a uma tela, usando o mecanismo padrão do framework. Para o detalhamento interno, veja [`02-arquitetura.md`](02-arquitetura.md).

## O modelo mental em 30 segundos

O realtime do Genesis tem **três peças** e você quase nunca precisa tocar em WebSocket diretamente:

```
1. lerSocket($room)      →  renderiza um <div> que o front conecta sozinho
2. enviarSocket($room,$p) →  o servidor publica uma "ação Galaxia" nesse room
3. RequestGalaxiaAct      →  o front aplica a ação no DOM automaticamente
```

O ponto-chave: **o servidor não manda HTML pronto nem comandos imperativos**. Ele manda um **payload declarativo** (uma "ação Galaxia"), e o framework no navegador sabe traduzir isso em manipulação de DOM, chamada de função, modal, notificação, etc.

Você **não** instancia Scaledrone, não gerencia conexão, não escreve `onmessage`. O `verificarSockets()` (em `func.js`, já rodando no tema) faz isso para qualquer `.rooms-socket` presente na página.

---

## Receita A — Atualizar uma tela a partir do servidor (caso comum)

**1. Na view (PHP), declare a escuta** — uma vez, em qualquer lugar do HTML da tela:

```php
<div class="hidden">
    <?= $galaxia->lerSocket('galaxia_logistica_central'); ?>
</div>
```

Isso gera `<div class="rooms-socket" data-channel="..." data-room="galaxia_logistica_central">`. O front conecta automaticamente ~500ms após o load.

**2. No backend (Satélite/Sinal com `$galaxia`), publique quando algo mudar:**

```php
$payload = $this->galaxia->galaxiaResposta([])
    ->chamarFuncao('recarregarListaDeRotas', ['rota_id' => $rotaId])
    ->feito();

$this->galaxia->enviarSocket('galaxia_logistica_central', $payload);
```

**3. No JS da tela, defina a função que será chamada:**

```js
window.recarregarListaDeRotas = function (dados) {
    // dados = { rota_id: 123 }
    // ... atualiza o DOM ...
};
```

Pronto. Quem estiver com a tela aberta (inscrito naquele room) vê a atualização sem reload.

---

## Receita B — Trocar/atualizar HTML de um elemento

Se você só quer substituir o conteúdo de um seletor, nem precisa de função JS própria — use o vocabulário declarativo:

```php
$payload = $this->galaxia->galaxiaResposta([])
    ->substituirConteudo('#badge-pendentes', '<span>7</span>')   // → $('#badge-pendentes').html(...)
    ->adicionarClassesElemento('#card-42', 'destaque')
    ->feito();

$this->galaxia->enviarSocket('galaxia_logistica_board', $payload);
```

O `RequestGalaxiaAct` aplica `elementsHtml`, `adicionarClasseElemento`, etc. automaticamente. Veja a tabela completa de ações em [`02-arquitetura.md`](02-arquitetura.md#vocabulário-de-ações-requestgalaxiaact).

---

## Receita C — Publicar de onde NÃO há `$galaxia` (Laboratório/Evento)

Laboratórios, Strategies e Eventos não recebem a instância `$galaxia`. Há dois caminhos:

**C1 — Receber `$galaxiaRoute` por parâmetro** (preferido quando o caller tem acesso a ele). Os Laboratórios de aprovação já recebem `$galaxiaRoute` no `executar()`:

```php
// Dentro do Laboratório, repasse para um Notifier dedicado:
LiberacaoSocketNotifier::notificar($galaxiaRoute, $solicitanteId, $tipo, $status);

// E o Notifier usa o método central:
$galaxiaRoute->enviarSocket($room, $payloadGalaxia);
```

**C2 — Notifier estático com POST direto** (quando não há `$galaxiaRoute` algum, ex: callback de API REST). Replica a publicação ao Scaledrone internamente. Ver `EdicaoRotaSocketNotifier` como referência. Use só quando C1 é impossível — C2 duplica a lógica de transporte.

Em ambos, o payload continua sendo uma ação Galaxia:

```php
$payload = [
    'chamarFuncoes' => [
        ['funcao' => 'atualizarCardLiberacao', 'dados' => ['tipo' => $tipo, 'status' => $status]],
    ],
];
```

---

## Convenção de nomes de room

| Escopo | Padrão | Exemplo |
|---|---|---|
| Global de um módulo | `galaxia_<modulo>_<recurso>` | `galaxia_logistica_central`, `galaxia_logistica_board` |
| Por usuário | `galaxia_<modulo>_<recurso>_user_{id}` | `galaxia_logistica_liberacoes_user_42` |
| Por missão | `galaxia_<modulo>_missao_{id}` | `galaxia_logistica_missao_123` |

Regras práticas:
- **Isolamento por usuário/missão** → inclua o `{id}` no nome do room. Só quem deve receber se inscreve naquele room.
- O **canal** (`data-channel`) é único e global do sistema. O que separa as audiências é o **room**, não o canal.
- Publisher e subscriber precisam usar **exatamente o mesmo nome de room**.

---

## Checklist — adicionar realtime a uma tela

- [ ] A tela renderiza `<?= $galaxia->lerSocket('<room>'); ?>`? (renderize **sempre** que a tela puder receber eventos — não só condicionalmente)
- [ ] O backend publica com `enviarSocket('<room>', $payload)` usando **o mesmo nome de room**?
- [ ] O payload é uma ação Galaxia (`->chamarFuncao()`, `->substituirConteudo()`, …) e não HTML cru?
- [ ] Se usa `chamarFuncao('x')`, existe `window.x` definido no JS da tela?
- [ ] A publicação é **best-effort** (não pode derrubar a operação principal se o socket falhar)?

---

## Troubleshooting

| Sintoma | Causa provável | Verificação |
|---|---|---|
| Console: `nao encontramos sockets` | Não há `.rooms-socket` no DOM no momento em que `verificarSockets()` roda | A tela renderiza `lerSocket()`? Está condicionado a algo que estava falso no load? Renderize sempre. |
| Publish retorna `['status' => 'nao']` / HTTP 500 | Problema no canal Scaledrone (conta/plano) | Teste o publish com `curl` no canal; ver [`02-arquitetura.md`](02-arquitetura.md#o-incidente-do-canal-quebrado) |
| Front conecta mas nada acontece | Publisher e subscriber em **rooms diferentes**, ou `window.<funcao>` não definida | Compare os nomes de room nos dois lados; confira o console por `A função X não está definida` |
| Card só atualiza após reload | `lerSocket` renderizado condicionalmente (só quando já havia estado) → socket nunca conectou | Renderize `lerSocket` incondicionalmente para a tela |
| SDK `Scaledrone is not defined` | Reincluiu o SDK na view e ele corre async via AJAX | **Não** reincluir — o tema genezes já carrega o SDK globalmente |

### Lições aprendidas (não repita)

- **Não reinvente a conexão.** Houve uma tentativa de instanciar `new Scaledrone()` numa view com IIFE próprio + reinclusão do SDK. Isso compete com o `verificarSockets()` nativo e quebra sob carregamento AJAX. Use `lerSocket` + o mecanismo nativo.
- **Não reincluir o SDK.** Já vem do tema (`genezes/_theme.php`).
- **Publish e subscribe usam o mesmo canal.** `lerSocket()` e `enviarSocket()` leem o mesmo `channel_id` no `GalaxiaRoute`. Se algum dia trocar o canal, troque nos dois.
- **`lerSocket` sempre, não condicional.** Se o socket só é injetado quando já existe estado pendente, ele nunca conecta a tempo de receber a transição.
