# Design: Atualização em tempo real do card de liberação (Scaledrone push)

**Data:** 2026-06-18  
**Status:** Aprovado

## Problema

O operador solicita uma liberação (Pesagem, Alocação WMS, Retira, Saída de Operação) e fica aguardando na tela `estrutura.php` (tela principal do App). Quando o admin aprova ou rejeita, o card continua mostrando "Em análise / Aguardando aprovação..." porque o estado foi renderizado server-side no carregamento. O operador precisa reiniciar a tela manualmente para ver o novo estado.

## Solução

Scaledrone push puro: ao aprovar ou rejeitar, o backend publica um evento no room Scaledrone do usuário. O JS no `estrutura.php` escuta esse room e faz swap do card no DOM sem recarregar a página.

Pré-requisito: corrigir o bug de HTTP 500 no REST publish do Scaledrone (`enviarSocket()` / `EdicaoRotaSocketNotifier::publicar()`), que está documentado no código como quebrado.

## Escopo

**Tipos de liberação cobertos:**
- `ACESSO_PESAGEM`
- `ACESSO_ALOCACAO_WMS`
- `ACESSO_RETIRA`
- `SAIDA_OPERACAO`

**Gatilhos:**
- Aprovação → `AprovarSolicitacaoLaboratorio`
- Rejeição → `RejeitarSolicitacaoLaboratorio`

**Fora do escopo:**
- Polling fallback (fix do Scaledrone é obrigatório)
- Outros sinais além do App (`estrutura.php`)
- Notificação sonora ou push notification mobile

## Arquitetura

### 1. Fix do Scaledrone REST publish

Investigar e corrigir o HTTP 500 em `GalaxiaRoute::enviarSocket()` e `EdicaoRotaSocketNotifier::publicar()`. Ambos usam o mesmo endpoint e credenciais — o fix se aplica aos dois.

Hipóteses a verificar na implementação:
- Payload ou Content-Type incorretos
- Endpoint URL da API mudou
- Autenticação Basic desalinhada com a versão atual da API Scaledrone

Critério de aceitação: `enviarSocket()` retorna `status: ok` e o WebSocket no browser recebe o evento.

### 2. LiberacaoSocketNotifier (novo)

```
componentes/Missoes/Logistica/sinais/Solicitacoes/Eventos/LiberacaoSocketNotifier.php
```

Responsabilidade única: encapsular o publish para o room do usuário.

```
LiberacaoSocketNotifier::notificar(int $solicitanteId, string $tipo, string $status): void
  room: "galaxia_logistica_liberacoes_user_{$solicitanteId}"
  payload: { action: "liberacao_atualizada", tipo, status, solicitante_id }
  best-effort: falha vira error_log, não propaga exceção
```

Room nomeado por usuário garante isolamento — cada operador escuta só suas liberações.

### 3. Hook nos Laboratórios existentes

`AprovarSolicitacaoLaboratorio` → ao final da aprovação (após a Strategy executar), chama `LiberacaoSocketNotifier::notificar($solicitacao->solicitante_id, $solicitacao->tipo, 'aprovada')`.

`RejeitarSolicitacaoLaboratorio` → idem com `status = 'rejeitada'`.

### 4. estrutura.php — templates + lerSocket

Para cada card em estado "pendente", renderizar:

```php
<!-- Card visível -->
<div data-tipo="ACESSO_PESAGEM" class="card-liberacao-pendente col-6">
    …HTML atual do card pendente…
</div>

<!-- Templates ocultos para swap -->
<template data-tipo-aprovado="ACESSO_PESAGEM">
    …HTML idêntico ao renderizado quando $acessoPesagemLiberado === true…
</template>
<template data-tipo-rejeitado="ACESSO_PESAGEM">
    …HTML idêntico ao renderizado quando o usuário ainda não solicitou…
</template>
```

Adicionar `lerSocket()` uma única vez no `estrutura.php`:

```php
<div class="hidden socket-container">
    <?= $galaxia->lerSocket('galaxia_logistica_liberacoes_user_' . ($usuarioLogado->id ?? 0)); ?>
</div>
```

### 5. JS no estrutura.php

Script inline (IIFE) incluído junto ao SDK Scaledrone.

**Inicialização:** encontrar o `.rooms-socket` do room de liberações → conectar ao Scaledrone → subscrever o room.

**Ao receber evento:**
1. Verificar `action === 'liberacao_atualizada'`
2. Localizar card pelo `data-tipo` correspondente
3. Encontrar o `<template>` correto (`data-tipo-aprovado` ou `data-tipo-rejeitado`)
4. Substituir o card pelo conteúdo do template (`.replaceWith(template.content.cloneNode(true))`)
5. Re-inicializar event listeners do novo card (botão de solicitar usa click handler inline)
6. Desconectar do Scaledrone após processar (conexão só é necessária enquanto há cards pendentes)

**Tratamento de erro de conexão:** logar no console; sem retry automático (o operador pode recarregar manualmente se necessário).

## Fluxo completo

```
Admin (browser A):
  clica "Aprovar" → AJAX → AprovarSolicitacaoLaboratorio
    → Strategy atualiza dia_efetivo_liberacoes do usuário
    → LiberacaoSocketNotifier::notificar(userId, "ACESSO_PESAGEM", "aprovada")
        → enviarSocket("galaxia_logistica_liberacoes_user_42", { action, tipo, status })
        → Scaledrone REST publish → distribui via WebSocket

Operador (browser B):
  estrutura.php conectado ao room "galaxia_logistica_liberacoes_user_42"
  recebe evento { action: "liberacao_atualizada", tipo: "ACESSO_PESAGEM", status: "aprovada" }
  → swap do card pendente → card liberado aparece, clicável
  → WebSocket desconecta
```

## Arquivos modificados / criados

| Arquivo | Ação |
|---|---|
| `GalaxiaRoute::enviarSocket()` | Fix HTTP 500 Scaledrone |
| `EdicaoRotaSocketNotifier::publicar()` | Mesmo fix (mantendo consistência) |
| `sinais/Solicitacoes/Eventos/LiberacaoSocketNotifier.php` | Criar |
| `sinais/Solicitacoes/Laboratorios/AprovarSolicitacaoLaboratorio.php` | Adicionar chamada ao notifier |
| `sinais/Solicitacoes/Laboratorios/RejeitarSolicitacaoLaboratorio.php` | Adicionar chamada ao notifier |
| `sinais/App/html/estrutura.php` | Templates ocultos + lerSocket + SDK + JS |

## Critérios de aceitação

1. Admin aprova → card do operador muda para estado liberado (clicável) em ~1 segundo, sem recarregar a página
2. Admin rejeita → card volta para "Toque para solicitar acesso" em ~1 segundo
3. Funciona para todos os 4 tipos de liberação
4. `enviarSocket()` retorna `status: ok` (sem HTTP 500)
5. Falha no publish Scaledrone não afeta o fluxo de aprovação/rejeição (best-effort)
