# História 24 — Estatísticas privacidade-first

- **Spec:** `tests/24-estatisticas.spec.ts`
- **Camadas:** E2E (Playwright + axe) no projeto `cms-desktop`
- **Dono:** Infraestrutura / Estatísticas (`Modules\Infraestrutura\Estatisticas`) + Core (ingestão)
- **Status:** ativa

## Jornada
Uma visita ao **portal** (`/politica-de-privacidade`) é agregada no fim da resposta pelo middleware
privacy-first (ADR-047): somente rota **nomeada**, módulo, locale, classe de status, duração e rótulo
de bot — nada de IP, user-agent, path ou query string. A pessoa da equipe entra no **painel** e abre
`/estatisticas`: a visão geral mostra a série diária do período (com a coluna “únicos (estimativa)”,
que é por **dia** e do portal inteiro — não por rota), e `/estatisticas/paginas?modulo=lgpd` lista
`lgpd.privacy` com contagem maior que zero no período padrão (7 dias). O filtro por módulo evita
depender da página 1 de um ranking global acumulado entre execuções.

O filtro é o mesmo das telas: escolher “Últimos 30 dias” e enviar preserva o estado na URL
(`?periodo=30`) e o resumo do período passa a dizer 30 dias; um período personalizado com
`de`/`ate` iguais ao dia UTC **capturado antes da visita** (sem depender do fuso do host e sem
cruzar a meia-noite UTC no meio do teste) mostra a mesma linha do dia. Todas as telas do caminho passam no **axe** (WCAG A/AA).

Na tela de páginas o link **“Páginas (ranking)”** do bloco “Exportar CSV” baixa um **anexo**
(`estatisticas-paginas-<de>-<ate>.csv`, `text/csv; charset=UTF-8`) com cabeçalho traduzido e a mesma
linha do relatório. O arquivo não tem path, query, IP, user-agent nem qualquer outro dado pessoal; a
leitura entra na trilha de auditoria da área (`*export*` em `config('audit.sensitive')`, ADR-035).
A visão geral oferece os três relatórios exportáveis (páginas, módulos e série diária).

## Critérios de aceite
1. A visita ao portal responde **200** e a métrica aparece no relatório do painel para o período,
   com a rota **nomeada** (`lgpd.privacy`) e contagem ≥ 1.
2. `/estatisticas` e `/estatisticas/paginas` respondem **200** para a equipe (perfil da semente demo)
   com `<h1>` único e tabelas com `caption`/`scope`.
3. O filtro de período aplicado pela UI preserva o valor na URL (`periodo=30`) e o resumo do
   período reflete a janela; o período personalizado (`de`/`ate`) é inclusivo e mostra o dia.
4. O link de export na tela de páginas baixa um **anexo** CSV cujo nome segue
   `estatisticas-paginas-<AAAA-MM-DD>-<AAAA-MM-DD>.csv`, com cabeçalho traduzido e a linha da rota
   visitada; nenhum path/query/PII no conteúdo.
5. A visão geral oferece os três exports (páginas/módulos/série).
6. **Axe sem violações A/AA** na visão geral e no ranking de páginas.
7. Cada spec cria os próprios dados (a visita ao portal é feita pelo próprio teste), a ordem dentro
   do arquivo é contrato e `fullyParallel: false` é mantido. Nenhum estado global é mutado, então
   não há limpeza nem projeto serial.

## Evidência
`make check E2E=1` (projeto `cms-desktop`, spec `24-estatisticas`) + PHPUnit
`EstatisticasExportTest` (permissão, anexo, mesma validação de filtros, neutralização de CSV
injection, colunas das três telas, locale do cabeçalho, trilha de auditoria e módulo inativo),
`EstatisticasCmsTest` (telas/filtros/cache determinísticos e histórico de módulo desativado que não
cresce e permanece no relatório), `MetricsRetentionTest` (dry-run/real, relatório do comando, janela
da config e auditoria intocada), `RetentionScheduleTest` (metrics diário, `retention:expunge`/
`audit:expunge` mensais e jobs existentes preservados) e `LgpdTest` (política de privacidade com o
parágrafo factual de métricas nos 4 locales).

## Observação de dados
A conta `admin@concordia.inf.br` vem da semente demo (ADR-041); o perfil `administrador` deriva as
permissões do manifesto, então `php artisan db:seed` (pré-condição do e2e) é o que concede
`estatisticas.ver` — nenhum atalho de permissão no teste. A visita ao portal é anônima e não cria
conteúdo; o agregado diário é a única escrita e é exatamente
o objeto da feature. Retenção (`metrics:expunge`, 400 dias) e expurgo real/dry-run são provados em
PHPUnit — não no E2E — porque envelhecer/limpar agregados na jornada exigiria atalho de banco e
tornaria o spec dependente de estado além do contrato de UI. A desativação de módulo (histórico
permanece, não cresce) também é provada em PHPUnit, pelo mesmo motivo: a spec `23-suporte-painel` já
cobre a ativação em runtime no E2E.
