# História 23 — Painel de suporte

- **Spec:** `tests/23-suporte-painel.spec.ts`
- **Camadas:** E2E (Playwright + axe) no projeto `cms-serial` (mutação global de módulo/manutenção)
- **Dono:** Suporte / Painel (`Modules\Suporte\Painel`)
- **Status:** ativa

## Jornada
O grupo de módulos `Suporte` existe para a equipe e **não existe** para ninguém mais (ADR-033). O
administrador do cliente — que tem o perfil de maior privilégio — entra no painel e **não vê** o
grupo na navegação; ao digitar `/suporte` recebe **404** (nunca 403, para não revelar a área). A
equipe de suporte entra com a identidade reservada, vê o item no menu, abre a visão geral e a tela
passa no axe (WCAG A/AA).

Na lista de módulos, a equipe desativa um módulo **não essencial** (`Blueprint`) com motivo
obrigatório e confirma o **404 imediato** no portal — sem `optimize` nem reload do FPM —, depois o
reativa e o portal volta a **200**. O estado global é restaurado no `finally` (e antes, se uma
execução anterior tiver sido interrompida); nenhuma outra spec depende da janela intermediária.

Por fim, a equipe abre o **diagnóstico** (`/suporte/diagnostico`) pelo link da visão geral: a tela é
somente leitura, mostra os cartões de estado (com o `scheduler` explicitamente **indisponível**,
porque o Laravel não expõe heartbeat nativo) e passa no axe. A tela reusa a mesma verificação do
`php artisan core:diagnose`; nada é mutado, então não há limpeza.

A equipe também abre o **leitor da trilha** (`/suporte/auditoria`) pelo link “Auditoria”, filtra por
evento (`painel.view` — o próprio acesso da visita anterior à lista), confere que o filtro permanece
na URL e abre o **detalhe** de um evento, onde aparecem o hash de integridade e os payloads
escapados. Lista e detalhe passam no axe. A trilha é `append-only` (trigger no banco): o spec não
cria dado mutável, portanto não há limpeza — os eventos de acesso são a própria evidência.

Por fim, a equipe liga o **modo de manutenção** (`/suporte/manutencao`, `PUT` com confirmação):
com `allow_cms` ligado, o painel continua respondendo **200** para a equipe, o portal devolve
**503** com `Retry-After: 120` e `X-Robots-Tag: noindex`, e a API devolve **503**
`application/problem+json` com `Retry-After` e `X-Request-ID`. A página 503 é acessível e passa no
axe; desligar a manutenção restaura o portal para **200**. O estado é normalizado antes e
restaurado no `finally` (mesmo se o teste falhar), e a spec roda no projeto **`cms-serial`** —
nenhum outro projeto observa a janela de manutenção.

A equipe também abre o **contato do fornecedor** (`/suporte/contato`) pelo link da visão geral.
Com a seção `support` vazia (normalizada antes), a tela mostra o **estado vazio explicativo** — nunca
uma página em branco — e a versão do pacote. A equipe preenche empresa, site, e-mail, telefone,
WhatsApp, endereço e horário em `/configuracoes/support`, volta à tela e confere os dados: site e
WhatsApp abrem em nova aba com `rel="noopener"`, e-mail/telefone são `mailto:`/`tel:`, o endereço é
texto e não existe `iframe`/mapa de terceiro. A seção é limpa outra vez no `finally` (e antes, se uma
execução anterior tiver sido interrompida). Lista, vazio e preenchido passam no axe.

## Critérios de aceite
1. Para quem não é da equipe, o link `Painel de suporte` e o rótulo do grupo **não** aparecem na
   navegação do painel.
2. `GET /suporte` sem a identidade da equipe responde **404**.
3. A equipe vê o item no menu e a tela (`<h1>` único, cartões `Módulos` e `Regras desta área`).
4. **Axe sem violações A/AA** no painel, na lista de módulos e no diagnóstico.
5. Desativar um módulo não essencial pela UI (motivo obrigatório) faz o portal responder **404** no
   request seguinte; reativar restaura **200** — e a UI **não** oferece toggle para essenciais nem
   para o grupo `Suporte` (recusa também no servidor).
6. O diagnóstico responde **200** para a equipe, exibe os checks (plataforma, banco, cache, fila,
   armazenamento) com estado visível e não expõe segredo/DSN/mensagem de exceção.
7. Nenhum dado de conteúdo é criado: o spec usa as identidades da semente demo (ADR-041), restaura a
   ativação global no `finally` e é independente dos outros arquivos (`fullyParallel: false`).
8. O leitor da trilha responde **200** para a equipe, a tabela tem `caption`/`scope`, o filtro por
   evento é preservado na URL e cada linha mostra o evento; o detalhe abre pelo UUID, exibe o hash de
   integridade e não oferece nenhuma ação de edição/exclusão (imutável). Axe A/AA em ambos.
9. Com a manutenção ligada, o portal responde **503** com `Retry-After` e `X-Robots-Tag: noindex`,
   a API responde **503** `application/problem+json` com `Retry-After`/`X-Request-ID`, o CMS segue
   **200** para a equipe (`allow_cms`) e a página 503 passa no axe A/AA. Desligar restaura **200**,
   inclusive no `finally` de uma execução interrompida.
10. A tela de contato responde **200** para a equipe e mostra **estado vazio explicativo** quando a
    seção `support` não tem dado (link real para `/configuracoes/support`), além da versão do
    pacote. Preenchida, mostra os canais como links seguros (`target="_blank"`/`rel="noopener"` nos
    externos, `tel:`/`mailto:` nos demais), endereço em texto e **nenhum** `iframe`. Os dados são
    normalizados antes e restaurados no `finally`; axe A/AA nos dois estados.

## Evidência
`make check E2E=1` (projeto `cms-serial`, spec `23-suporte-painel`) + PHPUnit `PainelCmsTest`
(404 para o perfil máximo, menu do grupo, 4 locales e contrato de layout), `PainelModulesCmsTest`
(lista determinística, motivo, CSRF, 404/200 em dois requests, essenciais/Suporte sem ação),
`PainelDiagnosticsCmsTest` (13 checks, 4 locales, falha de sonda → cartão de erro/HTTP 200 sem
segredo, `painel.diagnose`), `PainelAuditCmsTest` (somente leitura, filtros combinados, limite
por página, keyset sem repetir/perder linhas, detalhe com payload escapado, 404, imutabilidade e um
evento `painel.view` por request) e `AuditLogReaderTest` (keyset puro e uma consulta por página).
`PanelMenuTest` (o grupo só é montado para a equipe). Os checks em si
são provados com doubles em `src/core/Internal/Diagnostics/Test` e o comando em
`tests/Feature/DiagnoseCommandTest.php`. O modo de manutenção é provado por
`tests/Feature/MaintenanceModeTest.php` (portal 503/`Retry-After`/noindex, allowlist, API RFC 7807,
CMS 200/503 conforme `allow_cms`, restauração e fail-open) e por
`src/core/Internal/Maintenance/Test/SetMaintenanceModeTest.php` +
`src/modules/Suporte/Painel/Test/PainelMaintenanceCmsTest.php` (schema/validação, auditoria
before/after, 4 locales e contrato de layout). O contato do fornecedor é provado por
`SettingsSchemaTest`/`DatabaseSettingsTest` (campos, validação de e-mail/URL/telefone, compatibilidade
P0) e `PainelContactCmsTest` (200/404, estado vazio, links seguros, valor inválido no banco ignorado,
versão, 4 locales e layout), além do caso E2E acima.

## Observação de dados
A conta `suporte@maxima.inf.br` é semeada pela demonstração (perfil `suporte` protegido). A senha da
demo é usada apenas no ambiente de desenvolvimento; em produção a conta é provisionada pelo CLI
`core:user:create --role=suporte`.
