# História 19 — Usuários do painel

- **Spec:** `tests/19-cms-usuarios.spec.ts`
- **Camadas:** E2E (Playwright + axe), PHPUnit feature (`AcessoUsersCmsTest`)
- **Dono:** Infraestrutura / Acesso (`Modules\Infraestrutura\Acesso`)
- **Status:** ativa

## Jornada
Um administrador com `acesso.editar` cria um operador com o perfil Administrador e senha de 12+
caracteres. O operador entra no painel com a própria credencial. De volta ao administrador, a conta
é **desativada** pela confirmação nativa (popover, sem JS obrigatório) e uma nova tentativa de login
falha com a mensagem genérica — a conta inativa não autentica (P2.S1) e a sessão aberta seria
encerrada no próximo request.

## Critérios de aceite
1. `GET /usuarios` exige `acesso.ver`; sem a permissão responde **403** e a navegação omite o item.
2. A lista tem `caption`, `scope="col"`, busca por nome/e-mail, filtro `todos|ativos|inativos`,
   paginação nativa e situação em **texto** (não só cor).
3. Criar/editar exigem `acesso.editar`; o perfil é um `Role::assignable()` (o `suporte` nunca é
   oferecido nem aceito) e a senha usa Argon2id (`min:12|confirmed`).
4. Mutação só por `POST`/`PUT` com CSRF; desativar usa a confirmação nativa e nunca `GET`.
5. A conta desativada (`deactivated_at`) não autentica e a mensagem de falha é genérica.
6. A recusa de self-desativação, último administrador e perfil `suporte` é **422** com mensagem
   i18n — nunca 500.
7. **Axe sem violações A/AA** na lista, no formulário e no estado de erro; a tela funciona sem JS.

## Evidência
`make check` (PHPUnit; autorização, CRUD, filtros/ordem/paginação, ciclo, senha, i18n, layout)
+ `make check E2E=1` (projeto `cms-desktop`, spec `19-cms-usuarios`).

## Observação de dados
Cada execução cria uma identidade única (`e2e-acesso-<timestamp>@concordia.test`) e a deixa
**desativada** ao final (não há exclusão física — trigger de retenção, ADR-017/042). O e2e não
depende dessa conta em nenhuma outra história.
