# História 21 — Perfil próprio

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

## Jornada
Um administrador cria um operador. O operador entra no painel, usa o link **Meu perfil** do shell
(sem exigir `acesso.*`) e abre a tela do próprio perfil. A troca de senha começa com a senha atual
**errada** — erro de validação genérico, sem alterar nada — e depois conclui com a senha atual
correta: o hash é regravado (Argon2id), a sessão corrente é regenerada (o operador continua dentro)
e a nova senha autentica no login seguinte. Nome e e-mail são editáveis; perfil de acesso e
situação da conta nunca aparecem nessa tela.

## Critérios de aceite
1. `GET /perfil`, `PUT /perfil` e `PUT /perfil/senha` exigem **apenas autenticação** — nenhuma
   permissão `acesso.*`; convidado é levado ao login.
2. A tela usa um único shell do CMS, `<main>` único e não expõe `role_id`/`deactivated_at`.
3. Nome `required|max:120`; e-mail `email|max:255`, normalizado, único sem diferenciar caixa
   (inclusive contra contas soft-deleted) e sem mexer em `email_verified_at`.
4. Senha: senha atual conferida com `Hash::check`; nova `min:12|confirmed`; Argon2id pelo cast
   `hashed`; erro de validação no campo, nunca segredo em log/URL.
5. Toda mutação é `PUT` com CSRF; nunca `GET` que muta.
6. A trilha de auditoria tem `password` mascarado — nem a senha em texto nem o hash aparecem.
7. **Axe sem violações A/AA** na tela e no estado de erro; foco no primeiro campo inválido.
8. 4 locales (`pt_BR`, `en`, `es`, `fr`); nenhuma string visível fixa.

## Evidência
`AcessoProfileCmsTest` (PHPUnit: autorização por autenticação, edição própria, unicidade de e-mail
com caixa/soft delete, recusas de payload para perfil/estado/verificação, senha atual errada,
senha nova válida com Argon2id + regeneração de sessão + novo login, auditoria sem segredo,
span/log, rotas sem `GET`, i18n e contrato de layout)
+ `make check E2E=1` (projeto `cms-desktop`, spec `21-cms-perfil`).

## Observação de dados
Cada execução cria uma identidade única (`e2e-perfil-<timestamp>@concordia.test`) e troca a própria
senha; nenhum outro spec depende desta conta. Como não há exclusão física de usuário (trigger de
retenção, ADR-017/042), a conta permanece ativa na base de dev — o e2e não a reutiliza.
