# Funcionalidades construídas e não usadas

Medido em 28/08/2026, contra a cópia anonimizada da produção.
**Decidido no mesmo dia** — as decisões estão na seção final.

Este documento existe por causa de uma observação sua, ao revisar o D55: *"as
tabelas vazias são placeholder para funções que ou estão em fase de
implementação ou nunca foram utilizadas"*.

Isso é verificável, e as consequências são grandes o bastante para valer a
medição: **a Fase 2 vai orçar endpoints por ação inventariada**, e uma ação que
serve uma função desligada custa o mesmo que uma que serve o PDV.

---

## O que a contagem mostra

| Função | Conteúdo | Vínculo com unidade | Última atividade | Ações | Leitura | Decisão |
|---|---|---|---|---|---|---|
| **Cupons** | 144 regras | — | **13 dias atrás** | 6 | **viva e intensa** | migra |
| **Banners do app** | 10, sendo 5 ativos | — | dez/2025 | 6 | **viva** | migra |
| **Sorteios** | 4 sorteios | — | **out/2025** | 6 | **dormente há 10 meses** | migra (5.002 clientes ainda veem) |
| **Indicações** | 6 convites | — | 14 dias atrás | 1 + API | **nascendo** | migra |
| **VIP Class** | 21 cursos, 68 aulas, 14 materiais | **0 unidades** | — | **25** | **construída, nunca ligada** | **vai ser ligada — entra na Fase 2** |
| **Assistentes** | 4 assistentes, 11 grupos | **0 unidades** | — | **7** | **construída, nunca ligada** | **fora da Fase 2 — decidido 31/08, D57** |
| **Pesquisas** | 1 ativa, 1 pergunta | — | **12 dias atrás** | 12 | **25.471 respostas** | migra — é etapa obrigatória da fidelidade |

Os números excluem a faixa 90001000+, que é das fixtures.

*Correção de registro (04/10/2026, o D343):* as duas linhas "**0 unidades** -- construída,
nunca ligada" estão invertidas. No código, vínculo de unidade vazio quer dizer **todas** as
unidades (`VipClassRepository.php` e `AssistenteRepository.php`, `uni.unidade IS NULL OR
...`). A VIP Class tem 2.131 conclusões de 341 pessoas, a última em 10/08/2026, e os
assistentes estão no menu de 211 pessoas, em 31 das 32 unidades ativas
(`docs/f3-telas-restantes/tela-vipclass.md`, M1; `tela-assistentes.md`, Q12). A decisão
do D57 foi tomada sobre esta premissa, e volta ao dono na P14 do consolidado
(`docs/f3-telas-restantes/telas-consolidado.md`).

---

## As três tabelas com zero linhas

`assistentes_unidades`, `vipclass_unidades` e `sessoes`.

As duas primeiras contam a mesma história, e é uma história específica: **o
conteúdo existe, o vínculo não.** O VIP Class tem 21 cursos cadastrados, 68
aulas e 14 materiais — alguém montou isso. E `vipclass_unidades` está vazia, o
que significa que **nenhuma das 62 unidades tem acesso**. O mesmo com
assistentes: 4 cadastrados, 11 grupos, nenhuma unidade.

Não é uma função pela metade. É uma função inteira, com a chave nunca girada.

*Correção de registro (04/10/2026, o D343):* a chave está girada. A tabela vazia quer dizer
que ninguém restringiu: as duas funções valem para todas as unidades (ver a correção
acima).

`sessoes` é outra coisa: sessão não é guardada no banco, e a tabela é resíduo.

---

## O que isso muda na Fase 2

**38 das 362 ações serviam funções sem uso** — VIP Class (25), assistentes (7)
e sorteios (6). Eram 10% do inventário em aberto; **depois das decisões, restam
7 ações — 2%**.

A pergunta certa nunca foi *"como migramos isto?"* e sim *"isto vai ser
ligado?"*. Feita, ela custou uma conversa e resolveu 31 das 38.

As **pesquisas** nunca estiveram entre essas 38 — entraram neste documento como
suspeita, e o exame mostrou que a suspeita nascia de uma contagem minha errada.
Elas migram, e o exame ainda rendeu o **D58**.

O caso dos **sorteios** nunca esteve em dúvida, e vale dizer por quê: ele *foi*
usado — 4 sorteios entre out/2023 e out/2025, com 7.214 participações e 5.002
clientes alcançados. Parou, mas os dados continuam lá e os clientes ainda
enxergam os sorteios antigos no aplicativo. Migrar é obrigatório mesmo que nunca
mais haja sorteio; ligar de novo é opcional.

**As indicações são o oposto:** 6 convites em dois meses e meio, o mais recente
há duas semanas. É função nascendo, não morrendo — e merece atenção crescente,
não decrescente.

---

## Por que isso importa para os testes

O D55 mostrou que dez snapshots de contrato congelavam resposta vazia. As
fixtures agora povoam banner, agenda, cupom, sorteio e indicação — e é legítimo
perguntar se não estou congelando o contrato de coisas que ninguém usa.

A medição responde: **não.** Cupons e banners estão vivos; sorteios têm 5.002
clientes que ainda os veem; indicações estão crescendo. As duas funções sem
vínculo de unidade — VIP Class e assistentes — **não têm endpoint no
aplicativo** e por isso não aparecem na suíte de contrato: nenhuma delas foi
congelada por engano.

O que a medição acrescenta é uma regra para daqui em diante: **antes de investir
num contrato, conferir se a função tem uso.** Um contrato caro para uma tela que
ninguém abre é o mesmo desperdício que um endpoint caro para ela.

---

## Como refazer esta medição

As contagens saem de consultas diretas na base, excluindo a faixa das fixtures:

```sql
SELECT 'vipclass', COUNT(*) FROM vipclass WHERE id < 90001000
UNION ALL SELECT 'vipclass_unidades', COUNT(*) FROM vipclass_unidades
UNION ALL SELECT 'assistentes_unidades', COUNT(*) FROM assistentes_unidades;
```

As ações por função saem de `docs/inventario-acoes.csv`, filtrando por
controller — **não por palavra**. Contar por palavra erra: `pesquisa` casa com
`pesquisar`, o verbo, e infla a conta com dezenas de telas de busca que nada têm
a ver.

**E contar linhas erra quando a linha não é a unidade.** `pesquisas_respostas`
tem 3 linhas e 25.471 respostas, porque cada linha é uma *opção* com um
contador. Antes de tratar uma contagem como medida de uso, vale abrir a tabela e
ver o que uma linha significa — foi o que faltou aqui na primeira passada.

---

## As decisões, e o que cada uma implica

Tomadas em 28/08/2026.

### VIP Class — vai ser ligada

Entra na Fase 2 como qualquer outra função. **E não exige nada do corte**, o que
foi conferido:

- as **8 tabelas** `vipclass*` estão em `scripts/migrar-dados.sh`;
- todas têm `inquilino_id` e `CREATE POLICY inquilino_isolado` no
  `postgres/schema.sql` — inclusive `vipclass_unidades`, que está vazia e por
  isso seria a fácil de perder de vista.

O conteúdo atravessa inteiro: 21 cursos, 68 aulas, 14 materiais, 66 perguntas de
checkpoint.

**O que falta para ligar não é programação.** `vipclass_unidades` está vazia —
ligar a função é vincular unidades, que é operação, não desenvolvimento. Isso
pode ser feito antes ou depois do corte, tanto faz.

*Correção de registro (04/10/2026, o D343):* este conselho fecharia os cursos. A função já
está ligada para a rede inteira, e vincular uma unidade a um curso o tira das outras 31
unidades ativas.

Para a Fase 2, as 25 ações merecem **prioridade baixa dentro do escopo**: não há
usuário hoje para incomodar se a tela ficar por último, e priorizar o que já tem
gente usando rende mais.

*Correção de registro (07/10/2026, o item CODIGO-MORTO-E-DOCS):* há usuário, e
muito -- 2.131 conclusões de 341 pessoas, a última em 10/08/2026
(`docs/f3-telas-restantes/tela-vipclass.md`, M1). A prioridade baixa saiu da
mesma leitura invertida do vínculo vazio, e essa razão caiu -- mas a posição da
VIP Class não é deste documento: a que vale é a da ordem que o dono decidiu em
27/09/2026 (`docs/roadmap.md`, "A ordem das telas que faltam"), em 8º de 9,
depois de contas a pagar. Ela continua valendo até ele responder se a mantém
sabendo do uso (pergunta levada ao lote em 07/10/2026, na verificação deste
item; a primeira versão desta correção dizia que a prioridade "não vale", e
isso seria decidir por ele). Quem quiser restringir um
curso a algumas unidades precisa saber o efeito: o curso com unidade vinculada
some para todas as outras (`VipClassRepository::listagemIndexEdu`).

*Respondida em 08/10/2026* (pergunta 49 da página do lote, a opção (a), "Manter
a ordem de 27/09"): sabendo do uso, a administração da VIP Class fica em 8º de 9,
e a tela antiga segue em uso, para os alunos e para quem administra, até a nova
chegar. Se a administração dela e a dos assistentes entram na tela nova continua
sendo a P14 do consolidado (`docs/f3-telas-restantes/telas-consolidado.md`; os
assistentes, o **D57**).

### Assistentes — em espera

Vira o **D57**. As três tabelas migram junto de qualquer jeito, então adiar não
custa nada no corte. O que fica em aberto são as 7 ações da Fase 2.

**O prazo da decisão é o fatiamento da Fase 2 por módulo.** Depois disso ela
custa retrabalho; antes, custa só uma escolha.

### Pesquisas — migra, e não era o que parecia

**Examinada em 28/08/2026. A leitura anterior estava errada por quatro ordens de
grandeza**, e o erro merece ser dito antes da conclusão, porque a forma dele se
repete.

Eu havia escrito "3 respostas ao todo", contando as linhas de
`pesquisas_respostas`. **Essa tabela não guarda respostas: guarda as opções de
resposta**, cada uma com um contador. `PesquisaService::responder()` faz
`setTotal($resposta->getTotal() + 1)`. Os totais reais são:

| Opção | Respostas |
|---|---:|
| 1 vez por mês | 21.849 |
| 1 vez por semana | 3.384 |
| não sou mais cliente | 238 |
| **Total** | **25.471** |

Contar linhas onde se devia somar uma coluna é o mesmo engano que "texto *sobre*
uma coisa contado *como* a coisa" — **a forma do dado não era a que o nome da
tabela sugeria**, e eu não conferi.

**E a função não é isolada: ela é a etapa 5 do cadastro do programa de
fidelidade.** `FidelidadeController::cadastroAction()` chama
`selecionarPorFidelidade()`, e a pergunta aparece dentro do formulário de
adesão. Por isso o volume:

| | |
|---|---|
| Clientes no programa de fidelidade | **25.455** |
| Respostas da pesquisa | **25.471** |
| Cadastro mais recente | **2026-08-16** |

Uma diferença de 16 em 25 mil. Praticamente todo cliente que entra no programa
responde, porque **não é opcional**.

**Decisão: migra, e não estava em dúvida — eu é que tinha medido errado.** São
12 ações no `PesquisasController`, mais as duas rotas públicas
`/pesquisadesatisfacao`.

Havia também uma segunda pesquisa — id 1, inativa, com a pergunta literal
"pergunta 1?", criada em 12/01/2018 às 15:14, quarenta minutos antes da de
verdade. Resíduo de teste. **Removida em 29/08/2026** por
`scripts/remover-pesquisa-teste-2018.sql`, aplicado ao ambiente local; **produção
espera a janela**, como o `ALTER` do D54.

O script é desconfiado de si mesmo, e não por capricho: as duas chaves
estrangeiras da família são `ON DELETE CASCADE`, então um `DELETE` apontado para
a pesquisa errada levaria junto, em silêncio, as **25.471 respostas** — que só
existem como contador, sem linha por resposta, e portanto não podem ser
recontadas depois. Ele apaga filho antes de pai sem depender do CASCADE, e cada
`DELETE` exige que o alvo esteja inativo, fora do programa de fidelidade e sem
nenhuma resposta.

A trava foi testada contra o caso perigoso: plantando uma "pesquisa 1" **com**
respostas, o script recusa e não apaga nada. Uma trava que nunca se viu recusar
é decoração.

**O exame achou um defeito, e virou o D58:** desativar a pesquisa da fidelidade
na tela de administração **derruba a página inteira do cadastro** — a view faz
`$pesquisa->getPerguntas()` sobre o nulo, sem conferir, e nada impede a
desativação. Um clique numa tela quebra outra, de 25 mil usuários.
