# Fase 2 — a pesquisa da adesão

24/10/2026, a fatia 35 da fila das telas que faltam (PESQUISAS-API). O desenho é
`docs/f3-telas-restantes/tela-pesquisas.md`: a fatia C (a API, seção 2.1) e o que faltava da
fatia B (o DP-5, o DP-9 e o carimbo do DP-7). A tela nova é a fatia 36 (PESQUISAS-TELA); esta é só
o servidor -- a API nova e a tela antiga consertada -- e os tipos em `frontend/src/api/pesquisas.ts`.

**Não é o NPS** (a resposta 211 (a) do dono, a P1 do desenho): é a pergunta da etapa 5 da adesão ao
programa de fidelidade -- uma pesquisa só para a rede inteira, impressa na página pública de quem
adere. As três notas do NPS são perguntas fixas do código, e nada aqui as toca. A pesquisa não tem
unidade, e não há recorte.

---

## O recorte

| endpoint | verbo | origem no ERP | o que faz |
|---|---|---|---|
| `/adm/api/pesquisas` | `GET` | `index` | a lista: `titulo`, `estado` (`ativa`, o padrão, `inativa` ou `todas`), `pagina`; e `naAdesao` no envelope |
| `/adm/api/pesquisas` | `POST` | `novo` | 201: só o `titulo`; nasce ativa e fora da adesão |
| `/adm/api/pesquisas/:id` | `GET` | `editar`, leitura | as perguntas ativas, as opções com votos e porcentagem, as excluídas à parte |
| `/adm/api/pesquisas/:id` | `PUT` | `editar`, gravação | só o `titulo`; `{}` é 400; o estado, a marca, o `hash` e os votos, 422 com o nome |
| `.../:id/ativacao` e `.../:id/desativacao` | `POST` | `ativarRemover` | o estado pedido; 409 `mesmo_estado`; 409 `na_adesao` ao desativar a da adesão |
| `.../:id/adesao` | `POST` | `editar` com "Sim" | a troca numa transação (o D400); 409 `ja_na_adesao`, `inativa`, `incompleta` (a frase diz o que falta) |
| `.../:id/perguntas` | `POST` | `perguntaSalvar`, nova | 201: a pergunta com as `opcoes`, duas ou mais (até dez), numa transação |
| `.../:id/perguntas/:p` | `PUT` | `perguntaSalvar`, edição | `conteudo` e `ordem`, o que vier; 409 `excluida` |
| `.../:id/perguntas/:p/exclusao` | `POST` | `perguntaExcluir` | lógica; 409 `ja_excluida`; 409 `adesao_sem_pergunta` (a última respondível da pesquisa em uso) |
| `.../:id/perguntas/:p/opcoes` | `POST` | `respostaSalvar`, nova | 201; 409 `excluida` na pergunta excluída |
| `.../:id/perguntas/:p/opcoes/:o` | `PUT` | `respostaSalvar`, edição | `conteudo` e `ordem`; os votos ficam (a P3 (B): a tela diz antes quantos são); 409 `excluida` |
| `.../:id/perguntas/:p/opcoes/:o/exclusao` | `POST` | `respostaExcluir` | lógica; 409 `ja_excluida`; `adesao_sem_pergunta`; `pergunta_com_uma_opcao` |

Toda escrita devolve a pesquisa inteira, como ficou (`PesquisaJson::completo`).

**Cada 409 de estado vem do serviço, lido depois da trava** (a revisão de 24/10/2026; o D599): o
`PesquisaService` relê a linha com a pesquisa travada e devolve a frase (`PERGUNTA_EXCLUIDA`,
`OPCAO_JA_EXCLUIDA`, `JA_NA_ADESAO`...), e o controller só a traduz no `motivo` (`MOTIVOS`). Duas abas
que pedem a mesma escrita recebem um 200 e um 409, e a tela antiga recusa com a mesma frase. E a troca
da adesão trava todas as pesquisas em ordem de id antes de ler (`PesquisaRepository::travarTodas`; o
D598): duas trocas em pesquisas diferentes deixavam as duas marcadas no PostgreSQL.

**Fica de fora:** `selecionarPergunta` e `selecionarResposta` (o detalhe traz tudo). As páginas
públicas do link avulso já tinham saído (a P5 (A), o D399).

## O número é um contador, e o recurso diz o que ele conta

Cada opção guarda só um total (`pesquisas_respostas.total`), desde a criação dela: o sistema não sabe
quando o voto foi dado, em que unidade nem por quem (o DP-7). Por isso:

- **`votos` da opção**: o total dela.
- **`votos` da pergunta**: a soma das opções **ativas** -- o N da porcentagem. As excluídas vêm à
  parte (`excluidas`), com os votos, e a soma delas em `votosDasExcluidas`: elas sumiam da porcentagem
  sem aviso.
- **`porcentagem`**: sobre o N, com uma casa, em texto (`"85.8"`); `null` quando o N é zero.
- **`votos` da pesquisa**, na lista e no detalhe: o maior total entre as perguntas ativas, contando as
  opções excluídas -- cada adesão soma um em cada pergunta, então é quantas vezes a pesquisa foi
  respondida desde a pergunta mais antiga.

`naAdesao` é a marca **e** o estado ativo: o que `PesquisaRepository::selecionarPorFidelidade` exige,
e o que a etapa 5 pergunta agora. O `naAdesao` do envelope da lista é essa pesquisa INTEIRA, na forma
do detalhe (`PesquisaJson::completo`, com as perguntas ativas, as opções e as excluídas) -- desde a
verificação da fatia 36, para o cartão "Na adesão agora" dizer a pergunta, como a seção 1.1 do desenho
pede; antes ia na forma da linha, e os campos dela continuam todos lá --, ou `null`: o aviso forte da
tela ("a etapa 5 passa em branco"). `completa` é a
`RegraDaPesquisa::completa`, e `falta`, a frase do que falta (`RegraDaPesquisa::oQueFalta`).

"opcoes", e não "respostas": a linha é uma opção com um contador, e chamá-la de resposta faria a tela
mentir sobre o que conta.

## As guardas

- **Ler** (`configuracoes/pesquisa/ler`): os dois `GET`. **Escrever** (`configuracoes/pesquisa/escrever`):
  as dez escritas. **Quem tem o `escrever` escreve, administrador ou não** -- como hoje. A
  recomendação do desenho é "só o administrador" (a P2), e ela tira a escrita de uma pessoa: fica
  para o dono, e a troca é uma linha nas duas portas.
- A funcionalidade é posta no construtor (o D123); a guarda é inline, ou no ajudante da rota, um nível
  só (`conferir-acl-da-api.php`).
- **Sem sessão, 401.** A família `adm-api-pesquisas` está em `module/ISConfiguracao/config/rotas-publicas.php`,
  como as irmãs: sem ela, o listener do ERP respondia 200 com a página de login (o ensaio pegou, e não
  o caso em processo, que não passa pelo listener).
- **A escrita de outra origem**, 403 `origem` (o D290), antes da ação.
- **Aninhado pela URL, e conferido** (o DP-9): a pergunta de outra pesquisa e a opção de outra pergunta
  são 404 (`PesquisaPerguntaRepository::daPesquisa`, `PesquisaRespostaRepository::daPergunta`). O id
  passa pela `RegraDaPesquisa::id`: acima do `int4` é 404, e não erro do PostgreSQL.

## A mesma regra nas duas portas

O corpo passa pela `RegraDaPesquisa` (os tetos de 100, 150 e 100 caracteres, o emoji, o `<` e o `>`, o
controle, a ordem de 1 a 15 e de 1 a 10, a chave que não se altera por aqui), e a escrita pelo
`PesquisaService`, que a tela antiga usa:

- **a troca da adesão** numa transação, com as marcadas travadas (o D400);
- **a adesão não fica sem pergunta** (o D338): a última pergunta respondível e a última opção dela;
- **a pergunta da adesão não fica com uma opção só** (novo, 24/10/2026; o DP-3, camada 1): numa
  pesquisa em uso com outra pergunta respondível, a penúltima opção de uma pergunta não sai -- ela
  sumiria da etapa 5 calada (o D339 só mostra a que tem duas). A que já tinha uma, ou nenhuma, segue
  corrigível;
- **o estado e as exclusões com a pesquisa travada** (`comAPesquisaTravada`, novo): duas abas que
  excluíam as duas últimas opções liam três cada uma e deixavam uma; e o desativar no meio de um "pôr
  na adesão" deixava a adesão com uma pesquisa inativa. A tela antiga (`alterStatus`) passa pelo
  mesmo `definirEstado`;
- **o carimbo de toda escrita** de pergunta e opção (o DP-7): elas nunca gravavam `data_alteracao`.

## A tela antiga, consertada (o resto da fatia B)

- **O DP-5**: o campo "Status" saiu do formulário (`Form/Pesquisa.php`) e das duas telas (o novo e o
  editar). O editar o mostrava e nunca o gravou; o novo o ignorava. O estado muda pelo ativar e
  desativar da lista.
- **O DP-9**: o editar não guarda mais a pesquisa na sessão. A tela leva o id dela num campo escondido
  (`pesquisa_id`), e o `mod-configuracoes.js` (`?cache=169`) o manda nas seis ações do questionário; o
  controller confere que a pergunta é daquela pesquisa e a opção, de uma pergunta dela (o `idPergunta` do
  editar não muda a pergunta da opção), e a opção nova só entra em pergunta ativa. Com duas abas, a pergunta
  nova caía na pesquisa aberta por último; sem nenhuma na sessão, 500.
- **O carimbo do DP-7**, pelo serviço.

## Provado

- `PesquisaApiTest` (novo), em processo, nos dois bancos: os treze casos de ação com o 401; o 403 sem
  ler e sem escrever, com a metade que grava (quem tem o `escrever` sem ser administrador); a barreira
  de origem; o 404 de dez aninhamentos errados; os 409 do estado, do `/adesao` (com o que falta), das
  exclusões e das alterações do que foi excluído; catorze 422 com o nome do campo; os três `PUT {}`; os
  filtros da lista; a lista com o cartão da adesão; o detalhe com o N e a porcentagem; o cadastro; a
  pergunta com as opções; e os cinco ajudantes do repositório.
- `PesquisasPelosControllersTest`: os casos de antes, com a pesquisa no POST; o DP-9 (a pergunta nova
  vai para a pesquisa do POST, e sete alvos de outra pesquisa não se tocam); a opção nova só em
  pergunta ativa; o editar sem a sessão e sem o "Status"; o carimbo; a penúltima opção da adesão; a
  trava antes da leitura que decide; e o estado gravado de verdade fora da transação do teste. Na
  revisão: a tela antiga recusa o que já foi excluído, sem mudar o carimbo; o que outra aba fez entre a
  leitura e a escrita é recusado depois da trava; a primeira leitura da troca é a trava de todas, em
  ordem de id; e duas trocas ao mesmo tempo, com outro processo segurando as linhas, deixam uma só na
  adesão (vermelho no PostgreSQL sem a trava de todas).
- `RegraDaPesquisaTest`: o que falta, os corpos da pesquisa, da pergunta e da opção, os filtros e o id.
- `scripts/ensaiar-pesquisas-adm-api.php` (novo), pelo HTTP, 32 casos (24 recusas e 8 controles; os
  30 a 32, da revisão: a penúltima opção com outra pergunta respondível, excluir de novo e alterar a
  pergunta excluída),
  conferidos no banco, nos dois bancos; no CI, na lacuna, no `medir-telas-no-postgres.sh` e no ciclo.
  E as treze rotas no `ensaiar-permissao-adm-api.php`.

## O que fica de fora

- As P2, P3, P4 e P6 do desenho (quem escreve fica como hoje; não há "tirar da adesão"; os votos com
  o texto novo, sem trava; o contador, sem uma linha por resposta -- a LGPD é débito futuro).
- O `medir-adm-api-no-postgres.php`: ele exige que a lista volte com dados, e as fixtures não têm
  pesquisa (o ensaio semeia a sua). A lista no PostgreSQL está provada pelo caso em processo e pelo
  ensaio, no banco de contrato.
- A tela (a fatia 36). *Feita em 24/10/2026: `frontend/src/telas/Pesquisas.tsx`, `PesquisasLigado.tsx` e `formularioDaPesquisa.ts`,
  com os pedidos em `frontend/src/api/pesquisas.ts`; ver a fatia D de `docs/f3-telas-restantes/tela-pesquisas.md`.*
