# Roadmap da modernização

Mapa das cinco fases entre o ERP de hoje (PHP/Laminas + MySQL) e o alvo
(React + PostgreSQL), com o ponto exato em que o trabalho está.

Este documento existe porque o plano da Fase 0 vinha sendo tocado sem estar
escrito em lugar nenhum — vivia só no histórico de conversa. Agora é rastreável.

Atualizado em 21/09/2026.

---

## Onde estamos

### 15/09/2026 — o ERP não abria no banco de desenvolvimento, e a causa era o instrumento de teste

**O dia começou fechando dívida de portão e terminou consertando o que impedia
de trabalhar.** Sete commits, e o fio que os liga é o mesmo da semana inteira: o
conserto estava feito, e quem devia guardá-lo guardava menos do que dizia — ou
guardava coisa nenhuma.

#### Os números, medidos hoje e não copiados

| grandeza | valor | como foi medida |
|---|---:|---|
| commits | **530** | `git rev-list --count HEAD` |
| portões no corredor | **44** | `grep -cE '^rodar' scripts/conferir-tudo.sh` |
| arquivos `conferir-*` | 45 | `ls scripts/conferir-*.php scripts/conferir-*.sh` |
| testes de caracterização | **642** casos, 2.221 asserções | `rodar-caracterizacao-como-ci.sh`, exit 0 |
| testes de contrato | 67 | `grep -rh 'public function test' tests/Contrato` |
| ações da API nova | **98** | `conferir-acl-da-api.php` — e mais três réguas independentes dão 98 |
| débitos | **187** e 1 remissão | `scripts/contar-debitos.php` |
| — dos quais de pé | **51** | 39 abertos, 5 medidos, 7 parciais |
| — não mais de pé | **136 (72,7%)** | 129 fechados, 3 preparados, 4 adiados |

**As duas réguas de portão discordavam por 1, e a diferença tem composição
inteira:** 45 arquivos − 3 que existem e não rodam (o próprio corredor;
`conferir-migracao.php`, que leva ~10 min e compara dois bancos carregados;
`conferir-papeis.sh`, que exige `SENHA_APP`) + 2 que rodam e não se chamam
`conferir-*` (`auditar-group-by.php` e `marcar-fixtures.php --conferir`) = 44.
Duas réguas que discordam **e sabem por quê** não são o mesmo que duas réguas que
discordam.

#### 1. Ninguém entrava no ERP, e fazia dias

`ISConfiguracao\Permissions\Acl` registra cada funcionalidade como recurso da
ACL **pelo nome**, e o Laminas lança na segunda linha de mesmo nome — sem
`try`/`catch` em ponto nenhum do login. A cópia de desenvolvimento tinha **126
linhas para 63 nomes**, e a ACL do próprio projeto estourava no **64º recurso**.

**A duplicata não era da produção: era do instrumento.** Os 63 grupos eram todos
mistos, uma linha de produção e uma de fixture. A faixa de produção nunca teve
duplicata — 63 linhas, 63 nomes, por colação `ci`, por `BINARY` e por
`LOWER(TRIM)`.

A causa tinha endereço, e o próprio `tests/Caracterizacao/fixtures.sql` enuncia a
regra antes de quebrá-la: *"quando as fixtures entram num banco que já tem
produção, a linha real fica"* — nove `WHERE NOT EXISTS` no arquivo, e o bloco de
`funcionalidades` inserindo os 63 nomes **incondicionalmente**.

O conserto foi **ceder pelo nome**, mantendo os ids `90002xxx` onde não há
produção. E o `DELETE FROM funcionalidades WHERE id >= 90001000` que já existia
fez a limpeza: a recarga do arquivo devolveu a cópia a 63 linhas nos **dois**
bancos, e de quebra acertou uma divergência entre eles (o PostgreSQL tinha 7
concessões do gerente de fixture onde o MySQL tinha 8). Os 15.495 privilégios de
produção, intactos.

#### 2. Dois portões novos, e um deles impediu a mudança errada

**`scripts/conferir-limpeza-da-unicidade.php`** fecha um vão que ninguém
guardava: o gerador de DDL escreve, dentro do schema que emite, *"a carga FALHA
se os dados ainda tiverem duplicatas — `pre-migracao-limpeza.sql` roda antes"*.
Isso é uma **promessa sobre outro arquivo**, e nada a conferia.
`scripts/migrar-dados.sh` copia 83 tabelas e só aceita `COPY <n>`: uma violação
na 29ª deixa **54 tabelas nunca carregadas**.

A regra não é "toda entrada precisa de bloco" — isso seria mentira, e vermelha
sobre estado certo (`clientes_sessoes.token_hash` é única, tem zero duplicatas e
corretamente não tem bloco). A regra é o **par**: chave declarada + dado sujo na
origem ⇒ tem de haver quem limpe.

**E foi ele que impediu a mudança errada.** Antes do conserto da fixture, ele
recusava `funcionalidades` com `63 SUJA E SEM NINGUÉM QUE LIMPE`. Depois,
passou a dizer `0 limpa, e não precisa de bloco` — mesmo portão, mesma entrada,
veredito oposto, porque o dado mudou. Só então a chave entrou.

**`funcionalidades.nome` ganhou restrição de unicidade**, a quinta do schema.

#### 3. Quatro portões que só existiam no CI parado vieram para o corredor

Com o CI parado (D187), portão que só vive lá não defende nada.
`conferir-guarda-do-erp`, `conferir-recorte-por-unidade`, `conferir-rotas-sem-login`
e `auditar-group-by` passaram a rodar no `conferir-tudo.sh` — medidos do host, os
quatro saem com 0. **O corredor foi de 40 para 44.**

O mais gritante é o `conferir-recorte-por-unidade`: o portão é de 07/09 e rodou
no CI até 13/09, mas a **41ª entrada dele** — a porta da personificação do D127,
posta hoje — nasceu dois dias depois de o CI parar e **nunca tinha sido executada
por corredor nenhum**.

#### 4. O número contaminado tinha virado fato documentado

O `126` vazou do banco para a documentação do produto: **oito lugares em sete
arquivos** o afirmavam. Os dois que doem:
`module/ISConfiguracao/src/Controller/Adm/PrivilegiosController.php` justifica
**não paginar** com *"126 no máximo"*, e
`module/ISConfiguracao/src/Service/PrivilegioService.php` publicava "~123 linhas
por funcionalidade (15.495 divididas por 126)" — com as 63 reais, o número é
**~246**, o dobro.

#### Quatro erros meus, todos pegos por medida e nenhum por revisão

1. **A premissa que fechou o parecer da véspera era falsa.** Eu escrevera que
   tornar a fixture condicional obrigava a reusar os ids de produção. Não obriga:
   condicionando pelo `nome`, os ids ficam. E dos cinco acoplamentos que eu citara
   como impedimento, **quatro nunca veem produção**.
2. **Uma conferência minha foi falso verde.** Comparei os 63 nomes antes e depois
   com `grep -P`, que falhou por locale; os dois lados saíram **vazios**, e o
   `diff` disse "idênticos" comparando nada com nada. Refeita em PHP.
3. **Trocar o tipo de uma constante de teste quebrou quatro chamadas** que a
   usavam como id. **Os 44 portões passaram verdes**; quem achou foi a suíte — 27
   erros, dos quais 26 eram cascata de `EntityManager is closed`. Só o primeiro
   era real.
4. **Um ensaio do desfazer pegou defeito no próprio desfazer**: ele repunha 8
   concessões sobre as 8 existentes e devolvia 16.

#### O achado que fica aberto

O **`AUTO_INCREMENT` está envenenado em 49 das 84 tabelas** da cópia —
`clientes` em 90009713, `agendas` em 90010092, `vendas` acima de 990000000.
Qualquer linha que o ERP criar nessa cópia **nasce dentro da faixa de cenário de
teste**, e a próxima carga das fixtures a apaga como se fosse fixture. Apagar
linhas não abaixa o contador: o InnoDB 8.0 o persiste. Registrado no D167.

### 13–14/09/2026 — o CI parou, e as duas rodadas viraram auditoria dos próprios instrumentos

**O fato que muda a rotina de trabalho:** às 21:01 de 13/09 o CI parou por
cobrança da conta do GitHub (**D187**). Não é defeito do repositório e não se
conserta aqui — é pagamento, e é seu. A consequência prática é que **seis
portões só existem no CI** e pararam junto: eles passaram a ser rodados **à
mão**, commit a commit, e cada registro de commit desde então diz quais foram
rodados. É disciplina que ninguém sustenta por muito tempo, e está escrito assim
de propósito.

#### A Fase 3 ganhou um sistema de design, e ele foi exportado

A comanda foi a primeira tela a receber o sistema: o ouro `#D9B437` foi
**amostrado do logo** (`public/global/img/logo-1.png`), e a regra que separa
*acento* de *semântica* nasceu de um defeito medido no ERP de hoje — quatro
confirmações destrutivas usam o mesmo verde do Salvar. A medição de contraste
reprovou **cinco literais de cor** no tema escuro, e o pior deles era o texto que
avisa que a ação apaga pagamento.

Em seguida o console inteiro foi exportado para um projeto no Claude Design, com
as convenções e o guia da agenda (`frontend/docs/sistema-de-design.md` e
`frontend/docs/agenda-de-hoje.md`), para que o redesenho aconteça lá. A decisão
registrada junto: **a agenda mantém a grade por barbeiro**, porque quem olha a
agenda no balcão pergunta *"quem está livre agora"*, e isso se responde varrendo
uma linha com o olho.

#### Os números que esta casa publicava não valiam — três casos no mesmo dia

**D186.** Ao republicar o roadmap, contei os débitos resolvidos com uma régua
improvisada e deu 121; refiz com outra, no mesmo arquivo e no mesmo minuto, e deu
118. As duas eram minhas. O defeito estava na **pergunta**: "quantos estão
fechados" pressupõe dois estados, e o registro usa seis. A régua passou a existir
em disco — `scripts/contar-debitos.php` —, com a tabela de exceções e o motivo de
cada uma, e ela **recusa dar número** quando não consegue ler o registro.

**D178.** Eu havia escrito que o risco era baixo porque nenhuma tela chamava a
rota quebrada. No dia seguinte, abrindo a página no navegador, **o botão
CONTINUAR da pesquisa a chamava**. Meu `grep` procurava o nome em camelCase; a
URL é minúscula.

**D181.** As fixtures apagaram 2,3 milhões de linhas de `vendas_pagamentos` desta
cópia, e um registro anterior — meu — afirmava que as chaves estrangeiras tinham
bloqueado o estrago. Bloquearam em `vendas`; nesta tabela não havia nenhuma. Por
acreditar naquela frase, ninguém procurou por 28 dias.

#### A noite dos vigias: dez débitos, e o padrão era um só

Entre 13 e 14/09 foram fechados **D42, D99, D71, D25, D68, D16, D34, D188, D46 e
D52**. Em oito deles o defeito **já estava consertado** — o que faltava era o
vigia, e o vigia guardava menos do que a frase dele dizia guardar.

| Vigia | O que ele dizia | O que ele fazia |
|---|---|---|
| relógio do banco | "o sistema não pede a hora ao banco" | varria só `tests/` |
| cobertura de contrato | "43 rotas cobertas" | via 37 — não lia `child_routes` |
| rastro das rotinas | "toda rotina registra" | passava no defeito que criou o D68 |
| proteção do webroot | *não existia* | — |
| ordenação por coluna anulável | "nenhuma exposta" | ficava verde sobre uma com 15 nulos |
| comanda fantasma | "a abertura duplicada foi corrigida" | cobria um dos dois caminhos |

**Três coisas vale registrar como método, porque se repetiram:**

1. **O gesto que prova o conserto pode plantar o próximo.** O D34 provou o 403
   subindo um `.php` no diretório de upload — e o arquivo da prova entrou
   versionado no mesmo commit, porque faltava uma linha no `.gitignore`.

2. **Escrever "o que este instrumento NÃO cobre" é barato, e às vezes é a
   medição.** O parágrafo que dizia *"não olho o resto do webroot"* levou a olhar,
   e apareceu o **D188**: dois `.php` de biblioteca vendorizada que o nginx servia
   com 200 e o PHP executava, sem login.

3. **Comentário que promete o que o código não faz custa o mesmo que nome que
   mente.** No portão do D46 havia a frase *"a resolução real vem do Doctrine mais
   abaixo"*; abaixo havia um `continue;`, e 44 ocorrências eram descartadas em
   silêncio.

**E um instrumento cego por charset (D52).** O roteiro que a decisão de 31/08
nomeou como *a lente* — `scripts/medir-comandas-abandonadas.sql` — comparava
literais **acentuados** contra um cliente `mysql` que negocia `latin1`. Casava
zero linhas, sem erro e sem aviso, enquanto 43 existiam — e o comentário ao lado
dizia que vazio era o esperado.

#### O que continua de pé ao fim destas duas rodadas

- **Só você desbloqueia o CI** (D187, *Billing & plans*). Enquanto isso, os seis
  portões de CI e os sete ensaios são rodados à mão a cada commit.
- **Nada foi tocado em produção**, e continua valendo a decisão de 11/09: uma
  janela só, a do corte.
- O registro de débitos está em `docs/debitos-tecnicos.md`, e a contagem sai de
  `scripts/contar-debitos.php`, não de leitura a olho.

---

### 10–12/09/2026 — uma decisão que reorganiza o caminho crítico, e os portões que não rodavam

**A decisão que mais muda este documento não é técnica.** Em 11/09/2026, por
decisão sua e em caráter permanente: **nenhuma modificação no servidor de
produção até a migração final, depois de todas as validações.** Os `ALTER` do D54
e do D86, o `DROP USER` do D9, a remediação da F1.1, o teto do php-fpm do D67 —
tudo que antes aparecia aqui como "espera descongelamento" passou a ter **uma
janela só**, a do corte.

Isso não é adiamento: é consolidação. O que antes eram cinco pedidos de janela
em cinco momentos diferentes virou um procedimento único, e **procedimento único
se ensaia**. O que muda no plano está escrito no fim desta seção e refeito em
*Próximos passos*.

E muda o que o trabalho diário pode ser: **só o que não toca produção**. As três
rodadas abaixo são exatamente isso, e renderam 36 commits.

---

#### 10/09 — as recusas da API nova ganharam portão, e o portão achou defeitos em si mesmo

`POST /adm/api/comandas/:id/finalizacao` é a ação que mais faz coisa no sistema:
cria débito do cliente, debita crédito e fidelidade, baixa estoque, mexe no caixa
e troca o operador da venda. **Dez recusas a protegem, e nenhuma tinha portão
automático** (D170) — um `grep` pelas mensagens em `tests/` e `scripts/` devolvia
zero. Entre o ensaio de fluxo, que só andava pelo caminho feliz, e o de permissão,
que só media 403, sobrava esse vão.

`scripts/ensaiar-recusas-adm-api.php` monta o estado de cada recusa, faz **um**
pedido e afirma **três** coisas: código HTTP, `erro.codigo` e um fragmento da
mensagem. **Por que três, e isso foi medido e não suposto:** cinco das dez
respondem 409, então um estado mal montado dispara a recusa *anterior* com o mesmo
código e o instrumento fica verde sem medir o que diz medir. Tornando a comanda do
caso 8 fiada, o código HTTP batia, o `erro.codigo` batia, **e só a mensagem
pegou**.

**O portão nasceu com três defeitos, achados por revisão adversarial** — e o
primeiro vale mais que o vermelho que ele evita: duas execuções em paralelo
finalizavam uma venda de verdade, e a finalização escreve `clientes.ultima_visita`
numa tabela que **não é filha de `vendas`** — resíduo que a limpeza não alcança.
Corrigido com trava de banco de tempo limite **zero**: esperar seria pior que
recusar, porque duas execuções em fila mediriam o cenário que a outra montou.

Daí o ensaio cresceu, recusa por recusa, em vez de virar um segundo roteiro que
duplicasse login, cenário e limpeza — as três partes onde este já errou uma vez:

| débito | o que não recusava | virou |
|---|---|---|
| **D164** | finalizar **sem caixa aberto** — `setCaixa(null)` e método em nulo na linha seguinte: 500 com a venda já parcialmente escrita | 409, o irmão da recusa de caixa de outro dia, que existia desde o D28 |
| **D160** | pagamento em crédito **acima do saldo**: a finalização debitava sem piso e o saldo ficava negativo em silêncio | 422 nomeando `valor`, comparação inclusiva, como na tela velha |
| **D166** | cliente **sem linha** de crédito derrubava a finalização com erro fatal | fechou de graça com o D160: sem linha, o disponível é zero |
| **D162** | pagamento em comanda sem item; valor zero fora de Permuta e Pacote; forma desativada | 409 e dois 422 |
| **D163** | `DELETE` de item e de pagamento **não liam o estado da comanda** — alteravam o total de uma venda já fechada | 409, nas mesmas palavras das três ações irmãs |
| **D165** | a autorização de fiado **sobrevivia ao valor que autorizou**: remover item de 20 num total de 100 com 90 recebido deixa 80 de fiado, oito vezes o autorizado | `Venda::limparAutorizacao()` nas três ações que podem aumentar o fiado |

**O D160 é a armadilha do dinheiro morando exatamente onde ela dói.** O valor vem
do corpo em decimal de protocolo (`"89.90"`), e `Monetario::removeMask()` apagaria
o ponto por ser separador de **milhar** no formato brasileiro: 89,90 viraria 8990,
e a comparação aprovaria **cem vezes** o saldo. Provado por `curl` no limite, com
cliente de R$ 30,00: 20,00 passa, mais 15,00 recusa dizendo `disponivel: 10.00`,
mais 10,00 (soma exata) passa.

**E uma decisão sua destravou a prova do D160**: as formas de crédito entraram no
cenário de fixtures com os ids **literais 5 e 6**. Não havia escolha de id —
numeradas em 9000xxxx o cenário teria as *formas* e não teria a *regra*, porque os
dez usos de `CREDITO_CLIENTE` e `CREDITO_PROGRAMA_FIDELIDADE` comparam número.

##### E a data dos produtos respondeu uma pergunta que estava marcada como decisão sua

O **D169** dizia que o sistema tem três respostas para "o que tem estoque" e que
escolher entre elas era decisão sua. **A data dos produtos mostrou que não havia
escolha a fazer:**

| tipo | primeiro produto |
|---|---|
| `probar`, `proemp`, `ser` | 2016-05-29, o primeiro dia da base |
| `proins` | 2024-04-17, **oito anos depois** |
| `pro` | **nunca existiu** — zero produtos, zero linhas de venda |

Quem criou `proins` em 2024 ensinou **seis** lugares sobre ele e deixou **três**
em 2016. Não eram três desenhos discordando: era uma mudança feita pela metade, e
a lista de três era a de 2016 com um tipo morto dentro. **O custo, medido:** o
produto 9336 está em −3, com zero linhas de entrada e exatamente 3 unidades
vendidas — o negativo é o vendido, sem nada do outro lado, e sem número na tela
para alguém ver.

O **D171** é o espelho, registrado e fechado no mesmo dia: `POST
/adm/api/produtos/:id/estoque` aceitava **serviço**, porque guardava o
sub-recurso com a lista do catálogo. Era a API nova **mais frouxa que o ERP** — a
direção contrária da que esta casa costuma vigiar, e por isso ninguém olhava. A
medição que o registro deixou em aberto não deu zero: deu **uma** linha, de um
produto retipado em 2020, seis anos antes de a rota existir.

---

#### 11/09 — dois ensaios estavam no CI e nunca rodaram

**Este é o achado da rodada, e ele desqualifica retroativamente uma afirmação
deste documento.** `ensaiar-escritas-do-aplicativo.php` e
`ensaiar-recusas-adm-api.php` estavam escritos no `ci.yml` — e **nenhum dos dois
havia executado uma única vez**. O job morria antes, por falta de extensão do PHP
e por Composer sem versão fixada. *"Cobertura escrita no CI não é executada"*
entrou no caderno de lições ao lado de *"verde por ausência de execução também
não é verde"*.

Quando passaram a rodar, acharam o que portão nenhum tinha achado. O **D172** — o
SAC do aplicativo — tinha **três defeitos além do que o registro descrevia**: o
template quebrava, o destino estava errado, e havia um caminho que responde **200
com HTML** em vez de erro. O conserto fechou com duas decisões suas: o chamado vai
**sempre para a franqueadora**, nunca para a unidade reclamada, e a **unidade é
dita no corpo** do e-mail — o dado sem o endereçamento.

O **D61 fechou**: as **seis** escritas do contrato congelado do aplicativo têm
ensaio, com resíduo **zero** e nos dois bancos. Era a última área do contrato com
o aplicativo que tinha invólucro e não tinha carga.

**E o CI ficou verde pela primeira vez em semanas**, depois de cinco construções
exclusivas do MySQL e duas falhas de fiação:

| o que era | onde morria |
|---|---|
| `GET_LOCK` / `RELEASE_LOCK` | não existem no PostgreSQL — `scripts/trava-de-banco.php` passou a devolver o par certo por motor, com `pg_try_advisory_lock` e `crc32` da chave |
| crases em identificador | `quoteIdentifier()` |
| `ON DUPLICATE KEY UPDATE` | `ON CONFLICT (id) DO UPDATE SET` |
| `information_schema` do MySQL | consulta com ramo por plataforma |
| `status = true` numa consulta auxiliar | `vendas.status` é **inteiro** nos dois bancos — a família D81/D82 me pegando na minha própria medição |

O **D167** perdeu 5,3% de erro na régua, e não por contar linhas a mais. O
**D119** ficou **pronto e desligado** — a passagem de acento existe, é
configuração, e ligá-la reescreve coluna armazenada: é trabalho de janela. O
**D169** perdeu o terceiro lugar que escondia o saldo do insumo, que era o mais
exposto dos três.

---

#### 12/09 — há um SEGUNDO eixo de desacordo entre o mapeamento e o banco

O **D65** registrava 98 colunas em que a anotação do Doctrine e o banco discordam
**sobre nulo**. Em 12/09 apareceu o eixo que ninguém tinha recenseado: eles também
discordam **sobre o tipo**. O censo mediu **70** desacordos no MySQL e **54** no
PostgreSQL — e derrubou uma frase minha: *"nunca a anotação"* estava errada, há
casos dos dois lados.

A medição só ficou de pé depois de um erro meu que vale registrado: usei
`DATA_TYPE` do MySQL, que devolve `tinyint`, em vez de `COLUMN_TYPE`, que é o
**único** que distingue `tinyint(1)` — booleano — de `tinyint(4)` — inteiro. O
número errado nunca foi reportado; o portão nasceu do número certo.

`scripts/conferir-tipos-do-mapeamento.php` é o **33º portão**, com linha de base
por motor e falhando **nas duas direções** — número que não se repete apodrece.

O **D19 fechou com cobertura antes do corte**: o driver do Doctrine que o
`ISApi` declarava **não mapeava entidade nenhuma**, e removê-lo exigia primeiro um
teste que soubesse dizer quantas entidades cada *namespace* mapeia. O teste veio
primeiro, a remoção depois.

##### E o D10 deixou de ser "os 18 relatórios": são 37 métodos, com nome

O inventário de cobertura dos relatórios envelheceu, e o número certo **desmentia
o parágrafo que o citava**. Recontado: são **37 métodos de repositório** chamados
pelos relatórios sem teste nenhum, e **11 deles calculam dinheiro**. Os 19
relatórios têm cobertura de **fumaça** — as 36 ações são pedidas a cada CI e
nenhuma pode quebrar —, e fumaça não sabe dizer se o número está certo.

Três dos 37 ganharam teste de **valor**, nos dois bancos:

| método | o que o teste afirma |
|---|---|
| `selecionarPorMesAnoUindade` | a **fronteira** da faixa de data — o `>=` e o `<=` na igualdade, onde a coerção de tipo entre os motores divergiria sem erro, só com número menor |
| `totalDescontoPorCaixa` / `totalTrocoPorCaixa` | o valor é a soma independente em SQL, o filtro de status **morde**, caixa sem venda devolve **zero e não nulo**, e o texto que chega na tela é o mesmo nos dois bancos |
| `selecionarVendasProdutosPorColaboradorPeriodo` | a **média ponderada** que consertou um erro de até **R$ 1.240** numa linha, e que estava sem guarda desde 26/08 |

**O último é o que mais valia, e a falsificação reproduz o defeito original com
números.** Trocando a expressão de volta pela coluna crua, o MySQL escolhe 89,90,
e 89,90 × 2 = 179,80 contra os 199,90 reais — R$ 20,10 de erro num grupo de duas
linhas, com 380 grupos afetados em cada um dos dois relatórios quando o defeito
era real. O cenário distingue média de escolha arbitrária, **e isso foi conferido
antes de escrever**: duas linhas de 89,90 e 110,00, média 99,95, que não é nenhuma
das duas.

**Duas assimetrias entre os motores foram medidas e recebem tratamento
diferente:**

- **`SUM()` devolve tipos diferentes** — `double` no MySQL, `string` no
  PostgreSQL, para o mesmo valor. Varrido: **44 agregações**, e **zero** em
  caminho perigoso. `Monetario::applyMask()` normaliza os dois, conferido em dez
  pares. *Deixaria* de ser inofensivo com `removeMask()` ou comparação estrita —
  daí o teste afirmar o **formato** e não o tipo.
- **`DataHora::stringDate()` recorta por POSIÇÃO**, e não interpreta data. Mandando
  `2026-01-01` ela produz `1-01-6--20`: o PostgreSQL estoura com *invalid input
  syntax for type date* e o **MySQL devolve zero linhas em silêncio**. São 64
  sítios de chamada, e a assimetria ficou registrada no **D104** — o motor novo
  **grita** onde o velho mentia, o que é a direção boa, mas muda o que quebra no
  corte.

##### Uma lição de conduta, porque ela custou um commit vermelho

Rodei `portão | tail -4 && git commit` e **commitei com o portão vermelho**: num
*pipeline* o shell devolve o status do **último** comando, e o `tail` sai zero
sempre. Corrigido no commit seguinte e reconhecido na mensagem. A regra que fica:
**antes de `git commit`, o status do portão tem de ter sido lido como número**, e
não inferido de um texto na tela.

---

#### O que as três rodadas mudaram no plano

| | antes | agora |
|---|---|---|
| Portões | 31, sendo 1 vermelho conhecido | **34**, sendo 1 vermelho conhecido (D50, espera produção) |
| Caracterização | 416 testes | **626**, nos dois bancos |
| Ações sob `/adm/api` | 97 | **98**, todas conferindo privilégio |
| Recusas de escrita com portão | 0 | **22**, cada uma pelo motivo certo, em 23 casos — o 23º é o **controle**, o único que tem de passar |
| Escritas do aplicativo com ensaio | 0 que rodava | **6**, resíduo zero |
| Métodos de relatório sem teste de valor | "os 18 relatórios" | **nenhum** — 47 dos 48 pares (classe, método) cobertos; o 48º é o visagismo, fora de escopo por decisão do dono |
| Pedidos de janela de produção | cinco, em cinco momentos | **um procedimento**, na janela do corte |

**E o que isso tira de ambiguidade do caminho crítico:** a Fase 1 não tem mais
nenhum passo que possa ser feito fora da janela. Todo o trabalho disponível hoje é
da Fase 2 e da rede de proteção — que é exatamente onde ele esteve nos últimos
três dias.

---

### 09/09/2026 — o dia em que quatro débitos deixaram de esperar

**O Power BI saiu do sistema**, por decisão do dono, e a remoção destravou mais
do que ela mesma. Saíram a tela do painel, a coluna `unidades.iframe_bi` e o
papel `bvip_bi` do PostgreSQL — três estágios. O **D85** encerrou junto: ele
registrava um risco com prazo (*"se o banco migrar sem repontar, o BI quebra no
corte"*) e não há mais o que quebrar. O corte perdeu a única amarra que dependia
de terceiro.

O **D9 não fechou**, e a distinção importa: remover o consumidor não revoga a
credencial. O MySQL de produção segue com o usuário que o BI usava, ainda sem
nome conhecido — e agora ninguém o usa, que é a credencial que menos chama
atenção no dia em que for usada. O débito encolheu de "inventariar e repontar"
para `DROP USER`.

**A autenticação do ERP deixou de ser a grafia do nome da rota** (D146). Até
hoje quem decidia se uma rota exigia sessão era `strpos($routeName, 'admin')`.
Agora é recusa por omissão, com lista de liberação de 95 famílias, cada uma
dizendo o que a defende. A medida corrigiu duas coisas minhas: "109 chaves" é
contagem de primeiro nível — o que o roteador casa são **206 rotas** — e minha
enumeração por *glob* perdia uma rota de biblioteca que era **anônima e o portão
nunca vira**. Divergência zero entre casar o topo e casar o nome inteiro, medida
antes de escolher o desenho.

A revisão adversarial achou o bloqueador que eu não tinha visto: o portão que
executa as 22 rotinas de cron **era cego para a tela de login**. Ela responde
200 e não casa nenhuma agulha de erro — as 22 podiam estar todas desligadas com
o portão dizendo verde. Corrigido antes de tocar na guarda.

**O papel do grupo deixou de ser um número** (D124). A premissa do débito estava
errada e para melhor: a sessão **já publicava os quatro booleanos** ao lado do
id. Trocados os 27 sítios onde o conjunto é idêntico; o portão novo achou um que
eu tinha perdido — um `!= 1`, escrito ao contrário. E as três decisões que
sobraram voltaram respondidas no mesmo dia: o grupo 7 é gerente; o Líder ganha
visão de gerente — que, depois da troca, virou **uma linha de SQL** em vez de
mudança de código; e o XLS passou a esconder o que o PDF esconde (**D158**), uma
inconsistência que deixava 178 colaboradores verem por uma porta o que a outra
escondia.

**A parcela paga deixou de poder ser excluída** (D135). O débito parecia
contábil porque supunha um caso — parcela paga e depois excluída. Das 666
excluídas: **zero quitadas, zero com valor pago, zero com pagamento ativo**. Os
R$ 864.595,41 de diferença são inteiramente dinheiro que nunca saiu. A regra já
valia um nível acima e faltava embaixo; fechá-la não teria barrado nenhuma
exclusão real.

**E o hash de arquivos não estava esperando: estava morto.** O D147 dizia que a
coluna estava "preenchida em 101 deles, esperando". `getHash()` tem zero
chamadores, oito das 109 estão vazias, e todas vieram do `mt_rand`. A medida
quase me fez publicar um defeito falso — 32 caracteres onde o código espera 8 —
até eu ver que era o anonimizador reescrevendo com MD5. *Número que não vale não
se imprime como se valesse.*


**E há um achado novo que precede a quarta fatia** (D119): a busca de cliente
do ERP — `tags LIKE` — responde 5.710 no MySQL e 3.576, 2.223 ou **zero** no
PostgreSQL, conforme o acento e a caixa do que se digita. São **20.716 dos
242.587 clientes com acento nas tags**, 8,5% que somem da busca no dia do corte,
sem erro nenhum. **A metade barata foi feita**: as treze consultas que procuram nessa
coluna passaram a normalizar o termo por `BuscaPorTags`, a mesma transformação
que o sistema aplica ao gravar — no MySQL não muda nada, no PostgreSQL `JOSE`
deixa de responder zero. A outra metade, o acento, é reconstruir uma coluna
derivada de 242 mil linhas: janela de operação, e decisão sua.

**E a mesma pergunta, feita às outras buscas, achou coisa pior** (D120): são 27
consultas com `LIKE` fora daquela coluna, em campos que ninguém normalizou dos
dois lados. Quem digita "corte" no PDV encontra **129 de 590 serviços** no
PostgreSQL contra 590 no MySQL — 78% do catálogo somem, sem erro. E aqui não dá
para baixar a caixa da coluna, porque **ela é o que o usuário lê**: 95% dos
9.506 produtos têm maiúscula, e 100% dos 709.440 logradouros. A escolha —
`LOWER()`, índice funcional ou coluna derivada — muda por tabela, e é decisão de
quem opera. **As 27 estão corrigidas**, e a última fechou em 07/09/2026, com o
`pg_trgm` que você aprovou.

O dicionário de CEP era o caso que faltava, e ele guardava uma armadilha que a
medida de ontem não tinha visto: quem digita "presidente" achava **zero de
2.520**, mas quem digita "são" achava **272 de 16.203** — as poucas ruas que os
Correios gravaram em minúsculas. Lista curta passa por resultado; lista vazia
levanta suspeita. O `LOWER()` sozinho levaria a consulta de 86 ms para 282,
porque `LIKE` com curinga à esquerda não tem prefixo por onde começar; com o
índice GIN de trigrama sobre `LOWER(nome)`, **27,6 ms** — três vezes mais
rápido que a busca quebrada de hoje, por 31 MB.

**E apareceu um índice que ninguém tinha pedido**: `logradouros` é consultada
por CEP a cada endereço preenchido no aplicativo, e não havia índice em `cep`.
Eram 709.440 linhas varridas para achar uma — **60,2 ms contra 0,2 ms**. Não tem
nada a ver com collation; apareceu porque medir uma consulta obriga a olhar o
plano da outra.

O portão `conferir-busca-por-tags.php` passou a vigiar as duas regras e a
publicar os dois denominadores — **13 consultas** em `tags`, **27** nas demais —,
e `BuscaDeLogradouroTest` congela o comportamento nos dois bancos, inclusive o
que continua dependendo do banco: digitar "sao" sem til acha "SÃO" no MySQL e
não acha no PostgreSQL. Isso é o D119, e continua sendo decisão sua — com a
diferença de que, desde 11/09/2026, o caminho está pronto e **desligado**:
`scripts/normalizar-tags.php` reconstrói a coluna e uma chave em `local.php`
liga o resto. Até você rodar os dois, nada muda.

**E medir aquilo desencavou um medidor mentindo** (D122). `sh
scripts/rodar-caracterizacao.sh` mostrava **45 falhas, nenhuma real**: as
fixtures tinham crescido e os dois bancos de desenvolvimento — que ninguém
monta — tinham ficado onde estavam. Os mesmos 312 testes passam no CI, que
remonta o banco a cada execução. Quarenta e cinco vermelhos falsos são piores
que portão nenhum, porque ensinam a ignorar o vermelho.

O `fixtures.sql` passou a **declarar o sha1 do próprio conteúdo** e a gravá-lo no
banco ao ser carregado. São sete lugares que carregam fixtures; pedir a cada um
que lembre de marcar era pedir para um deles esquecer. Agora quem carrega o
arquivo marca o banco sem saber que marca, `CaracterizacaoTestCase` troca as 45
falhas por uma frase que diz o que fazer, e um portão novo garante que a marca
descreva mesmo o arquivo — marca que mente é pior que marca nenhuma. De quebra,
o carregador de fixtures do `setup-dev.sh`, que engolia todo erro com
`2>/dev/null || true`, passou a perguntar antes e a falhar alto.

**E a leitura para a quinta fatia achou um buraco nas quatro que já estão de
pé** (D123). Os controllers de `/adm/api` conferem permissão com
`$this->getAcesso('ler')`, que chama
`getAcl($this->modulo, $this->funcionalidade, $privilegio)` — e
**`$this->funcionalidade` nunca era definida em nenhum deles**. Sem ela a
consulta devolve o módulo inteiro, um arranjo que existe para todo usuário; e
arranjo não vazio é verdadeiro. O privilégio pedido não chegava a ser olhado, e
dois dos seis controllers não conferiam nada além da sessão.

Medido, não lido: um usuário de grupo sem privilégio nenhum recebeu **`recusou:
0 de 10`** — e uma das tentativas desativou um cliente de verdade. A
demonstração já rodava todo dia sem ninguém ler assim, porque
`ensaiar-fluxo-adm-api.php` faz os dezesseis passos como um usuário que as
fixtures descrevem como *"sem poder nenhum"*.

Corrigido nos seis, com `scripts/ensaiar-permissao-adm-api.php` no CI para não
voltar: **`recusou: 14 de 14`**, já com a quinta fatia dentro.

**A varredura do ERP foi feita em 08/09, e desmentiu o que eu tinha escrito**:
dos 48 controllers do ERP que chamam `getAcesso(`, **os 48 definem
`funcionalidade`**. A armadilha era exclusiva da API nova, e a razão é a forma:
as telas do ERP agem sobre `selecionarPorUsuario($this->usuarioId)` — as linhas
do próprio usuário —, e a API pega o id **da rota**. Ao mudar de forma, perdeu
a proteção que vinha de graça.

**A varredura achou outra coisa** (D124): **56 sítios em 12 arquivos** decidem
permissão comparando o id do grupo com um número — 1, 2, 3, 4, 7, 9, 11. E a
mesma pergunta tem listas diferentes: `FaturasController` aceita `3, 4`; a tela
de pacotes aceita `3, 4, 7`. O grupo 7 é "Gerente", com três pessoas, e o grupo
8 é "Líder", com cinco, e não aparece em lugar nenhum. Nada disso está na tela
de permissões — para mudar quem vê a tela de faturas é preciso editar PHP. A
medida está pronta; a decisão por sítio é sua.

**Três `salvarAction` gravavam sem conferir privilégio** (D145) — a porta da tela
guardada e o POST que grava não. A de clientes pesa: **242.592 cadastros** com
nome, telefone, CPF e endereço, alteráveis por qualquer sessão autenticada. As
outras duas **recebem arquivo enviado**.

E aqui a unidade **não** entra, o que também é achado: a listagem de clientes não
filtra por unidade, e a unidade só aparece para exibir. **Cliente é da rede, não
da loja** — 242.592 em 60 unidades, e qualquer franquia atende qualquer um.
Guardar por unidade quebraria o atendimento sem proteger nada. É a segunda vez na
varredura em que a resposta é *"não é defeito, é desenho"*.

**E uma correção no que eu mesmo tinha registrado.** O D138 dizia 140 pessoas
bloqueadas se a tela do PDV fosse guardada. Ao ler o `Acl` para outra coisa,
apareceu que **administrador passa por cima de tudo** —
`allow($grupo, [], [])` concede o ACL inteiro —, e sete dos 140 são
administradores. O número certo é **133**. A conclusão não muda, mas vale para
toda medida de privilégio: contar linhas em `privilegios_usuarios` sem descontar
administradores superestima quem seria bloqueado.

**Sortear o ganhador não conferia privilégio** (D144), e a mesma linha tinha um
segundo defeito: `!empty($sorteio) & $sorteio->getStatus()` usa `&`, que **não
faz curto-circuito** — com um id inexistente o lado direito roda assim mesmo e a
ação cai com *Call to a member function getStatus() on null*. Era 500 por id
errado, e não recusa. Conferido no próprio PHP da imagem antes de trocar por
`&&`.

**E uma porta que ninguém tinha fechado** (D143): as cinco ações de apoio da tela
de notificações não conferiam privilégio, e uma delas **cria e agenda a
campanha** — o próprio código limita a 200 contatos de WhatsApp por envio, ou
seja, ele sabe que aquilo dispara mensagens em nome da barbearia. Já foram 2.787
campanhas por essa tela.

Aqui **não havia travessia de franquia** — o serviço define a unidade a partir de
quem cria, e as leituras já conferiam. Faltava só a porta, e registrar essa
diferença importa: nem toda ação sem guarda é também um vazamento entre
inquilinos.

E medi antes de fechar, porque o D138 ensinou: **zero usuários têm `ler` sem
`escrever`** nessa funcionalidade, e os 181 que abrem a tela têm os dois. Fechar
não tirou nada de ninguém. A lista congelada caiu para **119**.

**E a décima sexta fatia é a permissão** (`docs/f2-fatia-privilegios.md`), a tela
onde o D148 apareceu. Cinco endpoints: o catálogo das 126 funcionalidades, a
leitura e a escrita do privilégio de um usuário, a exclusão, e a leitura do
molde do grupo. **Quatro leituras e uma escrita** — e a proporção é o assunto da
fatia.

Ela **não expõe a escrita da camada de grupo**. Quando foi escrita, o motivo
era o D157: aquela operação apagava em lote as linhas de todo mundo do grupo, e
oferecê-la por endereço antes de alguém decidir seria o silêncio do ERP com uma
porta a mais. **O D157 foi decidido e corrigido no mesmo dia**, e o que a mantém
fora da fatia passou a ser escopo, e não risco. O que a API faz é **contar** —
cada linha sai com o campo `molde`, dizendo se ela veio do grupo ou é decisão de
alguém.

E ela nasce com as guardas que a tela não tinha: privilégio na funcionalidade e
recorte por franquia em toda ação, com 404 em vez de 403 para usuário de outra
unidade — dizer "existe, mas não é da sua" conta ao pedinte que aquele id
existe.

**Uma coisa a fatia não decide, e está escrita nela:** quem tem
`configuracoes/usuarios`/`escrever` pode editar o próprio privilégio, e portanto
conceder a si mesmo qualquer uma das 126 funcionalidades. Isso é verdade no ERP
hoje, e a fatia repete a regra em vez de inventar uma — a API não abre nada que
já não estivesse aberto. Proibir a auto-edição é decisão de política, e fica
registrada para ser feita de olhos abertos em vez de herdada sem que ninguém
perceba.

**E o D138 caiu — a medida que faltava era de comportamento, não de privilégio.**

O registro dizia, com todas as letras, que os dados não distinguiam as duas
leituras: *"saber qual é exige olhar o que aquelas 140 pessoas fazem no sistema,
e isso é conhecimento do negócio, não do banco."* **Estava errado.** O banco
registra o que elas fazem — `vendas.usuario` e `caixas.usuario` dizem quem
vendeu e quem abriu caixa. A medida que travava a decisão era de **privilégio**;
a que a destravou é de **uso**.

Das **72 pessoas que venderam nos últimos 90 dias, nenhuma** seria bloqueada.
Das 71 que abriram caixa, nenhuma. E ampliando para os onze anos de histórico:
557 já venderam alguma vez, 239 estão hoje sem o privilégio — mas **só 4 ainda
estão ativas**, e as quatro têm **uma ou duas vendas na vida inteira**, a mais
recente uma venda única em junho.

As 133 pessoas do registro têm acesso a uma tela que **não usam**. A leitura 1
estava certa, e guardar não tira nada de ninguém que trabalhe ali.

**Confirmei a janela antes de acreditar no zero**, porque zero é o resultado que
mais merece desconfiança: se a cópia fosse antiga, 90 dias estariam vazios para
todo mundo. Não estão — 43.337 vendas por 72 pessoas, e 17.513 acessos.

**Duas correções ao registro, no caminho.** A funcionalidade que a tela usa é
`Vendas nova`, e o D138 mediu `Comandas nova` — as duas têm 170 titulares, então
o número não muda, mas a afirmação estava errada. E o **banco de desenvolvimento
tem o cenário de testes dentro**: dois "bloqueados" que quase entraram no
registro eram os meus próprios usuários de fixture. Reconferi o D157 com e sem
eles — 15.495, 347 e 342 não mudam —, mas fica o aviso para qualquer medida
futura sobre aquele banco.

**As 22 foram guardadas, e a escolha do privilégio é o assunto:** a guarda é a da
tela que **chama**, e não a do controller onde a ação mora. Foi exatamente isso
que originou o D138 — `pesquisarCupom` guardado por `marketing/cupons` fecharia o
desconto na cara de todo caixa. Como `getAcesso()` e `getAcesso2()` usam o módulo
do próprio controller, e quatro dos sete declaram outro, o `CrudController`
ganhou um `getAcessoDe($modulo, ...)` com o módulo **explícito** — quem escreve
uma chamada dessas precisa saber de que tela é a ação.

Com isso a categoria `PDV` da lista congelada **deixou de existir**. Restam
**34**, e nenhuma é trabalho pendente: 14 públicas de propósito, 6 fora de
escopo, 5 sobre o próprio usuário da sessão, 5 com guarda que não é
`getAcesso()`, 3 cotos e 1 URL-capacidade.

**Voltei a construir, e a leitura para a décima sexta fatia achou outra coisa**
(D157). Há **duas camadas de privilégio**, e a de cima destrói a de baixo em
silêncio.

A tabela `privilegios` é o **molde do grupo** — 139 linhas. A
`privilegios_usuarios` é a operativa — 15.495 —, e é a **única** que o `Acl` lê
no login: `SessaoAcl` carrega as linhas do usuário e nada mais; a tabela de
grupo nunca é consultada para decidir um acesso.

Ela é um **gerador**. `PrivilegioService::insert()` e `update()` terminam
chamando `atualizarPermissoesUsuarios()`, que **apaga todas** as linhas daquele
par (grupo, funcionalidade) e recria uma para cada usuário do grupo. Uma edição
em "Gerente unidade" reescreve as linhas de **105 pessoas**.

E o que se perde não é hipotético: das 15.495 linhas, **14.806 são iguais ao
molde** — reescrever não muda nada —, mas **347 divergem** (alguém ajustou
aquela pessoa à mão) e **342 não têm molde nenhum** (concessão que só existe por
pessoa). São **689 decisões individuais** apagadas sem perguntar e sem avisar.

Isso dá contexto ao D148: guardar a tela que edita a camada de baixo foi certo,
mas **o que ela grava é apagável de cima**, e nenhuma das duas telas diz isso.

**Não mudei o comportamento**, porque preservar ajustes individuais é decisão de
produto e há pelo menos três respostas defensáveis — manter e avisar quantas
linhas serão apagadas; preservar as divergentes; ou acabar com a camada de baixo
como coisa editável. A escolha é sua, e enquanto ela não vem a fatia de
privilégios **não expõe a escrita da camada de grupo** — não vou oferecer por API
uma operação destrutiva que ninguém decidiu manter.

**E a categoria `ABERTA` chegou a zero** (D156). As seis últimas: as três
consultas de convênio, a etiqueta de produtos, o estado do WhatsApp e o detalhe
de crédito do cliente. As de convênio não ganharam recorte por unidade, e não é
esquecimento — `Convenio` tem uma **coleção** de unidades, quais franquias
aderiram, e não uma unidade dona; a do WhatsApp já acertava a unidade sozinha,
porque monta o cliente a partir da unidade da **sessão**.

**Numa delas o privilégio "certo" teria sido o errado.** `creditoDetalhes`
devolve a pontuação e o crédito de um cliente, e existe uma funcionalidade
`clientes/credito`, com 168 pessoas — pelo nome, seria a guarda óbvia. Mas os
dois botões que a chamam estão na lista de clientes **sem condição nenhuma em
volta**, e a lista exige `clientes/cadastro`, que 362 pessoas têm. Guardar pelo
nome tiraria o botão de ~194 pessoas que o usam hoje. Guardei pela condição real
da tela, e a funcionalidade `clientes/credito` **continua órfã** — fechá-la é
mudança de política, não correção de defeito, e é sua.

**E três rótulos meus estavam errados.** A triagem que eu tinha feito horas antes
classificou 17 ações como `ABERTA`; lendo uma a uma, três não eram:
`Grupos::ativarRemover` é um coto que só recusa (minha regra só reconhecia
`novo` e `editar` como cotos ali); `Faturas::index` *tem* guarda —
`$grupoId == 3 || $grupoId == 4`, id de grupo no código, que é o D124; e a
geração do código de fidelidade é chamada do PDV, então cai no D138. **Uma
triagem por regra sintática erra** — o valor dela é reduzir o que precisa ser
lido, não substituir a leitura.

Com isso a lista congelada fecha em **56**, e nenhuma delas é trabalho pendente
meu: 22 estão travadas na sua decisão do D138, 28 estão corretas assim, 6 são
escopo adiado.

**E os relatórios guardavam a tela, não o detalhe** (D155). Sete controladores,
a mesma simetria em todos: um `index` que confere `ler`, e uma ou duas ações de
detalhe ou exportação que não conferiam nada — o PDF de atendimentos e o de
evasão, as retiradas de um caixa, o NPS por unidade e por colaborador, o
histórico de estoque de um produto, o extrato de plano de carreira e o gráfico
de fluxo.

**Aqui fechar não custou nada, e desta vez por construção.** Nas correções
anteriores eu precisei medir quantas pessoas perderiam acesso; aqui a medida é
desnecessária, e isso vale mais que uma medida: as oito são chamadas só pelo
`mod-relatorios.js`, carregado apenas nas próprias telas de relatório — e cada
tela já exige `ler` na mesma funcionalidade que a ação passou a exigir. Quem
alcança o botão já tem o privilégio.

Três precisavam também da unidade: o histórico de estoque devolvia **quanto a
franquia vizinha tirou, quando e por qual motivo**; o extrato de carreira, a
evolução de quem trabalha em outra loja; e o gráfico de fluxo lia `unidade` da
query com a sessão só como valor padrão — o desenho do D139 outra vez.

E dois montavam caminho de arquivo com texto da requisição:
`"./data/cache/relatorios/evasao/" . $unidade . ".json"`. A unidade já era
forçada para quem não é administrador, mas **para um administrador**
`?unidade=../../algo` mudava o arquivo lido. É leitura, e não escrita — o oposto
do D154, onde o mesmo descuido escrevia. Os dois passaram a converter para
inteiro.

A lista congelada caiu de 70 para **62**, e a categoria `ABERTA` de 17 para
**9**.

**E a última das quatro é de outra natureza** (D154): não é só acesso indevido,
é **escrita de arquivo com caminho vindo da requisição**. `uploadAction`, que
recebe a foto do usuário, não conferia nem `isPost()`, e passava o id cru ao
serviço, que monta `"./public/layout/img/avatar/cache/" . $usuario_id . "." .
$extensao` e chama `move_uploaded_file` nesse destino. Com `id` na forma
`../../algum/lugar` a escrita sai do diretório pretendido. A extensão já era
restrita a imagem, o que limita o estrago — mas **caminho vindo da requisição não
se corrige com lista de extensões**. Agora o id vem da entidade já conferida,
como inteiro.

E mesmo sem a travessia o defeito de acesso bastava: qualquer sessão trocava a
foto de qualquer pessoa da rede, inclusive a de um administrador. A tela que
chama as duas ações da foto sempre conferiu privilégio e unidade; elas não.

As outras duas: `localizarAcessos` devolvia os **últimos 500 acessos** de
qualquer usuário pelo id cru, e `selecionarPorUnidade` devolvia **os
colaboradores de qualquer franquia** com a unidade crua do POST.

Com isso a tela de usuários fecha: das 16 ações que o D131 listava nela, **10
foram fechadas** em três passos (D148, D149, D154) e **6 permanecem por estarem
certas assim** — cinco agem sobre o próprio usuário da sessão, e `autenticarComo`
tem guarda de administrador.

A lista congelada caiu de 74 para **70**, e o portão de recorte vigia **37
métodos**.

**E o clone estava copiado num segundo controlador — e a minha guarda estava
errada** (D153). Duas coisas.

`ServicosController::clonarAction` é **byte por byte** o mesmo defeito do D152:
duas franquias cruas do POST, sem privilégio e sem unidade. Os dois
controladores declaram a mesma funcionalidade, agem sobre a **mesma entidade**
(`Produto`, mudando só o `tipo`) e têm telas gêmeas. A mesma correção precisou
ser escrita duas vezes, e uma terceira cópia passaria despercebida — por isso as
duas entraram no portão de recorte, que vigia agora **33 métodos**.

**E a guarda que eu tinha escrito no D152 estava errada.** Eu usei
`getAcesso() && unidadePermitida(origem) && unidadePermitida(destino)`, que
*nega* o caso perigoso — para quem não é administrador as duas pontas viram a
própria unidade, e clonar um catálogo sobre ele mesmo não faz nada. Mas não é a
regra que o sistema declara: o botão Clonar das duas telas mora dentro de
`if ($administrador)`. Clonar catálogo entre franquias é operação de
administrador, e não de quem tem `configuracoes/produtos` — são 113 pessoas com
essa funcionalidade, e nenhuma delas vê o botão. Trocado por `usuarioAdmin` nos
dois, que é o princípio do próprio D150: escrever a condição da tela, e não uma
paralela que por acaso dá no mesmo.

**Os dois portões pegaram a mudança sozinhos**, e isso foi o melhor da noite: o
do D131 acusou `ACOES NOVAS SEM GUARDA` — porque `usuarioAdmin` não é
privilégio — e o de recorte acusou `esperava encontrar unidadePermitida`.
Nenhum dos dois se deixou enganar por uma guarda que eu considerava boa. As duas
`clonarAction` voltaram para a lista congelada, anotadas, pela mesma razão que o
`autenticarComo` já estava lá.

E apareceu o **segundo endpoint morto** da varredura:
`selecionarServicosPorUnidade` lia o catálogo de qualquer franquia com a unidade
crua do POST, e a função que a chamaria existe no JS mas não é invocada em lugar
nenhum. Guardei; remover é decisão sua, como no `retiradaPdv`.

**E no catálogo a forma foi outra** (D152): não uma linha alcançada pelo id
errado, mas uma **operação em lote entre duas franquias**. `clonarAction` copia
um catálogo inteiro de uma unidade para outra, e recebia `origem` e `destino`
crus do POST, sem privilégio e sem unidade — qualquer sessão autenticada
despejava o catálogo de uma franquia dentro de outra, sobre **4.121 produtos
ativos em 61 unidades**. Passou a conferir **as duas pontas**: verificar só o
destino deixaria metade do defeito de pé, porque clonar *da* vizinha para a
minha é ler o preço dela.

Junto vieram as duas baixas de estoque. Uma pegava o produto por id cru e
chamava `getQuantidade()` no resultado — produto de outra franquia ou id
inexistente caía com 500 em vez de recusar; agora usa o mesmo auxiliar que a
irmã já usava. **E a irmã não tem chamador nenhum**: procurei em todo o `public/`
e em todo o `module/`, minificados incluídos, e a única ocorrência de
`retiradaPdv` é a própria definição. É um endpoint morto com rota viva que dá
baixa em estoque — guardei, mas **remover é decisão sua**, porque apagar algo que
talvez seja chamado por um caminho que eu não enxergo é pior que guardá-lo.

E duas buscas quase idênticas receberam tratamentos opostos, o que resume a
varredura inteira: `pesquisar` é a janela da tela de configurações e ganhou
`ler`; `autoComplite` é o campo de busca do **carrinho do PDV** e **fica sem
guarda**, porque só 113 dos 356 não administradores têm
`configuracoes/produtos` — é o D138, e continua sendo sua decisão.

A lista congelada caiu de 78 para **74**, e o portão de recorte por unidade vigia
**31 métodos**.

**Duas telas autocontidas depois** (D151), e o que elas ensinam é sobre o outro
lado. No VIP Class, sete ações de administração — apagar aula, apagar material,
mexer no checkpoint — não conferiam nada; **zero** não administradores têm "VIP
Class - Admin", então fechar não custou nada. Na pesquisa de satisfação, seis
ações gravam e apagam as perguntas e as respostas possíveis do formulário; **um
único** não administrador tem o privilégio.

**E em cada uma o que sobrou aberto ficou assim por medida.** O
`materialDownload` do VIP Class continua sem privilégio porque quem baixa
material são os **351** usuários com "VIP Class - Edu", e não os
administradores — ela é uma URL-capacidade, com `i` e `h` resolvidos juntos, e
pôr o privilégio de admin nela tiraria o material de 351 pessoas para proteger o
que o hash já protege. E `responder` e `agradecimento` continuam públicas porque
são as páginas que **o cliente** abre pelo link.

Isso fecha o padrão da varredura inteira. A pergunta certa nunca foi *"esta ação
tem `getAcesso()`?"*, e sim *"quem usa esta ação, e por qual caminho?"* — e as
respostas divergiram: 2 fechadas nos arquivos, 3 nas permissões, 3 na cota-parte,
15 na agenda (uma delas pedindo `ler` e não `escrever`, por 104 pessoas), 7 no
VIP Class e 6 na pesquisa, com quatro ações deliberadamente deixadas abertas por
motivo medido em cada caso.

A lista congelada caiu de 91 para **78**.

**E o maior bloco da lista era a agenda** (D150): das dezessete ações do
`AgendasController`, só duas conferiam privilégio. As outras **quinze** —
agendar, desmarcar, remarcar nas duas formas, check-in, abrir e fechar agenda,
e as leituras de detalhe — não conferiam nada, e todas pegavam a entidade por
`find($request['id'])` sem perguntar de que franquia era.

**A regra estava escrita na tela, e eu a copiei em vez de inventar outra.** O
`tabela.phtml` envolve todo controle interativo em
`getAcl("agendas", "gerenciar", "escrever")`, e o ramo `else` desenha a mesma
tabela com `cursor: default` e sem `onclick`. Quem não tem o privilégio vê a
agenda e não mexe nela — então a guarda das treze que agem é exatamente essa
expressão, e não uma parecida.

**E numa delas `escrever` teria sido a resposta errada, por 104 pessoas.**
`atualizarAction` é a leitura que monta a tabela, chamada no carregamento e pelo
botão de busca, ambos fora do bloco `gerenciar`. Exigir `escrever` ali deixaria
sem agenda nenhuma as 104 pessoas que têm `ler` e não `escrever`. Ela pede
`ler`, como a tela. **Este é o produto da correção**: uma varredura que só sabe
pôr `getAcesso()` em tudo que não tem teria quebrado a operação de 104 pessoas
para fechar um buraco que não estava ali.

A unidade também entrou, e aqui a exceção não é "administrador": é
`agendas/geral`, que o `SessaoAcl` mapeia para a funcionalidade "Agendas
unidades" — a mesma expressão que a lista suspensa da tela usa. Hoje **zero** não
administradores a possuem, então na prática a regra é a própria unidade; escrever
a expressão certa importa para o dia em que alguém a receber. O pior caso era o
`atualizarAction`, onde `unidade` vinha do POST com a sessão só como valor
padrão: trocar o número lia a agenda da franquia vizinha, com nome e telefone no
balão.

E um detalhe que decidiu uma das guardas: o balão do ramo somente-leitura
**omite o telefone**, e o do ramo `gerenciar` mostra. O endpoint sem guarda
entregava justamente o que a interface tinha decidido esconder. As duas ações de
mensagem têm o mesmo formato — elas redirecionam para
`api.whatsapp.com/send?phone=…`, ou seja, **a URL leva o telefone** —, e
passaram a exigir `ler`.

De passagem, um 500 latente: `horariosMarcados` fazia `find()` e chamava
`getId()` no resultado sem conferir; id inexistente derrubava a ação em vez de
recusar.

A lista congelada caiu de 106 para **91**, e o portão de recorte por unidade
vigia agora **29 métodos**.

**A mesma tela tinha outras três** (D149), e o que elas gravam é dinheiro: a
cota-parte que o colaborador recebe sobre um produto. `comissaoExtraSalvar`,
`Pesquisar` e `Remover` não conferiam privilégio nem unidade, com os ids crus do
POST — 35 linhas para 16 colaboradores, valores de 15 a 70.

O modelo certo estava dois métodos acima: `comissaoAction`, a tela, já confere
`getAcesso()` **e** a unidade do usuário. As três ações atrás dela não conferiam
nenhuma das duas. E o produto também passou a ser conferido: a lista suspensa já
restringia ao catálogo da unidade, mas o id chegava cru, então dava para dar
cota-parte de um produto da franquia vizinha — mesma correção do
`adicionarServico` no D142.

A lista congelada caiu para **106**, e o portão de recorte por unidade vigia
agora **22 métodos**.

**E registrei também onde a ausência de guarda é a resposta certa**, porque uma
varredura que só sabe fechar portas acaba fechando as erradas. `meusDadosPerfil`
e `meusDadosSenha` buscam `find($this->usuarioId)` — o próprio usuário da sessão,
não um id da requisição —, e `salvarTema`, `obterTema` e `menuClose` gravam
preferência de interface de quem chamou. Não há o que guardar: quem chama só
alcança a si mesmo. Já `autenticarComo` **tem** guarda, ainda que não seja
`getAcesso()`: é o `usuarioAdmin` do D88. A lista congelada a conta porque a
regra dela é sintática — e afrouxar isso para "guarda parecida também vale"
abriria a porta para qualquer condição no lugar certo.

**E aí a varredura chegou na tela que define quem pode o quê** (D148), e este é
o pior buraco de todos. `UsuariosController::permissoesSalvarAction` — a ação
que **grava o privilégio** — não conferia nada:

```php
if ($request->isPost()) {                       // e mais nada
    $usuario        = $this->em->getReference(Usuario::class,        $data['usuario']);
    $funcionalidade = $this->em->getReference(Funcionalidade::class, $data['funcionalidade']);
```

`usuario` e `funcionalidade` vinham crus do POST, e o serviço escrevia `ler` e
`escrever` do jeito que chegassem. **Qualquer uma das 370 sessões ativas podia
conceder a si mesma qualquer uma das 126 funcionalidades** — "Vendas cancelar",
"Financeiro saídas", "Relatórios de franquias". A irmã, `permissoesRemover`,
apagava qualquer linha das 15.495 pelo id cru.

O controle era a tela: `permissoesAction` sempre exigiu `configuracoes/usuarios`,
e quem não tem não vê o botão. Mas o botão não guarda nada. **Terceira vez nesta
varredura** que a interface era o único controle, depois do D145 e do D147.

E não havia recorte por franquia, o que faz deste **o quinto defeito daquela
forma** — depois de D132, D137, D139 e D142. São 107 pessoas não administradoras
em 54 unidades com `escrever` nessa funcionalidade: cada uma editava o privilégio
de quem trabalha em outra franquia. E a listagem aceitava qualquer `?unidade=`,
porque a consulta usa esse valor justamente no ramo do não administrador. As
cinco ações entraram no portão de recorte, que passou a vigiar 19 métodos.

Junto veio um defeito de verdade: `getReference` **nunca** devolve nulo — monta
um proxy mesmo para id inexistente, e só estoura quando alguém toca no objeto.
O `!empty($usuario)` ao lado era decorativo, e id errado dava 500 em vez de
recusa. Trocado por `find`, a mesma correção que o `unidadeAutorizada()` do D77
já tinha recebido.

Medido antes de fechar, como sempre: 107 continuam trabalhando, **zero** têm só
`ler`, e os 10 administradores passam pelo `Acl` de qualquer forma. Conferi
também que `usuarios.unidade` é NOT NULL — zero nulos em 2.431 linhas — antes de
escrever uma guarda que chama `getUnidade()->getId()`. A lista congelada do D131
caiu de 112 para **109**.

**E a varredura seguinte achou a mesma forma um andar abaixo** (D147): a
listagem de arquivos conferia `configuracoes/arquivosExibir`/`ler`, e as duas
ações que **entregam os bytes** — `abrirAction` e `downloadAction` — pegavam o
arquivo por `find($id)` e devolviam o conteúdo sem perguntar nada. São **109
documentos** de id sequencial: 101 "Materiais" e 8 "Manuais Franquia", todos PDF
ou planilha da franqueadora.

O controle era o menu. O bloco de Arquivos do `layout.phtml` inteiro está dentro
de `getAcl("configuracoes", "arquivosExibir", "ler")`: quem não tem o privilégio
não vê o menu, não alcança a listagem e nunca recebe um link. **O controle da
interface era o único controle** — e ele não sobrevive a alguém digitando a URL.

E medi antes de fechar, porque o D138 ensinou que às vezes fechar custa: dos 356
usuários ativos não administradores, **111 têm o privilégio** e continuam
trabalhando; os 10 administradores passam pelo `Acl` de qualquer forma. A outra
tela que gera os mesmos links, a de cadastro de arquivos, é de administrador na
prática — **zero** não administradores têm privilégio nela. Ninguém perde acesso
que usava. A lista congelada do D131 caiu de 114 para **112**.

Fica anotado o que não fiz: a tabela `arquivos` **tem coluna `hash`**,
preenchida em 101 dos 109, e nenhuma das duas ações a olhava. O desenho de
URL-capacidade que o material do VIP Class já usa — `id` + `h` resolvidos juntos
— existia aqui como coluna e não como verificação. O privilégio responde *"esta
pessoa pode ver arquivos?"*; o hash responderia *"esta pessoa recebeu **este**
arquivo?"*. Hoje quem tem o privilégio vê os 109, sem recorte por categoria nem
por unidade, e se isso é o desejado é decisão sua.

**A varredura das ações sem guarda esbarrou em quem decide se há guarda** (D146),
e o achado é maior que a varredura. A única checagem de login do ERP é esta:

```php
if (strpos($routeName, 'admin') !== false || $routeName == 'home' || $rotaDocumentacaoApi)
```

**É casamento de substring no nome da rota.** Não há lista de rotas públicas, não
há recusa por omissão: quem decide se uma rota exige sessão é a *grafia da chave
dela* no `module.config.php`. De **109 chaves, 15 casam e 94 não** — e essas 94
chegam ao controlador sem identidade nenhuma.

Li as 94, uma a uma. **Nenhuma está aberta**: as catorze famílias têm guarda —
token de cron com `hash_equals`, hash da unidade, `Bearer` do `/api/v4`, ACL por
ação, sessão, ou são públicas de propósito (login, pesquisa NPS, catálogo do
aplicativo). O estado de hoje está certo.

O defeito é que o estado de hoje **não estava escrito em lugar nenhum**, e a
próxima rota nasce anônima a menos que alguém, sem ser avisado, escolha um nome
com "admin". Três consequências, nenhuma hipotética: renomear uma chave de rota
*é* mudar a autenticação dela; `strpos` casa em qualquer posição, então
`relatorio-admin-x` exigiria login por acidente de grafia; e não existe camada
que negue antes de alguém lembrar de negar — que é a raiz das 114 ações do D131.

**E o exemplo que fecha o argumento é meu.** As rotas da Fase 2 se chamam
`adm-api-...`, que **não contém "admin"**: a API inteira passa direto pela guarda
de login. O que a segura é a ACL de cada ação, conferida pelo portão do D123.
Funciona — e não foi escolhido. É o portão errado segurando a linha certa, e eu
não sabia disso enquanto construía quinze fatias em cima.

Virou o portão `conferir-rotas-sem-login.php`. Ele **recalcula** o conjunto
anônimo do `module.config.php` a cada execução e compara com um inventário onde
cada rota diz *o que a guarda*; falha nos dois sentidos, e o texto à direita é o
produto da tabela, não a chave. Quem acrescentar uma rota precisa escrever o que
a defende — e se não souber escrever, essa é a resposta.

**FEITO EM 09/09/2026.** A inversão está no ar: toda rota exige sessão, exceto
as que a lista de `module/ISConfiguracao/config/rotas-publicas.php` libera — 94
famílias, cada uma dizendo o que a defende.

**O que destravou a decisão foi a remoção do Power BI** (**D9**): ele era o
consumidor externo sobre o qual ninguém sabia responder. Sobrou o aplicativo
móvel, e esse a medida alcança — as 25 famílias que ele consome já estavam
inventariadas, e o teste diferencial prova que **nenhuma das 206 rotas mudou de
acesso**, salvo uma rota de depuração do DoctrineORMModule que passou a exigir
sessão de propósito. Enquanto isso não é decidido, o portão garante o que dá para garantir:
que o conjunto não cresça em silêncio.

No caminho apareceu `iscadastro-pesquisa-nps` **declarada duas vezes**, idêntica.
Em PHP a segunda sobrescreve a primeira, então era texto morto e não defeito.
Removida. O que eu procurava era o caso perigoso — duas chaves iguais com
destinos **diferentes**, uma encobrindo a outra calada. Medi os nove módulos:
não havia nenhum.

**A Fase 2 tem DEZESSEIS fatias de pé.** A décima sexta é a dos privilégios
(`docs/f2-fatia-privilegios.md`), de 09/09/2026 — **97 ações**, todas conferindo
privilégio e nenhuma inerte, medido pelo `conferir-acl-da-api.php`. A décima
quinta é a dos pacotes (`docs/f2-fatia-pacotes.md`). A quinta fatia já
devolvia os pacotes **de um cliente**; faltava o **catálogo** que a franquia
vende.

Pacote é produto com `tipo = 'pac'` — os dois vivem em `produtos` —, então o
recurso **delega** a `ProdutoJson` e acrescenta a validade e os itens. E toda
ação confere o **tipo** junto com a unidade: sem isso, um `PUT` no endereço de
pacote mudaria o preço de um serviço avulso.

Dois números que podem divergir de propósito ficam os dois no recurso: o preço do
pacote e a soma das sessões. **Pacote é desconto** — o cliente paga menos que a
soma —, e devolver os dois impede que uma tela nova "conserte" a diferença.

**A décima quarta** é a dos cupons
(`docs/f2-fatia-cupons.md`), de 09/09/2026 — **84 endpoints**. Cupom é
**desconto**: quem mexe nele mexe no preço que o PDV cobra, e foi ao ler essa
tela que apareceram os dois defeitos abaixo.

O recurso devolve o **valor** de `regra` e `momento`, e não o rótulo — e há um
motivo concreto além do princípio: `Arrays::cuponsMomentos()` devolve `null`
fora das duas opções, então mandar o rótulo mandaria `null` no lugar do dado.

E um nome que quer dizer duas coisas: `Cupom::$validade` são **dias** de
duração, `CupomCliente::$validade` é a **data** de vencimento do cupom já
emitido. O recurso chama o primeiro de `validadeDias`, e o teste cobra os dois
lados.

**Quatro defeitos da mesma forma em duas noites** — D132 nas saídas, D142 nos
pacotes, D137 nos
devedores, D139 nos cupons: pegar a entidade por id e mexer nela sem perguntar de
que franquia é. O do cupom alcançava o **desconto** que o PDV da unidade vizinha
aplica, e a listagem aceitava qualquer `?unidade=` da query — a lista suspensa
restringia a escolha, e o servidor não. Mesmo desenho do botão de devedores: **o
controle da interface era o único controle.**

O dos pacotes é o de maior alcance: duas das ações **mudam a composição** — quais
serviços o pacote dá, quantas sessões, por quanto — e nenhuma das duas conferia
privilégio **nem** unidade. São 238 pacotes em 21 unidades, e **3.474 já vendidos
a clientes**, que continuam sendo consumidos sessão a sessão.

Isso virou um portão novo, `conferir-recorte-por-unidade.php`, vigiando catorze
métodos. E ao ligá-lo à CI achei uma falha no trabalho da noite: o portão do
D131, escrito horas antes, **nunca tinha sido ligado** — ele existe para pegar o
commit de outra pessoa e só rodava quando eu o chamava. Os dois entraram juntos.

**E há uma decisão sua que mudou o enquadramento de tudo o mais** (D138), achada
em 09/09/2026 ao tentar guardar mais uma das ações do D131.

`pesquisarCupom` não tem guarda. Antes de pôr uma, fui ver quem a chama — e ela
roda **no PDV**, não na tela de marketing: os campos que preenche são
`#pdvcarrinho_item_desconto` e `#pdvcliente_id`. Guardá-la com `marketing/cupons`
fecharia o desconto de cupom na cara de todo caixa.

**E a tela do PDV não tem privilégio nenhum.** `comandaAction` confere o estado
da venda e a unidade, e nada mais — ela está na própria lista congelada. Não há
de quem herdar.

A medida é o que trava:

| | |
|---|---:|
| usuários ativos | 370 |
| ativos **sem** `Comandas nova`/escrever | **200** |
| entraram nos últimos 90 dias | 232 |
| destes, **sem** o privilégio | **140** |

Cento e quarenta pessoas que usam o sistema toda semana não têm o privilégio do
PDV, e hoje alcançam a tela assim mesmo. Ou elas não deveriam estar lá — e a
guarda fecha um buraco real —, ou usam o PDV todo dia e a concessão nunca foi
feita porque nunca foi cobrada — e a guarda para 32 barbearias amanhã de manhã.
**Os dados não distinguem as duas leituras**, e saber qual é exige conhecer o que
essas pessoas fazem.

Isso muda o que as 126 restantes significam: não são "126 guardas faltando", são
guardas que **nunca existiram** — e as concessões se acomodaram à ausência delas.
Ligá-las é uma **migração de permissões**, não uma correção: decidir por tela,
conceder a quem usa, e só então guardar. Foi por isso que as guardas desta noite
entraram só onde a medida mostrou que ninguém perderia acesso indevidamente.

**A Fase 2 tem TREZE fatias de pé.** A décima terceira é a dos devedores
(`docs/f2-fatia-devedores.md`), de 08/09/2026 — **77 endpoints**. Escolhida logo
depois das saídas e pelo mesmo motivo: procurar onde o dinheiro se move sem que
ninguém esteja olhando.

**A tela de devedores recebe pagamento**, e não conferia privilégio nenhum nem
unidade nenhuma.

O primeiro achado (D136) é de desenho: a funcionalidade `clientes/devedores`
existe na tela de permissões e governava **a visibilidade de um botão**, não uma
porta — o controlador conferia `clientes/cadastro`, e as outras duas ações não
conferiam nada. Entre 1.171 usuários com privilégio, **quatro viam o botão e
recebiam 404 ao clicar**. Alinhar a porta ao botão consertou um sintoma que já
existia.

**O segundo (D137) é o mais grave desta rodada.** `pagarDebito()` busca o caixa
aberto de **quem chama** e credita o valor nele. Sem conferir a unidade, um
usuário da franquia A quitava a dívida de um cliente da franquia B: o *a receber*
sumia de B e o dinheiro aparecia no caixa de A. Provado com ensaio vermelho — com
a guarda removida, o débito da outra unidade é baixado.

**E um terceiro apareceu ao escrever o teste:** sem caixa aberto,
`selecionarPorUsuario()` devolve `null` e a linha seguinte fazia
`$caixa->getUsuario()`. Era um fatal; agora recusa.

Também conferi antes de acusar: `clientes_debitos` tem coluna `unidade` própria
que a listagem não usa — duas fontes para o mesmo fato. **9.096 débitos, zero
divergências.** Concordam, e não virou achado.

**A décima segunda** é a das saídas
(`docs/f2-fatia-saidas.md`), de 08/09/2026 — **74 endpoints**. A razão de
escolhê-la é curta: por onde o **dinheiro sai** era a única área do sistema sem
uma linha de teste e sem uma linha de fixture.

**Quatro defeitos na primeira leitura.** Três corrigidos antes de a fatia
existir, e o quarto medido e esperando você.

**O primeiro é o mais sério deste projeto até aqui** (D132): `excluirParcelas()`
recebia os ids **crus do POST** e apagava sem perguntar de quem eram. A ação
irmã confere a unidade; a de lote não conferia nada. **Medido antes de
corrigir**: a parcela da unidade 90001100 saiu num pedido feito pela 90001000. É
escrita atravessando a franquia, num banco com ~32 delas, e bastava o privilégio
`financeiro/saidas` em qualquer uma.

O segundo (D133): a guarda que recusa excluir despesa com parcela paga contava
por `getParcelas()`, que **filtra as excluídas**. Bastava excluir a parcela paga
primeiro, pelo botão da mesma tela, e a despesa passava. A regra estava escrita e
não valia.

O terceiro (D134): `?categoria=` e `?local=` derrubavam a tela com 500 — a
entidade não tem esses campos, e o formulário de filtro nem os oferece. Só o
controlador os lia, da URL. Removidos.

**E o quarto é uma decisão sua** (D135). A tela lista parcelas por um caminho que
filtra as excluídas e soma por outro que não filtra: depois de qualquer exclusão
os dois números discordam. Na cópia de produção são **144 saídas e
R$ 864.595,41** de diferença. São duas leituras defensáveis — "o total tem de
bater com a lista" e "parcela paga e excluída é dinheiro que saiu de verdade" — e
elas dão números diferentes. **Não escolhi por quem responde pela
contabilidade.** O recurso novo não precisou esperar: ele soma o que devolve e
devolve o que soma, coerente consigo mesmo seja qual for a escolha.

**E o portão pegou o meu próprio desenho.** `conferir-acl-da-api.php` acusou
quatro ações da fatia nova porque a guarda estava a **dois** níveis de
delegação — e ele segue um, de propósito. Estava certo: duas camadas entre a ação
e a conferência são exatamente o que o D123 deixou passar.

**A décima primeira** é a das vendas
realizadas (`docs/f2-fatia-vendas.md`), de 08/09/2026. `/adm/api/vendas` existia
desde a primeira fatia e sabia fazer **uma coisa só**: cancelar. Dez fatias
construídas, 62 endpoints, e não havia como **listar** nem **ver** uma venda — a
tela que mostra o que foi vendido era a única sem recurso. Agora são **64**.

**O recurso delega, em vez de duplicar.** Comanda e venda são a mesma linha da
mesma tabela (D51), e `VendaJson` chama `ComandaJson` e acrescenta o que só
interessa depois de fechada. `status = 1` é "concluída" na tela de comandas e
"ativa" na tela de vendas: vocabulário duplo da interface, não do dado — a API
fica com um nome só.

**Duas restrições da tela entram no recurso, e não por capricho.** Quem é do
caixa só vê hoje e só as concluídas; quem não é administrador só vê a própria
unidade. A tela faz isso reescrevendo os valores no servidor — campo `disabled`
em HTML não impede ninguém de mandar outra coisa —, e uma API que ignorasse as
duas seria o caminho para contornar as duas.

**E há uma escrita dentro de um GET que a API não repete.** A tela chama
`npsContabilizar()` no meio da montagem: a rotina persiste resultado de NPS, com
`flush()` dentro do laço. Ela **já roda de dez em dez minutos pelo cron**, e a
cópia de produção tem 37.957 pesquisas com **zero pendentes** — na tela ela não
acha nada para fazer. A API não a chama, e a consequência está dita: uma
pesquisa respondida há menos de dez minutos pode não estar contabilizada, que é
a defasagem do cron.

**Duas investigações não deram em defeito, e isso está registrado.** As 43
vendas com `status = 3` numa janela de quatro dias eram as comandas abertas no
instante do retrato da produção. As 16 com `status IS NULL` são vendas
temporárias — `VendaService` escreve `setStatus(null)` com essas palavras. Nos
dois casos a medição chegou antes do anúncio.

**O que sobrou foi uma linha.** `ClienteService:95` gravava `setStatus(true)`
numa coluna de **quatro** significados. Coagia para 1, que por acaso é o certo —
mas `false` ali significaria *cancelada*. Das 29 chamadas booleanas de
`setStatus` do sistema, quatro caem em coluna inteira; três são colunas de dois
estados, e essa era a única que escondia a escolha.

**A décima** é a das unidades
(`docs/f2-fatia-unidades.md`), de 08/09/2026 — a dívida que a nona deixou
escrita na própria tabela de recorte. São **sete endereços**, e com eles a API
chega a **62 endpoints**. Sete fatias falam de unidade — o PDV, o caixa, a
agenda, o estoque, os usuários — e nenhuma sabia dizer *que unidade é essa*.

**Dois nomes iguais para coisas diferentes, e um teve de mudar.** As nove fatias
anteriores usam `estado` para `"ativo"`/`"inativo"`, em 55 endpoints. A coluna
`unidades.estado` quer dizer a sigla do estado brasileiro. Mantive `estado` com
o sentido que a API já tem e a sigla saiu como **`uf`** — senão alguém filtraria
`?estado=ativo` e receberia unidades de um estado chamado "ativo".

**E `applyMask` apaga a diferença entre "não apurado" e "zero".** Ela devolve
`"0,00"` para os dois, e cinco getters da unidade só sabiam devolver a máscara.
`potencial_franquia` e `nps` são colunas anuláveis: das oito primeiras unidades
da cópia de produção, **três têm potencial nulo** e cinco têm valor — pela
máscara, as oito diriam a mesma coisa. Os cinco getters ganharam o argumento que
`Produto::getValorVenda()` já tinha desde a sexta fatia.

**A leitura achou dois defeitos, e o segundo é grande.**

O primeiro (D130) é o **D125 outra vez**: o construtor de `Unidade` hidratava e
logo em seguida sobrescrevia seis campos sem condição. Não era hipotético — o
próprio formulário de "nova unidade" manda quatro deles. Medido: **cinco
enviados, cinco descartados; depois da correção, zero**.

O segundo (D131) é maior que a fatia. **Este sistema não tem guarda global de
privilégio**: o único listener que decide alguma coisa pergunta *"há sessão?"*, e
quem responde por privilégio é cada ação, no próprio corpo. A conta:

    296 ações na superfície do navegador
    130 não conferem privilégio nenhum

Sessão não é privilégio — cada uma delas está aberta a qualquer usuário
autenticado, inclusive um colaborador sem direito nenhum. **Corrigi cinco**, as
da tela de unidades. A pior devolvia o cadastro inteiro de um sócio da franquia
por id — CPF, nascimento, telefone, e-mail e endereço — e era a única que só lê,
o que permitia varrer os ids sem deixar rastro.

**As outras 130 não dão para corrigir em varredura, e as cinco explicam por
quê:** quatro pediam o privilégio de `unidades`, e a quinta —
`selecionarHorarios` — pedia o de `usuarios`, porque quem a chama é a tela de
editar usuário. Uma varredura teria fechado essa porta na cara de quem tem
direito. O privilégio certo é o da tela que chama, e descobrir isso é ler o
JavaScript que faz o POST, uma ação de cada vez.

Então a lista ficou **congelada** em `scripts/conferir-guarda-do-erp.php`, que
garante o que dá para garantir hoje: que ela não cresça. E ele guarda a lista, e
não o número — no ensaio vermelho, tirar uma guarda e pôr outra manteve o total
em 130 nas duas medições. Um portão que contasse teria passado.

**A decisão de quando pagar as 130 é sua.** A medida está pronta, por arquivo e
por ação.

**A nona fatia** é a dos convênios
(`docs/f2-fatia-convenios.md`), do mesmo 08/09: o PDV aplica desconto de convênio
desde a primeira fatia e a quarta já devolve o convênio do cliente, mas **nada
listava os convênios** — uma tela mostraria o convênio de alguém sem poder
oferecer a lista para trocá-lo.

Ela é o caso mais limpo da emenda à regra 1: `ativarRemover()` **alterna**
(`setStatus($x ? false : true)`), e `POST` que alterna não é idempotente. A API
tem dois endereços, e cada um confere o estado antes — sem isso, a segunda
chamada de `/ativacao` faria o **oposto** do nome dela.

E ela mostrou onde a fronteira ainda mente: `getContratoDataVencimento()` sem
argumento devolve `d/m/Y`, e o *setter* também espera `d/m/Y` — mandar ISO cai
num `createFromFormat` que devolve `false` sem lançar. É o terceiro getter desta
família em três fatias, depois do que mascara dinheiro e do que formata
nascimento.

A oitava é quem atende
(`docs/f2-fatia-usuarios.md`), também de 08/09: a agenda pedia `colaborador`
desde a segunda fatia e o PDV desde a primeira, e **nada listava os
colaboradores** — uma tela nova marcaria horário para um id que não teria como
descobrir.

**São duas leituras, e a lista do que ficou de fora é o recorte**: das vinte
ações do `UsuariosController`, tudo que escreve ficou — senha, permissões, foto,
comissão, horário de agenda, personificação. Cada uma é assunto próprio, e
escrever qualquer uma por API exige decidir regra que ninguém decidiu.

O que mais importa nessa fatia é **o que o recurso não devolve**. A entidade tem
78 getters, e seis abrem a porta: o bcrypt, os dois tokens, o id de push, os
documentos fiscais — e o `salt`, que `autenticarComo()` usa como **credencial**,
ou seja, é senha com outro nome. Há teste que serializa o recurso e procura cada
um deles.

**E o CI achou um defeito que as suítes verdes não tinham achado** (D128).
`GET /adm/api/usuarios` respondeu `invalid input syntax for type integer: "t"`:
eu passei `status` como booleano, exatamente como a quarta e a sexta fatias
passam — e nas duas está certo. Em `clientes`, `produtos` e `grupos` a coluna é
**boolean**; em `usuarios` é **integer**.

São **47 tabelas com uma coluna chamada `status`**, em quatro tipos: 32 boolean,
6 integer, 5 smallint, 4 varchar. **Quinze não são booleanas**, e o nome não diz
o tipo. Os portões de texto não pegam isso porque o booleano era **computado**
(`$estado === 'ativo'`), e varredura textual só reconhece literal. Quem pegou
foi o instrumento que **chama o endpoint** — e é a lição a guardar.

E ela achou o **D127**: personificar perde o próprio rastro. Enquanto se está
personificando, a porta abre para qualquer id, e o ramo de "voltar" limpa a
marca — um administrador entra como X e sai como Y, sem registro de que houve
personificação. Não é escalada (só administrador entra), mas o rastro é a única
coisa que distingue personificação legítima de acesso indevido. Fica anotado: o
conserto exige decidir o que a marca deve significar.

A sétima é o estoque
(`docs/f2-fatia-estoque.md`), do mesmo 08/09: três recursos, a movimentação que
a sexta tinha adiado. Ela também achou defeito lendo (D126) — e desta vez de
segurança: `estoqueCancelarAction` não conferia **unidade** nem **estado**.

São 33.573 movimentações com id sequencial, em 51 unidades: qualquer usuário com
permissão de produtos cancelava a de qualquer unidade trocando o número na URL —
e cancelar **reverte o estoque**. E cancelar duas vezes revertia duas: quem
impedia era o **template**, que esconde o botão. Template não é guarda; o botão
não é a única porta, e há 715 movimentações já canceladas na base. As duas
entraram na tela do ERP no mesmo dia.

A sexta é o catálogo
(`docs/f2-fatia-catalogo.md`), de 08/09/2026: **um recurso para as duas telas**
do ERP, e foi a ACL que decidiu isso — `ProdutosController` e
`ServicosController` já usam a mesma permissão, que se chama "Configurações
produtos/serviços", e as duas consultas são idênticas fora do conjunto de
`tipo`. Pacotes ficaram de fora porque têm permissão própria. Com ela são
**quarenta e quatro endpoints** e **329 testes** nos dois bancos.

Ela achou dois defeitos, e os dois por caminhos diferentes (D125). **Lendo**: o
construtor de `Produto` hidratava e depois sobrescrevia seis campos com o
padrão — `insumo` e `linha VIP` são selects da tela de produto novo, e nasciam
sempre falsos. Na cópia anonimizada, **6.339 de 9.502 produtos têm `visivel_app`**
— descontadas as linhas de fixture da faixa `>= 90000000` (D167) — e
1.637 têm `linha_vip`: todos precisaram de dois passos, criar e depois editar, e
ninguém sabia por quê. **Pelo teste**: `getValorVenda()` mascara por padrão, e o
recurso saía com `89.00` em vez de `89.90`, porque `(float) "89,90"` é 89. Esse
só um teste de **valor** pega — um de tipo passaria, as duas são string.

A quinta é o programa de fidelidade
(`docs/f2-fatia-fidelidade.md`), de 07/09/2026: crédito, pacote e pontuação —
sete recursos, e os três primeiros em que **o dinheiro sai**. Comprar crédito
cria uma venda e sobe o caixa; devolver e resgatar pedem gerente, com as mesmas
guardas do D86/D88 vigiadas uma a uma pelo portão. Com ela são **trinta e oito
endpoints** em `/adm/api` e **321 testes** nos dois bancos.

Ela custou onze vermelhos antes de ficar verde, e a lição vale registrar:
pendurar o crédito e o pacote no cliente que as outras fatias já usavam **mudou
o que acontecia com ele** — pacote ativo tem precedência sobre desconto de
convênio, e saldo parado não é saldo zero. Cenário de fatia nova não pode mudar
o cenário das anteriores; a quinta tem cliente próprio.

A quarta, do mesmo dia, é o cadastro de clientes
(`docs/f2-fatia-clientes.md`): seis recursos — listar com busca, ver, criar,
alterar e desativar. Foi ela que deixou a fidelidade de fora com o motivo
escrito, e foi lendo o controller dela que o D123 apareceu.

O ensaio de fluxo passou a começar pelo cadastro — o cliente que ele cria é o
mesmo que marca, é atendido, paga e **compra crédito** —, e são **dezenove
passos**. O medidor leva **14 dos 38 endpoints** ao trabalho; os outros 24 param
na guarda, que é o que se espera de um corpo vazio.

A pergunta que o encadeamento respondeu na quinta fatia: comprar crédito **cria
uma venda**, e o saldo que a fatia do caixa calcula tinha de enxergá-la. Enxerga
— `359,70` viram `409,70` com uma compra de `50,00`, e a sangria derruba os dois
juntos. As duas fatias estariam certas separadamente; só o fluxo mostra que
concordam.

Ela achou três avisos do PHP impressos dentro da resposta — `strtolower(null)`,
`str_replace(null)` e `mb_ereg_replace(null)` —, todos em campos **opcionais**
que a tela antiga sempre manda preenchidos e que um cliente JSON omite. E achou
que os ids 1 e 2 dos clientes são reservados **por convenção, não pelo banco**:
o primeiro cadastro feito pela API num banco novo nasceu com o id 2 e virou
"SEM CADASTRO" para o sistema inteiro.

**A Fase 2 tem três fatias de pé.** A terceira é o caixa
(`docs/f2-fatia-caixa.md`), escolhida e construída em 06/09/2026: seis recursos,
que fecham o dia que o PDV abre. Com ela são **vinte e cinco endpoints** em
`/adm/api` e **286 testes** nos dois bancos.

Foi a primeira fatia em que o mapa **não mudou durante a construção**, e a razão
é direta: o verbo saiu do serviço antes de o mapa ser escrito. Nas duas
anteriores ele saiu do nome da ação, e as duas precisaram de correção — é a
emenda à regra 1, pagando pela primeira vez.

E com três fatias de pé nasceu o que nenhuma sozinha permitia: um **ensaio de
fluxo encadeado** (`scripts/ensaiar-fluxo-adm-api.php`), dez passos que fazem um
dia da barbearia pela API nova e conferem os números — 269,80 + 89,90 = 359,70,
o pagamento entrando pela fatia do PDV e aparecendo no saldo que a fatia do caixa
calcula. É o que o medidor de `/adm/api` diz, no próprio cabeçalho, que não faz.

**A Fase 2 tem duas fatias de pé.** O PDV — "comanda aberta até venda
finalizada" — e a **agenda** — "abrir o dia até o cliente chegar" — somam
**dezenove endpoints** em `/adm/api`, com teste nos dois bancos e chamada real a
cada push. Nada substitui as telas atuais: `/mod-vendas/...` e
`/mod-cadastros/agendas/...` continuam servindo o ERP de hoje.

**E a chamada a cada push prova menos do que parecia** (D112): dos dezenove,
**cinco** chegam ao trabalho do endpoint; os outros catorze param na primeira
guarda — 422 de corpo vazio, 404 de recurso já apagado, 409 de comanda fechada.
São respostas certas, e o que provam é a borda: rota, verbo, sessão, ACL e
validação. O medidor passou a publicar esse denominador em vez de só dizer
"nenhum endpoint quebrou", e a guarda que veio junto achou que
`GET /adm/api/comandas` — da primeira fatia — nunca havia formatado uma comanda
sequer. Quem cobre o trabalho dos catorze é a suíte de caracterização.

**A mesma pergunta feita aos 31 relatórios teve resposta pior** (D113): eles
respondem sem quebrar, e nenhum precisava mostrar uma linha para passar. Sete
inspecionados à mão, sete sem dado — formulário de filtro, "Nenhum registro
localizado", uma planilha só com o cabeçalho, um TXT de zero bytes, dois JSON de
zeros. O medidor ganhou marca de dado e publica **23 de 31 conferidos** — dois na
primeira rodada, sete na segunda (dois deles **já mostravam dado e ninguém
sabia**) e quatro na terceira, incluindo as duas planilhas, que não aceitam
marca de texto e passaram a ser abertas por dentro para contar linhas, o NPS
na quarta, as tabelas de dashboard na quinta, o PDF inflado mais um 404 que
era falta de um id na sexta, o aniversário do colaborador na sétima e o
dashboard na oitava. Dos oito restantes, **cinco não têm tabela própria** —
são formulários e cascas de JS cujo dado já está provado noutro endereço —, e os
outros **três** são a dívida escrita, e cada um custa entender o
filtro daquele relatório. Isso também recalibrou a comparação entre os dois
bancos, e essa foi a maior consequência: ela comparava 190 artefatos que eram,
na maioria, formulários e tabelas vazias — **dois vazios são sempre idênticos**.
Com o cenário povoado passou a comparar **226 com conteúdo**, e achou na
primeira execução que `SUM()` volta número no MySQL e texto no PostgreSQL,
gravado num arquivo de cache que a tela e o XLS leem depois (D118).

**As fatias são verticais até onde dá**, e vale dizer com precisão: não existe
React neste repositório. O que está provado é o lado do servidor. Chamar isso de
"fatia vertical concluída" afirmaria mais do que se mediu.

O que a primeira mostrou está em `docs/f2-fatia-pdv.md`, e mudou coisas fora
dela — inclusive dois defeitos que **só apareceriam construindo**: a aplicação
imprimia avisos antes da resposta, o que anulava qualquer código de status HTTP,
e o decimal do protocolo multiplicava dinheiro por cem ao encontrar os serviços
da casa.

A segunda está em `docs/f2-fatia-agenda.md`, e o que ela mostrou é de outra
natureza: **o mapa acertou os oito recursos e errou dois dos oito verbos**,
porque foi desenhado por analogia com REST antes de alguém ler o serviço. E três
defeitos saíram de ler, não de medir — o medidor via 200 nos três.

**Fase 1 encerrada para efeito de avanço, por decisão de 02/09.** O portão da
fase está fechado, e as três frentes que não fechavam sem produção — F1.1,
F1.7 e F1.9 — **foram para a homologação como débito** (**D83**, **D84** e
**D85**). O trabalho segue para as **Fases 2 e 3**.

O que ficou para trás não é dívida esquecida: está escrita, com o que cada uma
carrega. Uma merece destaque porque tem PRAZO, e não só pendência: o corte passou a ser
**de nó único** por consequência de adiar a F1.7 (D84). **Eram duas** — o Power
BI quebraria no corte se não fosse repontado antes dele (D85) —, mas ele foi
removido em 09/09/2026 e levou o próprio prazo junto.

Desde 29/08 o trabalho já vinha migrando para o que a Fase 2 vai precisar — inventário das ações,
levantamento das regras escondidas em campos, correção de defeitos de operação
que a leitura do código expôs, e a rede de proteção do aplicativo, que se
revelou furada em dez pontos (D55).

**Duas decisões suas encolheram a Fase 2**: o VIP Class vai ser ligado e entra
no escopo; assistentes ficou em espera. Com isso, as ações que serviam funções
desligadas caíram de 38 para 7.

*Correção de registro (07/10/2026, o item CODIGO-MORTO-E-DOCS):* nenhuma das duas
funções estava desligada. O vínculo de unidade vazio quer dizer **todas** as
unidades (`VipClassRepository::listagemIndexEdu`, `uni.unidade IS NULL OR ...`;
o D343), e "ligar" a VIP Class vinculando unidades a um curso o **fecharia** para
as outras 31 unidades ativas. A decisão sobre os assistentes (o D57) foi tomada
sobre essa premissa e volta a você na P14 de
`docs/f3-telas-restantes/telas-consolidado.md`, sem resposta registrada até esta
data.

E em 29/08 o trabalho passou de pagar dívida da Fase 1 para **assentar a
fundação da Fase 2**: a convenção da API está escrita e decidida, e o analisador
de logs que destrava a pendência mais antiga do projeto está pronto para rodar.

**Em 30/08 o inventário de débitos virou trabalho.** Dez dos 62 foram fechados ou
reduzidos, quase todos por decisão sua depois de eu medir — e a medição mudou o
que havia a fazer em cinco deles.

**Na madrugada de 31/08 a leitura passou dos débitos para os procedimentos**, e
foi lá que estava o pior achado da semana: **o passo 7 do corte destruía o único
arquivo de que a volta atrás depende**. Junto vieram a sexta prova da carga (a
que confere a forma, e não o dado), um comando para os dez portões, e o
`orm:validate-schema` de volta a valer como portão — de 19 entidades reprovadas
para zero.

E veio um achado que enfraquece o resto: **a suíte de contrato falha sozinha**.
O mesmo código, duas execuções seguidas, 35/35 e depois 3 falhas. Isso está no
**D66**, e é a primeira coisa a resolver entre as que não dependem de produção —
não porque quebre algo hoje, mas porque tira o valor de toda evidência que a
suíte deu.

**Em 01 e 02/09 a suíte passou a cobrar os dois bancos de verdade.** O D73
fechou (17 injeções de DQL viraram parâmetro) e, ao escrever o teste que provava
aquilo, apareceu o **D81**: `cli.status = 1` compara inteiro com coluna booleana,
e **o relatório de clientes não executava no PostgreSQL** — nem errado, nem
nada. Medindo pelo metadata, eram 26 literais; executando os caminhos, **15 de
15 quebravam**, entre eles o PDV e a API do aplicativo.

Corrigido o D81, o **D82** apareceu ATRÁS dele: o mesmo erro de tipo na direção
contrária, valor booleano indo para coluna inteira. Oito sítios, também
corrigidos.

O resultado que importa: **os 126 testes de caracterização passaram a rodar nos
DOIS bancos, sem saltos** — antes eram 4 saltados no PostgreSQL. E o projeto
ganhou quatro portões novos, um deles executando 16 caminhos reais da aplicação
contra o banco de destino, que era o buraco de método que deixou o D81 passar:
havia comparação de esquema e geração de DDL, e **nada executava a aplicação**.

### 05/09 — o sistema inteiro passou a ser PEDIDO contra o PostgreSQL

Até esta data, três coisas eram conferidas contra o banco de destino: o schema, a
carga e os serviços de cálculo. **Nada abria uma tela, e nada chamava uma
rotina** — e o que ninguém pede, ninguém sabe se funciona.

A pergunta foi feita, e a resposta não foi "sim":

| Superfície | Antes | Depois |
|---|---|---|
| contrato do aplicativo | só MySQL | **67/67 nos dois bancos** |
| telas do ERP | sem cobertura | **153 pedidas, zero com erro** |
| rotinas de cron | sem cobertura | **22 chamadas, zero quebradas** |
| `/adm/api` — o código da Fase 2 | provado por `curl` uma vez | **11 endpoints a cada push** |
| relatórios | sem cobertura | **31 gerados, zero quebrados** |

As duas últimas linhas vieram de aplicar ao código **novo** a mesma pergunta que
o D96 fez ao antigo. A do `/adm/api` doeu mais: `grep -rn "adm/api" tests/`
devolvia uma linha, e era um comentário (**D101**). A dos relatórios abriu a
consulta pesada do sistema — agregação com data, que é onde os dois bancos mais
diferem — e que **nunca tinha sido pedida uma vez sequer** (**D104**).

**Onze defeitos consertados**, todos da mesma família — tipo. O primeiro foi o
**login do ERP**, que respondia HTTP 500 no PostgreSQL: ninguém entraria no
sistema depois do corte.

**E três portões estavam verdes enquanto os defeitos passavam por baixo.** O do
agrupamento (**D24**) carregava a dedução do MySQL 8 escrita dentro: tratava
`GROUP BY vp.formaPagamento` como equivalente a `GROUP BY fp.id`, o que é
verdade no MySQL e falso no PostgreSQL. **É por isso que a F1.4 está qualificada
acima:** o zero dela era verdadeiro e era incompleto, e `ONLY_FULL_GROUP_BY` no
MySQL — a ferramenta em que aquela frente se apoiou — não podia ter acusado o
caso, porque o modo estrito também o aceita.

Os outros dois eram cegueiras de leitura: o do **D81** não enxergava consulta
alguma quando a string interpolava uma variável, e o do **D82** não via
`$params['x'] = true`. Os três foram corrigidos e postos à prova com o defeito de
volta.

**Um mecanismo explicou três sintomas de uma vez** (**D100**): todo aviso do PHP
vai para a saída, e saída antes da resposta manda o cabeçalho com 200 — anulando
o status que o framework definir depois. Eram 64 avisos em sete sítios; hoje são
zero, e o zero virou portão.

#### E depois: os dois bancos dizem o mesmo?

Medir "não quebrou" não alcança a classe de defeito que não quebra nada:
ordenação por coluna anulável (**D46**), linha sorteada sem ordem (**D72**),
arredondamento. Para isso a pergunta é outra, e agora tem um comando:

```bash
sh scripts/comparar-relatorios-nos-dois-bancos.sh
```

Ele monta os dois bancos do zero, roda telas e relatórios em cada um e compara
resposta por resposta — **190 arquivos, 189 idênticos**, com uma divergência
conhecida e escrita.

**Três defeitos saíram daí, e nenhum era de banco de dados:**

| | |
|---|---|
| **D107** | 77% de cada PDF baixado era a página do ERP colada depois do `%%EOF` |
| **D108** | 168 bytes de `Notice` na frente de cada planilha — quatro irmãos de um defeito que o D100 já tinha previsto por escrito |
| **D109** | `DEFAULT NULL` virava `DEFAULT false` no gerador de DDL, e a listagem de usuários vinha vazia num banco só |

O D109 é o que mais importa para o corte: eram **cinco elos** entre o
`mysql.sql` e a tela, cada um inofensivo sozinho.

#### O portão de corte estava vermelho, e nenhuma das 61 provas era da carga

`conferir-migracao.php` — o portão do passo 5 — acusava 61 falhas. Conferi as 25
tabelas divergentes uma a uma: **18 por linhas de fixture, 4 pelo dicionário de
CEP esvaziado no MySQL por decisão, 2 por uma pesquisa apagada depois da carga,
1 por seis logins no ERP de desenvolvimento.** Nenhuma sobra.

A carga está boa; as cópias é que não são mais o que foi carregado. O portão
agora **recusa** — código 2, "a prova não vale" — em vez de falhar com 61 linhas
explicáveis, porque vermelho que se explica é pior que vermelho nenhum
(**D106**).

---

A Fase 0 fechou cinco das seis frentes; a sexta — remediação de produção —
virou a F1.1 e segue congelada por decisão de 23/08.

| Frente | Situação |
|---|---|
| F1.1 Remediação de produção | **adiada para a homologação** — decisão de 02/09 (**D83**) |
| F1.2 Decisões de arquitetura | **concluída** — SaaS e RPO/RTO decididos |
| F1.3 Conversão do schema | **concluída** — e reconfirmada contra o schema de produção |
| F1.4 Compatibilidade de consultas | **concluída** — 0 quebras, 4 supressões justificadas; **qualificada em 05/09** (ver abaixo) |
| F1.5 Estado fora do disco | **concluída** — provada no servidor de homologação |
| F1.6 Agendamento | **concluída** |
| F1.7 Alta disponibilidade | **adiada para a homologação** — ensaiada em laboratório, 30 a 36 s contra meta de 300 (**D84**) |
| F1.8 Migração dos dados e corte | **portão fechado** — 239 testes nos dois bancos desde 05/09 (461 asserções no MySQL, 460 no PostgreSQL — a diferença é um só ramo, explicado no `RelatorioClientesInjecaoTest`), mais contrato, telas, rotinas, `/adm/api` e relatórios executando contra o PostgreSQL, e 189 de 190 respostas idênticas nos dois. O portão de corte em si **recusa** hoje: as cópias locais não são mais as que foram carregadas (**D106**) |
| F1.9 Repontar o BI | **encerrada em 09/09/2026 por remoção do alvo** — o Power BI saiu do sistema (**D9**, **D85**). O que a frente deixou de permanente foi o `bvip_app` e o achado do **D48** |

### 30/08 — dez débitos, e o padrão de medir antes de agir

O que se fechou, em ordem de quanto mexe em dinheiro:

**D20 — a sangria acima do saldo era aceita.** A validação do controller tinha
`empty` onde cabia `!empty`. Medi antes de corrigir: **56 caixas negativos em
125.771**, com 41 deles num único ano — foi episódio, não regime. E a correção
não foi de um caractere: os testes falam com o *serviço*, que não validava nada,
então corrigir só a tela deixaria a correção sem cobertura. A trava foi para o
serviço.

**D22 — campos de Lava VIP gravados cruzados.** O débito mandava recalcular o
histórico; medir mostrou que não havia histórico — **zero das 32.544 linhas** tem
valor de Lava VIP, porque a categoria teve 7 vendas na história inteira e as
últimas são de 2022. Corrigir virou prevenção, não reparo.

**D23 — a apuração de royalties duplicada** entre serviço e controller. Conferi
por diff antes de remover: as cópias diferiam por uma linha em branco e a palavra
`private`. Saíram 84 linhas. E ficou um teste amarrando os dois caminhos, que era
o que faltava.

**D28 — a venda passou a poder ser registrada na data em que aconteceu.** Metade
do débito: o parâmetro existe, mas não há campo na tela para informá-lo, e criá-lo
é decisão de produto.

**D40 e D44 — a trava contra execução simultânea.** O D40 previa que a geração
"tolerasse" a violação de unicidade; tentar mostrou que o Doctrine **fecha o
EntityManager num flush que falha**, o que torna esse caminho pior que evitar a
corrida. A rotina passou a usar a trava que existia vinte linhas abaixo no mesmo
arquivo. Royalties ganhou a dela também.

**D25 e D62 — as funções de DQL.** As oito passaram a ser declaradas na
configuração, e as 154 chamadas espalhadas por 30 arquivos saíram. O teste
escrito para isso **achou um defeito vivo**: `TIMESTAMPDIFF` lia o token do lexer
como array, e o filtro de faixa de idade do relatório de clientes quebra hoje em
produção quando alguém o preenche.

**D46 e D47 — as ordenações, agora fechadas.** Cinco colunas tinham nulos onde o
débito dizia que não havia, a pior com 959 de 2.430. E as **36** listagens
paginadas ganharam critério de desempate — 33 em bloco, e as 3 restantes uma a
uma, porque o classificador as marcava com o mesmo `CONFERIR` **por não enxergar
o `FROM`**, não por serem brandas.

O pior dos 36 estava nessa pilha de três: a listagem de cupons gerados ordenava
por `cup.validade`, e o `WHERE` fixa `cup` numa única linha. Todas as linhas
compartilhavam o mesmo valor. A cláusula existia, lia-se como ordem definida, e
não ordenava nada — 933 páginas em ordem arbitrária no maior cupom.

**D63 — 78 entidades declaravam `@ORM\Entity` duas vezes**, achado de raspão ao ler
outro arquivo. Hoje não quebra, e só por ordem: a segunda ocorrência sobrescreve
a primeira, e é a que traz o `repositoryClass`. Mas **atributo de PHP 8 não
aceita repetição** — verificado por execução, não de memória —, e a conversão de
anotações para atributos é caminho normal ao subir de Doctrine na Fase 1. Seriam
78 fatais de uma vez.

O roteiro que as removeu passou por uma prova de que **consegue acusar**: uma
entidade forjada com a duplicata e sem `repositoryClass` apareceu na lista dos
casos que ele se recusa a corrigir sozinho — porque ali remover a linha nua
apagaria a única declaração existente.

**D17 — o mapeamento que destruiria o banco.** 114 anotações `OneToOne` sem
índice único que as sustentasse, mais quatro `unique=true` que o `schema-tool`
denunciou depois. Os `ADD UNIQUE INDEX` propostos caíram de **114 para 0**.

**Esta linha dizia "os comandos destrutivos caíram de 114 para 0", e era mais do
que se corrigiu.** O que caiu a zero foram os `ADD UNIQUE INDEX` — que era o
defeito do D17 e é a métrica que o próprio débito registra. O `schema-tool`
continua propondo **4 comandos que apagam coluna**, e três delas têm conteúdo:

| Coluna | Linhas | Preenchidas |
|---|---:|---:|
| `usuarios_acessos.local` | 1.880.078 | **1.804.389** |
| `logradouros.tipo` | 709.440 | **709.440** |
| `vendas.desconto_motivo` | 2.269.212 | 425 |
| `usuarios.senha_php83` | 2.430 | 0 (sai no deploy da F1.1 — D7) |

São **2.514.254 valores** que um `--force` destruiria hoje. O aviso do D17 —
*nunca executar* — continua valendo inteiro, e a linha resumida convidava a
concluir o contrário.

### O que a rodada ensinou, e vale mais que a lista

**Medir antes de agir mudou o que havia a fazer em cinco dos dez.** O D22 mandava
recalcular histórico inexistente; o D40 previa um conserto pior que a
alternativa; o D46 dizia que não havia colunas expostas; o D20 pedia um número
que eu podia levantar sozinho; e o D23 e o D22 afirmavam uma divergência que a
regra de escolha por ano torna impossível.

**Três testes existiam para congelar defeitos, e os três avisaram quando o
defeito saiu** — o do caixa, o do Lava VIP e o do registro de função DQL. Todos
traziam a frase "se esta asserção falhar, alguém corrigiu". Todos foram
invertidos em vez de apagados.

**E procurar por uma sintaxe encontra uma sintaxe, não o problema.** Depois de
corrigir as 114 anotações do D17, o `schema-tool` continuou acusando quatro
índices — vindos de `unique=true`, a mesma mentira em outra forma. Foi a
ferramenta que me corrigiu, e não o contrário.

**O ponto cego de um verificador é do tamanho do que ele deixa de ler.** O
roteiro do D63 disse "0 restantes" e estava errado: `ClienteCredito` traz um
`use` **entre o docblock e a palavra `class`**, e o padrão simplesmente não
casava esse arquivo. Eram 78, não 77. O erro só apareceu porque as contagens não
bateram — 82 entidades mapeadas contra 81 lidas —, e essa diferença de um foi o
único sinal que existia.

**Uma nota que dizia "duas" queria dizer dezenove.** Ao conferir se o D63 tinha
estragado o mapeamento, contei as reprovações do `orm:validate-schema`: **19
entidades, 31 queixas**. O débito registrava "duas relações sem `inversedBy`" —
verdade sobre o que se lia naquele dia, e um quinto do quadro. Duas dessas
queixas são de outra natureza: **apontam para campos que não existem**. Viraram
o **D64**.

E isso expõe o custo real: enquanto `orm:validate-schema` reprova sempre,
**ele não serve de portão** — foi preciso rodá-lo duas vezes, com e sem a
mudança guardada, e comparar contagens, porque "reprovado" sozinho não diz nada.

---

### 29/08 — a fundação da Fase 2, e um achado que mudou de tamanho

**A convenção da API está escrita**, em `convencao-api.md`, e as três decisões de
fundo foram tomadas: prefixo `/adm/api`, recursos **em português sem exceção**, e
autenticação por **token**.

Ela foi escrita **antes** da primeira fatia de propósito — o roadmap já dizia que
esse é o antídoto barato para a inconsistência que a fatia vertical cobra. E foi
escrita **a partir de medição**, não de gosto: 174 ações já devolvem JSON, sempre
como `{sucesso, mensagem, recurso}`, sempre com HTTP 200 — **nenhuma das 99 rotas
declara verbo**, e um único arquivo do sistema inteiro chama `setStatusCode`.

A regra que mais se paga sozinha é exigir verbo na rota, e não é purismo: **17
ações mutam estado respondendo a GET**. A família `ativarRemover` está entre
elas, e o caso extremo dessa mesma forma já custou caro ao projeto — a rota
`migrar-senhas` sobrescreve o hash de todos os usuários ativos, e um GET a
dispara.

**Sua pergunta sobre os dois públicos corrigiu um estreitamento meu.** Eu havia
recomendado sessão argumentando que token só compensa quando há cliente que não
é navegador. O argumento estava certo e a conclusão era estreita: **há** um
cliente que não é navegador — o aplicativo. Ele só não aparecia porque hoje não
se autentica. A resposta virou dupla, e o que a decide é o cliente, não o
usuário: **cookie `httpOnly` no ERP**, onde a ameaça é XSS e o cookie é o único
lugar que o JavaScript não alcança; **`Authorization: Bearer` no aplicativo**,
que não tem DOM e tem Keychain.

### O D59, que cresceu ao ser medido

Desenhar a autenticação expôs que **a API do aplicativo não autentica ninguém** —
e medir mostrou que a porta é mais larga do que eu havia registrado.

`GET /api/agendas/getCliente?telefone=...`, sem cabeçalho nenhum, devolve **nome,
CPF, RG, telefone e e-mail** — e o `id`, que destrava agenda, cupons, sorteios e
uma rota que expõe **nome e telefone de terceiros indicados**. Um número de
telefone devolve o documento de identidade da pessoa.

Demonstrado, não deduzido: criei um cliente só para isso e removi em seguida,
porque a anonimização reescreve o formato do telefone e não havia como casar com
dado existente.

Não é regressão — é o desenho original, de quando o aplicativo era menor.
Corrigir passa por endpoint novo em versão nova, porque exigir token nas rotas
atuais derruba o parque instalado na hora (D5). Ficaram registradas duas
contenções que **não** quebram o parque: limite de taxa no nginx, e mascarar
`cpf` e `rg` — esta mais barata do que parece, porque o snapshot de contrato
registra o **tipo** e não o valor, então a suíte não acusa.

### O passo que destrava tudo isso está pronto

O caminho do D59 depende de saber **quais versões do aplicativo estão em uso** — e
essa é a pendência da W3, aberta desde a Fase 0. É o único passo que não depende
de decisão nenhuma.

`scripts/inventariar-versoes-app.php` faz essa medição. Leitura pura, aceita
`.gz`, roda em produção sem cerimônia. Cruza os três sinais de versão que
existem, porque nenhum basta sozinho, e foi exercitado contra o log real deste
ambiente.

Duas coisas nele são deliberadas, e as duas são sobre não mentir. Ele classifica
rota sem chamada como **suspeita, nunca como morta**, e põe a janela observada em
destaque: *"não apareceu no log" não é "está morto", é "não apareceu na janela"* —
um cliente que vai à barbearia a cada dois meses é invisível num log de sete
dias. E ele **nunca copia valor de parâmetro para o relatório**, porque a query
string do log traz telefone em texto claro.

### A última lacuna da suíte de contrato

Fui medir se o D55 tinha deixado buraco e a resposta foi boa: **22 das 22 rotas
do aplicativo têm contrato**. A cobertura estava completa.

Mas a medição achou outra coisa — **três endpoints versionados sem teste
nenhum**: `/api/unidade/vendas`, `vendasV2` e `vendasV3`. São integração
contábil, não do aplicativo: devolvem número da nota, forma de pagamento, itens
vendidos. E ninguém versiona três vezes o que pode atualizar em conjunto — as
três versões coexistindo são a assinatura de um consumidor externo de longa data.

São também **a única parte da API que autentica**, por um `hash` da unidade.
Ganharam contrato, e a checagem do hash ganhou asserção própria: antes, afrouxar
a verificação não faria teste nenhum acusar, e afrouxá-la abre o faturamento de
qualquer unidade a quem souber o id — que é sequencial.

Daí saiu o **D60**: o hash é a credencial, e viaja na query string. Vai para o
log de acesso em texto claro, de toda requisição, para sempre. Mesmo defeito de
forma do D59, com uma diferença — aqui o que vaza não é o dado de uma pessoa, é
a chave do faturamento de uma unidade inteira.

**E o guarda do D55 provou seu valor três vezes, em casos que eu não tinha
previsto:** recusou o `vendasV2` por `produtos: []`, e depois o
`cliente-consumo` por `pontuacoes: []`. Precisei completar o cenário com itens
de venda e com crédito do cliente. Um contrato com lista vazia não descreve nada
do que há dentro dela — e foi o guarda que insistiu, não eu.

### Os casos que só tinham o vazio

Varrendo os snapshots atrás do mesmo defeito, apareceram **dez que terminavam em
`-sem-dados` e não tinham par**: o formato que o aplicativo realmente interpreta
não estava congelado em lugar nenhum. O guarda não pega isso, e não deveria —
eles se declaram casos de vazio, e são. O que faltava era o outro lado.

Todos eram POST sem parâmetro, e os endpoints leem da query: caíam no
`sucesso: false` inicial porque não havia o que buscar.

**Três eram de leitura e foram cobertos:** `cliente-selecionar` — que é o
endpoint do D59 —, `cliente-consumo` e `agenda-selecionar-v2`.

**Seis mudam estado e viraram o D61**, porque cobri-los faz a suíte de contrato
passar a escrever no banco: ela deixa de ser repetível sem recriar o banco antes.
Não é impeditivo — o CI já monta do zero a cada execução — mas muda o que a suíte
é, e isso merece decisão em vez de uma escolha minha no meio de outra tarefa.

### O atrito que virou conserto

Rodar a suíte de contrato contra o banco errado me custou duas execuções num
dia — e da primeira vez me fez **escrever o D55 com o diagnóstico errado**: li
as falhas como regressão de contrato quando eram outra base.

Isso agora é impossível de fazer em silêncio. `ContratoTestCase` recusa rodar
contra banco com dado real — reconhece pelas unidades fora da faixa 90000000 —
e recusa também quando a API responde sem JSON, que é a cara de banco ausente.
As duas mensagens dizem o comando que resolve.

E `scripts/rodar-contrato.sh` faz a dança inteira: aponta o `local.php` para o
banco de contrato, limpa os proxies, roda, e **restaura aconteça o que
acontecer** — suíte vermelha, Ctrl-C, erro de docker. Restaurar só no caminho
feliz seria pior que não restaurar, porque o esquecimento ficaria para a próxima
execução da caracterização, que rodaria no banco errado sem nada acusar.

### As duas dívidas de ordenação, medidas e pagas

**O D46 previa refazer periodicamente a contagem de nulos nas colunas
ordenadas.** Enquanto isso dependia de alguém lembrar, dependia de alguém
lembrar — então virou `scripts/conferir-ordenacao-nulos.php`, que sai com 1 se
alguma coluna sem correção tiver nulo.

Rodá-lo derrubou a premissa do débito, que dizia que as restantes não tinham
nulo nenhum. **Cinco tinham**, e a pior é `usuarios.data_contratacao` com **959
nulos de 2.430** — 39% dos usuários. Ordenar por ela no PostgreSQL joga essas
959 linhas para a outra ponta. As sete ordenações foram corrigidas.

**O D47 dizia "há paginação por cima" sem número.** Agora tem: **36 listagens
paginadas sem critério de desempate**, e as **36 foram corrigidas** — 33 em bloco
por decisão sua, e as 3 restantes uma a uma, porque cada uma exigia coisa
diferente. A última delas nem era falta de desempate: era `ORDER BY` por
constante, ordenando 933 páginas por um valor igual em todas as linhas. Sem desempate, nenhum banco promete ordem estável entre empates — e
com paginação por cima, uma linha pode aparecer em duas páginas ou em nenhuma.
Isso vale hoje no MySQL; o PostgreSQL só torna visível.

A correção **não era uniforme**, e foi o achado que mais importou: o paginador
do sistema roda sem output walkers, então o campo do desempate precisa estar no
`SELECT` — acrescentá-lo a uma consulta que não o seleciona pode devolver
resultado errado **sem erro**. Por isso o roteiro classifica caso a caso em vez
de mandar corrigir tudo, e as 3 restantes seguem marcadas para conferência.

### O mesmo erro, seis vezes, em roteiros diferentes

Vale juntar, porque a repetição é o dado:

**Medir texto *sobre* uma coisa como se fosse a coisa** — três vezes. A prosa
"esta rotina não está agendada" fazendo a rotina passar por agendada; um
comentário citando `getContratoVencido()` classificando o campo como decisor; e
comentários meus explicando um `ORDER BY` removido sendo contados como `ORDER
BY`. Os três roteiros agora removem comentários antes de procurar.

**Verificação que não sabe falhar** — três vezes também. O `montar-banco-contrato.sh`
filtrando a saída do mysql e engolindo os erros junto com o aviso de senha; o
guarda de ambiente que eu escrevi deixando passar justamente o caso mais
provável; e o classificador do D47 removendo parênteses por `()`, que casa com o
próprio padrão e fazia o laço convergir sem colapsar nada — devolvendo o alias
errado em silêncio.

E uma lição que não estava em nenhuma lista: **uma medição que devolve zero
parece boa notícia, e é quando merece mais desconfiança.** Procurei
`setFirstResult` para achar paginação, achei zero, e o zero significava que eu
procurava o sinal errado — não que o problema não existia.

### Duas correções e uma faxina

O **D58** foi corrigido nas duas camadas: desativar a pesquisa da fidelidade
numa tela de administração derrubava a página do cadastro de adesão, por onde
passaram 25.455 clientes. A trava ficou no serviço, não no controller, para
proteger qualquer chamador. **Nada muda no estado de hoje** — com a pesquisa
ativa, as duas camadas são inertes.

A **pesquisa de teste de 2018** saiu, com um script cuja trava é
desproporcional de propósito: as chaves estrangeiras são `ON DELETE CASCADE`, e
um `DELETE` apontado para a pesquisa errada levaria junto as 25.471 respostas —
que só existem como contador, e não podem ser recontadas. A trava foi testada
contra o caso perigoso, não só contra o feliz.

E o roadmap tinha um número velho: **as ações de contrato do aplicativo são 60,
não 55**. As seis linhas da tabela da Fase 2 somam 362; com 55 somariam 357 — o
número era anterior à correção que achou as cinco ações versionadas, que são,
justamente, do contrato do aplicativo.

---

### 28/08 — o que mudou na rodada anterior

**A Fase 1 parou de render trabalho técnico, e o esforço foi para preparar a
Fase 2.** O que saiu daí:

**As ações estão inventariadas e classificadas** — eram 362 quando isto foi escrito, são **360** desde 31/08 — (`inventario-acoes.md`,
gerado por `scripts/inventariar-acoes.php`). O número era 357 até o gerador ser
corrigido: o padrão `[a-zA-Z]+` não casa dígito, e **cinco ações versionadas —
`salvarV2`, `getAgendaV3` e afins — eram invisíveis**, justamente as que mais
importam para o contrato do aplicativo.

**Duas ferramentas de análise estática novas.** `campos-regra-ou-referencia.php`
procura campos que parecem regra e são referência; `conferir-agendamento.php`
cruza as rotinas de cron com o crontab documentado. A segunda achou uma rotina
que a documentação dava como agendada e não estava.

**Dois defeitos meus, do mesmo feitio, em análises diferentes:** texto *sobre*
uma coisa sendo contado *como* a coisa. A prosa "esta rotina não está agendada"
fez a rotina passar por agendada; um comentário citando `getContratoVencido()`
fez o campo ser classificado como decisor. Os dois geradores agora leem só
linhas de tabela, e removem comentários antes de procurar.

**O D52 saiu do papel:** a abertura de comanda passou a reaproveitar a comanda
aberta do cliente em vez de criar uma segunda, e a rotina de cancelamento
distingue `COMANDA_DUPLICADA` de `COMANDA_ABANDONADA`. A terceira ação — o que
fazer com as ~1.200 comandas abandonadas por ano — é decisão de negócio, e está
medida, não implementada.

**A rotina de vencimento de convênio ganhou endpoint de cron**, e o caminho até
lá expôs que `selecionarConvenios()` só devolve convênios não marcados: o ramo
que desmarcava era código morto, e um contrato renovado **ficava na lista para
sempre**.

**A validade da pontuação virou configuração de unidade**
(`unidades.pontuacao_validade_dias`, padrão 365). Era um ano fixo em duas
expressões do repositório. **Zero significa "não expira", de propósito:** se
significasse zero dias, uma unidade configurada com 0 por engano apagaria o saldo
de todos os seus clientes na primeira madrugada.

Daí saiu o **D54**, que não é dívida de código e sim de sequência: o `ALTER`
precisa rodar em produção **antes** de o `mysql.sql` ser refeito, ou o dump nasce
sem a coluna e leva o schema PostgreSQL junto. Virou o **passo 0** do
procedimento de corte.

Esse aviso já se cumpriu em miniatura: o `postgres/schema.sql` foi regerado com a
coluna e o banco local não foi recarregado. A suíte passou inteira no MySQL e
quebrou 44 testes no PostgreSQL. **Regerar o schema não é o mesmo que
aplicá-lo** — e o custo de descobrir isso aqui foi uma execução de suíte; no
corte, seria a aplicação no chão.

### O achado que mais importou em 28/08

**Dez dos 26 snapshots de contrato não descreviam carga nenhuma.** Descoberto ao
conferir os números desta própria atualização, e resolvido. Ver **D55**.

Quatro falhavam e seis passavam. Os que passavam são o problema: `{"cupons":
"(vazio)"}` afirma que existe uma lista de cupons e não diz nada sobre o que há
dentro de um — que é a única coisa que o aplicativo lê. E `unidades-selecionar`
gravava `estado`, `latitude`, `longitude` e `telefone` com o tipo **`null`**,
enquanto produção manda texto nos quatro.

**A causa não era a que a primeira leitura sugeria.** Escrevi que regravar
exigiria a réplica de produção. Medir mostrou o contrário: o CI monta o banco
com `mysql.sql` mais `fixtures.sql`, e as fixtures **não povoavam banner,
agenda, cupom, sorteio, área de atuação nem indicação**. Além disso os testes
pediam `/api/.../1`, e as fixtures criam a unidade e o cliente 90001000 — o id 1
nunca existiu ali.

Montando o banco do CI, os dez endpoints devolviam exatamente as estruturas
vazias que os snapshots guardavam. **O CI estava verde, e o verde não
significava nada.** Não faltava produção: faltava cenário.

**O que foi feito:** as fixtures ganharam o cenário do contrato, os testes
passaram a pedir os ids que existem, os snapshots foram regravados contra o
banco do CI (`scripts/montar-banco-contrato.sh`), e `assertContrato()` ganhou
uma trava que **recusa gravar estrutura sem carga** — a menos que o snapshot se
declare um caso de vazio pelo sufixo `-sem-dados`.

Resultado: **28 de 28**, com 177 linhas de contrato onde antes havia invólucro,
e as duas suítes passando nas duas ordens de execução.

Três defeitos apareceram no caminho, todos meus, todos do mesmo feitio — algo
que deveria acusar e não acusava:

- O cupom do cenário foi parar no cliente que `CupomTest` usa para contar
  quantidades exatas, e quebrou três testes financeiros. Cenário de contrato e
  cenário de cálculo precisam de sujeitos diferentes.
- As agendas novas apontam para um produto, e `DELETE FROM produtos` passou a
  falhar por chave estrangeira — o que faz o mysql **abortar o arquivo de
  fixtures naquela linha**, deixando o cenário pela metade sem que nada acuse.
- E o script que monta o banco filtrava a saída para esconder o aviso de senha
  do mysql, escondendo os erros junto: carregou pela metade dizendo que estava
  tudo bem. **Uma verificação que não consegue acusar é pior que nenhuma**, e é
  a terceira vez que essa mesma armadilha aparece neste projeto (D49, o
  comparador de ensaios, e agora esta).

Povoar o contrato também revelou o **D56**, **já corrigido**: o `status` do
sorteio chegava `null` ao aplicativo em toda resposta, desde sempre — a consulta
não selecionava o campo, e a função que o formata recebia o nome do sorteio no
lugar da chave. Só apareceu porque o endpoint passou a devolver carga.

A correção foram duas linhas, e o contrato mudou numa: `status` passa de `null`
para `string`. Alcança **5.002 clientes** que veem sorteios no aplicativo — uma
função dormente desde out/2025, o que torna o risco da mudança baixo.

### E uma observação sua que virou medição

Ao revisar o D55 você observou que **tabela vazia costuma ser função nunca
usada, ou em implementação**. Isso é verificável, e verificar mudou o tamanho de
um pedaço da Fase 2.

Três tabelas estão em zero na produção, e duas delas contam a mesma história:
o **VIP Class** tem 21 cursos, 68 aulas e 14 materiais cadastrados — e
`vipclass_unidades` vazia, ou seja, **nenhuma das 62 unidades tem acesso**. Com
os **assistentes** é idêntico: 4 cadastrados, 11 grupos, nenhuma unidade. Não é
função pela metade; é função inteira com a chave nunca girada.

*Correção de registro (04/10/2026, o D343):* a leitura acima está invertida. No código,
vínculo de unidade vazio quer dizer **todas** as unidades
(`VipClassRepository.php`, `AssistenteRepository.php`: `uni.unidade IS NULL OR ...`), e as
duas funções estão ligadas: 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). "Nenhuma
unidade vinculada" quer dizer "ninguém restringiu".

**38 das 362 ações serviam funções desligadas** — o total era 362 nesta
medição, e hoje é 360 — VIP Class (25), assistentes
(7) e sorteios (6). Eram 10% do inventário, e a pergunta certa não era *"como
migramos isto?"* e sim *"isto vai ser ligado?"*.

**Perguntada, ela resolveu 31 das 38 na mesma conversa.** O VIP Class **vai ser
ligado** e entra na Fase 2 com prioridade baixa — não há usuário hoje para
incomodar se a tela ficar por último. Os sorteios migram de qualquer forma.
Assistentes ficou em espera no **D57** e foi decidida em 31/08: as 7 ações
(2% do inventário) **ficam fora da Fase 2**.

*Correção de registro (07/10/2026, o item CODIGO-MORTO-E-DOCS):* "não há usuário
hoje" está errado -- a VIP Class tem 2.131 conclusões de 341 pessoas, a última em
10/08/2026 (`docs/f3-telas-restantes/tela-vipclass.md`, M1), e a prioridade baixa
nasceu dessa premissa. A premissa caiu, e a ordem que vale continua a sua, de
27/09/2026 ("A ordem das telas que faltam", abaixo: VIP Class em 8º de 9) até você
dizer outra coisa -- a pergunta foi ao lote de 07/10/2026. O lado do aluno está no
passo 13 do consolidado e não espera pergunta nenhuma; a administração dela e os assistentes esperam a P14. E a decisão
do D57 foi tomada sobre a mesma leitura invertida do vínculo vazio (a correção do
D343, acima). *Respondida em 08/10/2026* (pergunta 49 da página do lote, a opção
(a), "Manter a ordem de 27/09"): a VIP Class fica em 8º de 9, sabendo do uso. Se
a administração dela e a dos assistentes entram na tela nova continua sendo a P14.

**As pesquisas foram examinadas e migram** — e o exame corrigiu um erro meu de
quatro ordens de grandeza. Eu havia contado 3 respostas; a tabela guarda
*opções* com contador, e o total é **25.471**. A função é a **etapa 5 obrigatória
do cadastro do programa de fidelidade**: 25.455 clientes no programa, 25.471
respostas, cadastro mais recente há 12 dias. Nunca esteve em dúvida — eu é que
tinha medido errado.

O exame rendeu o **D58**, **já corrigido**: desativar essa pesquisa na tela de
administração derrubava a página inteira do cadastro de fidelidade, porque a
view fazia `$pesquisa->getPerguntas()` sobre o nulo e nada impedia a
desativação. Um clique numa tela quebrava outra, de 25 mil usuários.

A correção tem duas camadas — o serviço recusa desativar a pesquisa do programa,
e o nulo passou a ser tratado, de modo que sem pesquisa a etapa 5 é pulada em
vez de derrubar a adesão. **Nada muda no estado de hoje:** com a pesquisa ativa,
as duas camadas são inertes.

Conferi o que a decisão sobre o VIP Class exige do corte, e a resposta é
**nada**: as 8 tabelas `vipclass*` já estão em `migrar-dados.sh`, todas com
`inquilino_id` e política de RLS — inclusive `vipclass_unidades`, que está vazia
e seria a fácil de perder de vista. O que falta para ligar a função não é
programação: é vincular unidades.

*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.

No outro extremo, cupons segue vivo e intenso: 100.373 vínculos com clientes, o
mais recente há 13 dias. E as **indicações** são função nascendo, não morrendo —
6 convites em dois meses e meio. Detalhe em `funcionalidades-sem-uso.md`.

### Números verificáveis

| | |
|---|---|
| Commits | 177 |
| Testes automatizados | 154 (119 caracterização + 35 contrato) |
| Testes verdes contra PostgreSQL | **109 de 109**, com as mesmas 203 asserções do MySQL — e as 203 são o que prova que foi o banco certo (D69) |
| Débitos fechados ou reduzidos | **30 de 80**, entre 29/08 e 01/09 |
| `ADD UNIQUE INDEX` propostos pelo `schema-tool` | eram **114**; agora **0** (D17) |
| Comandos que ainda apagariam dados | eram **4**, agora **3** — e dos 2,5 milhões de valores, **2.513.829 são duas constantes**; a informação real são **425** (D17) |
| Testes de contrato verdes | **35 de 35** — mas a suíte **falha sozinha**, e esse número vale menos do que parecia (**D66**) |
| Chamadas de rede sem tempo limite | eram **9**; 7 corrigidas por decisão sua, restam as **2** do OneSignal congeladas (**D67**) |
| Rotinas de cron que não deixam rastro | **11 de 26**, e 5 delas são as que mandam push (**D68**) |
| Ações rotuladas "contrato do aplicativo" | **60** — mas **29 são rotinas de cron**, sem teste nenhum (**D70**) |
| Ids fixos de banco em regra de negócio | eram **7**; agora **0** — viraram constantes nomeadas (D18) |
| Vendas lançadas em caixa de outra data | **44** de 407.544 desde 2025 — 0,011% (D28) |
| Linhas de royalties duplicadas | **87** pares, 29 unidades, todos entre 25/11 e 14/12/2025 (**D71**) |
| Buscas que escolhem uma linha sem dizer qual | **37**, todas classificadas; **2** com consequência real: o telefone e **qual pacote é debitado** (**D72**) |
| Clientes com dois pacotes ativos do mesmo produto | **99** — 36 com valor por sessão diferente (até **R$ 45**), 84 com validade diferente (**D72**) |
| Clientes ativos com telefone repetido | **10.000** números, até **26** pessoas no mesmo (**D32**, **D72**) |
| Injeção de DQL alcançável pela URL | eram **18** de 54; **as 18 corrigidas** — 1 na busca de CEP em 31/08, as outras 17 em 01/09. Com o código antigo, duas cargas devolviam os **242.592** clientes (**D73**) |
| Dicionário de CEP | **709.440** logradouros trocados pela API do ViaCEP; as tabelas viraram cache (**D74**) |
| Ações cuja dependência de serviço o inventário escondia | eram **100** — 91 com a célula vazia; **corrigido** (**D75**) |
| O registro do `marketingEnvios` | estava morto desde **05/12/2025**; a produção mandou **217** campanhas nesse silêncio |
| Caracterização contra um banco de 36 linhas | passava **108 de 109** — agora recusa e explica (D69) |
| Teto acima delas | **ainda nenhum** — `request_terminate_timeout` indefinido, `max_execution_time` 0, 5 workers |
| Folga entre o teste mais lento e o limite do `curl` | 4,11 s contra 30 s — a falha do D66 **não é lentidão, é travamento** |
| Débitos registrados | 80 |
| Documentos de apoio | 29 — 28 documentos e 1 planilha |
| Scripts operacionais | 59 |
| Portões de conferência | **14**, num comando só — 13 verdes e 1 vermelho conhecido |
| Consultas auditadas | **102**, com 0 quebras e 0 fora da análise |
| Ordenações por coluna anulável | 7 corrigidas, e a varredura virou portão (D46) |
| Listagens paginadas com ordem definida | **36 de 36** — a última era `ORDER BY` por constante (D47) |
| Entidades com `@ORM\Entity` duplicado | eram **78 de 82**; agora **0** (D63) |
| Entidades reprovadas no `orm:validate-schema` | eram **19** com 31 queixas; agora **0** — o validador voltou a valer como portão (D64) |
| Ações inventariadas | **360**, das quais 233 só alcançáveis por rota curinga — e o inventário agora tem portão (D70) |
| Ações de funções desligadas | eram **38**; **todas decididas** — as 7 últimas ficaram fora da Fase 2 (D57) |
| Provas da carga | **5**, independentes, todas verificadas acusando |
| Provas dos papéis | **16**, todas tentando violar em vez de ler |
| Schema PostgreSQL | 85 tabelas, RLS em 75, 2 restrições de unicidade |
| Carga medida | 16,2 milhões de linhas em **511–521 s**, duas execuções — **medida antes do D74**, que tirou 758.297 linhas do dicionário de CEP |
| Mudanças em produção | remoção de arquivos expostos; **nada mais** |

---

## Fase 0 — Fundação e rede de proteção

Objetivo: poder mexer no sistema sem quebrá-lo silenciosamente. Nada aqui
moderniza nada — tudo aqui é o que torna a modernização segura.

| Frente | Situação | Entregue |
|---|---|---|
| **W1** Repositório, CI e segredos | Concluída | Git, CI em dois jobs, hook `pre-push`, 6 credenciais extraídas do código |
| **W2** Ambiente de desenvolvimento | Concluída | Docker com nginx + PHP-FPM + MySQL 8.4 + Mailpit + Adminer, espelhando produção |
| **W3** Contrato da API do aplicativo | Concluída, com 2 pendências | 35 testes congelam o formato da API móvel |
| **W4** Caracterização financeira | Concluída | 67 testes sobre venda, caixa, fidelidade, royalties, convênios e cupons — a suíte cresceu para 152 desde então |
| **W5** Auditoria de compatibilidade | Concluída | 71 consultas `GROUP BY` inventariadas; 4 de risco alto |
| **W6** Remediação em produção | **Preparada, não executada** | 2 scripts testados + 7 passos revisados; a execução é no servidor |

### As duas pendências da W3

Ambas exigem acesso ao servidor e não podem ser feitas localmente:

1. **Inventário do crontab.** Sabemos quais rotas de cron existem no código; não
   sabemos quais estão efetivamente agendadas nem em que frequência.
2. **Versões do aplicativo em uso.** Os logs do nginx dizem quais versões do app
   ainda chamam a API. Sem isso, não há como saber quais contratos podem ser
   aposentados com segurança — e, desde o **D59**, também não há como planejar a
   autenticação do aplicativo.

   **A ferramenta está pronta** (`scripts/inventariar-versoes-app.php`): leitura
   pura, aceita `.gz`, roda em produção sem cerimônia. Falta só o acesso ao log.
   Ela exige uma janela de **três meses no mínimo** e se recusa a declarar rota
   morta — só suspeita.

### O que a W6 já tem pronto

A parte mecânica virou script, testada contra uma árvore sintética:
`remediacao-dia0.sh` (audita por padrão, `--aplicar` move para quarentena datada,
e grava a apuração LGPD) e `verificar-local-php.php` (portão do deploy: confere
se toda chave lida pelo código está preenchida, classificando o que falta entre
fatal, grave e médio).

Continuam manuais, porque exigem julgamento ou credencial: preencher o
`local.php`, o deploy e a rotação das cinco credenciais.

Uma correção saiu daí: o passo 6 mandava verificar o endpoint de migração de
senhas com um `curl`. Aquela rota **sobrescreve a senha de todos os usuários
ativos**, e um GET a dispara — o comando de verificação causava o incidente que
pretendia detectar. A checagem agora lê o código implantado, sem tocar a web.

### O que a W5 confirmou por execução

Duas das quatro consultas de risco alto quebram de fato sob `ONLY_FULL_GROUP_BY`:
a geração de cupons no fechamento de venda, e o **relatório Dashboard de
Colaboradores, que devolve HTTP 500** — uma tela que o franqueado usa e que
deixaria de abrir no PostgreSQL.

As outras duas não foram alcançadas pela navegação: vivem em ações que exigem
parâmetros específicos ou executam só na submissão de filtros. Continuam
suspeitas.

---

## Fase 1 — MySQL → PostgreSQL

Primeira fase que toca produção. Plano detalhado em `fase-1-plano.md`.

| | Frente | Situação |
|---|---|---|
| F1.1 | Remediação de produção | **adiada para a homologação** (D83) |
| F1.2 | Decisões: SaaS e RPO/RTO | **concluída** |
| F1.3 | Conversão do schema | **concluída**, reconfirmada contra produção |
| F1.4 | Compatibilidade de consultas (D24) | **concluída**, 0 quebras em 102 consultas |
| F1.5 | Estado fora do disco local | **concluída**, provada no BVIP |
| F1.6 | Agendamento: os 2 eventos e o crontab | **concluída** |
| F1.7 | Alta disponibilidade com failover automático | **adiada para a homologação** (D84) — 30 a 36 s no laboratório |
| F1.8 | Migração dos dados e corte | **portão fechado**, corte real não ensaiado |
| F1.9 | Repontar o BI | **adiada para a homologação** (D85), e revelou o D48 |

O caminho crítico **F1.2 → F1.3 → F1.4 → F1.8** está percorrido inteiro. O que
resta da fase não depende mais de decisão de arquitetura: depende de acesso à
base real.

**Portão de saída da fase: FECHADO, e mais forte a cada semana.** Eram 84 testes
com 152 asserções quando a fase fechou; em 02/09 eram 126 nos dois bancos, sem
nenhum salto — o último caía porque o relatório de clientes não executava no
PostgreSQL (D81). Em 03/09 são **152 testes**, e os que entraram desde então não
são da Fase 1: cobrem autorização, hash de cliente, relógio e a API nova.

E passam com a aplicação conectando como papel comum, com o Row-Level Security
de fato em vigor. Registro em `f1-8-migracao-dados.md`.

**O que o portão não cobre**, e precisa ser dito junto: o corte real com janela,
tráfego e volta atrás não foi feito; a base é a anonimizada, que reescreve
exatamente as colunas onde moram os casos difíceis; e a cobertura, embora tenha
crescido para 126, continua sendo **o núcleo financeiro**.

Sobre relatórios e cron, a frase precisa de cuidado desde 02/09: o relatório de
clientes ganhou teste (o dos filtros de injeção, D73) e 16 caminhos da aplicação
— inclusive quatro rotinas de cron — passaram a ser executados contra o
PostgreSQL por `scripts/conferir-caminhos-postgres.php`. **Isso não é cobertura
de comportamento**: aquele portão pergunta "a consulta roda?", e não "o número
está certo?".

A sequência do corte está em `procedimento-de-corte.md`, e o roteiro para
validá-la antes, em `ensaio-geral-do-corte.md` — este já executado duas vezes no
laboratório, com veredito `ENSAIO VÁLIDO`.

**O passo 0 do procedimento é novo, e é obrigatório:** aplicar
`migracao-pontuacao-validade-unidade.sql` em produção antes de refazer o
`mysql.sql`. Ver **D54**.

---

## Fase 2 — API REST

O backend Laminas é mantido e passa a expor REST para o front novo. Não é
reescrita de backend — é fachada.

### As dezesseis fatias, em 12/09/2026

| # | fatia | recursos | o que ela foi medir |
|---:|---|---:|---|
| 1 | **PDV** (`f2-fatia-pdv.md`) | 8 | se as sete regras da convenção sobrevivem ao primeiro contato |
| 2 | **agenda** (`f2-fatia-agenda.md`) | 8 | o serviço compartilhado com o contrato congelado do aplicativo |
| 3 | **caixa** (`f2-fatia-caixa.md`) | 6 | quem calcula o saldo, e quando ele é persistido |
| 4 | **clientes** (`f2-fatia-clientes.md`) | 6 | as traduções de fronteira: data, telefone, logradouro |
| 5 | **fidelidade** (`f2-fatia-fidelidade.md`) | 7 | o dinheiro saindo — venda, caixa e autorização de gerente |
| 6 | **catálogo** (`f2-fatia-catalogo.md`) | 6 | duas telas que a ACL já tratava como uma |
| 7 | **estoque** (`f2-fatia-estoque.md`) | 3 | a movimentação que a sexta adiou, e as guardas que faltavam nela |
| 8 | **usuários** (`f2-fatia-usuarios.md`) | 2 | quem atende — e o que uma API **não** pode devolver |
| 9 | **convênios** (`f2-fatia-convenios.md`) | 6 | um interruptor virando dois endereços idempotentes |
| 10 | **unidades** (`f2-fatia-unidades.md`) | 7 | duas coisas se chamavam `estado`, e uma teve de virar `uf` |
| 11 | **vendas** (`f2-fatia-vendas.md`) | 2 | dez fatias construídas, e nenhuma sabia listar uma venda |
| 12 | **saídas** (`f2-fatia-saidas.md`) | 10 | por onde o dinheiro sai — e a única área sem um único teste |
| 13 | **devedores** (`f2-fatia-devedores.md`) | 3 | quitar dívida creditava o caixa de quem chamava, em qualquer franquia |
| 14 | **cupons** (`f2-fatia-cupons.md`) | 7 | editar cupom alcançava o desconto que o PDV da franquia vizinha aplica |
| 15 | **pacotes** (`f2-fatia-pacotes.md`) | 8 | mudar a composição alcançava qualquer franquia — 3.474 já vendidos |
| 16 | **privilégios** (`f2-fatia-privilegios.md`) | 5 | quem pode o quê — e a tela que concedia a si mesma qualquer poder |

São **98 ações** sob `/adm/api` e **626 testes de caracterização** nos dois
bancos, mais 67 de contrato. **Cinco** instrumentos os vigiam a cada push — e
dois deles **estavam escritos no CI e nunca haviam executado** até 11/09/2026:

- **`ensaiar-fluxo-adm-api.php`** — dezenove passos encadeados, do cadastro do
  cliente à abertura do caixa do dia seguinte. É o único que prova que as fatias
  **concordam entre si** sobre o mesmo dinheiro.
- **`medir-adm-api-no-postgres.php`** — chama os 44 um a um e recusa contar
  como verde a leitura que responde 200 com lista vazia.
- **`ensaiar-permissao-adm-api.php`** — entra com um usuário sem privilégio
  nenhum e exige 403 em todos. Nasceu do D123.
- **`ensaiar-recusas-adm-api.php`** — monta o estado de cada uma das **22**
  recusas de escrita da API nova e exige o código certo **pelo motivo certo**.
  Nasceu do **D170**, em 09/09/2026: os três acima cobriam o caminho feliz e o
  403, e nenhuma das guardas que separam uma venda legítima de uma que cria
  débito sem autorização tinha portão. **Nenhuma das 22 tem código próprio** —
  nove dão 422, nove dão 409, duas dão 404 e duas dão 403 —, e por isso ele
  afirma também a mensagem: sem esse critério, um estado mal montado cairia numa
  recusa anterior e o instrumento ficaria verde sem medir. Um dos 23 casos é o
  **controle**, o único que tem de passar: crédito dentro do saldo. Sem ele, uma
  guarda que recusasse tudo deixaria os três casos de crédito verdes. E um deles
  precisa de um pedido **bem-sucedido** antes do medido — o do D165, cuja recusa
  só existe depois de a API aceitar um lançamento.
  Ele também se recusa a rodar sobre cenário defasado ou sobre o caixa que o
  ensaio de fluxo deixa para trás — os dois portões nasceram de vermelhos que
  apontavam para o lugar errado.

**O padrão que se repetiu nas seis:** o custo não está nos endpoints. Escrever
os recursos é a parte curta; o que consome tempo é o que aparece ao redor —
defeitos que só um código de status podia expor, getters que formatam para a
tela, guardas que pareciam guardar. As fatias 4, 5 e 6 acharam um defeito de
produção cada uma, e a 5 achou o D123 antes de escrever a primeira linha.

### A primeira fatia, e o que ela mudou no plano

**Feita em 03/09/2026: o PDV**, o fluxo "comanda aberta até venda finalizada".
As nove ações têm recurso, em oito endpoints sob `/adm/api`. O relato completo
está em `docs/f2-fatia-pdv.md`; aqui fica o que muda o **planejamento**.

**As sete regras da convenção sobreviveram ao primeiro contato.** Nenhuma caiu;
três ganharam ressalva que discussão nenhuma teria produzido:

| Regra | O que a construção acrescentou |
|---|---|
| 2 — código HTTP diz o que aconteceu | **200 quando a criação reaproveita.** `comandaAbrir` devolve a comanda que já existia; 201 ali mentiria |
| 3 — corpo de erro com forma fixa | só se sustenta **fora da camada de visão** — com `JsonModel`, o Laminas funde o corpo do erro com o da página-não-encontrada |
| 7 — dinheiro em decimal, como texto | precisa de **tradução na fronteira**: o decimal do protocolo, entregue a um serviço da casa, multiplica o valor por cem |

**O custo real de uma fatia não está nos endpoints.** Escrever os oito foi a
parte curta. O que consumiu tempo foi o que apareceu ao redor: dois defeitos da
aplicação que só o primeiro endpoint com código de status podia expor, e três
correções no próprio inventário que dimensiona esta fase.

*Isso é sinal, e não acidente.* A primeira fatia paga a construção da estrada;
as seguintes andam sobre ela. `ApiAdmController`, `ComandaJson` e o padrão de
rota com verbo declarado já existem — a próxima fatia começa deles.

**Um critério novo para escolher a próxima**, que esta descobriu e vale além
dela: *uma superfície sem parque instalado pode nascer com as regras que a
superfície velha só consegue adotar depois de uma janela de implantação.* Foi
assim que o segundo tempo do **D86** — recusar venda em fiado sem autorização
registrada — passou a existir na API nova enquanto a tela antiga continua
esperando o cache virar. Vale procurar outros casos assim.

**O tamanho está medido**, em `inventario-acoes.md`, gerado por
`scripts/inventariar-acoes.php`:

| | Ações | |
|---|---:|---|
| Já devolvem JSON | **123** | a Fase 2 aqui é ajuste de forma, não criação |
| Devolvem tela ou redirect | **158** | é aqui que há endpoint a criar |
| Entregam arquivo | 16 | pdf/xls/txt — padrão binário, à parte |
| Contrato do aplicativo | **31** | não se mexe (D5) — e agora são só as que a suíte de fato cobre |
| Rotina de cron | **29** | disparadas por URL pelo crontab; o aplicativo não as chama |
| Morta | 3 | devolve 404 — some, não se reconstrói |

As seis linhas somam **360**.

**O 31 já foi 60, e a correção é de 31/08/2026.** O gerador atribuía
`CONTRATO-APP` a **toda** ação do módulo `ISApi`, em bloco — e o
`CronJobController` mora ali. Vinte e nove rotinas de cron estavam rotuladas
como contrato do aplicativo protegido por testes, e o rótulo dizia duas coisas
falsas ao mesmo tempo: que o aplicativo instalado depende delas, e que há teste
segurando. **A suíte de contrato exercita 25 caminhos distintos e nenhum é rota
de cron** — conferido antes de mexer.

O estrago não era de rótulo: **este número dimensiona a fase**, e ele dizia que
metade da superfície "não se mexe" quando não era o caso. E quase custou o
**D43**, que só foi removido porque fui conferir se o rótulo tinha lastro.

**O 60 já tinha sido 55**, e aquela diferença eram as cinco ações versionadas que
o gerador não enxergava — justamente as do contrato do aplicativo, que é onde
errar custa mais.

**O total já foi 362**, e caiu para 360 em 31/08/2026, por dois motivos
diferentes:

- saiu o `yungasNpsAtualizar` (**D43**, removido por decisão);
- e saiu uma **ação que nunca existiu**: `ISCadastro\VisagismoController::processarImagem`
  está inteiramente comentada, e o gerador a contava porque procurava
  `function ...Action(` com expressão regular sobre o texto. Comentário é texto.

A `Convenios::atualizarVencimento` também mudou de módulo para o
`CronJobController` durante o trabalho do **D50** — o inventário estava velho e
ninguém teria notado, o que virou parte do **D70**.

A fase é **menor do que parecia**: um terço da superfície já fala JSON. E 189
das 360 ações mutam — essa é a fatia cara, porque endpoint de leitura custa uma
fração de endpoint de escrita.

**O 189 é teto, não contagem** — e isso ficou claro em 02/09/2026, ao encontrar
o primeiro contraexemplo. O gerador decide `muta` por dois sinais, e declara o
segundo como aproximação: *"uma ação que recebe POST quase sempre muta, ainda
que pela mão de outro"* (`scripts/inventariar-acoes.php:303`).
`VendasPagamentos::autorizarVendaFiado` recebia POST e **não gravava nada** —
só respondia se um gerente confere. *(Ela passou a gravar no mesmo dia, ao
fechar o **D86**, e por isso já não serve de exemplo; o que ela ensinou,
sim.)* Para dimensionar a fase o teto serve; para planejar uma fatia, não: quem
for construir precisa abrir a ação, porque o rótulo pode estar prometendo
escrita que não existe.

Os 35 testes de contrato protegem o parque instalado: o aplicativo móvel na mão
dos clientes não pode ser atualizado junto, e há versões antigas ativas (D5).
**Essa proteção era aparente em dez endpoints até 28/08/2026** — os snapshots
congelavam o invólucro, não a carga (D55). Corrigido antes de a Fase 2 começar a
mexer em forma de resposta, que é exatamente quando teria custado caro.

**Gate de saída:** contratos do app inalterados, com a suíte verde.

### Um requisito que vem do legado, decidido em 31/08

**Toda rotina agendada deixa rastro do que fez** — não só que rodou, mas o quê e
quantos.

Não é preferência de estilo: no legado, 12 das 27 rotinas não registram nada, e
uma delas ficou **oito meses** mandando campanhas em silêncio porque a linha de
log estava depois de um `return` (**D68**). Ninguém percebeu, porque uma rotina
que para não dá erro — ela simplesmente deixa de acontecer, e o sintoma aparece
longe.

Por decisão sua, as doze **não** foram instrumentadas no legado: ele será
substituído, e a produção congelada não receberia a mudança de qualquer forma.
O requisito atravessa para cá em vez de virar trabalho que morre junto com o
código que o recebeu.

---

## Fase 3 — React

Reconstrução das telas, módulo a módulo, sobre a API da Fase 2. É a fase mais
longa em volume, e a mais previsível em risco — o comportamento já está
caracterizado e a API já está estável.

Ordem sugerida, por dependência operacional: PDV e caixa primeiro (o que para a
operação se falhar), depois agenda, depois cadastros, relatórios por último.

**As Fases 2 e 3 devem andar juntas, módulo a módulo, nessa mesma ordem** — não
como duas fases horizontais. Uma API construída inteira antes das telas espelha
os controllers de hoje, com os acidentes deles; construída por fatia, ela
responde ao que a tela precisa. O preço da fatia vertical é a consistência da
API, e o antídoto é barato: uma convenção escrita antes da primeira fatia —
**escrita em 29/08/2026, em `convencao-api.md`**, com as três decisões de fundo
já tomadas: prefixo `/adm/api`, recursos **em português sem exceção**, e
autenticação por **token**, com carregadores distintos por cliente: cookie
`httpOnly` no ERP, onde a ameaça é XSS, e `Authorization: Bearer` no aplicativo,
que não tem DOM e tem Keychain. O mecanismo fica deliberadamente para depois.

Desenhar isso expôs o **D59**: a API do aplicativo **não autentica ninguém**, e a
porta de entrada é mais larga do que parecia. `GET
/api/agendas/getCliente?telefone=...` devolve, sem cabeçalho nenhum, **o CPF e o
RG** da pessoa — mais o `id`, que destrava agenda, cupons, sorteios e uma rota
que expõe nome e telefone de terceiros indicados. É o desenho original, não uma regressão, e
corrigi-lo passa por endpoint novo em versão nova. O pré-requisito é a pendência
da W3: saber quais versões do aplicativo estão em uso, que sai dos logs do nginx.

Inverter a ordem — React antes da API — foi considerado e descartado: não há API
do admin para o React chamar, então inverter significa construir contra
simulações, e uma simulação não pode contradizer quem a escreveu. O motivo usual
para inverter também não se aplica aqui, e isso foi medido: **nenhuma view toca o
banco e nenhuma faz conta com dinheiro**. Não há regra de negócio escondida na
apresentação para a tela revelar.

### Começada em 09/09/2026, com a Fase 2 ainda aberta

Decisão do dono: *"vamos iniciar a fase 3 e ir descobrindo o que há de furo e
vamos e voltamos com as fases 2 e 3 de forma paralela"*. É a mesma tese do
parágrafo acima — fatia vertical —, levada até o fim: **a tela como instrumento
de medida da API**, e não como consumidora de uma API dada por pronta.

O andaime: **Vite + React + TypeScript** em `frontend/`, no mesmo repositório,
com proxy de mesma origem para `http://127.0.0.1:8080`. **Sem camada de token**
— o front usa a sessão do ERP, porque o cookie viaja quando a origem é a mesma.
Isso foi provado ponta a ponta antes de escrever tela: 403 sem cookie, 200 com.
No dia em que o front sair para origem própria, a troca é em dois arquivos, e os
dois dizem isso no cabeçalho (`vite.config.ts` e `src/api/cliente.ts`).

**O que a tela cobrou da Fase 2 em três dias**, e nenhum dos quatro apareceria
lendo o servidor de novo:

| # | O que a tela achou | Onde ficou |
|---|---|---|
| 1 | `listarComandas` prometia um filtro `estado` que o servidor ignora em silêncio — e o erro era **meu**, não da API | corrigido no nome da função |
| 2 | O contrato de `adicionarItem` não dizia que lançar item **apaga os pagamentos** | frase acrescentada ao servidor |
| 3 | **O troco sobrevivia aos pagamentos apagados** e virava crédito ou gorjeta sem lastro — 5 vendas e R$ 117,10 em produção | corrigido, com regressão nos dois bancos |
| 4 | **Não havia catálogo de formas de pagamento** em `/adm/api` — a tela teria de cravar ids que mudam de banco para banco | endpoint novo |
| 5 | **Os catálogos de produto e de colaborador eram invisíveis a quem opera o caixa** — 60 de 60 do caixa e 4 de 4 líderes levavam 403 | **D168**, guardas alargadas |
| 6 | Três defeitos meus na tela — filtro obrigatório omitido, listas cortadas no item 20, erros engolidos — que só apareceram **abrindo a página no navegador** | corrigidos |
| 7 | `pagarDebitos` **perdoava dívida sem cobrar** — 1.141 débitos abertos, R$ 67.011,85 | **D159**, recusa 422 na API nova |
| 8 | A baixa de estoque varria para dentro dele **serviço e pacote** — 119 dos 570 negativos eram conta que nunca deveria existir | **D161**, regra com nome nos dois sítios |

O relato está em `docs/f2-fatia-pdv.md`, nas seções 9 a 12, e nos débitos D159 a
D170.

**Os dois últimos mudaram a régua da Fase 3.** Nenhum apareceu usando a tela:
apareceram lendo os contratos de escrita **para poder** construí-la. É o mesmo
mecanismo da fatia vertical, um degrau acima — não é só a tela que mede a API, é
a obrigação de explicar cada campo ao operador que força a ler o servidor com uma
atenção que nenhuma revisão de código produziu em dezesseis fatias.

**O terceiro é o que justifica a decisão sozinho.** Ele estava no código desde
sempre, atravessou dezesseis fatias de API e 408 testes de caracterização sem
ser visto, e apareceu na primeira vez que alguém tentou *usar* a sequência
"pagar, depois lançar mais um item" — que é o que um operador de balcão faz
quando o cliente pede a barba depois de pagar o corte.

*A tela não achou o defeito porque é React. Achou porque é uso.*

### O desenho da interface, decidido em 13/09/2026

Decisão do dono: *"mantenha a grade por barbeiro e comece pela comanda"*. A
grade da agenda — uma coluna por colaborador — fica, porque quem olha a agenda
no balcão não pergunta *"o que vem agora"*, pergunta *"quem está livre agora"*,
e essa segunda pergunta se responde varrendo uma linha com o olho. Consequência
para todo o resto: **a largura é recurso escasso**, e nenhuma decisão global
pode gastá-la.

O registro está em `docs/f3-desenho-da-interface.md`: o ouro `#D9B437`
amostrado do logo, a regra que separa acento de semântica, e a medição que
motivou a troca dos cinco literais de cor — `#1a7f4b` e `#b4232a` passavam no
tema claro e **reprovavam no escuro** (3,78:1 e 2,91:1), e o pior dos dois era
a cor do texto que avisa que a ação apaga pagamento.

A comanda foi a primeira tela. Ela também ganhou **paginação**: a lista dizia
*"1 de 11"* e não tinha como ver as outras dez — o vão passava batido porque a
tela não mentia, ela parava.

### A ordem das telas que faltam, decidida em 27/09/2026

Até aqui só valia a regra geral do começo desta seção — PDV e caixa, depois
agenda, depois cadastros, relatórios por último —, e as três primeiras etapas
estão feitas: PDV, caixa, agenda, clientes, devedores, vendas realizadas e o
catálogo em leitura. **Dentro de "cadastros" não havia ordem decidida**; a
escolha era minha e não estava escrita. Decisão do dono, nesta ordem:

| # | frente | observação |
|---:|---|---|
| 1 | **terminar o catálogo** | edição, cadastro, ativar/desativar e estoque |
| 2 | **pacotes, convênios e cupons** | |
| 3 | **fidelidade** | |
| 4 | **usuários e colaboradores** | comissão, agenda do colaborador, permissões, unidades, privilégios |
| 5 | **marketing e aplicativo** | banners, mensagens, pesquisas NPS, sorteios, arquivos — **sem** VIP Class e **sem** assistentes de IA |
| 6 | **relatórios** | **todos serão remodelados, uns entram e outros saem — perguntar ao dono antes de começar** |
| 7 | **contas a pagar** | `SaidasController` |
| 8 | **VIP Class** | |
| 9 | **assistentes de IA** | |

Continuam fora da migração, por decisões anteriores: visagismo, OneSignal e SMS
(débitos futuros). O WhatsApp ainda está por confirmar.

**O item 6 é uma parada, e não uma etapa.** Os 21 relatórios do inventário de
ações não são a lista a reconstruir: o desenho novo é do dono, e nada ali se
escreve antes dele.

---

## Fase 4 — Corte

Operação em paralelo e corte **por unidade**, não em bloco. Com 32 unidades, dá
para começar por uma, observar um ciclo de fechamento de caixa completo, e só
então avançar.

---

## Frentes paralelas

As três levantadas em 14/08/2026. Nenhuma é fase própria; cada uma se encaixa
num ponto específico e fica mais cara se for deixada para depois.

| Frente | Onde entra | Por que não depois |
|---|---|---|
| **Integração do BI** | Fase 1 | O BI lê o MySQL direto; se o banco migrar sem ele, quebra no corte |
| **Multi-idioma** | Fase 2–3 | Extrair strings depois de escrever as telas custa várias vezes mais |
| **SaaS multi-inquilino** | Fase 1 (schema e RLS), Fases 2–3 (produto) | A coluna e o particionamento saem de graça durante a migração; depois seriam `ALTER TABLE` em centenas de milhões de linhas |

O SaaS é o único dos três que muda o desenho do banco. A decisão precisa ser
tomada **antes** da conversão de schema, mesmo que a implementação venha depois.

---

## Ambientes

Decidido em 17/08/2026. Detalhe em `ambiente-homologacao.md`.

| Servidor | Papel |
|---|---|
| Hostinger `linkwise` 2/8 | Réplica e testes pessoais — MySQL 8.4, não serve para homologar |
| **BVIP** 24/64 bare metal | **Homologação + laboratório de migração** — o MySQL do host é 8.0.46; a homologação sobe 8.4 em contêiner, como a produção |
| Orange Pi 8/8 ARM64 | Nó de redundância — MySQL 8.0.45 |
| Hostinger 4/16 | Produção |
| HostGator 4/16 | E-mail da empresa — não é VPS ociosa |

---

## Próximos passos

Os cinco anteriores foram feitos. Estes são os de agora, em ordem de quanto
destravam.

**ATUALIZADO EM 11/09/2026, e a mudança é de forma, não de conteúdo.** Por decisão
sua e em caráter permanente, **nenhuma modificação no servidor de produção até a
migração final, depois de todas as validações**. Os passos 0, 1 e 3 da tabela
abaixo não são mais pedidos de janela independentes: os três passaram a ser
**partes do procedimento de corte**, e é nessa ordem que eles continuam valendo.

O que isso tira e o que isso dá:

- **Tira** a possibilidade de antecipar os `ALTER` aditivos do D54 e do D86, e
  portanto de refazer o `mysql.sql` a partir da estrutura real antes do corte. O
  passo 2 — o ensaio geral que produz o **número da janela** — continua sem data,
  e é o que mais custa: hoje há repetibilidade, não dimensionamento.
- **Dá** um procedimento único em vez de cinco pedidos de janela em cinco momentos
  diferentes. Procedimento único se escreve, se revisa e **se ensaia**; cinco
  pedidos avulsos nunca foram ensaiados juntos. O `dia0-remediacao-producao.md`
  já carrega a decisão e marca cada passo com o que vale enquanto ela durar.
- **Não muda nada** na Fase 2 nem na rede de proteção, que é onde o trabalho está
  e onde ele continua podendo avançar sem decisão nenhuma.

**Uma mudança de 03/09: a Fase 2 deixou de depender do descongelamento.** Até a
primeira fatia, todos os passos abaixo esperavam produção. A fatia do PDV mostrou
que a API nova pode ser construída e provada sem tocar em produção — ela não tem
consumidor, e a sessão atual do ERP basta enquanto o token não existe. **Escolher
a próxima fatia é o único item desta lista que não depende de decisão sua nem de
janela de produção** — e em 05/09/2026 ela foi escolhida e **feita**: a
**agenda**, com o porquê, o mapa e o diário em `f2-fatia-agenda.md`.

Os oito recursos estão de pé — duas leituras e seis escritas —, com teste nos
dois bancos e chamada real a cada push. O que a fatia foi medir era o que o PDV
não pôde: **o serviço é compartilhado com o contrato congelado do aplicativo**.
Das três saídas previstas — estender, compor por fora, duplicar —, a fatia usou
duas, e **nenhuma linha de `AgendaService` foi tocada**.

O mapa acertou os oito recursos e errou **dois dos oito verbos**, pela mesma
causa nas duas vezes: foi desenhado por analogia com REST, e o verbo depende do
que o código faz. `desmarcar` apaga a linha, então é `DELETE`; `remarcar` cria
outra e apaga esta, então não é `PUT`.

E três defeitos saíram de **ler o serviço**, não de instrumento — o medidor via
200 nos três: booleanos tratados como instante, um estado inventado copiado do
PDV, e os bloqueios de agenda fechada saindo como agendamentos de cliente.

| # | O quê | Depende de | Destrava |
|---|---|---|---|
| 0 | **Aplicar o `ALTER` do D54** — agora **dentro da janela do corte** | a janela (11/09/2026) | É pré-requisito do passo 1: sem ele o dump refeito perde a coluna e leva o schema PostgreSQL junto |
| 0 | **Aplicar o `ALTER` do D86** — idem | a janela (11/09/2026) | Mesmo pré-requisito, mesma consequência: `vendas.autorizado_por` e `vendas.autorizado_em`, que registram quem autorizou a venda em fiado |
| 1 | **Atualizar `mysql.sql` com a estrutura de produção** | a janela (11/09/2026) | Os geradores leem esse arquivo; velho, o schema gerado não é o de produção. Era **só leitura** e mesmo assim entrou na janela, por decisão |
| 2 | **Ensaio geral contra cópia da produção**, com limite de contêiner | passo 1 | O **número da janela** — hoje só há repetibilidade, não dimensionamento. **É o item mais caro de adiar**, porque é o único que diria quanto tempo o corte leva |
| 3 | **A remediação da F1.1** — rota `migrar-senhas` e as oito credenciais | a janela (11/09/2026) | Deixou de ser "descongelar": virou item do procedimento de corte |
| 4 | **Decidir o desenho do corte**: nó único ou com HA | decisão sua | Os cinco itens que faltam à F1.7 custam tempo e dinheiro |

**ERAM SEIS.** O passo "inventariar as consultas do Power BI e repontá-lo" saiu
em 09/09/2026: o Power BI foi removido por decisão do dono (**D9**). Era o único
que dependia de terceiro — a Microsoft —, e o caminho crítico encurtou por
subtração, não por trabalho.

O passo 0 é aditivo, e os dois `ALTER` dele **não tocam o mesmo volume**: o do
D54 acrescenta uma coluna a `unidades`, que tem 60 linhas na cópia anonimizada, e
o do D86 acrescenta duas a `vendas`, que tem 2,27 milhões. Precisa vir **antes**
do passo 1, e essa ordem não tem alternativa (D54).

Os passos 1 e 2 são o caminho crítico e são a mesma coisa vista de dois ângulos:
**o ensaio local prova repetibilidade, não janela.** As duas execuções variaram
2% no total — mas rodaram sem limite de contêiner, numa máquina de 20 núcleos,
com origem e destino no mesmo host. Produção tem 4 vCPU.

Nenhum dos quatro depende de terceiro. **Até 09/09/2026 um dependia**: repontar
o Power BI exigia trocar o conector, instalar o provedor Npgsql no gateway e
republicar, tudo do lado da Microsoft. Saiu com o Power BI (**D9**).

O passo 4 é uma decisão de custo, não de engenharia: cortar para um PostgreSQL
de nó único é legítimo, desde que consciente.

### E há uma segunda fila, que não espera janela nenhuma — 15/09/2026

Os cinco passos acima são todos da Fase 1, e **todos esperam a janela do corte**.
Enquanto ela não chega, o trabalho que anda é o da Fase 3, e ele destravou hoje:
até esta manhã **ninguém conseguia entrar no ERP** no banco de desenvolvimento, e
sem entrar não há como exercitar a tela contra dado real.

Medido hoje, o que existe e o que não existe no `frontend/`:

| | estado |
|---|---|
| andaime | React 19.2 + Vite 8.2 + TypeScript 6.0; `tsc --noEmit` e `oxlint` saem com **0** |
| código | **10** arquivos `.ts`/`.tsx` em `src/` (3.120 linhas) + 1.300 de CSS |
| telas montadas no app | **1** — `App.tsx` monta só `Comandas` |
| componentes prontos e não montados | `Agenda.tsx` (6) e `Moldura.tsx` (4) chegam ao mundo só pelo `src/index.ts`, que serve ao Claude Design |
| publicado no Claude Design | **17** componentes, em três rodadas: 13/09 o PDV, 14/09 a agenda, 15/09 a moldura |
| a fiação com a API | **funciona** — medido: `/adm/api/comandas` atravessa o proxy do Vite e chega ao ERP com o 403 JSON da convenção |
| rotas `/adm/api` consumidas | **4** de 17 declaradas (`comandas`, `produtos`, `usuarios`, `formas-pagamento`) |
| arranjo de teste de componente | **não existe** — zero arquivos, zero dependências, zero scripts, zero CI |

A ordem de dependência, com o que cada item já tem e o que lhe falta:

1. ~~**Uma sessão do ERP no navegador.**~~ **FEITO em 15/09/2026 — e a
   credencial já existia.** Este item chegou a ser publicado como bloqueio
   dependente de decisão, sob o argumento de que
   `scripts/definir-senha-contrato.php` recusa banco com mais de 10 unidades.
   **O argumento estava olhando o roteiro errado:** aquele script serve ao banco
   de *contrato*, e a cópia de desenvolvimento nunca dependeu dele.

   `docs/ambiente-desenvolvimento.md` já documentava a credencial, e o
   `scripts/anonimizar.sql.modelo` a escreve em toda carga. Medido na cópia:
   **2.427 usuários, todos com login `user<id>`, todos em bcrypt, um único hash
   distinto**, e `salt`/`senha_php83` anulados — a coluna reversível do D78 não
   existe aqui.

   ```
   login: user1        senha: dev123456
   ```

   `user1` é do grupo **Gerente geral**, que é `administrador = 1` — abre o menu
   inteiro. Provado por HTTP, sem digitar em formulário: o POST devolve
   `{"sucesso":true}`, e a home passa de ~900 bytes (tela de login) para
   **97.175**. Com um colaborador (`user200`) a mesma home dá **77.305** — o menu
   encolhendo por privilégio, medido.

   **Uma armadilha que custa uma tarde se não for dita:** o ERP emite os cookies
   sem argumento de domínio (`SessaoAcl.php:391-392`), então eles são *host-only*,
   e `localhost` ≠ `127.0.0.1`. Como o Vite serve a tela React em `localhost`,
   **o login tem de ser feito em `http://localhost:8080`** para que a sessão
   atravesse o proxy de mesma origem.

2. ~~**Massa de comanda.**~~ **FEITA em 15/09/2026.** Eram **2** comandas
   abertas, as duas de fixture. Agora são **32** — 30 abertas **pela própria
   API** (`POST /adm/api/comandas`), e não por SQL à mão, para que nasçam como o
   sistema as cria.

   A forma da massa, medida: 7 comandas sem item, 6 com um, 13 com dois, 6 com
   três; totais de **R$ 0,00 a R$ 630,00**, em 17 valores distintos. A lista da
   unidade 1 devolve `total=30, porPagina=20`, e a página 2 traz 10 — **a
   paginação passou a ter o que paginar**, que era exatamente o que faltava.

   **A massa é descartável por construção:** as linhas nascem com id ≥ 990099223
   por causa do `AUTO_INCREMENT` envenenado (D167), e `fixtures.sql:160` apaga
   `vendas WHERE id >= 90001000`. A próxima carga das fixtures a remove sozinha.
   Nenhum pagamento foi recebido e nada foi finalizado — caixa, estoque, crédito
   e fiado continuam intocados.
3. **O arranjo de teste de componente.** Falta escolher o corredor, acrescentar
   as dependências, criar a configuração reaproveitando o `vite.config.ts` e o
   script `test`. A superfície já está aberta: os componentes são exportados por
   causa do design-sync.
4. **Um dublê para a API nos testes.** A fronteira é estreita — tudo passa por
   `pedir` em `src/api/cliente.ts` —, o que torna o dublê barato. Depende do 3.
5. **Falsificar a camada de API contra o servidor.** Os tipos são espelho
   **escrito à mão** dos `*Json.php`, e **nada confere um contra o outro**: não há
   OpenAPI e não há geração. Depende do 1.
6. **Roteador e montagem das outras telas.** Nenhum roteador instalado — as
   dependências de runtime são exatamente `react` e `react-dom`.

**O item 6 tem uma ressalva do próprio dono:** o cabeçalho de `Agenda.tsx`
registra que a decisão da grade por barbeiro está **reaberta**, e que o módulo
"será severamente alterado". Montar a agenda no app antes dessa decisão é
construir sobre forma que já se avisou que vai mudar.

---

## Quanto falta até o fim da Fase 3

Estimativa de baixo para cima, a partir do volume medido do sistema: **63
controllers, 360 ações, 172 views, 98 rotas, 82 entidades, 38 serviços**.

*(Eram 362 quando esta conta foi feita. Saíram duas: o `yungasNpsAtualizar`,
removido por decisão, e uma ação que **nunca existiu** — um método inteiramente
comentado que o gerador contava por ler texto. Ver D43 e D70.)*

| Fase | Dias-pessoa | O que domina |
|---|---:|---|
| **Fase 1** — resto | ~100 | Alta disponibilidade (~25) e corte com ensaios (~20) |
| **Fase 2** — API REST | ~120 | ~60 recursos sobre as 98 rotas |
| **Fase 3** — React | ~240 | ~130 telas reais, das quais ~10 complexas (PDV, agenda, caixa) |
| | **~460** | |

**A primeira fatia não recalibra estes números, e vale dizer por quê.** Ela
entregou oito endpoints do fluxo mais complexo do sistema, o que tentaria sugerir
que os ~120 dias da Fase 2 estão altos. Duas razões para não mexer ainda:

1. **Boa parte do custo foi de estrada, e não de fatia.** `ApiAdmController`,
   `ComandaJson`, o padrão de rota com verbo e a tradução de dinheiro na
   fronteira são de uso geral. A próxima fatia não os paga; a primeira pagou por
   todas.
2. **E boa parte foi de achados, não de código.** Dois defeitos da aplicação e
   três correções no inventário. Isso deve diminuir — mas *quanto* diminui é o
   que a segunda fatia mede, e uma amostra de uma não mede nada.

**A segunda fatia é o número que interessa.** Se ela custar uma fração desta, a
estimativa da Fase 2 desce com evidência; se custar o mesmo, o que a primeira
mostrou foi o piso, e não a estrada.

### A revisão, e ela vem em DUAS UNIDADES que não se somam

*Medido em 12/09/2026, depois de dezesseis fatias.* A tabela de agosto acima fica
como está — é a conta de baixo para cima, e apagá-la esconderia de onde os números
saíram. Esta é a revisão, e ela traz **duas colunas de esforço porque há duas
unidades em jogo**:

| Fase | Dias-pessoa (ago) | Dias-pessoa (revisto) | Horas medidas de trabalho assistido | O que mudou |
|---|---:|---:|---:|---|
| **Fase 1** — resto | ~100 | **~30** | **não se aplica** | Schema, migração e ensaios prontos; o passo do BI saiu. O que resta é **operação numa janela**, não código |
| **Fase 2** — API REST | ~120 | **~80** | **~150 h** (120–200) | Dezesseis fatias entregues. A taxa observada já inclui o que cada uma encontra ao redor |
| **Fase 3** — React | ~240 | **~240** | **amostra de 1** | **Uma tela desde 09/09** — a comanda, em 7 commits. A janela 09–13/09 custou **19,6 h**, mas a Fase 3 foi uma frente entre várias nela (7 dos 57 commits), então o custo dela está entre 2,4 h e 19,6 h. Amostra de uma não dá ritmo, e ~240 fica de pé |
| | **~460** | **~350** | — | |

**AS DUAS COLUNAS NÃO SE SOMAM, E NÃO SE COMPARAM.** Dias-pessoa é estimativa de
equipe humana, feita de baixo para cima a partir do volume do sistema. Horas
medidas é o tempo de trabalho assistido **observado no próprio repositório**. As
duas podem estar certas ao mesmo tempo sobre o mesmo trabalho; somá-las, ou dividir
uma pela outra para tirar um "fator", seria inventar um número que ninguém mediu.

#### Como as horas foram medidas, para poderem ser contestadas

Heurística declarada, não cronômetro: somam-se os intervalos entre commits
consecutivos **limitados a 60 minutos** — acima disso conta-se como nova sessão,
com 20 minutos de arranque. O que sai:

| | |
|---|---:|
| projeto inteiro | 497 commits, **~159 h**, 27 dias de calendário |
| janela em que a Fase 2 foi a frente principal (03–09/09) | 197 commits, **~69 h** |
| **por recurso REST entregue** | **~4,3 h** |

**E 4,3 h é taxa MISTURADA, de propósito.** Apenas 22% dos commits daquela janela
tocaram arquivos da API; o resto foi o que esta página já chamava de "o que aparece
ao redor" — os defeitos que cada fatia achou, os testes, os portões. A taxa de só
escrever endpoint seria ~0,9 h por recurso, e mentiria sobre o custo real.

Os 120–200 h vêm de **42 a 46 recursos restantes × 4,3 h**, com dois ajustes em
direções opostas que se anulam em parte: para baixo, a estrada está paga e os
controllers que faltam são menores; para cima, as escritas **deliberadamente
diferidas** dentro dos domínios já abertos não entram nessa conta, e as quatro
últimas fatias acharam um defeito de produção cada uma.

#### Onde a Fase 2 está, e por que há dois percentuais

| medida | entregue | total | % |
|---|---:|---:|---:|
| **recursos REST** — a unidade do plano de agosto | 18 | ~60 | **30%** |
| controllers do ERP com fatia | 18 | 57 | 32% |
| ações em escopo em domínio **aberto** | 153 | 295 | 52% |
| telas em domínio **aberto** | 62 | 122 | 51% |

As duas primeiras e as duas últimas discordam porque **as fatias pegaram os maiores
controllers primeiro** — 8,5 ações cada, contra 4,8 nos que faltam. Por volume já se
passou da metade do que foi aberto; por contagem estamos em um terço.

**E "aberto" não é "pronto", o que está medido e não suposto:** o diário da fatia de
usuários registra que `UsuariosController` tem **vinte ações e duas entraram** — tudo
que escreve ficou de fora, com motivo por grupo. O mesmo padrão em `Clientes` (28
ações no ERP, 6 na API) e `Agendas` (20 contra 3). Por isso **o número honesto é
cerca de um terço**, e os 52% são teto.

#### O que falta, pelo inventário

Das 359 ações, **295 estão em escopo** — 31 são contrato do aplicativo, 29 são
rotinas de cron, 3 mortas e 1 a conferir. Tirando o visagismo, que por sua decisão
não se migra:

| o que falta | ações | controllers |
|---|---:|---:|
| núcleo do ERP, domínio nunca tocado | 110 | 23 |
| relatórios (`ISRelatorio`) | 23 | 13 |
| escritas **diferidas** nos 18 domínios já abertos | não mensurável por máquina | 18 |
| visagismo — fora por decisão sua | 6 | 2 |
| WhatsApp — **a confirmar** se entra no "ignorar onesignal, visagismo e sms" | 3 | 1 |

Traduzindo para calendário, **com duas pessoas**:

| Ritmo | Dias-pessoa/semana | Até o fim da Fase 3 |
|---|---:|---|
| Sustentável — 8 h, 5 dias | 10 | **~46 semanas** (11 meses) |
| Intenso — 12 h, 6 dias | 18 | **~26 semanas** (6 meses) |
| 18 h, 7 dias | 31,5 nominal | ~15 semanas no papel; **~22 na prática** |

Sobre a última linha, com franqueza: 18 h por dia sustentadas não entregam 18 h
de trabalho. Depois das primeiras semanas o rendimento cai e a **taxa de defeito
sobe** — e neste sistema defeito significa dinheiro errado, com retrabalho que
custa mais que o tempo economizado. A linha existe porque foi o cenário
perguntado, não porque eu a recomende.

**A tabela acima é da unidade dias-pessoa.** Na unidade medida, e só para a Fase 2
— a única com ritmo observado —, a leitura de calendário é outra: **120 a 200 h** na
intensidade medida no repositório, que foi de **9,9 h ativas por dia** na janela da
Fase 2 e **6,1 h/dia** na média do projeto. Dá **15 a 25 dias de calendário** para
fechar a Fase 2. As duas leituras respondem perguntas diferentes: a de cima, "quanto
custa se uma equipe fizer"; esta, "quanto leva no ritmo que está acontecendo".

**Margem de erro: ±40%**, concentrada na Fase 3. As Fases 1 e 2 são bem
delimitadas; a Fase 3 depende de quanto do layout muda junto, que ainda não foi
decidido.

Três coisas que já reduziram esse prazo, e que não aparecem como progresso:

- Os **626 testes de caracterização** eliminam a fase de "descobrir o que o
  sistema faz" em toda tela reconstruída. *(Eram 84 quando este parágrafo foi
  escrito e 152 quando ele foi revisado; o número cresce porque cada defeito
  corrigido deixa um teste.)*
- Os **67 testes de contrato** dispensam mexer no aplicativo móvel, e desde
  28/08/2026 congelam a carga e não só o invólucro (D55) — e desde 11/09/2026 as
  **seis escritas** do aplicativo têm ensaio que de fato executa (D61).
- O **inventário das 360 ações** trocou estimativa por contagem: a Fase 2 é
  menor do que parecia, porque 123 ações já devolvem JSON.
- E a **primeira fatia já construiu a estrada**: `ApiAdmController`,
  `ComandaJson` e o padrão de rota com verbo declarado são de uso geral. A
  próxima fatia não paga esse custo de novo.
- A escolha de **manter o backend Laminas** na Fase 2 evita reescrever regra de
  negócio — o que dobraria a Fase 2 e traria de volta o risco que a
  caracterização acabou de eliminar.

---

## O que trava o avanço hoje

**Desde 11/09/2026 o congelamento não é mais um estado à espera de decisão: é a
decisão.** Por determinação sua, em caráter permanente, **nada se modifica no
servidor de produção até a migração final, depois de todas as validações**. A
pergunta "quando descongelar" saiu da mesa; a que ficou é "o que precisa estar
validado antes da janela", e essa tem resposta técnica e não depende de você.

O efeito prático é que **a Fase 1 não tem mais nenhum passo executável hoje** — e
isso é mais honesto do que a lista anterior, em que cinco itens apareciam como
pendências vivas quando nenhum deles ia acontecer antes do corte.

**O congelamento de produção continua sendo o único bloqueio real *para a Fase
1*.** O trabalho técnico dela acabou, e o que se fez desde então — inventários,
correções de operação, a convenção da API, o analisador de logs — é fundação da
Fase 2, não avanço da Fase 1.

**Mas ele deixou de travar a Fase 2, e isso mudou em 03/09.** A primeira fatia foi
construída e provada inteira sem tocar em produção: a API nova não tem consumidor,
e a sessão atual do ERP basta enquanto o token não existe. **Há trabalho de valor
disponível hoje que não espera decisão nenhuma** — escolher e construir a próxima
fatia.

Isso não diminui o que o congelamento trava; muda o que se pode fazer enquanto
ele dura.

**Uma coisa mudou de urgência, e vale dizer com clareza.** O D59 é hoje o item
mais sério da lista: `/api/agendas/getCliente?telefone=...` devolve CPF e RG sem
autenticação nenhuma. Comparado ao `migrar-senhas`, que exige alguém descobrir a
rota, este exige apenas ter um telefone. **Isso é observação, não pressão** — a
decisão sobre quando descongelar continua sendo sua, e há duas contenções que
não quebram o parque instalado, registradas no D59.

**Tres delas foram adiadas em 02/09** para a homologacao, por decisao sua, para o trabalho poder avancar para as Fases 2 e 3. Ficam riscadas abaixo, com o que continua valendo enquanto isso.

Seis coisas pendiam de voce. **Em 11/09/2026 quatro delas deixaram de pender:**
a decisão permanente as recolheu para a janela do corte, o que as tira da fila de
espera sem as resolver. Ficam abaixo com o estado novo, porque o que elas
significam **em produção, enquanto a janela não chega**, continua valendo.

1. ~~**Leitura da produção**~~ — **RECOLHIDA PARA A JANELA em 11/09/2026.** Era
   para atualizar o `mysql.sql` e rodar `medir-datas-invalidas.sql` na réplica, e
   era **só leitura**. Mesmo assim entrou no procedimento de corte, por decisão.
   O custo de adiá-la é o passo 2 dos *Próximos passos*: sem a estrutura real não
   há ensaio geral, e sem ensaio geral **não há número de janela** — só
   repetibilidade medida numa máquina que não é a de produção.
2. ~~Descongelar a remediação (F1.1)~~ — **ADIADA para a homologação em
   02/09** (**D83**). Continua valendo em produção enquanto isso: a rota
   `migrar-senhas` sobrescreve o hash de todos os usuários ativos num GET, e as
   seis credenciais seguem em texto claro.
3. ~~O Power BI (D9)~~ — **REMOVIDO em 09/09/2026**, e o prazo saiu com ele
   (**D85**). Enquanto existia, o BI lia o MySQL direto e **quebraria no corte**
   se não fosse repontado ANTES dele. Não há mais o que repontar. O resíduo é
   outro e está no **D9**: a credencial que ele usava continua em produção.
4. ~~A decisão sobre alta disponibilidade~~ — **DECIDIDA por consequência**
   (**D84**): a F1.7 foi adiada, logo **o corte vai de nó único**. Se o primário
   cair na janela, a volta atrás é manual.
5. ~~**Os `ALTER` do D54 e do D86**~~ — **RECOLHIDOS PARA A JANELA em
   11/09/2026**, e eles já eram o passo 0 do corte. São aditivos, e **não tocam o
   mesmo volume**: o do D54 acrescenta uma coluna a `unidades`, que tem 60
   linhas. O do D86 acrescenta `vendas.autorizado_por` e `vendas.autorizado_em`;
   a tela de vendas já as lê, e a API nova já as **limpa** quando o fiado muda
   de valor (**D165**, 10/09/2026) — o código está adiante do schema, e isso é
   sustentável porque nada em produção passa pela API nova. Junto deles viaja
   `scripts/remover-pesquisa-teste-2018.sql`, que é opcional e apaga duas linhas
   de resíduo de 2018.
6. ~~**Os logs do nginx**~~ (W3, e também **D59**) — **RECOLHIDOS PARA A JANELA
   em 11/09/2026.** Era leitura, não mudança, e era o único item da lista que não
   dependia de decisão nenhuma; a decisão permanente alcança também a leitura. A ferramenta está
   pronta: `scripts/inventariar-versoes-app.php --dir=/var/log/nginx`. **Junte os
   rotacionados** — com menos de três meses o relatório avisa, e o número não
   serve para aposentar nada.

### E um segundo grupo, que não depende de produção

Estas nasceram das rodadas de 30 e 31/08. Ficam listadas juntas porque
**acumulam**, e porque quase nenhuma tem prazo — o que é justamente o risco de
esquecê-las.

**A lista chegou a esvaziar em 31/08, e voltou a duas.** O **D81** saiu em 02/09 e o **D82** entrou no lugar — ele estava ATRAS do D81. Corrigido no mesmo dia, saiu tambem. Restam o D77 e o D78/D79.

**As duas pedem coisas diferentes de voce.** O **D77** espera um SE: se voce aceita quebrar quem ja usa a URL de vendas — e **e o unico item deste documento que pode ser resolvido hoje com uma frase sua**, porque nao toca producao. O **D78**/**D79** nao dependem de voce: dependem da leitura da producao, que desde 11/09/2026 esta **recolhida para a janela do corte**. `scripts/medir-senhas-producao.sql` continua pronto, e passa a rodar dentro do procedimento.

| O quê | Onde |
|---|---|
| **Regerar os hashes já emitidos** — o gerador foi trocado em 01/09; os 60 hashes de unidade e 242.586 de cliente continuam os antigos, e regerar o da unidade quebra quem usa a URL de vendas | **D77** |
| **As senhas recuperáveis e o `setSenha()` quebrado** — decidido em 01/09: **medir a produção antes**, porque o conserto pode trancar usuários para fora. `scripts/medir-senhas-producao.sql` está pronto e espera a leitura ser descongelada | **D78**, **D79** |

**Ela esteve em aberto por decisão sua, e foi resolvida em 01/09** — os 17 sítios
que faltavam viraram `setParameter`. O que o registro guardava sobre o alcance
continua valendo, e agora ao lado do número que mostra o fechamento:

| | |
|---|---:|
| Alcançáveis por dado de usuário | eram **18**, em 4 métodos; hoje **0** |
| O que a maior delas expunha | os clientes das **32 unidades** entre si — 242.586 pessoas com nome, CPF, telefone e e-mail |
| Quem alcançava | usuário autenticado com permissão de **ler** o relatório de clientes |
| A prova de que fechou | com a correção guardada num `git stash`, as duas cargas devolviam **242.592** linhas; hoje devolvem **0** e **2** |

**Continua sem teste por HTTP.** A prova está no nível da DQL e da consulta,
o que basta para saber que existia e que fechou, mas não cobre o caminho do
controlador.

**A do D57 saiu da lista em 31/08**, decidida antes do prazo dela: as 7 ações
ficam fora da Fase 2, e a remoção do código vira tarefa pós-corte.

*Correção de registro (11/10/2026, o item RESPOSTAS-SEM-RODADA):* a remoção não
está decidida. A decisão de 31/08 foi tomada sobre "nenhuma unidade vinculada"
lida como função desligada, e o D343 mostrou o contrário: vínculo vazio quer dizer
todas as unidades, e os assistentes estão no menu de 211 pessoas, em 31 das 32
unidades ativas. O que fazer com eles -- e com a remoção -- volta a você na P14 de
`docs/f3-telas-restantes/telas-consolidado.md`, ainda sem resposta; o D57 continua
ABERTO.

O **D66** também saiu da lista em 31/08, mas de um jeito diferente: ele passou a
esperar **evidência**, não decisão. A instrumentação está posta e a próxima
falha trará o diagnóstico. Enquanto isso, todo "35 de 35" deste documento carrega
um asterisco — e a regra que protege contra o pior lado disso é: **quando a suíte
falhar, leia o diagnóstico antes de rerodar.**

O **D67** saiu para o desenho do corte, com o requisito escrito no débito — o
número do teto e a razão de `max_execution_time` não servir.

**E cinco esperam o corte, não uma decisão:** as limpezas do **D36** (o
`app.json` virar configuração no banco), do **D51** (a coluna fóssil dentro do
maior índice), do **D53**/**D65** (a coluna que responde "venceu ao nascer", e as
98 anotações que discordam do banco sobre nulo), a remoção das tabelas e do
código de **assistentes** (**D57**, decidido em 31/08) e o **teto do php-fpm**
(**D67**, decidido em 31/08: `request_terminate_timeout` acima de 21 min, com
pool separado para os crons — o requisito está escrito no débito para não ser
redecidido lá).

*Correção de registro (11/10/2026, o item RESPOSTAS-SEM-RODADA):* a dos
**assistentes** não espera só o corte. A remoção foi decidida sobre a leitura que o
D343 desmentiu (o vínculo vazio é "todas as unidades", e a função está no menu de 211
pessoas), e volta a você na P14 do consolidado; até a resposta, ninguém remove as
tabelas nem o código, nem depois do corte.

O que já **não** pende: o crontab chegou e a F1.6 fechou; o fuso foi medido e
corrigido — e o achado foi maior que a pergunta original, porque o problema não
estava no MySQL e sim na carga para o PostgreSQL, que entrava com **três horas
de atraso em toda data do sistema**.

### Um risco que cresceu, e não estava aqui antes

A limpeza pré-migração agora conserta **2.416 linhas em 6 colunas**, e 2.397
delas só apareceram porque a homologação foi carregada da réplica em vez do
arquivo versionado. **A anonimização reescreve as colunas onde o defeito mora**,
então análise local sobre elas é ficção.

Isso vale além das datas: tudo que a anonimização toca — nome, telefone, e-mail,
CPF, senha, data de nascimento, chaves de WhatsApp — é ponto cego para qualquer
medição feita na base de desenvolvimento.

### E um segundo, que estava escrito como condicional e não é

O **D16** dizia que o rastro de pilha vaza "**se** `display_errors` estiver
ligado". Medido em 30/08: não é condicional. Quem manda é o `display_exceptions`
do Laminas, e ele está `true` em `module/ISBase/config/module.config.php` — config
de módulo, que **carrega sempre** —, sem nada em `config/autoload/` que o
desligue.

E a mensagem que vai para a tela não é só o caminho do arquivo. Verificado
executando:

```
An exception occurred while executing
  'SELECT nome FROM ... WHERE telefone = ?'
  with params ["(11) 98765-4321"]
```

O Doctrine põe **o SQL e os parâmetros ligados** dentro de `getMessage()`, e o
template imprime `getMessage()`. Uma consulta de cliente que falhe publica o
telefone do cliente na página de erro.

São **50 ações de API sem um único `try`** — 20 no aplicativo, 30 no cron —, e o
único tratador de `EVENT_DISPATCH_ERROR` que existe apenas tira o layout.

**Aplicado por decisão sua em 30/08.** As duas chaves saíram do config de módulo
e passaram a viver em `config/autoload/local.php`, que está no `.gitignore` — é
arquivo por máquina. Desenvolvimento liga; produção, que copia de
`local.php.dist`, não liga.

Verificado nos dois sentidos: **`true` com a chave do ambiente, AUSENTE sem
ela**. Medir só o lado ligado não provaria nada — foi preciso remover a chave e
conferir que o valor some.

**Continuam pendentes** o tratador que responde JSON nas rotas `/api/*`, que vem
com a reescrita da API, e o `display_errors` do PHP em produção.

### A suíte de contrato falha sozinha, e isso vale mais que o débito que a revelou

Apliquei os 29 `inversedBy`, e a execução seguinte deu **6 falhas** no contrato.
A atribuição óbvia era a mudança de mapeamento.

**Não era.** Com a mudança guardada no stash — **o mesmo código, byte a byte** —
duas execuções seguidas deram **35/35** e depois **3 falhas**.

Todas as falhas trazem `"status": 0`: é o `curl` desistindo em 30 segundos, e
não payload diferente. Elevando o limite para 120 s, passa 35/35 **com e sem** a
mudança.

**E o número não fecha.** Medido direto contra o banco de contrato, o endpoint
mais lento dos que falham responde em **1 a 1,6 segundo**, estável em quatro
medições. Por que dentro da suíte ele às vezes passa de 30, não sei. Descartei
carga da máquina, cache de configuração, banco errado e divergência de payload;
sobraram duas hipóteses não medidas — geração de proxy do Doctrine a cada
requisição, e a janela de 2 segundos do opcache depois que
`rodar-contrato.sh` reescreve o `local.php`.

**Por que isto importa mais que a falha.** "35 de 35" foi reportado como
evidência dezenas de vezes nesta semana, neste documento inclusive. **Parte
desses verdes pode ter sido sorte.** E o lado pior é o simétrico: a partir de
agora existe a tentação de descartar uma falha real como "flakeou de novo".

Virou o **D66**. **As duas hipóteses que restavam foram medidas em 31/08, e as
duas caíram**: os proxies do Doctrine são gerados uma vez, não por requisição
(a contagem é 2 já na primeira e nunca muda), e a lentidão persiste por 51
segundos, não pelos 2 da janela do opcache.

A medição trocou o problema de forma. Cinco execuções completas deram 35/35 as
cinco vezes, e o `--log-junit` dessas execuções mostra que **o teste mais lento
das 175 medições leva 4,11 s contra um limite de 30 s** — 25,89 s de folga. Uma
falha por tempo limite não é a suíte se arrastando: é alguma coisa **travando**.
Procurar desempenho era procurar no lugar errado.

**A saída continua não sendo elevar o limite** — isso esconderia o sintoma, e o
número certo depende de decidir quanto tempo um endpoint do aplicativo pode
legitimamente levar, que é pergunta de negócio.

*Correção de registro:* a mensagem do commit `30d4696` diz que esta suspeita
ficou "registrada no D61". Não ficou — o D61 é sobre snapshots que só têm o caso
vazio, e o D66 não existia quando aquela mensagem foi escrita.

### Nove chamadas de rede sem tempo limite — sete corrigidas

Procurando o que poderia travar por 30 segundos no D66, apareceu outra coisa —
que **não** é a causa do D66, e é mais grave que ele.

Nove chamadas `curl` no código de produção não definem `CURLOPT_TIMEOUT`: duas
no `OneSignal`, cinco no `Asaas`, duas nas do Yungas. (Eu havia escrito dez,
contando com `grep` uma linha comentada; quem corrigiu foi o portão novo, que
lê o PHP pelo tokenizador e não vê comentário.) O padrão do `curl` sem essa
opção é **não desistir nunca**. As outras integrações do sistema — Evolution,
WAHA, visagismo, S3 — todas definem o seu.

E não há teto acima delas. No container medido, `max_execution_time` é 0, o
`request_terminate_timeout` do php-fpm não está definido, e `pm.max_children` é
5. O `fastcgi_read_timeout` de 300 s do nginx limita quanto o **chamador** espera,
não quanto o worker vive: aos 300 s o nginx devolve 504 e desliga, e o processo
PHP segue rodando. Como as rotinas de cron são chamadas por URL, elas disputam
esse mesmo pool de cinco com o aplicativo dos clientes.

O tamanho: o envio é serial, uma chamada por destinatário, e a maior lista da
cópia da produção tem **6.573 destinatários**.

Duas suspeitas foram conferidas e **não** se confirmaram, o que também vale
registrar: a remoção do id da lista está correta (as 1.398 listas envolvem os ids
em `;`), e um travamento retoma de onde parou em vez de reenviar a campanha —
o `flush` é por destinatário.

Isto virou o **D67**. Não foi corrigido: o OneSignal está congelado por instrução
sua, e embora Asaas e Yungas não estejam, a correção é a mesma linha nos três e o
número do tempo limite é uma decisão só.

**Por decisão sua, sete das nove foram corrigidas** em 31/08: as cinco do Asaas
e as duas do Yungas, com `CURLOPT_CONNECTTIMEOUT` de 5 s e `CURLOPT_TIMEOUT` de
30 s. Os números foram provados mordendo contra causas fabricadas — 5,01 s
contra um endereço que engole o pacote, 30,00 s contra um servidor que aceita e
cala — sem tocar nas APIs reais, porque chamar o Asaas de verdade é mexer num
meio de pagamento. Quem confirmou que as sete pegaram foi o portão, não eu.

Restam **as duas do OneSignal**, congeladas, e é o par que mais assusta: são as
que rodam em laço. E **o teto do php-fpm não foi posto** — os sete `curl_setopt`
protegem sete lugares; o teto protegeria os que ainda não existem.

Ao abrir esses arquivos apareceram **mais duas credenciais em texto claro** que
não estavam em registro nenhum: a chave de **produção** do Asaas e a API key
mais a **senha** do Yungas. O D3 passou de seis para oito, e a lista de rotação
do dia 0 — que tinha cinco — passou a ter as oito. Lista de rotação incompleta é
pior que nenhuma: ela é percorrida até o fim e dá a sensação de terminada.

Também foi feito impedir que a lista cresça.
`scripts/conferir-tempo-limite-rede.php` lê o PHP pelo `token_get_all` — não por
texto — e falha quando aparece uma décima, ou quando uma das nove some sem que a
lista seja atualizada. Ele entrou no comando dos portões, que passou a ser onze.
Foi posto à prova nos dois sentidos: acusou uma chamada nova sem tempo limite e
ignorou a irmã dela que tinha um.

### A caracterização passava 108 de 109 contra um banco de 36 linhas

Isto veio de um erro meu, e o erro valeu o achado. Rodei a caracterização em
segundo plano e o `rodar-contrato.sh` por cima dela — e esse roteiro reescreve o
`local.php`, que é compartilhado por tudo. A suíte leu o banco de contrato do
começo ao fim.

O que ela devolveu foi **uma falha só**, com uma mensagem que não fala de banco
nenhum. Apontei a suíte ao banco de contrato de propósito e rodei inteira: o
resultado reproduziu byte a byte.

| Banco | Linhas | Resultado | Tempo |
|---|---|---|---|
| `franquiabv_dev` | ~15,2 milhões | 109 de 109, 203 asserções | ~14 min |
| `franquiabv_contrato` | **36** | **108 de 109**, 201 asserções | **1min25** |

Não é defeito dos testes — 14 dos 19 arquivos citam a faixa das fixtures, que
existe nos dois bancos, e cada teste roda numa transação revertida no fim. Não
depender do que já estava lá é boa qualidade. Mas o preço é este: **um teste e
duas asserções separavam "109 de 109" de "108 de 109 sobre 36 linhas"**.

O espelho já existia do outro lado desde o D55: a suíte de contrato recusa um
banco com dado real. Faltava a metade de cá, e o buraco oposto era pior — o D55
falhava em cascata e gritava, este passava.

Corrigido no **D69**: `exigirCopiaDaProducao` exige uma unidade com id abaixo de
90000000, que só a cópia anonimizada tem. Contra o banco errado, os testes
passaram de 1 falha confusa para 7 erros com a explicação inteira.

E isso **não** invalida a linha dos 109 contra PostgreSQL: aquela execução tem
203 asserções, e o banco errado dá 201. A contagem de asserções foi o que
permitiu afirmar isso.

### O caixa não atravessa mais a virada do dia

A regra foi enunciada por você em 31/08, e ela **inverte** a resposta que o D28
tinha recebido no dia anterior:

> Não permitir venda retroagida, apenas na data atual ao lançamento. Se o caixa
> passar de 23:59:59, pedir para fechar o caixa e abrir um na data vigente.

Em 30/08 a saída escolhida tinha sido permitir informar a data do fato —
`setDataCriacao()` ganhou um parâmetro opcional. Ele foi **desfeito**, com os
testes que o cobriam: uma capacidade que a regra proíbe usar é armadilha, porque
quem a encontrar depois vai supor que é permitida.

No lugar, a guarda: `finalizar()` recusa a venda quando o caixa aberto é de data
anterior, **antes de qualquer escrita** — as primeiras linhas do método debitam
crédito, e sair no meio deixaria o cliente sem o crédito e sem a venda.

**E o tamanho disto é muito menor do que eu apresentei, o que é correção
minha.** Levei a você 19,18% dos caixas fechando em data diferente da abertura,
dizendo que a guarda encostaria na operação todo dia. Aquele número mede caixa
deixado aberto de um dia para o outro **sem venda no meio**. O número que mede o
risco é outro:

| | |
|---|---:|
| Vendas desde 2025 | 407.544 |
| **Em caixa de outra data** | **44 — 0,011%** |

É pouco porque `VendasController::validarCaixaAberto()` já barrava a entrada na
tela de venda. **As 44 são o que escapava** — abrir comanda, finalizar venda já
aberta, ou estar com a tela aberta quando o dia virou —, e é exatamente isso que
a guarda no serviço fecha. Lição do D20 pela terceira vez: trava na tela protege
a tela; trava no serviço protege qualquer chamador.

Cinco testes cobrem, verificados desligando a guarda de propósito. **Um deles
passava sem provar nada** e só apareceu por isso: buscava a venda antes de um
`em->clear()`, então a entidade chegava desanexada e nada era gravado. A prova
de que um teste acusa é a única forma de saber que ele mede.

### Oito rotinas de cron não deixam rastro

Ao conferir se o registro do `marketingEnvios` chegava a acontecer — ele estava
depois de um `return` —, o log das rotinas deu a resposta e mais do que ela.

`data/logs/log_scripts.txt` tem 5.451 linhas e vai até 28/07/2026, a data da
cópia da produção. A última linha dessa rotina é de **05/12/2025**. E a tabela
`notificacoes` diz que a produção enviou **217 campanhas** entre uma data e a
outra. A rotina rodou, trabalhou e ficou oito meses sem escrever uma linha.

Corrigido: o registro passou para antes do laço, que é onde a própria mensagem
dele diz que ele pertence. Não executei a rotina para provar — executá-la manda
mensagens para pessoas de verdade; a prova é estrutural.

A pergunta maior veio junto. Das 27 rotinas de cron (três das 30 ações são
endpoints de consulta), **15 registram alguma coisa e 12 não registram nada**. E
as doze incluem as **cinco que mandam push**, que são as mesmas que chamam o
OneSignal sem tempo limite. As que mais podem ficar penduradas são as que não
deixariam rastro de ter ficado.

Eu havia contado **oito**, à mão, e parei nas ações cujo método de serviço tem o
mesmo nome; quatro delegam para um método de outro nome. Terceira contagem à mão
que erra nesta semana, e a terceira corrigida por um roteiro — então o roteiro
virou portão: `conferir-rastro-das-rotinas.php` recusa a décima terceira rotina
muda, e o comando passou a doze portões.

Virou o **D68**. É o terceiro modo da mesma família: o D43 é a rotina que
ninguém dispara, o D50 é a que dispara e cujo efeito ninguém vê, e esta é a que
roda e não conta.

E o teto do php-fpm ganhou o número que faltava. A rotina mais lenta leva **21
minutos**, então qualquer teto abaixo disso a mataria toda noite — o número útil
está acima. De quebra apareceu que **o crontab dá 600 s a ela**: o agendador
desiste aos 10 minutos de um trabalho de 21, todas as noites, e o worker segue
sozinho até o fim sem que ninguém saiba.

### O `orm:validate-schema` voltou a valer como portão

Por decisão sua, os **29 `inversedBy`** do D64 foram aplicados.

```
[OK] The mapping files are correct.
```

De 18 entidades reprovadas e 29 queixas para **zero**. Era o ganho maior que
qualquer das correções isoladas: antes, para saber se uma mudança estragou o
mapeamento, era preciso rodar a validação **duas vezes** — com e sem a mudança
guardada — e comparar contagens, porque "reprovado" sozinho não dizia nada.

**A lista não foi minha, de propósito.** Cada edição saiu de uma queixa do
próprio validador, que já diz a entidade alvo, o campo alvo e o valor exato do
atributo.

**E as duas listas foram comparadas antes de aplicar.** Derivei uma segunda do
código e ela deu **30** — a diferença era falso positivo meu:
`SaidaParcelaPagamento` declara `targetEntity` sem namespace, meu roteiro não
achou o arquivo e reportou como faltante. Já tinha o atributo. Fora essa, as 29
são idênticas nas duas.

O diff é **58 linhas: 29 fora, 29 dentro**, e nenhuma linha acrescentada sem
`inversedBy`. Todas `ManyToOne` — nenhuma `OneToOne`, o que mantém o caso
simples.

### Um comando para os dez portões, e uma categoria que faltava

São dez roteiros de conferência com três formas de invocação — PHP local, PHP
dentro do container (os que falam com o banco pelo host `mysql`, que só existe
na rede do Docker) e shell. **Ninguém decora isso**, e portão que ninguém lembra
de rodar não protege nada.

```bash
sh scripts/conferir-tudo.sh
```

**E rodá-los juntos revelou uma categoria que faltava.** O de agendamento acusa
duas rotinas sem crontab, e está certo em acusar: uma espera decisão de negócio
(D43), a outra espera a produção. Sem tratamento, ele deixaria o conjunto
vermelho para sempre — e **conjunto sempre vermelho é conjunto que ninguém
roda**. É a mesma doença das listas de exceção que nunca encolhem, e do
`orm:validate-schema` que reprova sempre.

Virou **vermelho conhecido**: roda igual, mostra o motivo, e não conta como
falha. E se um dia ele *passar*, isso é reportado como falha — quer dizer que a
lista envelheceu.

### A sexta prova da carga confere a forma, e não o dado

As cinco provas de `conferir-migracao.php` conferem **o dado**: quantas linhas,
quanto somam, se há órfão, se as sequências estão à frente, se as datas batem.
Nenhuma olha para a **forma** da tabela.

Uma coluna que existe num banco e não no outro derruba o `\copy` na hora — esse
caso se anuncia sozinho. Mas **uma coluna com nulabilidade diferente atravessa
todas as cinco**: a carga passa, as contagens batem, as somas batem, e o defeito
só aparece meses depois, quando alguém grava um nulo onde um dos lados não
aceita.

O próprio cabeçalho do roteiro já dizia como se descobre um buraco num conjunto
de provas: *"pelo defeito que atravessou"*. Este foi encontrado antes.

**Na medição, os dois bancos concordam em 890 colunas.** A única ausência é
`usuarios.senha_php83`, removida do schema novo de propósito (**D7**), e entrou
como exceção nomeada — com aviso para sair quando o deploy da F1.1 a remover
também do MySQL.

**E a prova achou o que a minha conferência manual não viu.** Eu comparei
nulabilidade nas colunas presentes nos dois e contei as que sobravam no
PostgreSQL; não olhei as que sobravam no MySQL. A prova olha os dois lados.

### O portão do `local.php` ganhou uma saída, para não ser apagado

O `verificar-local-php.php` passou a recusar `display_exceptions` em `local.php`.
Mas ele também roda em **homologação**, onde o rastro pode ser legítimo — e
**portão sem saída legítima acaba apagado em vez de discutido**.

A saída é nomeada e cara de digitar por acidente:

```bash
PERMITIR_RASTRO=1 php scripts/verificar-local-php.php
```

### O portão de referências acusou a documentação que eu tinha acabado de escrever

Ao registrar a conferência do D34, escrevi que o código cita **public/app** — sem
crases aqui de propósito, porque é citação de um erro e não referência a arquivo.
Não cita: o caminho é `public/app.json`, e eu havia recortado a string pela
metade.
O portão pegou na execução seguinte.

E o conserto rendeu um segundo caso: a frase que **explicava** o erro citava o
caminho errado entre crases, e o portão acusou de novo — corretamente, porque
crase ali marca referência a arquivo. Virou negrito, que é o que a frase queria
dizer.

### Uma coluna fóssil que não parou de ser escrita — piorou

Contar os nulos do D65 revelou uma data que o **D53** não tinha: **06/09/2024**.

Até ali, `clientes_creditos.pontuacao_validade` guardava **365 ou 366 dias** de
validade, com uma cauda de prorrogação. Depois, passou a guardar **0 dias** — a
data de criação do próprio crédito — em **44.922 linhas**. E as linhas nulas
acabaram na mesma data.

"Fóssil" descreve o que a coluna **significa**, não o que acontece com ela: ela
continua sendo escrita, a última em 16/08/2026. Como validade, o que ela diz é
"venceu no instante em que nasceu".

**E isso é pior que uma coluna abandonada.** Coluna vazia manda perguntar; coluna
preenchida com o padrão do construtor **responde, e responde errado**. Qualquer
tela nova da Fase 3 que leia esse campo como "quando estes pontos vencem"
concluirá que 44.922 créditos venceram no dia em que nasceram.

A regra de negócio continua cumprida pelo razão, como o D53 já provava. O que
mudou é a força do argumento para remover: enquanto a coluna existir, é uma
resposta errada esperando quem pergunte.

### O mapeamento e o banco discordam sobre nulo em 98 colunas

O **D51** dizia de passagem que `vendas.comanda_temp` é declarada `nullable=true`
enquanto o MySQL diz `NOT NULL`. Não era uma coluna: são **98 de 732** — 13% do
mapeamento —, e nos dois sentidos.

Das 52 que a anotação jura serem `NOT NULL`, **33 têm nulo de verdade**; 25
depois de descontar o que a anonimização zera. A maior é
`agendas.add_comanda`, com **708.877 nulos em 4 milhões de linhas**.

**E a primeira medição quase virou um defeito nosso registrado como da
produção.** Ela acusou `unidades.nome` com 1 nulo em 62, e eu escrevi que uma
unidade sem nome merecia olhada. Merecia: era a unidade `90001000`, criada pelas
**nossas fixtures de teste**. Excluindo a faixa `9000xxxx`, o nulo desaparece.

A regra que fica: a base local tem a cópia anonimizada **mais** as linhas que os
testes criam. Em tabela grande é ruído — 708.879 vira 708.877. Em `unidades`, que
tem 60 linhas reais e 2 nossas, "1 em 62" podia ser artefato inteiro. Medição em
tabela pequena pede `WHERE id < 90000000`.

**O código já sabe o que a anotação nega.** `AgendaService:212` grava
`$addComanda === null ? null : (...)` — trata o nulo explicitamente, três
arquivos adiante de uma anotação que diz `nullable=false`. Quem escreveu o
serviço sabia; quem lê a entidade, não.

**Não é risco de corte, e a verificação é o que permite dizer isso:**
`gerar-ddl-postgres.php` lê o `mysql.sql`, o dump de estrutura — nunca a
anotação. `postgres/schema.sql` traz `add_comanda boolean DEFAULT false`, sem
`NOT NULL`, igual ao MySQL. Onde morde é no `schema-tool`, em quem um dia
regenerar o schema a partir do mapeamento, e em todo leitor humano que acredite
na anotação.

Virou o **D65**, para depois do corte — mapeamento é o que a suíte de
caracterização exercita, e o portão "o comportamento não mudou" precisa falar de
uma coisa por vez.

*E o portão de duplicatas acusou este débito novo contra o D42, por
"discordam sobre" — coincidência de verbo, não de assunto. Entrou na lista de
pares explicados, que é exatamente o fluxo que ele foi feito para provocar.*

### Dois defeitos no procedimento de corte, e o primeiro mata a volta atrás

Encontrados em 30/08/2026, conferindo se cada comando do
`procedimento-de-corte.md` ainda existia. Os arquivos existiam todos. O problema
era outro.

**O passo 7 destruía o único arquivo de que a volta atrás depende.** Ele
sobrescreve `config/autoload/local.php` com a versão PostgreSQL — e o `local.php`
**não é versionado**: é o único lugar onde os dados da conexão MySQL existem. O
plano de contingência manda restaurar `local.mysql.php.guardado`, arquivo que só
era criado em **outro documento**, o `ambiente-homologacao.md`.

Quem seguisse o procedimento sozinho destruiria a conexão MySQL no passo 7 e
descobriria no momento de voltar — o pior momento possível para descobrir. A
linha que guarda a cópia entrou no passo 7.

**E o plano de contingência dizia "o MySQL não foi tocado".** Foi: o passo 3 roda
`pre-migracao-limpeza.sql`, que **escreve** — reconstrói datas inválidas em
`agendas` e `agendas_fechamentos`, anula três colunas de data, e **apaga
duplicatas de `privilegios`**. São 2.416 linhas em 6 colunas pela medição na
réplica.

Nada disso é acidente: sem o script a carga falha no meio. Mas **a volta atrás é
segura sem ser integral**, e chamá-la de integral era o erro. Quem volta, volta
com o MySQL já limpo.

*A contagem não se confere na base local — a anonimização reescreve
`data_nascimento`, então lá tudo aparece como zero (D41). É o ponto cego que
aquele débito já descrevia, aparecendo de novo por outro caminho.*

### O D14 registrava uma rota órfã. São três, e uma era nossa

A varredura de todas as rotas contra os controllers achou mais duas além da do
débito. **A do meio foi criada por este projeto:** ao mover a rotina de
vencimento de convênio para o cron (D50), a ação de admin foi removida em 28/08 e
a rota `/convenios/atualizarVencimento` **ficou para trás**, apontando para o nada
por dois dias. Nada a referenciava, e ela saiu em 30/08.

A terceira é antiga e continua de pé: `/mod-vendas`, a raiz do módulo de vendas,
tem `'action' => 'index'` como padrão, e `VendasController` nunca teve
`indexAction` — só `indexVendasAction` e `indexComandasAction`. O menu sempre
apontou para as ações completas, então ninguém tropeçou.

**O roteiro errou três vezes antes de acertar, e as três valem:**

1. **Nome curto não identifica controller.** Há dois `VendasController`, em
   módulos diferentes; indexar por nome curto faz um sobrescrever o outro. A
   primeira versão acusou **11**.
2. **PHP não diferencia caixa em nome de método** — verificado por execução.
   Comparar caixa exata acusava duas rotas perfeitamente sadias.
3. **Guardei o nome com o sufixo `Action`** e comparei com a ação sem ele:
   **90 órfãs de 92**. Absurdo o bastante para se denunciar sozinho — e é por
   isso que a contagem vem impressa antes do veredito.

**E esse roteiro não usa regex, de propósito.** Duas versões dele foram
destruídas por barras invertidas comidas na escrita do arquivo, e **padrão
quebrado não falha alto: ele casa outra coisa.** Leitura por texto simples é mais
longa e não tem esse jeito de errar.

### Toda verificação passou a recusar-se a aprovar o que não leu

O padrão apareceu três vezes em sequência, e nas três **o veredito estava certo e
o motivo errado**:

| O que o roteiro disse | O que tinha acontecido |
|---|---|
| "0 restantes" | uma duplicata de pé, num arquivo que o padrão não casava |
| saída 1, como se acusasse | 8 bytes impressos: nem chegou à comparação |
| "nenhum par novo" | **zero débitos lidos** — o travessão multibyte derrubou o padrão |

Em nenhuma o veredito acusou. Em todas, um **número lateral** acusou: 82 contra
81, 8 bytes, zero lidos. Conjunto de achados vazio é indistinguível de "não li
nada" — e a diferença é tudo.

Os oito roteiros de conferência ganharam a mesma guarda: **contagem de coisas
examinadas igual a zero é falha, e não sucesso**.

**E a guarda errou onde foi pendurada no contador errado.** Em
`conferir-relacoes-doctrine.php` ela guardava as *afirmações de unicidade
encontradas* — e zero delas é exatamente o que o D17 perseguiu e alcançou. O
roteiro passou a anunciar "NÃO LI NADA" sobre 162 arquivos bem lidos, e o
sucesso virou falha. A guarda mora no que se **examina**, nunca no que se
**acha**.

**E linha de base que só cresce deixa de ser linha de base.** Cinco portões
guardam listas de exceções conhecidas — as 27 rotinas de cron que respondem
sucesso, os 10 caminhos ausentes com motivo, as 2 rotas órfãs, os 2 pares de
débitos, as 4 colunas que o `schema-tool` apagaria. Se um item da lista for
consertado e ninguém o retirar, a lista vira folclore: descreve um passado, e o
portão passa a proteger contra o que já não existe.

Os cinco passaram a avisar quando um item conhecido **deixa de aparecer**, e a
sair com código 1 até que a lista encolha. A lista diminui por conserto, nunca
por conveniência — e não fica parada por esquecimento.

**E um número de portão que inclui a própria ferramenta manda a próxima pessoa
atrás de um fantasma.** O de desempate dizia "40 listagens examinadas" contra as
36 do D47; a diferença era o `AdapterQuery` — o paginador — contado como se
fosse duas listagens sem `ORDER BY`, porque contém a palavra `getPaginator`.
Gastei três sondas atrás dessa diferença, e **duas delas mediram nada**: eram
cópias no scratchpad, e o roteiro resolve a raiz por `dirname(__DIR__)`, então
não havia `module/` para varrer. Os dois zeros eram vazio, não ausência de caso.
São **38** listagens, e o roteiro agora descarta o próprio encanamento.

### O registro tinha o mesmo defeito escrito duas vezes

O **D30** e o **D49** descrevem as mesmas 85 colunas `double`, com dez dias de
diferença. E já tinham divergido: um mandava converter para `numeric(12,2)`, o
outro para `numeric(14,2)`.

Duplicata em registro de débito não é desarrumação. São **duas páginas que
envelhecem separadas**, e quem consertar uma vai achar que fechou as duas. O D30
virou remissão — não foi apagado, porque link antigo aponta para o número.

`scripts/conferir-debitos-repetidos.php` passou a varrer os títulos. E a
primeira versão dele **perdeu um par que eu já conhecia**: comparava palavras
inteiras, então "autenticação" e "autentica" não casavam, e o D13/D59 passou
limpo. Um varredor que perde o caso conhecido não serve para procurar os que
ninguém conhece — passou a comparar radicais de 6 letras.

Ele também **leu zero débitos e disse "nenhum par novo"** na primeira execução: o
travessão do título tem 3 bytes em UTF-8 e o `.` do padrão casava um só. Verde
sobre nada lido, que é o pior verde que existe. O `u` no padrão fecha isso, e
está comentado no arquivo para não voltar.

O D13 e o D59 **não** são duplicata, e ficaram com remissão mútua: um registra
por que não há autenticação, o outro o que se alcança sem ela.

### "Comandos destrutivos: 0" era mais do que se corrigiu

O D17 mede uma coisa precisa e verdadeira: **`ADD UNIQUE INDEX` propostos, 114 →
0**. Este roadmap resumiu isso como "os comandos destrutivos caíram de 114 para
0", e o próprio D17 chegou a escrever que "nenhum deles destrói dados agora".

**É falso, e agora medido.** O `schema-tool` continua propondo **quatro comandos
que apagam coluna**, e três delas têm conteúdo:

| Coluna | Linhas | Preenchidas |
|---|---:|---:|
| `usuarios_acessos.local` | 1.880.078 | **1.804.389** |
| `logradouros.tipo` | 709.440 | **709.440** |
| `vendas.desconto_motivo` | 2.269.212 | 425 |
| `usuarios.senha_php83` | 2.430 | 0 — sai no deploy da F1.1 (D7) |

São **2.514.254 valores** que um `--force` destruiria hoje. E `logradouros DROP
tipo` está nomeado como exemplo **no próprio texto do D17**, quatro parágrafos
acima da frase que o contradizia.

O salto foi de "os 114 índices únicos sumiram" para "nada destrói dados" — e
ninguém o verificou, porque nada o conferia. Agora `scripts/conferir-schema-tool.sh`
confere, com linha de base 4.

**E a primeira prova de que ele acusava era falsa.** Desmapeei uma coluna
quebrando a anotação, o roteiro saiu com código 1, e eu quase registrei isso como
prova. Não era: ele imprimiu **8 bytes** — não chegou à comparação. A anotação
inválida fez o `schema-tool` falhar, e o `set -e` matou o roteiro antes. Código
de saída certo, motivo errado.

A prova honesta veio pelo outro lado: uma coluna **real** criada no banco e não
mapeada. Aí ele contou 5, nomeou `bairros.prova_do_portao` e disse por quê. A
coluna foi removida em seguida.

### O cron responde sucesso quando o token está errado — 27 das 30

Achado ao conferir se o D43 tinha razão em dizer "rota pública". Não tinha: a
rota **tem** token. O problema é outro, e maior.

```php
if ($token != $this->tokenPadrao) {
    return new JsonModel(['sucesso' => true]);
}
```

**Nenhuma das 27 recusa.** As 3 únicas honestas são as que se autenticam por
`hash` da unidade e terminam em `die("Acesso negado.")` — o mecanismo mais tosco
é o único que diz a verdade.

O **D4** já registrava isso como item 3. O que faltava era a data: **a rotação de
credenciais é o passo 5 da remediação dia 0.** Rotacionar o token sem atualizar o
crontab para as 27 rotinas de uma vez — royalties, dashboards, NPS, envios — e
todas continuam respondendo sucesso. Monitoramento que olhe a resposta HTTP vê
verde para sempre.

**E o defeito se espalhou pela nossa mão.** A auditoria de 16/08 contou 29 ações;
hoje são 30. A que entrou foi criada por este projeto em 28/08, e copiou o padrão
da vizinha — que é o que se faz ao acrescentar uma rotina.

`scripts/conferir-resposta-cron.php` não exige o conserto das 27: isso muda o
contrato de 27 rotas em produção de uma vez, e é decisão de quem opera. As 27
ficam numa linha de base, e **uma ação nova com o defeito faz o roteiro sair com
código 1**. Ele também avisa quando uma das 27 é consertada, para sair da lista —
que encolhe por conserto, nunca por conveniência.

### A rotina que criamos e nunca agendamos

`conveniosAtualizarVencimento` tem rota, tem teste e não roda. Foi criada em
28/08 e a linha do crontab nunca entrou, porque a produção está congelada.

O passo 9 do corte não resolvia sozinho: ele restaura
`/root/crontab-antes-do-corte.txt`, que é de **antes** de a rotina existir. Sem
uma linha nova, ela continua sem rodar depois do corte. O procedimento passou a
trazer a linha e a janela sugerida (01:30), e `conferir-agendamento.php` deve
passar a acusar uma pendência em vez de duas.

### Um portão novo que nasceu limpo, e quase não nasceu

`scripts/conferir-referencias-docs.php` confere se os caminhos citados na
documentação existem. Ele encontrou **10 ausências, e todas as dez estavam
certas** — arquivos removidos da produção (D1), um relatório gerado no servidor e
ainda pendente, e um roteiro renomeado de propósito depois de um incidente.

Zero defeitos, e aí estava o risco: **um portão que acusa dez itens sabidamente
bons a cada execução vira ruído, e ruído faz o portão ser ignorado.** Era a mesma
doença do `orm:validate-schema`, que reprovava sempre — e que o D64 curou depois,
no mesmo dia. Cada exceção entrou numa lista **com o motivo escrito**, e agora
ele sai limpo.

O limite dele está dito no próprio arquivo: confere existência, não conteúdo.
Não pegaria o caso do D41, que por quatro dias afirmou que a limpeza
pré-migração não cobria `clientes.data_nascimento` quando `40eabe1` já a cobria.

### A interrupção que deixou o ambiente errado

Uma rodada de suítes foi interrompida no meio e **deixou `local.php` apontando
para o PostgreSQL** — a linha que restaura o arquivo nunca chegou a rodar, e o
comando seguinte foi para o banco errado sem avisar. Foi visto porque a mudança
seguinte exigiu olhar o arquivo; não houve sinal nenhum por conta própria.

A sequência vinha sendo escrita à mão a cada rodada. Virou
`scripts/rodar-caracterizacao.sh`, com `trap ... EXIT INT TERM` — que é o que
`rodar-contrato.sh` já fazia desde que foi escrito, e esta faltava. **A mesma
lição, na terceira vez que o ambiente errado morde:** o que restaura estado tem
de estar preso à saída do processo, não à última linha do roteiro.

---

## 16 a 20/09/2026 — a agenda chegou ao front, e a revisão que eu mesmo pedi achou 14 defeitos

### O nono recurso da fatia da agenda

`GET /adm/api/agendas/grade?data=AAAA-MM-DD` — uma coluna por colaborador. Com ele a
Home em React deixou de desenhar uma grade vazia com um aviso ao lado dizendo que a
agenda não tinha API.

**A receita foi extraída, e não copiada.** A montagem era um método PRIVADO do
`AgendasController` legado — só a própria tela alcançava. Copiar criaria a segunda
verdade sobre a agenda. Ela virou `GradeDaAgendaService`, e o controller legado passou
a chamá-la de lá. **Provado byte a byte:** A/B com `git stash`, dois lados de 297.746
bytes, sha1 `0b656ca7`.

**O vocabulário de estado ficou o da API** (`marcado`, `em-atendimento`, `atendido`,
`bloqueado` — congelados por `AgendaApiTest`), e a tela traduz em cinco linhas.
Alinhar o servidor à tela criaria um segundo vocabulário dentro do mesmo servidor.

### O recurso da sessão, e uma quarta forma de guarda

`GET /adm/api/sessao`. O próprio `App.tsx` registrava em comentário: *"Não há endpoint
que diga quem entrou"* — e por isso a barra do topo escrevia "unidade não medida" e a
agenda habilitava botões para todo mundo.

**O formatador existe para ESCOLHER.** A sessão do ERP carrega `usuario.token` e
`unidade.whatsapp_api_key` dentro dela; um `return $acl` entregaria os dois sem sintoma
nenhum. Medido nos dois lados: `user1` recebe 124 privilégios, `user380` recebe 40, e
nenhum dos dois recebe token ou chave.

**E "quem sou eu" não tem privilégio de módulo que sirva.** Pendurar um arbitrário
deixaria quem não tem aquele módulo sem o próprio nome. Em vez de uma lista de isentas
dentro do portão — motivo escrito não é guarda —, nasceu `apenasSessao()`: código que
roda, que `conferir-acl-da-api.php` **conta e nomeia em toda execução**. Falsificada:
arrancando-a, o portão sai de `EXIT=0` para `EXIT=1`.

### A revisão adversarial, e o falso verde que ela produziu primeiro

Pedi uma revisão multi-agente do commit da grade. Ela voltou com `confirmados: []` — que
se lê como "nada de errado". **Não era:** 33 dos 41 agentes morreram no limite semanal, e
a minha regra de classificação (`sobreviveu = vivos.length > 0 && refutaram === 0`)
carimbou como **refutado** todo achado cujos dois céticos morreram.

Consertado o roteiro para ter três estados — com `nao-verificado` que nunca se disfarça
de aprovado — e repetido: **41/41 agentes, 18 achados, 14 confirmados, 4 refutados, zero
sem veredito.**

A lição é a de sempre, num lugar novo: **ausência de medição não vira permissão**, e isso
vale também para o instrumento que eu mesmo escrevo para me medir.

### O achado crítico não era meu — e não foi consertado

`checkinAction:579` e `abrirAction:212` usam a unidade que o POST mandar, sem conferir
nada. A primeira devolve a grade renderizada de qualquer franquia, com **nome e telefone
do cliente** no balão; a segunda **apaga** o fechamento de agenda de outra unidade.

O comentário de `atualizarAction` no mesmo arquivo afirma que esse buraco foi fechado —
e foi, **só ali**. O `git blame` aponta o próprio commit do D150: a correção nasceu
incompleta. Confirmado por leitura direta e registrado como **D190**.

### E a correção do D150 nunca funcionou

Ao consertar o D190, a primeira medição derrubou a premissa do registro. Não era só
que as duas ações ficaram de fora: **a guarda que o D150 criou era inerte**.

```php
return $this->sessao->getAcl('agendas', 'geral')   // DOIS argumentos
        || (int) $unidadeId === (int) $this->unidadeId;
```

Com dois argumentos, `getAcl` devolve `['ler' => false, 'escrever' => false]` — arranjo
não vazio, logo **verdadeiro**. O primeiro termo do `||` era sempre `true` e o segundo
nunca era olhado. É o padrão do D123 dentro do conserto do D150.

**Medido pela tela de sempre**, com `user200` (unidade 1, gerencia agenda, **sem**
`Agendas unidades`):

| pediu | colunas | células | **telefones** | bytes |
|---|---|---|---|---|
| unidade 1 (dele) | 15 | 313 | 66 | 311.320 |
| **unidade 48** | **12** | **262** | **88** | **269.643** |

A mesma forma estava em `UnidadeRepository::popularComboboxAgenda`, na negação — o
`AND u.id = :unidade` nunca entrava, e a lista saía com **36 unidades**. A tela escondia
o seletor (`index.phtml:63` usa a forma certa), e por isso nada aparecia usando o sistema.

**Depois do conserto:** pedir a unidade 48 devolve **os mesmos 311.320 bytes** da própria;
`user1` (administrador) continua alcançando a 48 com os mesmos 269.643 de antes. As duas
direções medidas.

`abrirAction` levou três consertos — `find()` no lugar de `getReference()` (o proxy
preguiçoso fazia `!empty()` ser sempre verdadeiro, e o ramo de "não localizado" era código
morto), a unidade do fechamento e a da grade. Medido **sem apagar nada**, por
`acao=pesquisar`. E o check-in era pior que a leitura: `selecionarAgendaCliente` filtra por
cliente e data e mais nada, então ele invertia o estado na rede inteira.

**Virou portão:** `scripts/conferir-acl-como-booleano.php`, o 45º. Varre 836 arquivos,
acha as 119 chamadas a `getAcl()` e acusa as que decidem com menos de três argumentos.
**A falsificação dele achou um defeito nele mesmo** — a primeira versão olhava o caractere
colado na chamada, e em `!$sessao->getAcl(...)` há o receptor no meio: saiu **verde sobre o
defeito que motivou escrevê-lo**. Corrigido e refalsificado nas duas formas.

### Os seis defeitos meus, consertados

| achado | conserto |
|---|---|
| `Erro` não distinguia sessão de permissão | a afirmação falsa saiu; a mensagem passou a dizer as duas possibilidades. A conflação é sistêmica — 22 pontos, 19 controllers, e o 403 congelado em cinco roteiros: **D191** |
| `podeEscrever` constante `true` | `podeGerenciar` passou a sair no corpo; a prop sumiu. Falsificado: voltando o `true` fixo, o teste fica vermelho |
| bloqueio "marcado pela web" | `origem` e `primeiraVisita` saíram do corpo comum. Medido: `temOrigem=False` |
| id do fechamento jogado fora | o repositório traz `fec.motivo`, e o recurso publica `fechamento{id, motivo}`. Medido: `id=262`, `motivo="luiz"` |
| testes escrevendo "Agenda fechada" à mão | passam pelo Serviço de verdade — e um revelou erro da minha premissa: a receita testa férias ANTES de agenda aberta |
| `$colaboradorId` nunca exercitado | o caso novo torna visível o segundo colaborador da fixture, para haver DUAS colunas: com uma só, o verde não diria nada |

### E o portão pegou a rota nova, duas vezes

`GuardaDeLoginTest` congela a inversão de 09/09: toda rota tem de bater com a regra
antiga, com **uma** divergência declarada. A `adm-api-sessao/ver` era a segunda — porque
a família dela não estava em `rotas-publicas.php`, ao contrário das 17 irmãs de
`/adm/api`.

O conserto não foi abrir exceção no teste. Foi pôr a família na lista, e isso resolveu
três coisas de uma vez: o endpoint passou a responder **403 JSON** em vez de 200 em
`text/html` (a página de login — o disfarce do D146), `apenasSessao()` deixou de ser
código inalcançável, e o teste voltou a ter uma divergência só. **Medido sem cookie: 403
`application/json`, 104 bytes.**

Depois disso, `conferir-rotas-sem-login.php` cobrou o nome no bloco congelado — o
segundo pego —, e ele entrou com a medição ao lado. As anônimas foram de 185 para 186.

### A sessão chegou à tela, e a agenda aprendeu a andar

Três coisas entraram depois do D190, todas no front e nenhuma dependendo de decisão.

**`/adm/api/sessao` passou a ser consumido.** Ele existia desde 20/09 e ninguém o
usava. Agora `ConsoleLigado` o consome, e a barra do topo mostra
**"Brasil - SC - Florianópolis - Santa Mônica"** e **"Gerente geral"** em vez de
"unidade não medida" — com os **124 privilégios** de verdade alimentando o menu, o
que fez sumir o aviso de que o menu estava mostrando tudo.

**Enquanto a sessão não responde, a moldura não monta.** O `Console` trata
`privilegios === undefined` como "é o administrador, mostre tudo"; deixar isso
aparecer por um segundo seria ausência de medição virando permissão. Falsificado:
montando cedo, quatro casos ficam vermelhos.

**A navegação por data.** A tela mostrava só hoje. O botão "hoje" **limpa a
escolha** em vez de mandar a data de hoje — mandar a data afirmaria o dia pelo
relógio do navegador, e um computador de balcão com a data errada pediria outro dia
sem nada acusar.

A aritmética é em UTC, e o teste guarda o motivo com um caso histórico: em
17/02/2019 o Brasil saiu do horário de verão e o relógio voltou para as 23h do dia
16, de modo que `16/02 00:00 + 24h` cai **no próprio dia 16**. Falsificado trocando
para fuso local: `expected '2019-02-16' to be '2019-02-17'`. O arquivo fixa
`TZ=America/Sao_Paulo` antes de qualquer `Date` nascer — em UTC, que é o padrão do
contêiner, as duas aritméticas dão a mesma resposta e o caso mediria nada.

**O painel de detalhe.** A revisão tinha acusado: a grade inteira era botão com
cursor de ação e nenhuma ação. E o dado já vinha na resposta, existindo só como
`title` — que **some no toque**. Medido no navegador, com clique de verdade:
`09:30 · Colaborador`, CLIENTE `Cliente 61670`, SERVIÇO `Barba`, TELEFONE
`(11) 90000-1670`, CONVÊNIO `PERMUTA CORTE E BARBA`.

**E fica uma lacuna declarada, o D192:** o painel não abre para quem só lê, porque
o botão está `disabled` — e a tela legada mostra o balão aos dois. Não tirei o
`disabled` junto porque ele é a única coisa que `podeEscrever` guarda hoje, e
arrancá-lo devolveria a guarda ao estado decorativo que a revisão acusou. Ele sai
quando houver ação de escrita para guardar. Há um caso de teste que **afirma a
lacuna**, para que ela não vire comportamento aceito por esquecimento.

Front: **87 testes**, de 65 ontem. `tsc --noEmit` limpo.

---

## 21/09/2026 — o D189 foi medido, e a medição desmentiu três coisas que eu já tinha escrito

Autorização explícita para **criar e remover o índice** na cópia local. Foi o que
se fez, nos dois motores, e os dois voltaram ao estado original — `SHOW CREATE
TABLE` com sha1 idêntico ao de antes no MySQL, lista de índices idêntica no
PostgreSQL, árvore do git limpa o tempo todo.

### O defeito, dito pelo número que não depende de ambiente

Os 14 colaboradores da unidade 1 somam **241.218 linhas** em `agendas`. Para
desenhar **um dia**, o plano lê todas — e devolve no máximo 189. O dia mais cheio
que esses colaboradores já tiveram tem 501 linhas. Um dia SEM nenhum agendamento
custa o mesmo que um cheio, porque o preço é a varredura e não o resultado.

### Os 22 segundos eram do meu laptop, não do sistema

Mesmo código, mesma consulta, mesmo dia, mudando só o tamanho do buffer pool:

| `montar()`, dia cheio, sem índice | pool de 128 MB | pool de 2 GB |
|---|---|---|
| tempo | 20,9 a 35,7 s | **0,90 s** |
| páginas vindas do disco | 3 a 5% | **0%** |

`agendas` ocupa 546 MB; o buffer pool deste ambiente é **128 MB, padrão de
fábrica** — o `docker-compose.yml` não o define e não há `.cnf` no projeto. A
tabela não cabe, e cada rodada relê do disco.

**E o valor de produção nunca foi lido.** A VPS tem 16 GB, mas o padrão do MySQL
é 128 MB *independentemente da RAM*. Enquanto ninguém ler, "a agenda leva 22
segundos" é afirmação sobre esta máquina. Entrou na lista da Fase 1.

### As duas metades consertam coisas diferentes, e a segunda é a que dura

| pool 128 MB, consulta da grade | sem índice | com índice |
|---|---|---|
| forma `DATE(age.data) = :data` (hoje) | 17,5 s | **0,080 s** |
| forma faixa | 15,6 s | **0,014 s** |

**O índice conserta hoje**, e o mecanismo é *Index Condition Pushdown* — medido
pelo contrafactual, não lido no rótulo do `EXPLAIN`: desligando o pushdown por
variável de sessão, com o índice posto, a forma `DATE()` volta de 0,080 s para
**30,1 s**. A forma em faixa não se mexe (0,014 → 0,016), porque ela busca em vez
de filtrar.

**A reescrita conserta amanhã.** Na forma `DATE()` o índice é usado com UMA coluna
de chave; a segunda vira filtro varrido sobre o histórico inteiro do colaborador.
Medido, variando só o histórico:

| linhas do colaborador | `DATE()` | faixa |
|---|---|---|
| 88.604 | 24,58 ms | 0,76 ms |
| 37.049 | 10,68 ms | 0,78 ms |
| 16.027 | 6,23 ms | 0,90 ms |
| 59 | 0,67 ms | 0,64 ms |

`DATE()` segue o histórico numa reta; a faixa é plana. Com só o índice, a agenda
encarece a cada agendamento gravado, para sempre.

### No PostgreSQL a reescrita é obrigatória — e isso é achado da Fase 1, não da agenda

**Não existe Index Condition Pushdown no PostgreSQL.** Medido no `postgres:16`
deste ambiente, mesmas 4.000.084 linhas, com o papel de aplicação `bvip_app`
(RLS ativo, sem `BYPASSRLS`) e o índice equivalente criado:

| com o índice | plano | tempo |
|---|---|---|
| forma `DATE(data) = :dia` | cai no índice VELHO e descarta 88.575 de 88.604 linhas | **237,5 ms** |
| forma faixa | `Index Scan` com a condição inteira, RLS incluída | **0,102 ms** |

**2.330×.** Na forma `DATE()` o planejador não alcança a coluna `data` de jeito
nenhum, nem com o índice perfeito disponível. **Aplicar só o índice no MySQL
produziria um conserto que evapora na migração.**

Dois parâmetros do Postgres apareceram de passagem e valem para *todo* plano do
sistema migrado, não só para a agenda: `random_page_cost` está em **4** — valor
de disco giratório, num servidor com NVMe — e com ele o planejador prefere varrer
4 milhões de linhas a usar índice; e `shared_buffers` está nos mesmos **128 MB**
de padrão de fábrica.

### A revisão adversarial, e o que ela corrigiu em mim

Cinco afirmações minhas, três céticos independentes cada, 15 de 15 agentes vivos
(`agents_error: 0` — conferido antes de crer no número, que é o que o D188 me
ensinou). **As cinco receberam objeção.** O que sobreviveu e o que caiu:

- **Caiu: "a contagem de páginas transporta para produção".** Retirada.
  `Innodb_buffer_pool_read_requests` é contador **global**, sem escopo de sessão;
  um verificador mediu deriva de fundo de 1,1 milhão numa janela de 20 s — maior
  que o número que eu atribuía à consulta. O que transporta é o do `EXPLAIN`:
  241.218 linhas examinadas para devolver 189.
- **Caiu: "a reescrita é um arredondamento".** Eu tinha escrito isso no meio da
  noite, corrigindo para o lado errado uma formulação que já estava certa. A
  tabela de crescimento acima é a resposta.
- **Caiu: "medir num dia vazio não invalida nada".** Serve de controle do estado
  SEM índice; não serve de prova do conserto, porque COM índice o dia vazio pula
  duas consultas e diverge do cheio por 21× em páginas. É *verde por ausência de
  dado*, e eu caí nele sem perceber: 16/09 não tem agendamento porque a cópia
  anonimizada **termina em meados de agosto** — inclusive o "hoje" que a tela
  abre por padrão.
- **Sobreviveu, contra o palpite deles: "o ganho é do ICP".** Um agente previu
  que a forma `DATE()` ficaria rápida mesmo sem pushdown. Medido: 375× mais
  lenta. Eles estavam certos em **exigir** o contrafactual e errados no palpite.

E um erro que peguei sozinho, antes deles: inflei o custo do `primeiraVisita` em
120× ao reproduzir sua consulta em SQL cru **incluindo o cliente 2** — o genérico
de balcão, com 63.517 vendas — que o serviço exclui de propósito. Medir o serviço
de verdade, em vez da minha transcrição dele, deu 0,026 s no lugar de 3,18 s.

### O que ficou provado sobre a reescrita

Ela é equivalente, verificado em **todas as 4.000.084 linhas** e não por amostra:
zero linhas caem fora da janela do próprio dia, zero datas nulas, inválidas ou de
ano zero. `data` é `DATETIME` e 3.999.907 linhas têm hora diferente de meia-noite,
então a faixa tem de ser `[dia, dia+1)` — nunca igualdade.

(A primeira tentativa foi amostrar 19.811 pares colaborador-dia. Desenho ruim:
mais lento e mais fraco. A equivalência é propriedade de cada linha e se prova na
tabela inteira com uma consulta.)

### O que o índice custa, e o que sobra depois dele

Criar leva **11 a 20 s** no MySQL (81,6 MB) e **6 s** no PostgreSQL (120 MB). Os
índices de `agendas` vão de 252 para 334 MB — menos que o índice de `cliente`,
que já existe.

Com o índice posto, o próximo da fila é `filtrarClientesPrimeiraVisita`: **23.409
das 25.991 páginas** de um `montar()` em dia cheio. É `vendas` com `cliente IN
(...) AND status = 1`, e `status` não está no índice. Mesma doença, outra tabela
— registrado dentro do D189, e não como débito novo, porque é a mesma forma e o
mesmo tipo de conserto.

**Nada foi consertado nesta rodada.** O pedido era medir, e o conserto toca uma
consulta que nove arquivos alcançam, mais um índice em produção. Isso é decisão
de quem manda.

---

## 21/09/2026, noite — o D191 caiu, e o defeito tinha um advogado escrito que o registro não citava

`/adm/api` respondia o **mesmo par** — `403 sem_permissao` — para "não entrou" e
para "entrou e não pode". Quem estava logado e só não tinha o privilégio era
mandado **refazer um login que já tinha**.

### A primeira leitura achou o que o registro tinha deixado de fora

A escolha do 403 estava **defendida por escrito**, dentro de um dos ajudantes
privados:

> *403, e nao 401. […] nao ha desafio a devolver, nem cabecalho
> `WWW-Authenticate` que faca sentido enquanto o mecanismo de token nao existir.*

Registrar como defeito algo que tem defesa escrita, **sem citá-la**, é o mesmo
erro que o D191 acusa: decidir sem olhar os dois lados.

**A defesa está factualmente certa e mesmo assim não vence.** A RFC 7235 exige
`WWW-Authenticate` no 401, e não há desafio honesto para sessão de cookie. Mas o
403 também é desvio, e é **o desvio que engana**: ele diz ao cliente que entrar
não adianta, quando entrar é exatamente o que resolve. A escolha não era entre
desvio e conformidade — era entre omitir um cabeçalho que nenhum cliente desta
casa lê, e mandar a pessoa tentar o que não funciona.

E `docs/convencao-api.md` **já tinha escolhido**, em dois lugares. O que havia era
uma segunda verdade sobre a convenção, morando num docblock privado que ninguém
lia junto com ela.

### A duplicação provou o próprio custo, no material

22 lugares guardavam a mesma regra. Medido, eles **já tinham divergido em quatro
formas**: 14 eram o método pelado, sem uma linha de motivo; um carregava a defesa
por extenso; um apontava para ela; e seis escreviam a recusa à mão dentro da
ação. **Catorze cópias seguiam um argumento que não sabiam existir.**

### O que entrou

**Uma regra, um lugar:** `ApiAdmController::semSessao()`, devolvendo `401
nao_autenticado`. As 16 cópias privadas saíram e as 6 recusas à mão passaram a
chamar o herdado — **152 linhas removidas contra 124 acrescentadas**.

O 403 ficou onde sempre deveria: nas **66 chamadas** de falta de privilégio, que
não foram tocadas. O mundo do aplicativo (`/api/v4`) também não — lá o 403 está
certo, porque o telefone **está** autenticado e o cliente é que não é dele.

**A tela voltou a afirmar a causa**, com dois ramos: "a sessão do ERP caiu", com
o link de entrar; e "esta tela não está liberada para o seu acesso", **sem** o
link — pôr a saída errada ao lado da certa é o defeito antigo em formato menor.

**E um ramo invertido apareceu no caminho:** `Comandas.tsx` usava `pedeLogin`
para mostrar *"sem permissão — fale com o gerente"*. A condição dizia uma coisa e
o texto a oposta, e passava porque a API respondia o mesmo código nos dois casos:
acertava metade das vezes por acaso.

### Falsificado dos dois lados

**No servidor**, por HTTP: devolvendo o defeito, as rotas voltam a **403**;
restaurado, **401** — com o arquivo de volta ao sha1 exato. **No front**, pela
suíte: devolvendo a cláusula do código ao `pedeLogin`, **dois casos ficam
vermelhos**, um em cada arquivo.

Front: **88 casos**, de 87. `tsc --noEmit` limpo.

### A lacuna que sobrou durou uma hora — e a escrita autorizada não foi usada

Faltava a prova **em tempo de execução** de que "entrou e não pode" continua
respondendo 403. Pedi autorização para a escrita que ela exigia, e recebi. Ao
executá-la, duas coisas:

**O roteiro da senha tem guarda própria, e ela recusou:** *"este banco tem 64
unidades — não é o de contrato. O roteiro TROCA SENHA, e não roda fora do cenário
de fixtures."* Ele protege exatamente o que a restrição do dono protege. Nada foi
escrito na cópia de produção, **nem com autorização na mão**.

**E o cenário certo já existia, fresco.** O CI roda esse ensaio contra o banco de
FIXTURES, e o `franquiabv_cilocal` — montado do zero pela suíte meia hora antes —
tinha a marca idêntica à do `fixtures.sql` de hoje. Bastou apontar o `local.php`
para lá, em passos discretos e sem confiar em `trap`, e devolvê-lo depois: sha1
`177f82c6`, com a restauração conferindo a si mesma.

| ensaio | resultado |
|---|---|
| recusa por falta de privilégio | **39 de 39 rotas com 403** |
| caso 1 do ensaio de recusas | `sem sessao` → **401 `nao_autenticado`** |
| caso 2 do mesmo arquivo | `sem privilegio` → **403 `sem_permissao`** |
| o ensaio inteiro | **23 de 23** |

Os dois lados ficam medidos **no mesmo arquivo**, que é o que impede uma correção
futura de derrubar metade sem ninguém ver. E a cópia anonimizada ficou intocada —
provado, não alegado: o estado dos dois usuários de fixture foi capturado antes e
comparado depois, **byte a byte idêntico**.

### E duas fichas que mentiam por um bloco no lugar errado

Antes de tocar no código, o registro do **conserto do D190** — feito ontem —
estava dentro da ficha do **D191**. Efeito: o D190 ainda dizia *"fica aguardando
quem manda"* e *"o portão que faltava"*, coisas que deixaram de ser verdade
quando ele foi consertado; e o D191, aberto, exibia uma seção "CONSERTADO" sobre
outro assunto. O bloco voltou para onde é.

---

## 21/09/2026, fim do dia — o conserto do D189, e a segunda metade mudou de forma quando foi medida

De manhã o D189 foi medido. À noite ele foi consertado — e a receita que a
medição da manhã tinha escrito **estava errada na segunda metade**.

### O que o registro dizia, e o que medir desmentiu

Dizia: reescrever o predicado e **criar** um índice composto. Medido:

| | grade de um dia | listagem por unidade |
|---|---:|---:|
| como está hoje | 17,5 s | 21,4 s |
| só a reescrita, sem índice | 15,6 s | 20,4 s |
| reescrita + índice **ao lado** do velho | 0,08 s | **30,5 s** |
| reescrita + índice **no lugar** do velho | **0,72 ms** | **95,7 ms** |

**A terceira linha é o achado.** Com o composto ao lado do
`FK_AGENDAS_colaborador`, o otimizador do MySQL **continua escolhendo o velho** —
estima o plano bom como mais caro (297.713 contra 258.624) e acredita nisso,
inclusive depois de `ANALYZE TABLE`. Com o composto **no lugar**, 320× melhor.

E sai **mais barato**: troca um índice de 69,6 MB por um de 81,6 — **+12 MB**, e
não +82. É seguro porque `colaborador` é PREFIXO do composto: testado em banco
descartável, o MySQL **aceita** remover o da chave estrangeira, e a restrição
continua de pé.

### O gêmeo, que ninguém tinha ligado ao D189

`selecionarPorUnidadeData` tem a mesma doença e o mesmo tamanho: o plano percorre
os **127 colaboradores** da unidade e, de cada um, busca o **histórico inteiro** —
389.636 buscas de linha para devolver 189.

**E isso explica um número que estava solto desde 16/09:**
`/adm/api/agendamentos` devolvia *"48 bytes em 73 segundos"*, reprodutível, e a
causa não era conhecida. É esta consulta.

### D194 — um defeito de ESCRITA que o `DATE()` escondia

Para trocar o predicado eu precisava saber o que os chamadores passam. Um deles,
`AgendaService::fechar()`, manda `DateTime::createFromFormat('d-m-Y', …)` — que
**não zera a hora: ela herda o relógio**. O MySQL, ao comparar `DATE(coluna)` com
um datetime, promove a data para meia-noite, então aquilo só casava **às 00:00:00
em ponto**. Nos outros 86.399 segundos do dia a consulta devolvia **zero** — e
`fechar()`, que decide o que bloquear por "este horário está livre", concluía que
a agenda inteira estava livre.

Medido **sem escrever nada**, reproduzindo só os dois passos de leitura, no dia
06/08: 34 horários julgados livres onde eram 5; 20 onde eram 7; 30 onde eram 12;
20 onde eram 15. São **65 horários com cliente marcado** que receberiam um
bloqueio por cima, num único dia e quatro colaboradores.

O código checa `status == 1` justamente para não fazer isso. A consulta vazia
derrotava a checagem.

O efeito para quem usa não é perder o dado: é o **barbeiro não ver que tem
cliente** naquele horário enquanto o fechamento existir — justamente quando ele
precisaria ligar para remarcar.

### O que entrou, e onde a regra passou a morar

A normalização foi para **dentro da consulta**, e não para cada chamador: é a
consulta que define o que "um dia" significa, e espalhar essa definição por oito
pontos foi o que deixou um deles divergir sem ninguém ver. A aritmética é em UTC
porque a coluna é `DATETIME` e a conta é de calendário — em fuso com horário de
verão `+1 day` pode valer 23 ou 25 horas, e a tabela tem linhas de 1924 a 2030.

No PostgreSQL o gerador de DDL passou a **ampliar** o índice —
`agendas_FK_AGENDAS_colaborador (inquilino_id, colaborador, data)`. A Fase 1
nasce com ele, sem índice redundante.

### Verificado

**Equivalência**, e não por amostra escolhida: 521 pares (colaborador, dia),
**6.362 linhas comparadas**, incluindo as bordas de meia-noite e de depois das
23h — **zero divergências**. Mais 4 de 4 no método por unidade.

**Falsificação:** devolvido o predicado antigo, **três casos ficam vermelhos** —
entre eles o que reproduz a expressão **inteira** que o fechamento usa. Os casos
montam a hora à mão (14:32:07, 23:59:59, 00:00:00) porque um caso que só usasse
`createFromFormat` seria **verde uma vez por dia sobre o defeito**.

Suíte: **670 casos**, 2.320 asserções, zero falhas. Corredor: 45 portões, nenhuma
falha inesperada.

### O D189 conta como PREPARADO, e não como corrigido

Sem o `ALTER` em produção **nada ficou mais rápido** — medido: a reescrita
sozinha dá 15,6 s contra 17,5 s. Contar um defeito de pé como fechado é o erro
que o D186 ensinou a não cometer, e a régua em disco existe justamente para não
deixar.

Ficaram seis predicados `DATE(age.data)` no mesmo arquivo, todos por cliente:
**115 linhas, 15 ms**. Mesmo padrão, outra ordem de grandeza — registrados, não
reescritos.

---

## 21/09/2026, depois — o D194 tinha uma dedução no fim, e ela virou medição

A ficha do D194 dizia, com todas as letras, que o último passo era **dedução**: a
consulta devolve zero (medido), a grade marca tudo livre com lista vazia
(medido), o serviço decide por esse estado (lido) — mas a linha de bloqueio
gravada por cima do cliente **não tinha sido vista**.

Agora foi:

```
com o conserto:   5 casos, 23 asserções, verde
com o defeito:    Failed asserting that 2 is identical to 1
```

**Dois é o defeito inteiro numa linha:** duas linhas no horário das 09:00 onde
deveria haver uma.

### E o arquivo que exercita `fechar()` já existia, e não pegava o defeito

`FecharAgendaTest` tem quatro casos desde antes, e todos passavam **antes e
depois** do conserto. O motivo é de **cenário**, não de código: eles fecham a
janela **10:00–11:00**, e o agendamento da fixture é às **09:00**. As duas nunca
se cruzavam.

Um teste que exercita o caminho certo com o recorte errado não é cobertura; é a
aparência dela. O caso novo fecha **08:00–12:00** — a única forma de a pergunta
ser feita — e traz o **controle** junto: ele também afirma que a janela bloqueou
*alguma* coisa. Sem isso, ficaria verde se `fechar()` não fizesse nada, e "não
faz nada" passaria por "faz a coisa certa".

### A armadilha é de classe, e medir disse onde ela NÃO chega

`createFromFormat` sem parte de hora é trap do PHP. Varrido o projeto, a função
central é `DataHora::stringToDate()`, com **28 chamadores** — quase todos setters
de entidade. O susto não sobreviveu à medição:

| coluna | tipo | com hora ≠ meia-noite |
|---|---|---:|
| `usuarios.ferias_inicio` | `date` | **0** de 253 |
| `usuarios.data_nascimento` | `date` | **0** de 2.428 |
| `clientes_pacotes.validade` | `date` | **0** de 3.475 |
| `saidas_parcelas.data_vencimento` | `date` | **0** de 15.076 |

Sete das oito são `date`: o banco trunca na escrita e a hora morre ali. **Mas o
mapeamento diz `type="datetime"` mesmo assim** — a segurança é acidente do
esquema, não escolha. Trocar qualquer uma dessas colunas para `datetime` na
Fase 1 **acorda a armadilha de uma vez**, e isso ficou registrado.

A exceção é `clientes.data_nascimento`, `datetime` na coluna e no mapeamento:
ali a hora chega ao banco, por mecanismo determinístico. **E a cópia anonimizada
não serve de prova** — os 242.592 zeros podem ser o anonimizador, já que data de
nascimento é dado pessoal. Medir ali seria medir o anonimizador, e não o sistema.

**Nada foi mudado em `DataHora`:** mexer numa função com 28 chamadores por um
dano que a medição não achou seria trocar risco conhecido por risco desconhecido.

---

## 21/09/2026, madrugada — o D192 fechou, e abrir a tela mostrou que a guarda estava no bit errado

A ficha do D192 prescrevia quatro passos, e os quatro aconteceram: o painel
ganhou **ação de escrita** (registrar a chegada), o `disabled` saiu do horário, o
painel passou a abrir para todo mundo — como o balão da tela legada faz desde
sempre — e o caso que afirmava a lacuna virou o contrário, de propósito.

`HorarioDaAgenda` **perdeu a prop `podeEscrever` inteira**, e `ColunaDoBarbeiro`
também: ela só repassava, e prop morta é o estado decorativo que a revisão
acusou em 20/09. A permissão passou a travar **o agir**, e não **o ver**.

### O defeito que a tela achou, e nenhum teste acharia

A primeira versão guardava o botão com `podeGerenciar` — o único bit de permissão
que a grade publicava. **Está errado.**

`podeGerenciar` é `agendas/gerenciar/escrever` e governa o **recorte**: telefone
e nome inteiro. As **ações** vivem em outro controller, que declara outra
funcionalidade — criar, check-in, remover e remarcar exigem
`agendas/visualizar/escrever`.

Medido sobre `privilegios_usuarios`, a camada que o `Acl` lê no login, entre os
**371 usuários ativos**:

| situação | usuários |
|---|---:|
| **PODEM agir, e `podeGerenciar` diz que não** | **164** |
| **`podeGerenciar` diz que sim, e o servidor RECUSA** | **94** |
| têm os dois | 87 |
| não têm nenhum | 26 |

**258 de 371 — 70% — ficariam com o botão errado.**

E não é estatística. Provado **na tela, com dois usuários reais**, sem escrever
nada — id inexistente devolve 404 quando o privilégio passa e 403 quando não
passa, e a conferência vem antes da busca:

| usuário | privilégio de escrita | grade dizia | servidor |
|---|---|---|---|
| `user380` | `agendas/visualizar/escrever` | `podeGerenciar: false` | **PERMITE** |
| `user5` | `agendas/gerenciar/escrever` | `podeGerenciar: true` | **RECUSA** |

Exatamente invertido.

### O conserto do conserto

O recurso da grade passou a publicar **os dois bits**, cada um com o nome do que
governa. O parâmetro novo do formatador é **obrigatório**, e não com valor
padrão: padrão silencioso é como um chamador esquece e ninguém vê.

E **o aviso da tela nomeava o privilégio errado** — dizia "falta
`agendas/gerenciar/escrever`" a quem, na verdade, pode agir.

**Uma correção minha no caminho:** a primeira medição disto eu fiz sobre a tabela
`privilegios`, que é só o **molde** do grupo, e a sessão do `user200` me
desmentiu na hora — ela trazia privilégios que o molde do grupo dele não concede.
A camada que decide é `privilegios_usuarios`, e o projeto diz isso por escrito.

### E o CSS, que o jsdom nunca contaria

`.agenda-detalhe-acoes` **não existia** na folha de estilo: o envoltório da ação
herdaria a margem padrão de parágrafo. Os casos ficariam verdes com a classe
existindo ou não.

Conferido depois na tela viva, com sessão de verdade: painel aberto em "10:00,
agendado · marcado pela web · Cliente 15052", com cliente, serviço, telefone,
convênio e o botão **Registrar chegada** — margem de 9px, borda,
`cursor: pointer`.

### Falsificado três vezes

| o que se devolve | vermelhos |
|---|---:|
| a permissão deixa de guardar a ação | 2 |
| o painel volta a não abrir para quem só lê | 2 |
| a tela volta a ler o bit do **recorte** | **4** |

Os dois casos da distinção existem porque todos os outros movem os dois bits
**juntos**, e passariam com a tela lendo qualquer um deles.

Front: **94 casos**, de 88. Suíte: 671, zero falhas. Corredor: 45 portões.

### O D195, que é a causa de raiz e precisa de você

A agenda é governada por **dois privilégios diferentes**, e ninguém decidiu isso:
foi consequência de as ações terem ido para outro controller. Unificar **tira
acesso** de um dos dois grupos — 164 ou 94 pessoas —, e 347 linhas de
`privilegios_usuarios` divergem do molde (D157), então parte disso pode ser
decisão consciente de alguém. É decisão de produto, e está pedida.

---

## 21/09/2026, tarde — o D195 não era uma decisão, e atacá-lo achou uma porta aberta desde a importação inicial

Eu tinha registrado o D195 como **decisão de produto**, com quatro opções e a
frase *"precisa de você"*. Estava errado, e o erro era meu: **uma das duas
regras é a que o ERP usa há anos, e a outra nasceu por acidente de lugar.**

### O que a medição desfez

Agir sobre a agenda no ERP pede `agendas/gerenciar/escrever` — são **13 ações**
guardadas pela mesma expressão, e o docblock diz isso com todas as letras. A API
nova pedia `agendas/visualizar/escrever`, e não por escolha: as ações foram
parar num controller que declara essa funcionalidade, e a guarda seguiu a
declaração.

**E o bit que a API nova escolheu nunca governou nada de verdade na agenda.** A
única ação do ERP guardada por ele era a de remover bloqueio — e essa estava com
a guarda invertida (abaixo).

### A quinta opção, que não estava na minha lista

A lista de quatro opções tinha um erro de análise. Eu escrevi que mover as ações
para `gerenciar` *"tiraria a ação de 164 pessoas"*. **Não tira.** Essas 164
pessoas **não conseguem agir na agenda hoje** — o ERP as recusa. O único lugar
onde podiam era a API nova, que **não tem consumidor em produção**.

| opção | quem perde algo que USA hoje |
|---|---|
| exigir os dois para agir | 94 |
| aceitar qualquer um | ninguém perde, mas 164 ganham o telefone |
| **alinhar a API ao ERP** | **ninguém** |
| mover o recorte | 94 perdem o telefone |

A terceira entrou. Não é decisão de produto: é a Fase 2 cumprindo o que promete
ser — **fachada**. A regra de negócio não muda de lugar.

### O D196, que apareceu no caminho e é pior que o D195

`bloqueiosRemoverAction`, no ERP, **desde a importação inicial**:

```php
if ($this->getAcesso()) {          // sem o "!"
    return $this->notFoundAction()->setTerminal(true);
}
...
$this->service->abrir($fechamento);   // remove() + flush()
```

**O sinal está trocado.** Quem tem o privilégio leva 404; quem não tem, apaga.
Era a única guarda das 22 do arquivo com essa forma.

| | |
|---|---:|
| fechamentos apagáveis, por GET | **134.334** |
| unidades que eles cobrem | **48** |
| clicavam Remover e levavam 404 | **260** de 366 |
| passavam e apagavam | **106** |

Os 260 recusados **incluem os 10 administradores**. E o botão aparece para todo
mundo: a tela desenha **Remover** em cada linha sem conferir nada. Quem gerencia
a agenda clicava e recebia uma página 404 — por anos.

Havia mais dois defeitos, **os mesmos que o D190 consertou na ação gêmea** em
20/09: `getReference()` tornando o ramo de "não existe" código morto, e
**nenhuma conferência de franquia** — o helper existe, escrito no D190, e não
era chamado aqui.

**Por que ninguém pegou:** o D150 contou esta ação como *já guardada* — ele
olhou se **havia** guarda, não se ela guardava. O portão de recorte não a
vigiava (a lista é escrita à mão, e nem ela nem a gêmea estavam lá). E nenhum
portão olha polaridade.

### Os meus números estavam errados, e a revisão adversarial pegou

A medição que publiquei ontem ignorava duas coisas: **administradores passam por
fora da tabela de privilégios** (`Acl.php:29-30` libera tudo para quem tem
`administrador = 1`), e **5 dos "371 ativos" eram usuários de fixture meus**.

São **366** pessoas reais, com **10** administradores. **O racha 164/94 estava
certo**; o denominador e os outros dois baldes, não. E a conta só fechou depois
de achar **onze pessoas sem uma única linha de privilégio** — dez administradoras
e o **usuário 1377**, com 6.005 acessos, que não pode nada na agenda exceto
passar pela guarda invertida.

### O instrumento que não existia

Nenhum portão distinguia os dois bits, e a razão é estrutural: **o único login
de ERP da suíte de contrato tem os dois**, então qualquer uma das quatro opções
passava. Verde por indistinção não é verde.

`conferir-guarda-da-agenda.php` confere **19 ações** nas duas pilhas — o
privilégio, a polaridade e o total de ações. Falsificado nas três direções; a
terceira me pegou chutando 22 ações onde havia 20.

Corredor: **47 portões, EXIT=0**. Suíte: 671 testes, 2.326 asserções.
Front: 94, `tsc` limpo.

### O que continua sendo seu

Se o **ERP** deve mudar a própria regra: hoje quem enxerga a agenda não pode
marcar nela, e são 164 pessoas assim. Isso é produto, e nada quebra enquanto
espera.

---

## 22/09/2026, madrugada — o CI está parado há oito dias, e medir isso derrubou três coisas que esta página afirmava

### O bloqueio é real, e agora tem data de hoje

Pedi **uma** reexecução, sobre o commit que já estava no remoto, sem empurrar
nada. Rodou às **23:31 de 21/09** e falhou em **10 segundos**, com a anotação de
cobrança idêntica à de 13/09. **Oito dias.** Medido, não suposto.

### E há um segundo motivo, que é meu

A última execução do CI é de **16/09** — não porque o bloqueio tenha começado
ali, mas porque **nada foi empurrado desde então**. O remoto está em `d55036d`;
há **18 commits locais** que o GitHub nunca viu, incluindo os consertos do D189,
D191, D192, D194, D195, D196 e D197.

Esta página dizia que *"os commits ficam vermelhos no histórico"*. Pior que
vermelho é **não estarem lá**. **Empurrar é sua decisão** — com o bloqueio de
pé, cada push vira mais uma fileira de vermelho, e o vermelho continua sendo da
conta.

### A lista dos "seis portões que só existem no CI" estava errada, nos dois sentidos

Conferidos um a um: **os seis rodam no corredor local**, e dois deles nem estão
mais no CI. A frase que justificava o alarme deixou de ser verdade em algum
momento que ninguém registrou — e continuou sendo repetida, inclusive por mim,
no fim de várias sessões.

**Os que de fato só existem lá são 14.** Derivados do `ci.yml` contra o
`conferir-tudo.sh`, não digitados:

| estado | quantos |
|---|---:|
| **medidos aqui, e verdes** | **3** |
| pedem o banco de contrato | 8 |
| estoura o tempo desta máquina | 1 |
| não rodado de propósito — ESCREVE | 1 |
| roda, mas sozinho | 1 |

**E o motivo de serem "só do CI" era mais bobo do que parecia:** os dois do
PostgreSQL resolvem o host `postgres`, que só existe dentro da rede do compose.
De dentro do contêiner eles rodam e passam. **Ninguém tinha tentado.**

### O instrumento novo diz em voz alta o que não foi medido

`conferir-lacuna-do-ci.sh` deriva a lista a cada execução, **exige que cada
passo exclusivo seja declarado** com o que defende, e roda o que dá. Na primeira
execução ele me pegou: eu havia deixado um passo de fora. O que não roda sai
como **NÃO MEDIDO**, nunca como verde.

E o CI ganhou **gatilho manual**: até hoje só disparava por `push`, então testar
a cobrança exigia empurrar. Agora é `gh workflow run CI` — uma pergunta, sem
publicar nada.

### Três falsos vermelhos meus, e o terceiro quase cobrou dinheiro

O primeiro custou uma rodada inteira: **11 portões vermelhos** que eu sabia
estarem verdes — era o Git Bash convertendo `/var/www/html/...` em caminho do
Windows antes de o `docker` ver.

O segundo era de classificação: oito saíram como vermelho com **saída 2**, e 2
nesta casa é *"não consegui medir"*. Contar isso como vermelho enche o relatório
de alarme falso até ninguém mais olhar.

**O terceiro não aconteceu porque uma guarda de 14/09 estava lá:** o medidor de
cron pede rotinas, e **duas delas cobram de verdade** pelo Asaas. A lista `FORA`
do próprio roteiro as exclui. Conferido antes de deixar rodar.

### E o portão da cobertura era cego a 35% do sistema

Atacando o D193 na sequência: a ficha dizia que ele tinha perdido **duas**
chamadas de vista. Medido, perdeu **noventa e sete**.

| | declarado | real |
|---|---:|---:|
| pares (repositório, método) | 179 | **276** |
| **sem teste nenhum** | 64 | **153** |
| ISApi | 24 | **121** |

A parte cega não é periferia: royalties, NPS, dashboard, cupons, sorteio,
pontuação e vendas. **97 pares novos, nenhum perdido.**

**A base não subiu, e isso é deliberado:** com o alcance maior, as chamadas que
o resolvedor não lê vão de 6 para 22. Congelar um número já sabidamente
incompleto seria trocar a fechadura e perder a chave. O que entrou foram **duas
formas de olhar** — antes era impossível listar os pares sem quebrar o portão de
propósito.

### O que continua sendo seu

**Destravar a cobrança**, em *Billing & plans*, e **decidir sobre os 18
commits**. Enquanto nenhuma das duas acontece, **11 dos 14 não estão
conferidos** — e agora há um roteiro que diz isso toda vez, em vez de depender
de eu lembrar.

---

## 22/09/2026, tarde — a suíte que falhava sozinha perdeu o esconderijo

O **D66** é o débito mais antigo de instrumento desta casa: desde 31/08, a suíte
de contrato falha de vez em quando **sem mudança de código**, sempre do mesmo
jeito — *"conectou e a aplicação não respondeu byte nenhum"*. Três dias de
medição hoje, a pedido seu.

### A minha hipótese de ontem caiu

Ontem medi que o pool do PHP-FPM tem **5 filhos** e já bateu no teto nove vezes,
e apostei que era isso. Rodei a suíte quatro vezes observando o FPM:

| # | condição | duração | resultado |
|---:|---|---:|---|
| 1 | logo após recriar o banco | 237 s | **3 falhas** |
| 2 | quente | 106 s | OK |
| 3 | recriando o banco de novo | 108 s | OK |
| 4 | com o limite novo | 94 s | OK |

**O flake reproduziu — e o pool não encheu em nenhuma das quatro.** A hipótese
morreu na medição que eu mesmo propus. A de custo frio também: a rodada 3 teve a
condição idêntica à 1 e passou.

### O que apareceu no lugar é mais afiado

```
innodb_lock_wait_timeout   50 s
CURLOPT_TIMEOUT            30 s   (era)
```

**O instrumento desistia antes do banco.** Numa espera por trava o MySQL aguarda
50 s; o curl largava aos 30. Uma trava, portanto, **nunca podia aparecer como
trava** — chegava sempre como silêncio, igual a worker preso, chamada de rede
sem tempo limite ou laço infinito. A ficha dizia desde 31/08 que *"com 120 s
passa tudo — é tempo, e só tempo"*. Não era só tempo: era o limite passando por
baixo do limite do banco e apagando a única pista que teria nome.

### E você mandou provar segurando a trava de propósito

Uma transação que **não grava nada**, auto-liberável, no banco descartável:

- **Durante o preparo:** estourou com `1205 Lock wait timeout exceeded` e o SQL
  por extenso, antes de rodar um caso. Trava perdida não vira vermelho esquisito
  — estoura na porta, com nome.
- **Durante os testes:** a falha chegou como **HTTP 500**, recebido. Com os 30 s
  de antes, teria chegado como silêncio. **O conserto está provado por
  experimento**, não por aritmética.

E o relatório ainda jogava fora a testemunha: dizia *"500 is identical to 202"*
e nada mais. Agora diz o endereço, o corpo, por que o corpo é genérico, e **em
que registro está o erro de trava por extenso**.

### Sem querer, reproduzi o mecanismo que eu suspeitava

Ao encerrar o primeiro braço do ensaio, **a transação continuou de pé**: o
cliente morreu e a conexão dentro do contêiner sobreviveu segurando as linhas.
Precisei derrubá-la à mão. É exatamente o que eu suspeitava da rodada 1 — antes
dela eu tinha interrompido uma rodada de portões no meio.

### O que isto não prova

**Não prova que a rodada 1 foi trava.** Prova que, se for, da próxima vez ela
chega com nome. O flake continua sem causa atribuída; o que ele perdeu foi o
esconderijo.

---

## 22/09/2026, noite — o portão da cobertura passou a ver os serviços

Ontem medi que o portão que vigia *"cada método de repositório chamado tem
teste?"* era cego a 35% do sistema — tudo o que mora em serviço — e **não
estendi**, porque 16 chamadas ficavam ilegíveis e congelar um número sabidamente
incompleto seria trocar a fechadura e perder a chave. Hoje o conserto entrou,
na ordem que a ficha pedia.

### A causa que eu tinha escrito estava errada

Ontem escrevi que as 16 eram atribuições "fora do alcance" do leitor de código.
Lendo, era mais simples: **o leitor guardava as variáveis por arquivo, e PHP as
guarda por função.** Num serviço grande, a mesma variável é um repositório num
método e outro no método seguinte — e o leitor a marcava como ambígua no
arquivo inteiro, perdendo inclusive as chamadas onde ela era inequívoca.

Escopo por função entrou, e o número mudou na direção que tinha de mudar:

| | antes | agora |
|---|---:|---:|
| pares vigiados | 179 | **285** |
| chamadas que o leitor não consegue ler | 22 | **2** |

**Nenhum dos 179 saiu**, e as duas que ficam foram lidas uma a uma — nenhuma
esconde par. Essa era a condição para a linha de base subir, e ela subiu.

**E uma apareceu que diz a verdade:** num controller, o repositório chega por
parâmetro, e o leitor antigo o "resolvia" com a atribuição de **outro método**
— certo por coincidência de nome. O novo diz o que de fato sabe.

### Falsificado

Apagar a única chamada de um par que só um serviço alcança derruba o portão.
Devolver o escopo por arquivo derruba o portão. Restaurado byte a byte;
corredor inteiro verde.

### O que isto entrega, e não fecha

**106 métodos de repositório passaram a ser vigiados, e 96 deles não têm teste
nenhum.** Só o de vendas tem 25. Não é defeito deste débito — este débito era
*não ver*, e agora vê. É o número que a leitura de cobertura precisa refazer,
porque a de 12/09 foi feita sobre um conjunto que não os tinha.

---

## 22/09/2026, noite — a remoção de bloqueio deixou de ser um link, e medir isso achou o padrão da casa inteira

### O conserto

A remoção de bloqueio da agenda apagava por **GET** — um link. Link que apaga é
apagado por qualquer coisa que siga link: o pré-carregamento do navegador, uma
imagem numa página de fora, alguém abrindo a URL do histórico. Das treze ações de
escrita da agenda no ERP, **doze já exigiam POST**; esta era a exceção.

Agora exige POST, e a tela envia POST. **Verificado sem apagar nada:** um GET feito
por quem *pode* remover, sobre um bloqueio *da unidade dele*, devolve 404 — e a
linha continua idêntica ao backup, byte a byte. No navegador, o clique real abre o
modal com o formulário POST apontando para o bloqueio certo. O "Sim" não foi
apertado: seria escrita na cópia de produção.

Um portão ganhou a regra, e outro aprendeu um caso: o que confere se todo botão tem
quem o sirva só olhava os arquivos de script globais, e acusou este como morto.

### O que a medição achou ao redor

**A sessão sai sem proteção.** O arquivo de configuração promete que o cookie de
sessão tem `HttpOnly` e `SameSite` — e o cookie sai sem os dois, porque essa
configuração **nunca é aplicada**: ninguém pede ao sistema a peça que a leria. Sem
`HttpOnly`, qualquer script na página lê a sessão; sem `SameSite`, o POST sozinho
não fecha a requisição forjada, e as remoções não conferem token de formulário
(os cadastros conferem — uma versão anterior desta frase dizia "em lugar
nenhum", e estava errada).
**D198.** É conserto de uma linha com uma pergunta na frente: o que mais depende do
cookie numa volta vinda de outro site.

**O GET que apaga é o padrão da casa, não a exceção.** A função que abre o modal de
"Deseja realmente remover?" em 22 telas remove navegando — por GET. **Todas as 21
ações que ela alcança aceitam GET.** E o inventário de ações classifica **seis delas
como leitura**, porque decide "altera algo?" por escrever direto ou receber POST —
e elas delegam a escrita a um serviço e aceitam GET. Entre as seis: **apagar parcela
de pagamento, cancelar retirada do caixa, cancelar movimentação de estoque.**
**D199.**

A primeira contagem, casando só o nome da ação, dizia 23 de 27. Casando controller e
ação, 21 de 21 — os "que exigiam POST" eram homônimos de outros controllers.

## 22/09/2026, fim da tarde — as três remoções que mexem em dinheiro passaram a POST, e a leitura em volta achou o defeito mais grave aberto

### O conserto

**Excluir parcela de conta a pagar, cancelar retirada do caixa e cancelar
movimentação de estoque** deixaram de ser links. As três exigem POST, e as três
telas enviam POST. **Verificado sem apagar nada:** um GET de quem *pode* fazer a
ação, sobre uma linha *da franquia dele*, dá 404 — e as cinco linhas envolvidas
(parcela, movimentação, quantidade do produto, retirada, saldo do caixa) ficaram
idênticas ao backup. No navegador, o clique real abre o modal com o formulário
POST apontando para a movimentação certa; o "Sim" não foi apertado.

**Cancelar retirada creditava o caixa em dobro.** Ninguém conferia se ela já estava
cancelada: um segundo clique devolvia o valor de novo, e a trava que impede
retirar mais do que o caixa tem lia o saldo inflado. Agora o serviço recusa, e a
tela avisa. Um teste novo ficou vermelho antes do conserto e verde depois.

As quatro telas que removem por POST passaram a usar uma função só, num arquivo
próprio. **Um portão novo** confere que tela e ação concordam — tela em POST com
ação que aceita GET, ou o contrário, que dá **botão morto** — e trava em 19 as
remoções que ainda são por GET: só pode descer.

### O que a medição achou ao redor

**Os botões de editar e remover parcela nunca apareciam** (**D200**). A tela
comparava um valor já formatado — `"0,00"` — com zero; no PHP 7 isso era
verdadeiro, no PHP 8 é falso. Consertado: **463 parcelas, em 18 unidades,**
voltam a mostrar os dois botões, como a regra da tela pede.

**A API nova do catálogo deixava ver, editar, movimentar, ativar e desativar
produto de QUALQUER franquia** (**D201**) — **112 pessoas em 54 unidades**
tinham esse alcance. Consertado com a mesma regra da tela do ERP, e medido: 404
para a franquia alheia, a própria continua respondendo. **O meu primeiro conserto
deixou duas das sete rotas de fora**; achei ao montar a tabela a partir das rotas
reais, e não de memória, e medi o furo antes de fechá-lo.

### A revisão adversarial, e o que ela derrubou

Seis agentes: três leitores, um por ação, e um adversário para cada leitura. Os
seis terminaram. Derrubaram **duas afirmações minhas**:

- **O portão novo olhava se a guarda existe, não se ela guarda** — procurava o
  texto `isPost()`, e com o sinal trocado ficaria verde. Consertado para ler a
  forma da guarda, e **falsificado nas três inversões possíveis**. O portão da
  agenda tinha a mesma fraqueza e ganhou o mesmo conserto.
- **O limite que escrevi para o conserto estava com o motivo errado.** O duplo
  clique no mesmo navegador não corre — a sessão em arquivo faz o segundo pedido
  esperar o primeiro. A corrida que sobra é entre sessões diferentes.

### O que ficou aberto — e o primeiro é o mais grave do projeto hoje

**D204 — quem pode editar usuário se põe no grupo de administrador.** A lista de
grupos que a tela oferece é só da tela: o servidor aceita qualquer grupo. São
**107 pessoas em 54 unidades** com esse privilégio, e administrador passa em toda
guarda de toda franquia. Somado ao **D202** — seis telas de editar conferem a
franquia de um id e gravam outro, o do campo oculto —, dá para trocar o e-mail
de um administrador e pedir "esqueci a senha". **Conferido por leitura, não
medido**: medir é gravar, e isso pede decisão sua — autorizar a escrita no banco
de desenvolvimento, ou montar o caso num banco descartável.

Também registrados, sem conserto: **D203** — pelo menos 122 textos do banco
impressos sem escape em 37 telas, o que anula a proteção do POST (latente: nenhum
motivo de retirada tem `<`); **excluir saída contorna a regra que protege parcela
com pagamento** (12 parcelas, R$ 8.308,79); **139 caixas continuam abertos**, o
mais antigo de 2015, e o cancelamento de retirada aceita qualquer um deles.

### Verificado

Corredor verde, com o único vermelho conhecido (D50). Caracterização **675** e
contrato **67**, nos **dois** bancos. E os cinco ensaios que só existiam no CI —
permissão, recusas, fluxo da API, escritas do aplicativo, rotas públicas —
rodaram aqui contra o banco de contrato, com o arquivo de configuração trocado e
restaurado idêntico. Ficam sem medir os três medidores do PostgreSQL, a rotina de
cron (estoura o tempo nesta máquina) e o ensaio que escreve na agenda (nunca aqui).

## 22/09/2026, noite — excluir a saída deixou de esconder parcela que já recebeu dinheiro

### O conserto

Em 09/09 a casa fechou uma porta: parcela de conta a pagar que já recebeu
dinheiro não pode ser excluída. E escreveu que, com isso, "uma parcela excluída
nunca mais representará dinheiro que saiu". **Estava errado.** Havia uma segunda
porta, um nível acima: excluir a **saída inteira** conferia só se alguma parcela
estava *marcada como quitada* — e dinheiro lançado nem sempre liga essa marca.

**Medido na cópia:** 12 saídas, de 3 unidades, passavam com R$ 8.308,79 pagos —
4 com pagamento parcial, 7 pagas por inteiro sem a marca, e 1 com um pagamento
lançado de R$ 0. Agora a exclusão de saída faz a mesma pergunta que a de
parcela — *esta parcela já recebeu dinheiro?*, pela marca, pelo valor pago e
pelo rastro dos pagamentos —, e recusa. A regra mora num lugar só, por onde a
tela antiga e a API nova passam.

### A revisão achou, e eu corrigi

Três leitores independentes e um cético por achado; treze agentes, todos com
resultado. Eles derrubaram três coisas **minhas**: eu tinha chamado os 12 casos
de "pagamento parcial"; a mensagem nova dizia "com pagamento" também para 335
saídas que só têm a marca, sem dinheiro — agora diz "quitada ou com pagamento";
e os testes não distinguiam duas mutações, porque no cenário as contagens
coincidiam — dois casos novos as separam, e as mutações aplicadas deram
vermelho. E acharam um defeito antigo no caminho: a parcela recém-criada nascia
com a lista de pagamentos **nula**, e perguntar por ela quebrava. Consertado.

Ficaram registrados, sem conserto, e o primeiro é o próximo da fila: a tela de
**identificação** da saída grava com o formulário inteiro e pode inativar a
saída sem passar por esta guarda — mais uma da família do D202.

### Verificado

Um teste novo lança um pagamento parcial pelo caminho real e pede a exclusão:
**falhou antes, passa depois**. Caracterização **680** no MySQL e no
PostgreSQL, corredor verde, e os cinco ensaios da API contra o banco de
contrato. Pela porta real, como o gerente de uma das três
unidades, a exclusão de uma dessas saídas foi **recusada**, e nada mudou no
banco — conferido contra o backup.

## 22/09/2026, noite — a tela de identificação da despesa grava só o que mostra

### O conserto

A tela que edita uma despesa mostra dois campos: o **tipo** e a **descrição**.
Mas ela entregava ao sistema o formulário inteiro, e o sistema gravava cada campo
que viesse. Havia um terceiro campo, a **situação**, desenhado numa linha
escondida — e por ele a despesa podia ficar inativa sem passar pela exclusão,
que é onde está a regra consertada horas antes (D205). E o registro gravado era
escolhido por um campo oculto do formulário, e não pela despesa da página.

Agora a tela grava **só tipo e descrição, e sempre na despesa da página**. O
campo de situação saiu. A situação de uma despesa só muda pela exclusão. Na
cópia, nenhuma despesa está inativa: a porta nunca foi usada.

### Provado, e o que não foi

Um teste manda a situação "inativa" junto com a edição: **falhou antes, passa
depois**. O cadastro, a troca de tipo e o filtro do formulário ganharam teste
próprio, e cada um fica vermelho quando o conserto é desfeito. **684 testes nos
dois bancos**, corredor verde, e as duas telas abrem.

**O envio do formulário não foi exercitado por pedido real** — salvar grava, e o
usuário de testes não tem o privilégio de saídas. O medidor da API, rodado a
pedido da revisão, parou na guarda de permissão, e isso ficou escrito.

### A revisão

Seis agentes independentes, **nenhuma regressão**. Os pontos fracos que eles
acharam eram de prova — um caminho sem teste, um teste que não via o tipo mudar,
e um comentário meu que prometia um vermelho diferente do que eu tinha visto —, e
foram corrigidos. O próximo da área é o **estorno de pagamento** no ERP, que
ainda grava sem as conferências que a API faz.

## 22/09/2026, noite — o estorno de pagamento no ERP deixou de estornar duas vezes

### O conserto

Estornar um pagamento de despesa tira o valor do que a parcela tem pago. A API
nova conferia, antes de estornar, de que franquia era o pagamento e se ele **já
tinha sido estornado**; a tela antiga não conferia nenhuma das duas, e cada
estorno repetido tirava o valor de novo. Agora a regra mora num lugar só, por
onde as duas passam, e a tela antiga confere a franquia como as telas vizinhas.
Na cópia, são 126 estornos e **nenhum repetido** — o defeito estava à espera.

### Provado

Dois testes — estornar duas vezes, e estornar de novo depois de a parcela ter
sido paga outra vez — **ficam vermelhos sem a regra** e verdes com ela. **686
testes nos dois bancos**, corredor verde. E pela tela real, o gerente de uma
unidade pediu o estorno de um pagamento já estornado: **recusado**, e nada mudou
no banco.

### O que o caminho achou

**Um portão meu enganava.** Ele confere que a guarda de franquia continua
escrita em cada ação vigiada — e ficou verde com a guarda arrancada, porque o
comentário que a explicava citava o nome dela. Agora ele lê o código **sem
comentários**, e a marca desta ação é específica o bastante para pegar o erro
mais provável, que é copiar a linha da ação vizinha.

**E a revisão achou o próximo débito (D208):** lançar e pagar parcela pela tela
antiga não validam o que recebem. O mais sério: um número de parcelas que não é
número faz o sistema gravar parcelas **sem parar**, até o tempo da requisição
acabar. Conferido lendo o código, não executado.

## 22/09/2026, madrugada — lançar e pagar parcela pela tela antiga passaram a conferir o que recebem

### O conserto

A tela antiga de contas a pagar gravava o que chegasse. Um número de parcelas
que não fosse número fazia o sistema gravar parcelas **sem parar**; uma data
como 31/02 virava 03/03; valor negativo passava; quem pedia 12 parcelas e
esquecia o período recebia uma, com "sucesso"; parcela e despesa excluídas
aceitavam pagamento; e uma observação longa demais deixava o valor pago gravado
sem o pagamento. Agora tudo isso é conferido **antes** de gravar, num lugar só,
por onde a tela antiga e a API passam, e o pagamento vai junto com o pago da
parcela.

### Provado

Os testes novos falharam antes do conserto — o do teto gravava **101 parcelas em
vez de 1** —, e passam depois: **701 testes nos dois bancos**, corredor verde, a
tela abre. E cada conferência tem um teste que fica vermelho quando ela é
arrancada: seis mutações, seis pegas. O caso do número que não é número não foi
rodado contra o código antigo, porque ele não termina.

### Duas perguntas para o dono

**Pagamento de R$ 0 continua aceito pela tela antiga** — há 26 na cópia, em 4
unidades, o último de fevereiro. A API nova recusa. É prática, ou deveria passar
pelo "quitar sem pagamento"? **Valor negativo passou a ser recusado** — eram 4 na
cópia, o último de 2023, com cara de ajuste; o caminho para isso é o estorno.

### O próximo

A revisão achou o **D209**: parcelas mensais lançadas no dia 29, 30 ou 31 pulam
um mês — de 31/01 a próxima vence em 03/03, e todas as seguintes no dia 3.

## 22/09/2026, manhã — o desenho do Claude Design começa a entrar no front, e o menu volta a ter tudo o que o ERP tem

### O pedido

Aplicar no front o desenho feito no Claude Design, com as mudanças de servidor
que ele precisar. O trabalho vai em etapas; esta é a primeira, e começou pelo
menu, porque foi onde a comparação achou um defeito.

### O que estava errado (D210)

O menu novo escondia **nove destinos** que o ERP antigo mostra — de todo mundo,
o administrador inclusive: Contas a Pagar inteiro, Privilégios, Versão, Push,
Serviços, Faturas, Docs APIs e o Acesso Yungas. Quando o menu foi copiado do ERP,
a guarda desses nove não foi lida, e o menu esconde o que não sabe. A regra
estava certa; faltava medir.

### O conserto

As nove guardas foram lidas no ERP e copiadas. Duas delas não são permissão, e
sim o **papel** da pessoa: Faturas é de quem é **gerente**, e Docs APIs de quem
é **administrador** — e administrar não é gerenciar, são marcações diferentes do
grupo. Para o menu saber quem é gerente, a sessão da API passou a informar isso.

### Provado

Testes novos para a sessão, incluindo um que o sistema não tinha: o token de
acesso e a chave de WhatsApp da unidade **não saem** na resposta. **706 testes**
nos dois bancos, e **100 no front**. Cada regra nova tem um teste que fica vermelho quando
ela é arrancada.

### O próximo

O resto da moldura: o tema seguindo o sistema e lembrando a escolha, a tela
ocupando a largura inteira, o menu sumindo em tela estreita e a Home só com a
agenda. Depois a comanda, e depois a agenda.

## 23/09/2026, madrugada — a moldura do desenho: o tema acompanha o sistema, a tela usa a largura inteira e a Home é a agenda

### O que mudou para quem usa

- **O tema acompanha o computador.** Quem usa o sistema no escuro abre a tela no
  escuro; até agora abria sempre no claro. Quem escolher um tema tem a escolha
  lembrada — e escolher de novo o tema do computador volta a acompanhá-lo.
- **O modo computador ou toque também é lembrado.**
- **A tela usa a largura inteira.** A agenda ficava presa a uns 990 px no meio
  de um monitor largo; agora ocupa tudo. O menu tem a altura da tela, e menu e
  agenda rolam cada um por si.
- **No celular o menu sai do caminho** e volta por um botão "menu", por cima da
  agenda. Antes ele ocupava quase metade de uma tela de 375 px.
- **A Home é só a agenda**, como o desenho pede. *(Emenda de 29/09/2026: para o gerente que
  não é administrador, a home passou a ser o painel da unidade, por decisão do dono -- ver a
  entrada daquele dia. Para os outros, continua a agenda.)*
- **As telas ainda não migradas não fingem mais:** a busca e os botões que não
  faziam nada ficaram apagados, com o motivo ao passar o mouse.

### Provado

**105 testes no front**, 11 novos; cada regra do tema tem um teste que fica
vermelho quando ela é arrancada. E na tela, com a sessão real: em 1366 px a
página não rola e a agenda ocupa a largura; com o computador no escuro a tela
abre escura sem gravar nada; em 375 px o menu some e volta pelo botão.

### O próximo

A comanda do desenho: a lista e o detalhe separados, a busca de produto que
lança o item certo com Enter, o catálogo para toque, o desconto com motivo, o
cancelamento com a senha do gerente e a nova comanda com escolha do cliente.
Parte disso precisa de mudanças no servidor.

## 23/09/2026, madrugada — a comanda aberta passa a mostrar o desconto que tem (D211)

### O que estava errado

A API da comanda dizia **desconto zero em toda comanda aberta**, mesmo com itens
descontados dentro: ela lia um campo que só é preenchido quando a comanda
fecha. A tela do PDV mostraria "Desconto R$ 0,00" onde havia R$ 10. Na cópia não
há comanda aberta, mas há **698 canceladas ainda abertas** com o mesmo sintoma.

### O conserto

A comanda aberta soma o desconto dos próprios itens; a fechada continua com o
valor gravado ao fechar. E a comanda ganhou o que o desenho pede para a lista:
quantos itens tem, quanto valeria sem desconto e quando fechou.

**Um achado de passagem:** numa comanda fechada, o sistema antigo reescreve a
data de abertura com a de fechamento. A hora em que ela foi aberta se perde, e
só voltaria com uma coluna nova.

### Provado

**712 testes nos dois bancos**, e cada conta nova tem um teste que fica
vermelho quando ela é arrancada.

## 23/09/2026, madrugada — a comanda do desenho entra no ar (B8, D212, B11)

### O que mudou para quem usa

A tela de comandas agora é a do desenho. A **lista** das abertas procura no
servidor, por cliente ou pelo número, em todas as páginas — antes só filtrava as
20 que estavam na tela. A **comanda aberta** mostra à esquerda o que ela é
(cliente, itens com o motivo do desconto, totais, pagamentos, fiado) e à direita
o que se faz com ela (lançar, receber, finalizar).

Lançar tem os dois jeitos do desenho, e o "modo de uso" do topo escolhe: **pela
busca**, no computador — digita, as setas escolhem, o Enter lança o que está
destacado —, e **pelo catálogo**, no toque — um toque, um item; dois toques
rápidos são um item só. A **nova comanda** pede o cliente, e diz quando ele já
tinha uma aberta. **Cancelar** pede o login e a senha de um gerente, e o motivo.

### O que estava errado, e foi consertado no caminho

- **Lançar item pela API não conferia nada (D212):** cobrava qualquer preço,
  aceitava desconto sem regra e item de produto que não existe. Agora só gerente
  ou administrador lança fora da tabela, e desconto tem regra e motivo. **Na
  cópia de produção, 47% dos itens com desconto de 2026 não têm motivo nenhum**
  (13.764 de 28.990, em 21 unidades): a tela antiga só pedia o motivo no
  navegador. A tela antiga fica como está — é decisão sua.
- **Quem abre comanda não tinha como escolher o cliente pela API (B11).** Agora
  tem, e recebe **só o nome**: telefone, e-mail e crédito ficam de fora. E ele
  **procura, não folheia** — a busca exige um termo e devolve uma página, como a
  do PDV antigo, para que ninguém leia a base de clientes da rede inteira.
- **O foco do teclado se perdia** em quase todo gesto da comanda — abrir,
  voltar, tirar item, a comanda nova —, e também quando o botão apertado ficava
  desligado durante o envio. Quem opera no teclado, ou com leitor de tela,
  voltava para o começo da página. Achado ao abrir a tela no navegador; agora o
  foco vai para onde o gesto levou.
- **Receber não aceitava vírgula:** "129,90", que é como se escreve dinheiro
  aqui, era barrado pelo navegador sem mensagem nenhuma na tela, e o pagamento
  não ia. Agora aceita, como o lançamento.

### Provado

**727 testes nos dois bancos, 162 no front**, e cada conserto tem um teste que
fica vermelho quando ele é arrancado. O ensaio de recusas por HTTP passou a
**35 casos**, todos verdes. E na tela, com o banco de contrato: lista, busca,
abrir, lançar com desconto e motivo, tirar, receber com vírgula, finalizar,
voltar, nova comanda, catálogo e cancelamento, com o foco conferido em cada
gesto.

### O próximo

- **Cancelar comanda aberta devolve crédito que nunca foi tirado:** os
  pagamentos em crédito do cliente só são debitados ao finalizar, mas o
  cancelamento os devolve mesmo assim — crédito inventado. Lido no código; a
  cópia não tem nenhum pagamento em crédito, então não dá para medir quantas
  vezes já aconteceu. E o cancelamento não confere a unidade antes de
  responder, nem o tamanho do motivo.
- **Ajustar a linha do item (B9):** o − e o + da quantidade e o desconto na
  própria linha, que o desenho tem e o servidor ainda não sabe fazer.
- Depois, a agenda do desenho.

## 23/09/2026, madrugada — a linha da comanda se ajusta no toque, e cancelar deixou de inventar crédito (B9, D213, D214)

### O que mudou para quem usa

No modo **toque**, cada linha da comanda ganhou **−**, **+** e **desconto**, como
no desenho. O "−" em uma unidade pede para tirar o item; o desconto abre na
própria linha, com o motivo obrigatório. E **tocar no catálogo num item que já
está na comanda soma na linha** — duas linhas de "Corte, 1" para a mesma pessoa
eram uma de "Corte, 2" escrita errado.

### O que estava errado, e foi consertado

- **Cancelar uma comanda aberta paga em crédito devolvia crédito que nunca
  tinha saído (D213).** O crédito do cliente só sai do saldo quando a comanda
  fecha; o cancelamento devolvia mesmo assim. Não dá para medir quantas vezes
  aconteceu: a cópia não tem nenhum pagamento em crédito. E o cancelamento
  dizia, a quem só tinha a sessão, se uma venda de **outra unidade** existia e
  se estava cancelada — agora responde "não encontrada", como a consulta.
- **Um toque duplo em "tirar item" mandava duas remoções** — defeito meu, da
  noite: consertado antes de sair daqui.

### O que ficou anotado, sem conserto (D214)

Com convênio, o motivo do desconto do PDV é **apagado pelo nome do convênio**:
na cópia, **460 linhas em 2025 e 260 em 2026** têm os dois descontos somados e um
motivo só. A conta é do serviço que a tela antiga também usa, e mudar o que ele
grava muda o que as 32 lojas registram — fica com a direção escrita.

### Provado

**735 testes nos dois bancos e 174 no front**; o ajuste da linha dá
exatamente a linha que o lançamento daria com os mesmos números, inclusive a
comissão; o ensaio de recusas por HTTP passou a **44 casos**; e na tela, contra o
banco de contrato, o "+" numa linha com desconto (o desconto ficou), o desconto
de 10% na linha e dois toques no catálogo virando uma linha de 2.

### O próximo

A agenda do desenho.

## 23/09/2026, manhã — a agenda do desenho: um bloco por agendamento, a linha do agora, e fechar a agenda sem sair dela (B4, B5, A5.8, D215)

### O que mudou para quem usa

- **A grade é a do desenho.** Cada agendamento é um bloco com a altura da
  duração: o corte de 60 minutos numa agenda de 30 aparecia como dois horários
  iguais, sem nada dizendo que eram a mesma pessoa. Todas as colunas usam a
  mesma régua de horas, e o cabeçalho de cada colaborador diz quantos
  agendamentos tem e quanto da agenda está tomado.
- **No dia de hoje, uma linha marca a hora** — a do servidor, e não a do
  computador do balcão. E a grade de hoje se relê a cada minuto enquanto a aba
  está à vista: o cliente que marca pelo app aparece sem ninguém recarregar.
- **O título é o dia por extenso**, a legenda fica atrás de um botão, e "hoje"
  só aparece para quem saiu de hoje.
- **Fechar a agenda abre por cima da grade**, já preenchido com o dia na tela e
  as horas da grade. Sem motivo não fecha; com o fim antes do início não fecha;
  e, se houver agendamento no período, a tela diz quantos — "Há 1 agendamento
  neste período" — e só fecha no segundo toque, em "fechar mesmo assim". Fechar
  não desmarca ninguém, e a tela avisa isso.

### O que estava errado, e foi consertado (D215)

O fechamento pela API aceitava um motivo maior que a coluna do banco (100
letras) e respondia com erro de servidor. Nunca aconteceu na cópia — o maior
motivo tem 95 letras, em 134.334 fechamentos —, mas a tela nova seria a
primeira a chegar lá.

### O que o servidor passou a dizer (B4, B5)

A grade diz a que agendamento cada horário pertence, e que dia e hora são pelo
relógio do servidor.

### Provado

**738 testes nos dois bancos e 204 no front**; o ensaio de recusas por HTTP
passou a **45 casos**; cada comportamento novo do front foi falsificado — a
regra arrancada deixa um caso vermelho; e na tela, contra o banco de contrato,
fechar 24/12 com o agendamento de exemplo no meio: a pergunta, o "fechar mesmo
assim", o aviso, e a grade voltando com o fechamento em volta do cliente.

### Decisões em aberto

- **O fechamento vale para a unidade inteira**, como o "Todos" da tela antiga.
  Fechar a agenda de um colaborador só continua na tela antiga até haver
  decisão sobre o desenho oferecer a escolha.
- **O campo de data ficou** ao lado das setas: o desenho só tem as setas, e
  chegar a 24/12 a partir de setembro seriam noventa toques.
- **Não há botão de "novo agendamento"** na grade: marcar pede uma fatia
  própria, com a escolha de cliente, serviço e horário.

### O próximo

O modo compacto e as outras duas formas da agenda (a linha do tempo e o
"agora").

## 23/09/2026, manhã — a agenda do desenho ganha as outras duas formas, o modo do celular e o balão (A5.4, A5.5, A5.7)

### O que mudou para quem usa

- **Três formas de olhar o mesmo dia**, em abas no cabeçalho, como no desenho:
  - **Colunas**, a grade de sempre.
  - **Linha do tempo**: uma linha por colaborador, as horas correndo para a
    direita. Ela rola de lado e, no dia de hoje, abre na hora atual.
  - **Agora**: um cartão por colaborador dizendo se ele está **livre** (e até
    quando), **ocupado** (e quando fica livre), no intervalo ou fora do
    horário. Embaixo vem a lista do dia inteiro, com uma linha marcando o agora.
    Tocar num cartão filtra a lista para aquela pessoa.

  A forma escolhida fica guardada no navegador.
- **No celular**, a grade mostra um colaborador por vez, escolhido numa fila de
  abas com os nomes. O título encurta para "Qua., 23/09".
- **Passar o mouse** por um agendamento mostra o resumo ao lado (cliente,
  serviço, telefone para quem gerencia): é o balão da tela antiga, que no toque
  não existe. No toque, tocar continua abrindo o painel.
- **O fechamento da agenda** passou a abrir numa janela de verdade: o Tab não
  sai dela para os botões de trás.

### O que a tela deixou de afirmar

Antes das 9h, a primeira versão da forma "Agora" teria mostrado a equipe
inteira **livre**. Agora ela diz **"fora do horário · começa às 09:00"**. O
buraco do almoço mostra **intervalo**, e quem já foi atendido não conta como
ocupado.

### Provado

**246 testes no front**; cada comportamento novo foi falsificado (35 mutações,
cada uma deixando um caso vermelho); e no navegador, contra o banco de
desenvolvimento e só lendo:
- as três formas em 23/09 e em 14/08 (140 atendimentos);
- a forma sobrevivendo à troca de dia;
- o modo do celular em 375px, com 14 abas e sem rolagem lateral;
- o balão, abrindo embaixo do bloco quando não cabe ao lado.

### O próximo

A personificação na barra do topo (B1).

## 23/09/2026, manhã — a personificação aparece na tela nova (B1)

### O que mudou para quem usa

Quem administra e entra "como" outra pessoa passa a ver, na barra do topo da
tela nova, a mesma faixa que o ERP mostra com o cadeado: *"Você está vendo o
sistema como Fulano, e continua sendo Beltrano"*. O botão **voltar a ser eu**
desfaz a personificação e recarrega a tela, que volta com o nome e o menu de
quem entrou.

Até aqui a tela nova escondia a faixa. O servidor dizia só o número de quem
entrou, e escrever "usuário 412" não servia a ninguém. Essa faixa é o único
rastro de que houve personificação (D127).

### O que não mudou, e é decisão do dono (D127)

A personificação continua **sem registro gravado**. O caminho de volta do ERP
continua aceitando qualquer usuário. A tela nova usa esse mesmo caminho, com o
número de quem entrou, e não abre um caminho próprio.

### Provado

Três casos novos no servidor. Seis mutações na tela, cada uma deixando um caso
vermelho. E no navegador, contra o banco de contrato: personificar, ver a faixa,
voltar, e o servidor responder que a sessão não personifica mais.

### O próximo

Republicar o roadmap publicado, e o relatório da noite.

## 23/09/2026, manhã — a revisão adversarial da agenda e da personificação

Quatro revisores independentes leram o que entrou desde a grade de blocos, cada
um procurando um tipo de defeito: a conta, a acessibilidade, o servidor e os
testes. Os cinco achados mais graves passaram por um verificador que tentava
derrubá-los. **Os cinco se confirmaram**, e nenhum revisor morreu no caminho.
Os doze que ficaram abaixo do corte foram lidos à mão, e os que se sustentaram
também foram consertados.

### O que estava errado

- **"Livre até 13:00 · 80 min" às 11:40, com o almoço no meio.** A conta dos
  cartões do "agora" atravessava o buraco do almoço. O mesmo cartão, às 12:10,
  dizia "intervalo". Agora a conta para no buraco: "até 12:00 · 20 min".
- **O botão "voltar a ser eu" podia começar uma personificação.** O endereço do
  ERP é uma chave, e não uma volta: com a sessão já normal, ele entra "como"
  alguém. Isso acontece com um segundo clique, ou com outra aba que já voltou.
  Agora a tela faz um pedido por vez, e só depois de a sessão confirmar que
  ainda personifica. E o 404 do ERP deixou de contar como sucesso.
- **A aba "Colunas" não mostrava o foco do teclado.** O anel ficava recortado
  pela caixa das abas. Agora ele é desenhado por dentro, e vale também para os
  blocos, a lista e as abas de colaborador.
- **O leitor de tela não ouvia o que o olho vê** na lista do agora: a primeira
  visita e a origem da marcação. E não ouvia que o motivo do fechamento é
  obrigatório.
- **O balão do resumo ficava preso** sobre a tela nova quando a forma trocava
  pelo teclado, e não havia como fechá-lo sem mexer o mouse. Agora ele é da
  tela em que nasceu, e o Esc o fecha.
- **A janela do fechamento podia ser fechada pelo navegador por baixo da
  tela.** O botão então não abria de novo. Agora o estado acompanha o
  navegador.
- **Três testes não seguravam o que diziam:**
  - o da janela modal passava com uma janela que não prende nada;
  - o da posição do balão passava com a conta errada;
  - o da busca de quem entrou de verdade não alcançava a busca, que então saiu
    do controller para um lugar que o teste alcança.

### Provado

**265 testes no front e 744 nos dois bancos.** Cada conserto foi falsificado:
a regra arrancada deixa um caso vermelho, em 19 mutações. Duas delas ficaram
verdes na primeira rodada, e os testes que elas mostraram fracos foram
reescritos. No navegador, o foco visível na aba "Colunas".

## 23/09/2026, manhã — o B10, o foco que atravessa o modo do celular e o balão que se deixa ler

### O que mudou para quem usa

- **Fechar a agenda com agendamento no período** continua perguntando "Há 3
  agendamentos neste período", e o 3 agora vem num campo próprio da resposta.
  Até aqui a tela o tirava da frase do servidor com uma expressão regular: a
  regra 3 da convenção da API proíbe exatamente isso, porque corrigir a frase no
  servidor quebraria a pergunta na tela. Sem o número, a tela mostra a frase
  inteira do servidor.
- **O foco do teclado sobrevive à troca para o modo do celular.** Aumentar a
  lupa do navegador, girar o tablet ou abrir o menu pode estreitar a agenda para
  menos de 720px. A grade era desmontada e o foco caía no começo da página.
  Agora o bloco com foco continua com foco, e a aba que abre é a da pessoa que se
  estava olhando. Se o foco estava na aba de um colaborador e as abas saem, ele
  vai para o primeiro horário dessa pessoa.
- **O balão do resumo se deixa ler.** Quem leva o mouse até o balão para ler
  devagar, ou com lupa, via o balão sumir no vão de 8px entre o bloco e ele. Ele
  agora espera 200 ms antes de sumir, e fica enquanto o mouse estiver em cima
  (WCAG 1.4.13). O Esc continua fechando na hora.

Os dois últimos eram os pontos que a revisão adversarial da manhã tinha deixado
para depois.

### Provado

**746 testes nos dois bancos e 274 no front.** O ensaio de recusas por HTTP
passou a **46 casos**: o novo fecha 24/12 sem confirmar e confere o número na
resposta. Cada peça foi falsificada:
- tirar o número do controller deixa o caso 46 vermelho;
- tirar os `detalhes` do corpo deixa vermelho um dos dois casos novos do
  servidor, e pô-los sempre deixa vermelho o outro;
- a tela voltando a ler a frase deixa cinco casos vermelhos;
- seis mutações no foco e no balão, cada uma com um caso vermelho.

### O que o levantamento achou

Um levantamento com dez agentes comparou o desenho com a tela de hoje, área por
área, e um cético tentou derrubar cada achado. De 63 achados, **53 se
confirmaram**. Há repetidos entre as áreas: a recarga que falha, por exemplo,
aparece três vezes. Os mais graves são da agenda:
- uma falha na recarga de minuto troca a grade pela mensagem de erro e desliga a
  recarga;
- trocar de dia desmonta a tela inteira, e o foco cai no começo da página;
- o painel do horário abre abaixo de uma grade de 1.200px, fora da vista, sem
  receber o foco.

Eles são o próximo lote.

## 23/09/2026, tarde — a agenda que não some: o dia que carrega, a recarga que falha e o painel que se deixa achar

Os três achados mais graves do levantamento eram da agenda, e os três faziam a
tela sumir debaixo de quem a usava.

### O que mudou para quem usa

- **Trocar de dia não apaga mais a tela.** O título, as setas e as abas ficam, o
  painel diz "Carregando a agenda…", e o botão usado continua com o foco. Antes
  a tela inteira virava a frase, e quem usa o teclado recomeçava do alto da
  página a cada dia.
- **Um dia que não carrega fica no lugar do painel, com as setas em cima** e um
  botão "tentar de novo". Antes sobrava só a mensagem, sem caminho de volta.
- **A recarga de minuto que falha só avisa.** A grade de hoje fica, um aviso
  diz que ela pode ter mudado, e o relógio continua: a volta seguinte que der
  certo limpa o aviso. Antes a falha trocava a grade pela mensagem e desligava
  a recarga, e a tela do balcão ficava no erro até alguém apertar F5.
- **O painel do horário recebe o foco e a página rola até ele.** Ele fica abaixo
  de uma grade de 1.200px e nascia fora da vista. Fechar devolve o foco ao bloco
  que o abriu, mesmo quando a recarga trocou o bloco.
- **O painel acompanha a recarga.** O agendamento desmarcado noutro caixa perde
  o "Registrar chegada" na hora, em vez de oferecer uma ação que o servidor
  recusava com 404.
- **"Registrar chegada" diz o que houve.** A recusa vem num alerta, o sucesso é
  anunciado, e o botão no ar continua com o foco. A resposta só mexe no painel
  do próprio horário: a recusa de um cliente não aparece mais no painel de outro.
- **Apagar um pedaço da data no campo** não pula mais a agenda para hoje.

### A revisão adversarial deste lote

Três revisores leram o lote, cada um com um foco (estado e corridas,
acessibilidade, testes que não seguram o que dizem), e um cético por foco tentou
derrubar cada achado. **Os 22 se confirmaram**, com repetição entre os focos. O
que eles acharam:

- **o foco ainda caía em três lugares:** o "tentar de novo" e o "hoje" somem no
  próprio clique, e o "Registrar chegada" some sob o foco quando a recarga traz
  a chegada registrada noutro caixa. Agora o título do painel ou o do dia
  recebe o foco;
- **o dia que falha era mudo**, o mesmo texto repetido não era anunciado de novo,
  e o `role="status"`, que é atômico, relia o dia velho junto com cada aviso. O
  anúncio do dia ganhou uma região própria, e cada evento é um nó novo;
- **a resposta do "Registrar chegada" reconhecia o painel por um contador**, e
  reabrir o mesmo horário contava como outro: a recusa ia para o topo, fora da
  vista. Agora é pelo horário e pelo dia;
- **seis testes passavam com o defeito de pé**, entre eles o do título do painel:
  o happy-dom foca um `h3` sem `tabindex`, e o navegador não.

E um achado meu, no navegador: **com a janela sem foco, o Chrome não dispara
`focusin`** (medido no painel do navegador). O resgate do foco passou a guardar
o último foco a cada desenho também, e não só pelo evento.

### Provado

**298 testes no front.** Cada conserto foi falsificado: a regra arrancada deixa
um caso vermelho. Três mutações sobreviveram na primeira rodada, e cada uma
disse uma coisa:
- duas guardas eram redundantes, e saíram;
- um caso media só o fim do caminho, e passou a medir o meio.

Na tela, contra o banco de desenvolvimento e só lendo:
- o "dia seguinte" com o foco durante o carregando, e o anúncio do dia novo;
- o dia que falha com as setas em cima, e o "tentar de novo" carregando;
- o painel das 17:00 rolado para a vista, com o foco no título, e o foco de
  volta no bloco ao fechar;
- o "hoje" sumindo e o foco no título do dia, com a janela sem foco.

### O próximo

A moldura: o menu com as guardas que o ERP tem e a tela nova não tinha, os
endereços de verdade, e o foco ao navegar.

## 23/09/2026, tarde — o menu com as guardas do ERP, os endereços de verdade, e a comanda que espera há mais tempo em cima

### O que mudou para quem usa

- **O menu esconde o que o ERP esconde.** O `layout.phtml` decide três destinos
  por coisas que não são privilégio, e a tela nova mostrava os três a quem tinha
  o privilégio:
  - o **"Colaborador caixa"** (grupo 2, 374 pessoas na cópia) vê Relatórios ›
    Vendas como o PDF exportado, em aba nova, e não a tela nem a "Vendas Ver. 2".
    Até aqui ele via as três, com dois "Vendas" iguais;
  - o **"Colaborador"** (grupo 1, 1.749 pessoas) só vê o Dash. Colaboradores com
    a marca `visivel_dashboard`;
  - o **WhatsApp** só aparece na unidade que o tem ligado.

  A sessão (`/adm/api/sessao`) passou a mandar as três guardas já decididas. Ela
  não manda o id do grupo: o 1 e o 2 são números mágicos do layout, e a tela não
  os repete.
- **O menu aponta para o endereço de verdade.** O `href` era a chave interna
  (`/isvenda/...`), que nenhum módulo declara: o clique do meio e o "abrir em
  nova aba" davam 404 no ERP (A2.4). Agora é o endereço da rota
  (`/mod-vendas/...`). O PDF das vendas e o Docs APIs abrem em aba nova, como no
  ERP.
- **A lista de comandas abertas começa por quem espera há mais tempo**, como no
  desenho. A mais nova vinha primeiro, como na tela antiga. A ordem é do
  servidor, e não da tela, porque a lista é paginada.

### Provado

**750 testes nos dois bancos e 307 no front**:
- três casos novos da sessão e um da ordem das comandas;
- no front, as guardas na tela e os destinos em aba nova;
- o endereço de cada módulo conferido contra o `module.config.php` do próprio
  módulo, e não contra a lista.

Cada regra foi falsificada: três mutações no servidor e sete no front, cada uma
com um caso vermelho. O primeiro caso da ordem das comandas passou **verde com a
ordem errada**: o ajudante que ele usava ordena os ids para comparar conjuntos.
O caso passou a ler a ordem direto da consulta.

## 23/09/2026, noite — a moldura que leva o foco junto, a gaveta que fecha, e o tema escolhido desde o primeiro quadro

### O que mudou para quem usa

- **Navegar leva o foco à tela nova.** Escolher um destino no menu deixava o
  foco no link. No celular o link some junto com a gaveta, e o foco caía no
  começo da página. O "‹ voltar à agenda" também desmontava o próprio botão.
  Agora o título da tela nova recebe o foco. Na agenda, quem recebe é o
  "Carregando…", e ele passa o foco ao título do dia quando o dia chega. Abrir o
  sistema não mexe no foco: só navegar mexe.
- **A gaveta do celular fecha com Esc e com um toque fora dela.** Antes só o
  botão "menu" e a escolha de um destino fechavam. A sombra em volta era só
  pintura: o toque atravessava e caía na agenda por baixo. Agora a sombra é um
  véu de verdade. Tocar nele fecha a gaveta, e o Esc devolve o foco ao botão
  "menu". Com a gaveta aberta, a tela coberta sai do Tab.
- **"Computador" e "toque" andam pelas setas**, como um grupo de opções deve
  andar. Só a opção marcada entra no Tab. O anel do foco passou para dentro do
  botão: o de fora era cortado pela borda do grupo.
- **O botão do tema diz primeiro o tema que mostra** ("claro: mudar para tema
  escuro"). Quem usa comando de voz e diz "clicar claro" alcança o botão.
- **O tema guardado vale desde o primeiro quadro.** Antes ele só entrava depois
  que a sessão respondia. Quem guardou "escuro" num sistema claro via o
  "Carregando…" claro piscar antes da troca. A tela de sessão caída nunca seguia
  a escolha.
- **A aba do navegador diz a tela aberta** ("Agenda · Barbearia VIP",
  "Clientes · Barbearia VIP"). Ela dizia "Comanda · Console VIP" em toda tela,
  resto de quando o app era só a comanda. O leitor de tela e o histórico também
  anunciavam "Comanda" na agenda. Achado ao abrir a página no navegador.

### A decisão que fica com o dono

A moldura antiga (`MolduraDoErp`) e a primeira `Home` não aparecem em nenhuma
tela desde o Console. Parecem código morto, mas **são dois componentes
publicados no projeto do Claude Design**: o `.design-sync/config.json` os lista,
e há uma prévia de cada um. Apagá-los quebraria o próximo `/design-sync`. Ou
então tiraria os dois do projeto, e o agente de design deixaria de enxergá-los.
Os dois ficaram. Aposentá-los é decisão de quem cuida do projeto de design, e
pede uma sincronização.

### Provado

**314 testes no front**:
- o foco no título depois de navegar, e no "Carregando…" da agenda;
- o Esc, o véu e a tela coberta fora do Tab;
- as setas, o Home e o End nas duas opções, com só a marcada no Tab;
- o nome do botão do tema;
- o tema guardado já na raiz antes de a sessão responder.

Cada regra foi falsificada: doze mutações, cada uma com um caso vermelho. O caso
do Esc passou **verde com o foco sem voltar**. Ele apertava Esc com o foco já no
botão "menu", onde o clique o tinha deixado. Agora o foco está num link da
gaveta quando o Esc é apertado, como no uso.

Só o front mudou. A suíte do servidor e o contrato não rodaram de novo.

### O próximo

A agenda como no desenho:
- os selos de "em atendimento" e "finalizado";
- o cabeçalho das colunas preso ao rolar;
- a coluna fechada hachurada;
- a marca do agora na régua;
- o fechamento que diz a unidade e o período.

## 23/09/2026, noite — a grade diz o estado de cada atendimento, e a hora do agora

### O que mudou para quem usa

- **"Em atendimento" e "finalizado" ganharam selo**, como no desenho: no bloco
  da grade, na linha do tempo e na lista do Agora. Um bloco atendido saía
  praticamente igual a um agendado, com o mesmo fundo, a mesma borda e o mesmo
  peso.
- **As bordas que a legenda explica agora aparecem.** A borda tracejada do site,
  a pontilhada do aplicativo, a verde do "em atendimento" e o peso do agendado
  sumiam da grade. Uma regra de estilo do bloco vinha depois das outras e as
  apagava.
- **Passar o mouse num bloco não o pinta mais de dourado.** A regra geral dos
  botões vencia a da grade, e na agenda o dourado é reservado às ações.
- **A hora do agora fica escrita na régua**, na grade e na linha do tempo. A
  linha passou a ser escura, como no desenho, e não vermelha: o sistema de
  design reserva o vermelho para o que não volta atrás.
- **A legenda ganhou os eixos "status" e "cliente".** O "C" de convênio que a
  grade mostra não tinha chave. A primeira visita saiu do eixo "atendimento" e
  foi para o cliente.
- **Na lista do Agora**:
  - o "C" aparece, e o leitor de tela diz o convênio;
  - o atendimento que já passou deixou de ficar apagado: só a barra de cor
    esmaece, e o texto fica legível;
  - os títulos, a situação "agenda fechada" e a "primeira visita" passaram a um
    tom mais escuro. Com o do desenho, o contraste ficava entre 3,38:1 e
    4,30:1, abaixo do mínimo de 4,5:1.

### Onde a tela desvia do desenho, e por quê

- **O selo do estado desce para a linha de baixo** quando não cabe ao lado do
  horário. O caso medido foi uma unidade de 14 colunas de 128px, em 1366px:
  - na primeira versão, o selo encolhia com reticências, e os 159 "finalizado"
    do dia viravam "F…" de 19px;
  - na segunda, o selo descia inteiro, mas espremia o nome do cliente a 8px,
    cortado ao meio.

  Agora quem cede a altura é o serviço, que some inteiro. Na coluna larga cabe
  tudo numa linha.
- **O horário de 15 minutos não tem selo de estado.** Ali o selo sairia cortado
  ao meio. O estado segue na borda, no fundo, no nome acessível e no balão.
- **O atendimento passado não fica com opacidade 0,6.** Com ela, o texto caía
  para 2,99:1.

### Provado

**320 testes no front**:
- os selos no bloco, e a ausência deles no horário curto;
- o estado e o convênio na lista do Agora;
- os eixos da legenda;
- a hora do agora nas duas réguas, na posição da linha.

Cada regra foi falsificada: doze mutações, cada uma com um caso vermelho. A
primeira versão do caso da régua passou **verde com a hora escondida**: ele
lia o texto e não se o texto aparecia.

A cascata do estilo não aparece no teste, que não aplica CSS. Ela foi medida no
navegador em 15/08, um dia com 165 agendamentos na cópia:
- a borda tracejada do site, a pontilhada do aplicativo e a clara do atendido;
- a verde, com peso 600, do "em atendimento", numa sonda, porque a cópia não
  tem ninguém na cadeira;
- nenhum texto cortado ao meio nos 165 blocos.

### O que fica com o dono

A legenda é peça do pacote do Claude Design. A tela nova já a mostra, mas o
projeto de design só a recebe numa sincronização, que é decisão de quem cuida
dele.

## 23/09/2026, noite — o nome do barbeiro fica no topo, a coluna fechada se vê, e o horário curto cabe no dedo

### O que mudou para quem usa

- **O cabeçalho das colunas fica preso no topo ao rolar a grade.** O nome e a
  cor de cada barbeiro saíam da tela, e às 18h o balcão olhava colunas sem
  nome. A grade agora rola por dentro, na altura da tela. A régua de horas fica
  à esquerda ao rolar de lado.
- **A coluna fechada o dia inteiro (férias, agenda fechada) se vê.** Ela é a
  trilha coberta pela hachura, na altura do dia, com o motivo no topo e "100%"
  no cabeçalho, como no desenho. Antes era uma frase solta, e a coluna encolhia
  até ela. No Agora, o cartão diz "agenda fechada", e não "indisponível".
- **A agenda de 10 minutos tem horários de 24px.** Com a escala do desenho, cada
  horário livre tinha 16px, a cada 18: no balcão, o dedo acertava o vizinho. A
  escala cresce até o menor passo da grade dar 24px, e vale para todas as
  colunas, para a mesma hora continuar alinhada. No modo compacto ela é a do
  desenho, 2px por minuto.

### Provado

**329 testes no front**:
- a escala no modelo e na grade, e a coluna de 30 minutos ao lado da de 10 na
  mesma escala;
- a coluna fechada na grade, na linha do tempo e no cartão.

Cada regra foi falsificada: onze mutações, cada uma com um caso vermelho. O
caso das duas colunas entrou antes da bateria: com uma coluna só, nada mediria
se a escala é mesmo comum a todas.

No navegador, em 15/08, com 14 colunas em 1366×900:
- a grade rola por dentro (750px), e a página não rola;
- depois de rolar 600px para baixo e 400 para o lado, o cabeçalho continua no
  topo, a régua e o canto continuam à esquerda, e o que está no ponto do
  cabeçalho é o próprio cabeçalho, e não um bloco por cima;
- a coluna fechada daquele dia tem 1.080px, a altura do dia inteiro.

## 23/09/2026, noite — o cabeçalho da agenda como no desenho, e o fechamento que diz de quem e quando

### O que mudou para quem usa

- **O cabeçalho é a faixa do desenho.** Ele tem fundo e um traço embaixo, e vai
  de borda a borda. A ordem é "‹ [campo da data] › Quarta-feira, 23 de setembro
  [hoje]", com as abas e as ferramentas juntas à direita. A data vinha antes
  das setas, e as setas desciam 0,3rem por uma margem que sobrou.
- **A data é o título principal da tela (h1).** Na tela inicial, quem navega por
  títulos não achava o principal. Os títulos de dentro (as colunas, o painel do
  horário, o Agora, o fechamento) passaram a h2, para não pular nível.
- **A janela de fechar a agenda diz a unidade**, "Fechar a agenda · Unidade
  Centro", **e que vale para todos os colaboradores**. O fechamento é da
  unidade inteira, e a janela não dizia de quem era a agenda.
- **A confirmação diz de quem e quando**: "Agenda de Unidade Centro fechada em
  04/06, das 09:00 às 10:00 — Feriado (2 colaboradores)." Antes era "Agenda
  fechada — Feriado", sem unidade e sem período.
- **Os avisos ficam logo abaixo do cabeçalho**, e não acima dele, encostados no
  menu.

### Um defeito que a mudança fez aparecer

Mover os avisos pôs a região que o leitor de tela lê (um `<p>` invisível) no
começo da tela. A primeira busca que falha mostra um "Carregando a agenda…" que
também é `<p>` e está com o foco. O React **reaproveitou o nó**: o foco ficou na
região invisível, e o resgate do foco não viu nada sumir. O teste "a primeira
busca que falha oferece tentar de novo" pegou. A região foi para o fim da tela,
e a falsificação provou que é o lugar que conta: de volta ao começo, o caso fica
vermelho. Uma `key` que eu tinha posto junto sobreviveu à mutação e saiu, por
ser enfeite.

### Provado

**333 testes no front**:
- a ordem do cabeçalho;
- o h1 e os h2;
- a unidade e o alcance na janela;
- o período no mesmo dia e em dias diferentes;
- o aviso abaixo do cabeçalho.

Doze mutações; onze ficaram vermelhas, e a sobrevivente era a `key` de enfeite.

No navegador, em 1366×900:
- a faixa tem 0px de folga dos dois lados e do topo;
- os sete itens do cabeçalho estão na mesma linha, na ordem do desenho;
- a grade continua cabendo sem a página rolar.

Em 375px, nada rola de lado, e o cabeçalho quebra em quatro linhas.

### O que fica com o dono

`NavegacaoDaData` e `FecharAgenda` são peças do pacote do Claude Design, e
ganharam as props opcionais `titulo` e `unidade`. O projeto de design só as vê
numa sincronização, que é decisão de quem cuida dele.

## 23/09/2026, noite — a lista das comandas: a linha inteira abre, e a comanda encerrada sai com uma frase

### O que mudou para quem usa

- **Clicar em qualquer ponto da linha abre a comanda**, como no desenho. Só o
  número abria, embora a linha inteira acendesse ao passar o mouse. No toque, o
  alvo era um número de uns 21px de altura. O número continua botão, que é o que
  o teclado alcança.
- **A comanda finalizada ou cancelada sai da lista com uma frase**: "Comanda
  1234 finalizada: o recebido foi para o caixa aberto.", ou "Comanda 1234
  cancelada.". Ela sumia sem explicação: o aviso era do detalhe, e morria com
  ele. A frase sai ao buscar, ao trocar de página e ao abrir outra comanda.
- **Abrindo a comanda, a tela já é a dela.** O cabeçalho tem o ‹ e o título
  "Comanda N", e o título tem o foco. Era só a frase "abrindo a comanda…", sem ‹:
  em rede lenta não havia como voltar, e o foco caía no começo da página.
- **O título conta as abertas da unidade, e não as da busca.** Com a busca no
  servidor, "Comandas abertas · 1" aparecia com várias abertas. Com busca, uma
  consulta de um item só traz o total sem filtro, e o tamanho do recorte vai no
  corpo ("1 comanda com “1234”"). Se essa consulta falhar, o título fica sem
  número, e não com um número velho.
- **"Itens" mede uma coisa só**: a soma das quantidades, a medida do servidor.
  Uma comanda com "Corte ×2" dizia 2 na lista, "Itens · 1" no detalhe e "Os 2
  itens" no cancelar.
- A contagem do título e o "abrindo" passaram a um tom mais escuro. Estavam em
  4,30:1, abaixo do mínimo de 4,5:1.

### Provado

**338 testes no front**, e cada regra foi falsificada: catorze mutações, todas
vermelhas. As duas que sobreviveram à primeira bateria ganharam caso:
- a contagem que falha e deixaria o número velho;
- abrir outra comanda, que deixaria a frase da anterior.

O caso do botão confere que a comanda abre **uma vez só**: a linha e o botão
dentro dela não disparam dois pedidos.

No navegador, na cópia:
- o cursor da linha é a mão;
- clicar no nome do cliente abre a comanda;
- enquanto ela chega (29s), o cabeçalho com o ‹ está na tela, o título "Comanda
  990099317" tem o foco, e "abrindo a comanda…" é anunciado;
- quando ela chega, o foco passa ao título do detalhe.

## 23/09/2026, noite — a revisão adversarial da moldura e da agenda: dez achados confirmados

### Como foi feita

Três revisores leram o intervalo `8e88509..HEAD`, cada um por um lado:
- o React e o foco;
- o CSS e o layout;
- os dados e a honestidade dos testes.

Cada achado foi entregue a um cético, com a ordem de refutá-lo. Saíram 16
achados. Doze foram confirmados, e dois deles eram repetidos. Quatro foram
refutados, entre eles a escala infinita com passo zero, que nenhum dado
produz. Nenhum dos 19 agentes morreu.

### O que mudou para quem usa

- **Em "Vendas › Nova", o cursor fica no campo do cliente.** O foco da
  navegação chegava depois e o levava para o título da lista.
- **Escolher na gaveta o destino que já está aberto leva o foco ao título.**
  Antes a gaveta fechava com o foco dentro, e ele caía no começo da página.
- **A gaveta fecha sozinha quando a tela deixa de ser estreita.** Girar o
  celular com ela aberta deixava a agenda inteira inerte: sem toque, sem Tab,
  sem leitor de tela, e sem nada na tela para fechar.
- **Com a agenda falhando na primeira busca, o foco vai ao "tentar de novo"**,
  e não ao começo da página.
- **O bloco com foco não fica escondido sob o nome do barbeiro.** A grade, que
  agora rola por dentro, desconta a faixa presa e a régua ao rolar um bloco
  para a vista. Medido: sem o conserto, o bloco parava a 10px do topo, debaixo
  de uma faixa de 42px; com ele, a 42px.
- **A grade tem piso de altura.** Com a lupa alta, ela virava uma tira de 10px.
  Abaixo de 18rem, quem rola é a página: em 300px de altura a grade ficou com
  270px, e não com 150.
- **Na linha do tempo, o selo "em atendimento" só entra no bloco que o
  comporta.** O bloco de 30 minutos tem 76px, e o selo saía cortado sem
  reticências.
- **Na linha do tempo, a hora do agora passa por baixo da coluna presa dos
  nomes**, como a linha dela. Antes ficava por cima. Medido no ponto de encontro
  dos dois: com o conserto, o que está lá é o canto; sem ele, o rótulo.
- **O cartão da agenda fechada não repete o rótulo**: "agenda fechada · o dia
  inteiro", e não "agenda fechada / Agenda fechada".
- O caso do nome da aba passou a limpar o título antes de montar. Ele passava
  pelo título que o caso anterior tinha deixado.

### Provado

**344 testes no front.** Sete mutações nos consertos de código, todas
vermelhas. Os três consertos de CSS foram medidos no navegador com e sem a
regra: o rolar para a vista, o piso da grade e a camada da régua.

## 23/09/2026, noite — o lançar da comanda: rótulos legíveis, o desconto da linha, e os três grupos de rádio com setas

### O que mudou para quem usa

- **Os rótulos do formulário de lançar se leem.** Eram cinza-claro de 12px:
  3,94:1 no claro e 3,38:1 no escuro, abaixo dos 4,5:1. Agora são o tom de
  corpo de 13px, como no desenho, com uns 7:1. A prévia do desconto e o
  estoque ("8 un.") foram junto, pelo mesmo motivo.
- **O desconto da linha é o da linha**, como no desenho:
  - não diz "opcional", porque o formulário abre só para dar desconto;
  - o motivo fica à vista desde o começo, marcado obrigatório;
  - a prévia diz em quanto a linha fica: "− R$ 4,50 → linha fica R$ 40,50".
- **No botão do produto, o preço e o estoque são o rodapé**: numa linha só,
  presos ao pé, com o estoque à direita. O preço fica na mesma altura em toda a
  grade.
- **A comanda vira uma coluna só abaixo de 960px, como no desenho, e não em
  1024px.** Numa media query o rem vale 16px, e não os 15px da página, então
  64rem dava 1024px. Conferido no navegador: em 1000px, duas colunas; em 950px,
  uma.
- **O tipo de desconto (% ou R$) e as pílulas do catálogo andam pelas setas**,
  e só a opção marcada entra no Tab. O papel de grupo de rádio prometia esse
  teclado e não o entregava. Os três grupos da casa passaram a usar uma peça só,
  `useGrupoDeRadio`: esses dois e o "computador / toque" da moldura.

### Provado

**348 testes no front**:
- as setas nos dois grupos novos;
- o desconto da linha;
- o rodapé do produto.

Nove mutações, todas vermelhas:
- o grupo de rádio pondo todos no Tab;
- a seta que escolhe e não leva o foco, e a que leva o foco e não escolhe;
- as pílulas com o índice errado;
- as quatro partes do desconto da linha;
- o rodapé.

Os rótulos e a quebra em 960px foram medidos no navegador, numa comanda da
cópia.

## 23/09/2026, noite — a comanda dentro do Console tem caso (TS-1)

Os casos da comanda recebiam as permissões prontas, e o que o Console faz com
elas não tinha teste. Três coisas podiam mudar sem nenhum caso ficar vermelho:
- o modo de uso decidindo se a comanda lança pelo catálogo ou pela busca;
- os privilégios virando permissões;
- o papel de gerente destravando o valor unitário.

Três casos novos montam o Console na lista de comandas e abrem uma. Cinco
mutações no mapeamento, e as cinco ficaram vermelhas:
- o lançamento sempre pela busca;
- escrever sempre liberado;
- o valor sempre campo, e nunca campo;
- abrir comanda sempre liberado.

Os outros dois pedidos do TS-1 já tinham caso: a comanda nova com a escolha do
cliente e o link externo em aba nova. **351 testes no front.**

## 23/09/2026, noite — a revisão adversarial da comanda: cinco achados confirmados

Três revisores leram o intervalo `7931a36..HEAD` (a comanda, o lançar e os
consertos da revisão anterior), com um cético por achado. Saíram 12 achados: 8
confirmados, com três repetidos, e 4 refutados. Nenhum dos 15 agentes morreu.

### O que mudou para quem usa

- **A comanda que não abre é anunciada.** O "abrindo a comanda…" era a única
  região que o leitor de tela lê, e saía de cena junto com ele. O erro entrava
  fora de qualquer região, com o foco parado no título, e quem ouvia a tela
  ficava achando que a comanda ainda vinha. Agora a região é uma só, nos dois
  estados, e só o conteúdo troca.
- **Home e End no tipo de desconto já marcado não apagam o valor.** Numa linha
  com R$ 4,50 de desconto, End no "R$" limpava o campo, e "aplicar" retirava o
  desconto. O clique no tipo já marcado fazia o mesmo antes desta noite, e foi
  junto.
- **A frase do recorte fala da lista que está na tela.** Com a busca no ar, a
  lista de antes fica na tela, e a frase juntava o termo novo com as linhas
  velhas: "40 comandas com “ana”" sobre 20 linhas sem filtro. A busca que produziu
  a lista agora mora na lista.
- **Sem busca, a lista que falha ao reler deixa o título sem número**, e não
  com o de antes.
- **O caso do "abrindo" não media a guarda da resposta atrasada.** Ele segurava
  o pedido inteiro, e os catálogos recebiam a resposta da comanda. Ficava verde
  sem a guarda. Agora a resposta sai da fila na hora, e só a entrega espera.

### Provado

**357 testes no front.** Seis mutações, todas vermelhas.

A guarda do grupo de rádio sobreviveu na primeira rodada: o campo de desconto
tem a dele, e a escondia. Ela ganhou um arquivo de caso próprio, e não foi
arrancada, porque a peça é da casa e quem a usa pode ter uma escolha que não é
neutra.

No navegador, numa busca de verdade na cópia, não há frase enquanto a busca
está no ar. Quando ela chega, a frase é "1 comanda com “Cliente 4225”", com o
título ainda contando a unidade.

Um caso do `ConsoleLigado` falhou uma vez na suíte inteira ("se a volta
confirmada nem sair, vai ao endereço do ERP") e passou três vezes sozinho. É
intermitente, e fica registrado aqui.

## 23/09/2026, noite — o `ConsoleLigado.teste.tsx` não toca mais a rede, e o caso intermitente era o mesmo defeito

Rodar `npx vitest run src/telas/ConsoleLigado.teste.tsx` cuspia, de vez em
quando, `AggregateError ... connect ECONNREFUSED ::1:3000`, com os 19 casos
verdes. Em 10 rodadas, 5 vazaram, com até 3 erros cada. Aparecia também no
`8e88509`, então vem de antes desta noite.

### Quem saía

Um `fetch` barulhento, posto por baixo de tudo, achou: a grade da agenda. A
`AgendaLigada` pede a grade num efeito, e o efeito pode rodar DEPOIS de a
moldura aparecer — o `findBy` volta com o DOM, e não com os efeitos. Quando ele
rodava na desmontagem, o `afterEach` já tinha devolvido o `fetch` de verdade, e
o do happy-dom tentava `localhost:3000`, a origem que o vitest dá à página.

O caso intermitente da entrada anterior é o mesmo efeito atrasado, noutro
lugar. Ele troca o `fetch` à mão logo depois de achar o botão, e a grade gastava
a resposta que era da sessão. O `fetch` feito à mão registrou
`["/adm/api/agendas/grade", "/adm/api/sessao"]` na rodada que reprovou, e a
sessão primeiro nas 19 que passaram. A conferência recebia a rede caída, não
confirmava, e a tela recarregava em vez de ir ao ERP. Não é defeito da tela:
fora do teste, cada pedido tem a sua resposta.

### O conserto, só no arquivo de caso

- **Desmonta antes de devolver o `fetch`.**
- **Por baixo de todo dublê há um que recusa tudo e anota.** O pedido que chegar
  nele reprova o caso, com o endereço. A regra de que teste não toca a rede é do
  `Console.teste.tsx`; a tranca é só deste arquivo, por enquanto.
- **Os sete casos que clicam em "voltar a ser eu" esperam a agenda pedir a
  grade** antes de clicar ou de trocar o `fetch`.

### Provado

- O arquivo, 20 rodadas: nenhum `ECONNREFUSED`, e 19 de 19 em todas. Noutras 20,
  o `fetch` feito à mão do caso intermitente viu a sessão primeiro todas as vezes.
- Devolver o `fetch` antes de desmontar, com a guarda: 2 de 10 rodadas
  vermelhas, e a mensagem da guarda nomeia a grade.
- Os ganchos de antes, sem a guarda: 2 de 10 rodadas com `ECONNREFUSED`, mesmo
  com a espera no botão.
- O botão sem a espera: 1 de 20 rodadas vermelha, no caso intermitente. É uma
  taxa, e não prova de que ele sempre reprova.
- A suíte inteira, 5 rodadas: **357 testes**, nenhum `ECONNREFUSED`. `tsc` e
  `oxlint` limpos, com os dois avisos de antes.

### A revisão adversarial

Três revisores leram o conserto, com um cético por achado: as saídas que
sobram, as frases dos comentários e o desenho da guarda. Saíram 4 achados: 3
confirmados, dois deles iguais, e 1 refutado. Nenhum dos 7 agentes morreu, e o
revisor das saídas voltou vazio depois de 25 leituras.

Os dois confirmados eram frases minhas nos comentários, e estão corrigidos:
- **"Como no `Console.teste.tsx`"** exagerava. Lá o dublê é obrigatório, mas
  não há guarda, e o `fetch` volta antes de o `preparo.ts` desmontar: a ordem
  que este conserto culpa. Nas 5 rodadas da suíte ele não vazou, e por que não
  eu não medi. Fica anotado, e não mexido.
- **A janela "poderia inverter a ordem" nos casos de fila** não inverte: a
  grade ainda leva a segunda resposta. A espera fica neles para todo clique
  partir do mesmo ponto.

O refutado era a porta do Vite, que eu já tinha tirado do comentário: o `3000` é
a origem que o vitest dá ao happy-dom, e o Vite daqui roda na 5173.

### A suíte inteira, com o mesmo instrumento

Depois do commit, pus o `fetch` barulhento no `preparo.ts`, por baixo de todo
arquivo de caso, e rodei a suíte 5 vezes: **nenhum pedido chegou a ele**, com os
357 verdes nas 5. O instrumento vê vazamento: com os ganchos de antes de volta no
`ConsoleLigado.teste.tsx`, ele pegou 2 pedidos da grade em 10 rodadas, com o
arquivo e o caso. Então o `Console.teste.tsx`, com a ordem velha, não vazou em 5
rodadas. É uma taxa, e não prova de que nunca vaza, e a tranca lá fica como
sugestão à parte.

## 23/09/2026, noite — a rede trancada em cada arquivo de caso

A tranca que nasceu no `ConsoleLigado.teste.tsx` mudou para o `preparo.ts`, que
roda antes de todo arquivo de caso. Ela nasce quando o `preparo.ts` carrega,
recusa tudo e anota, e não sai mais: o `fetch` de verdade não volta enquanto o
arquivo roda. Cada caso, depois de desmontar e de esperar uma volta do relógio,
confere o que chegou a ela, e reprova com o endereço. Depois do último caso, um
`afterAll` confere o arquivo. A cópia local saiu do `ConsoleLigado`.

A revisão da entrada anterior tinha achado a ordem velha — devolver o `fetch`
antes de desmontar — em mais três arquivos: o `AgendaLigada.teste.tsx`, o
`ComandasLigada.teste.tsx` e os dois blocos do `Console.teste.tsx` que montam a
agenda e a comanda. Nenhum vazou nas rodadas medidas, mas com a tranca a ordem
velha viraria vermelho de vez em quando, e os quatro ganchos passaram a
desmontar primeiro.

### A revisão adversarial mudou o desenho

A primeira versão punha uma tranca nova a cada caso e devolvia o `fetch` de
verdade no fim dele. Três revisores, com um cético por achado, confirmaram 4
achados, e nenhum dos 8 agentes morreu. O revisor dos vermelhos falsos, que leu
os 13 arquivos, voltou vazio.
- **A tranca só valia do começo ao fim de cada caso.** Um pedido depois do último
  caso de um arquivo ainda saía à rede, e o caso ficava verde: um revisor provou
  com uma sonda, e o `ECONNREFUSED` apareceu.
- **O pedido atrasado de um caso reprovava o SEGUINTE**, que nunca o fez.
- **O cabeçalho do `preparo.ts` prometia a suíte inteira**, e a tranca não cobria
  isso.
- **O meu comentário no bloco da comanda dava o motivo errado.** Os catálogos
  saem no clique de abrir, e não num efeito; a ordem dos ganchos não os protege.

Por isso a tranca passou a durar o arquivo inteiro. Por isso, também, cada caso
espera uma volta do relógio antes de conferir, e a mensagem avisa que o
vermelho pode ser a cauda longa de um caso anterior. Os dois comentários estão
corrigidos.

### Provado

- A suíte, 10 rodadas: **357 testes** em todas, nenhum `ECONNREFUSED`, e nenhum
  pedido chegou à tranca. A rodada leva 21 s, como antes.
- Sondas de um arquivo só, criadas e apagadas depois:
  - o caso que pede sem dublê reprova, com o endereço;
  - a cauda curta (`setTimeout(0)`) reprova o caso que a deixou, e não o
    seguinte. Sem a espera, reprova o seguinte;
  - a cauda de 15 ms cai no caso seguinte, com o aviso na mensagem;
  - o pedido depois do último caso reprova o arquivo. Sem o `afterAll`, passa
    calado;
  - nenhuma sonda saiu à rede.
- **Não medido:** a cauda que dispara depois do `afterAll`. Uma de 40 ms não
  disparou antes de o vitest desmontar o ambiente, nas 3 rodadas, com a tranca
  e sem ela. Que ela bateria na tranca vem do código — nada devolve o `fetch` de
  verdade —, e não de uma medição.
- `tsc` e `oxlint` limpos, com os dois avisos de antes.

## 23/09/2026, noite — o que a auditoria do desenho deixou: a data que se digita, a recarga sem rede, e o botão que não fazia nada

Oito agentes conferiram os 53 achados confirmados do levantamento contra o código
de hoje, e um crítico reconferiu os "feitos" de evidência mais fraca: **48 feitos,
3 em parte, 1 aberto e 1 decisão sua.** Nenhum agente morreu, e o crítico não
derrubou nenhum "feito". O que restava que não dependia de você entrou agora.

### O que mudou para quem usa

- **Digitar a data na agenda não anda mais dia a dia.** O navegador dá uma data
  completa a cada tecla: digitar o dia 15 pedia o dia 01 antes, e o ano 2027
  passava pelos anos 0002, 0020 e 0202 — uma busca por tecla, e o dia errado
  piscando. Agora o que a tecla muda é rascunho: o Enter ou sair do campo pedem o
  dia, e o Esc o descarta. O calendário do navegador continua valendo na hora,
  porque escolhe sem tecla. Sair da janela no meio da digitação guarda o
  rascunho, e um ano antes de 1900 volta ao dia da tela.
- **"Horário livre." e "Carregando…" ficaram legíveis.** Estavam em `--tinta-3`,
  a 3,62:1 e 3,92:1 no escuro, e foram para `--tinta-2`, acima de 7:1.
- **No celular, os destinos que abrem noutra aba fecham a gaveta**, e o foco vai
  ao botão do menu, e não ao começo da página. O Acesso Yungas deixava o foco
  cair; o Docs APIs e as Vendas em PDF deixavam a gaveta aberta sobre a agenda.
- **A lista de produtos reaberta pelo "trocar" não fecha sozinha.** O blur agenda
  o fechamento para 120 ms depois; escolher o produto e apertar "trocar" nesse
  intervalo deixava o atraso da busca anterior fechar a lista nova. Um caso da
  suíte reprovou assim uma vez, sob carga; hoje há um caso que reproduz o
  defeito sempre, e ficou verde com o conserto.
- **A faixa de personificação sem quem atenda a volta não desenha o botão.** No
  app nada muda, porque o `ConsoleLigado` sempre passa a volta; a `MolduraDoErp`
  e a prévia de design desenhavam um botão que não fazia nada.

### Os casos que faltavam

- A recarga que falha por rede, e não por recusa do servidor. O dublê ganhou
  `redeCaida()`, que faz o `fetch` rejeitar como sem conexão.
- A grade que falha logo depois de registrar a chegada.
- Cancelar a comanda pede `vendas/cancelar/escrever`, passando pelo Console:
  liberar o cancelar no mapeamento ficava verde.
- O administrador que não é gerente também edita o valor, como na regra do
  servidor (`podeCobrarForaDaTabela`).
- Tirar a "nova comanda" do mapeamento já deixava um caso vermelho. A auditoria
  dizia que não; a mutação mostrou que sim.

### A revisão adversarial

Três revisores e um cético por achado: 10 achados, 8 confirmados e 2 refutados;
nenhum dos 13 agentes morreu. O mais sério era meu: a marca de "digitando"
durava o foco inteiro, e depois de qualquer tecla — até o Alt+↓ que abre o
calendário — o dia escolhido no calendário esperava o Enter. Agora a tecla marca
só a mudança dela, de apertar a soltar. Os outros: o blur aplicava o rascunho ao
trocar de janela e depois do Esc; três guardas do campo de data sem caso; um
comentário que dizia que as setas substituem o rascunho (no navegador elas o
aplicam antes); o inventário de `--tinta-3` no `App.css` e nas notas do pacote de
design, que esqueciam cinco bordas; e os links de aba nova que deixavam a gaveta
aberta. Todos estão corrigidos.

### Provado

- **377 testes**, 5 rodadas, nenhum pedido na tranca. `tsc`, `oxlint`, `build` e
  `build:ds` limpos.
- Mutações, todas vermelhas menos uma: 12 no campo de data (uma, mal escrita, foi
  trocada por outra; a marca que o `aplicar` limpa só ficou vermelha depois de um
  caso novo — a tecla que se solta fora do campo), 3 na recarga que falha, 4 no
  mapeamento da comanda (o "cancelar sempre liberado" ganhou caso), 1 no fechar
  atrasado da lista, e 5 na barra, no foco e na aba nova. A viva: focar o botão
  do menu mesmo com a gaveta fechada. Ela é equivalente no navegador — fora do
  estreito o botão é `display: none`, e com a gaveta fechada o link não se
  alcança —, e o happy-dom, que não aplica CSS, não a distingue.
- No Chrome, por uma página de sonda apagada depois: o dia 15 mostrou 01/09 no
  meio da digitação sem pedir nada, e o Enter pediu um dia só; o ano 2027 passou
  por 0002 e 0202 sem pedir nada; o Esc devolveu o dia; o ano 0202 com Enter voltou
  ao dia da tela sem pedir; as duas frases saem na cor da `--tinta-2`, no escuro;
  em 375 px, o Acesso Yungas pelo teclado abriu uma vez e deixou o foco no botão
  do menu.
- **Não conferido no navegador:** o calendário do navegador, que é uma janela
  fora da página, e a troca de janela — no painel o documento nunca tem o foco da
  janela. Os dois têm caso de teste.

### Decisões que ficam com você

- **O token `--tinta-3` reprova 4,5:1** no escuro nas três superfícies (3,38 a
  3,92:1), e no claro fora da superfície branca (3,94 e 4,30:1). O desenho o usa
  para rótulo — "Cliente", "buscar", os rótulos de fechar a agenda —, e a casa
  manteve rótulo em `--tinta-3`. Levar o token a `#736C5B` no claro e a
  `#8E8672` no escuro passaria em tudo (4,52:1 no pior caso) e continuaria
  distinto da `--tinta-2` (1,56:1). Mudar o token muda o desenho, e mexe também
  nas cinco bordas de estado que o usam.
- Seguem como estavam: aposentar a `MolduraDoErp` e a `Home` no pacote de design
  (MO-8, DS-1), o horário livre no Tab (C1), o aviso de só leitura com o código do
  privilégio (C2) e o primário da tela ainda não migrada (C9).

## 24/09/2026, madrugada — remarcar pela agenda nova, e o endereço que nunca tinha rodado (D216 a D221)

### O que mudou para quem usa

- **O painel do horário ganhou "Remarcar"**, ao lado de "Registrar chegada", para
  quem gerencia a agenda e só em agendamento que ainda não começou. Abre uma
  janela com o colaborador e o novo horário. A lista de horários só oferece onde
  o agendamento **inteiro** cabe — um corte de 40 minutos não é oferecido às
  11:40 se as 12:00 estão ocupadas —, e mostra o período: "10:20 às 11:00".
- **O agendamento pode andar sobre o próprio lugar.** Empurrar um serviço de
  dois horários um horário para a frente era recusado como "ocupado" — pelo
  próprio agendamento. Na cópia, das 101 remarcações assim no mesmo dia,
  nenhuma caiu sobre o próprio lugar: a tela antiga recusava e quem estava no
  balcão desistia.
- **Trocar de colaborador diz quando a duração muda.** A remarcação conserva o
  número de horários, e não os minutos: 60 minutos numa agenda de 30 viram 40
  numa agenda de 20. A janela avisa antes.
- **Remarcado, a tela diz para onde foi** — "Agendamento de Rita remarcado para
  10:20 com Joaquim." — e o foco vai para o bloco novo. Recusado, a janela fica,
  com o porquê que o servidor escreveu, e a escolha feita.
- **Tudo pelo teclado.** O arrastar do desenho ainda não entrou: ele vem em
  seguida, como atalho para a mesma janela.

### O que estava errado, e foi consertado (D216)

A remarcação da API existia desde a segunda fatia e **nenhum ensaio a
chamava**. Lida e medida, ela deixava passar:

- **horário com a agenda fechada** — a conferência de "ocupado" não enxerga as
  linhas de fechamento;
- **horário fora da grade, no almoço, nas férias ou em dia sem agenda** — o
  agendamento ficava gravado onde a grade não o desenha;
- **hora malformada** — "9h" e uma observação longa davam erro de servidor, e
  hora repetida gravava duas linhas no mesmo horário;
- **quem já tinha chegado** — remarcar apagava a chegada: 621 vezes no arquivo
  da cópia;
- **o agendamento maior que o serviço** — o serviço de uma hora virava de meia,
  sem erro;
- **sem transação** — umas seis gravações separadas, e uma falha no meio deixava
  dois agendamentos, ou nenhum.

Agora ela apaga, confere e marca **numa transação**, com a raiz e o colaborador
travados; qualquer recusa desfaz tudo. **A tela antiga continua como estava** —
mudar a regra dela muda a rotina das 32 lojas e o contrato do aplicativo.

### O que estava errado, e só o navegador mostrou

**As duas janelas da agenda sumiam no clique que as abria** — a de fechar a
agenda, da revisão de ontem, e a de remarcar. O React de desenvolvimento monta,
desmonta e monta de novo cada efeito, e a desmontagem fechava a janela: o
aviso de "fechou" chegava com ela já reaberta, e a tela a descartava. Nenhum
teste montava a agenda como o aplicativo monta; 405 casos verdes, e o botão não
abria. Hoje dois casos a montam assim, e ficam vermelhos com o defeito de volta.
Em produção o React não remonta, e o defeito não aparecia — mas nenhuma das
duas janelas pôde ser conferida no navegador desde ontem.

### Provado

- **785 testes nos dois bancos** (35 novos: 18 da regra pura, 17 do serviço —
  inclusive a trava medida com uma segunda conexão segurando a linha) e **409 no
  front**. `tsc`, `oxlint`, `build` e `build:ds` limpos; o corredor de portões,
  verde fora o vermelho conhecido do D50.
- **Por HTTP, contra o banco de contrato**: o ensaio de recusas foi de 46 para
  **59 casos**, e o 55 lê o banco depois da recusa — o agendamento continua lá e
  o arquivo de exclusões não ganhou linha. O de permissão cobra a rota nova
  (**40 de 40**), e o fluxo de um dia remarca e registra a chegada no id novo
  (**20 passos**).
- **Mutações**: as 13 regras da classe pura e as 11 peças do serviço, uma a uma;
  9 no controller e no serviço medidas pelos ensaios — com `commit` no
  lugar do rollback, o caso 55 responde o mesmo 422 com a mesma frase, e **só o
  banco o acusa** —; e 27 na tela, 26 vermelhas. A viva guarda o foco no bloco
  novo para o Chrome sem foco de janela, que o ambiente de teste não reproduz.
- **A revisão adversarial** — quatro revisores e um cético por dimensão, nenhum
  agente morto — confirmou 15 achados e refutou 4. Os que eram defeito:
  agendamento sem serviço dando 500; a resposta dizendo o dia seguinte para quem
  ia às 23:30; o foco perdido quando o bloco novo não está na tela, ou quando a
  recarga depois de remarcar falha; o botão desligado sob o foco quando a
  recarga de minuto tira o horário escolhido; a família partida sem poder se
  juntar no próprio início. E dois instrumentos meus mediam menos do que diziam:
  o caso 48 ficava verde sem a guarda da unidade, e um caso de teste chegava ao
  "sumiu" por um id que o Doctrine anula. Todos consertados, com o caso que fica
  vermelho sem o conserto. **E uma frase minha era falsa:** a trava não protege
  a API contra ela mesma — o check-in, o desmarcar e o marcar gravam sem ela
  (D219). É o próximo passo.
- **No Chrome, contra o banco de contrato**: o serviço de dois horários das 10:00
  foi para as 10:20 pelo teclado, sobre o próprio lugar, e o foco foi ao bloco
  novo; com as 09:40 tomadas por outro caixa depois de escolhidas, a janela
  mostrou "O horário 09:40 não está mais disponível", e o banco confirmou o
  agendamento das 09:00 no lugar e nada arquivado. Em 375 px e no tema escuro,
  a janela cabe sem rolar de lado.

### Anotado para depois

- **D217** — o arrastar da tela antiga grava o terceiro horário em cima do
  segundo nos serviços de três horários: 18 agendamentos assim na cópia. O
  conserto é uma linha, na tela antiga.
- **D218** — o arquivo de exclusões não distingue remarcação de cancelamento, e
  a produção o esvazia a cada 60 dias.
- **D219** — a trava da remarcação nova só vale entre duas remarcações: o
  check-in, o desmarcar e o marcar — da própria API, da tela antiga e do
  aplicativo — gravam sem conferir de novo, e a tabela não tem índice que impeça
  dois agendamentos no mesmo horário (34 na cópia).
- **D220** — achar os horários de um agendamento varre a tabela inteira; o custo
  em produção não foi medido.
- **D221** — remarcar para outro colaborador não muda a comanda já aberta, que
  fica com o colaborador — e a comissão — de antes.

### Decisões que ficam com você

1. **Outro dia.** A janela remarca no dia que está na tela; 326 das 4.046
   remarcações do arquivo mudaram de dia.
2. **O colaborador de destino precisa fazer o serviço?** Nada confere isso hoje;
   só 29 colaboradores têm a lista.
3. **Quem já chegou ou já pagou.** Hoje: não se remarca (a tela antiga deixava, e
   perdia a chegada). Alternativas: deixar e manter a chegada, ou deixar como a
   tela antiga.
4. **A comanda aberta** (D221): ignorar, avisar ou recusar a troca de
   colaborador.
5. **O passado.** Hoje vale, como na tela antiga — um quarto das remarcações do
   arquivo foi para um horário que já tinha passado, corrigindo quem atendeu.
6. **Conflito de propósito** — encaixar um cliente num horário ocupado ou
   fechado, com "mesmo assim". Hoje: sempre recusa.
7. **O arrastar**: grava ao soltar, como no desenho, ou pede confirmação; e se
   haverá "desfazer" (que seria outra remarcação, com ids novos).
8. **Passos diferentes entre colaboradores**: conservar o número de horários
   (hoje) ou os minutos, ou recusar a troca entre agendas de passo diferente.
9. **A origem** volta a "web" depois de remarcar, como na tela antiga, ou fica a
   de antes ("app", por exemplo).
10. **Arrastar no toque**: nenhum (hoje) ou toque longo.
11. **A frase do aviso**: o desenho diz "Rita remarcado para…", que tem gênero;
    a tela diz "Agendamento de Rita remarcado para…".
12. **Registrar a remarcação no arquivo** (D218) — muda o esquema nos dois
    bancos.

## 24/09/2026, madrugada — as escritas da agenda pegam a mesma trava, e o check-in deixou de desfazer chegada (D219)

### O que mudou para quem usa

- **Dois caixas no mesmo agendamento não se atropelam mais pela tela nova.** Se
  um remarca e o outro registra a chegada no mesmo instante, o segundo espera o
  primeiro terminar e vê o que ficou: o agendamento já não está naquele lugar, e
  a tela diz isso, fecha o painel e mostra a agenda de novo — em vez de
  responder que a chegada foi registrada numa linha que acabara de ser apagada.
  O mesmo vale para desmarcar. E marcar no horário para onde outro caixa acabou
  de remarcar é recusado, em vez de gravar dois clientes no mesmo horário.
- **A chegada não se desfaz mais.** O sistema antigo *alterna* a chegada de cada
  horário, e a tela nova mandava o agendamento inteiro: num serviço de dois
  horários em que um já tinha a chegada, registrar a do outro desfazia a
  primeira. Agora só se registra quem ainda não chegou.

### O que estava errado (D219)

A revisão adversarial de ontem achou que a trava da remarcação só protegia uma
remarcação da outra. O check-in, o desmarcar e o marcar esperavam a trava soltar
e gravavam o que tinham lido **antes** da espera. Agora as quatro escritas pegam
a mesma trava, e só leem e conferem depois dela.

### Provado

- **807 testes nos dois bancos** (22 novos, com a trava medida por uma
  segunda conexão segurando a linha, e a ordem — trava primeiro, leitura
  depois — lida no registro de SQL) e **411 no front**.
- **Mutações**: a trava e as treze peças das escritas, uma a uma, no MySQL; as
  quatro de trava também no PostgreSQL; os 24 da remarcação de novo, depois de
  a trava sair dela; e o 404 da chegada na tela. Uma delas
  mostrou que um caso meu não media o que dizia — ficava verde sem reler a
  família —, e ganhou o caso que mede.
- Os cinco ensaios por HTTP, contra o banco de contrato: o fluxo de um dia marca,
  remarca e registra a chegada pelos caminhos novos.
- **Uma segunda revisão adversarial**, só deste passo, confirmou 5 achados. Quatro
  eram buracos nos meus testes — o pior: trocar a ordem trava/conferência no
  marcar trazia o defeito de volta com tudo verde —, e todos ganharam o caso que
  mede. O quinto era defeito antigo: **marcar às 23:30 respondia o dia seguinte**,
  como a remarcação respondia; as duas agora leem as linhas novas do banco.

### O que continua

A tela antiga e o aplicativo gravam sem trava nenhuma, e a tabela não tem índice
que impeça dois agendamentos no mesmo horário. Levar a trava ao código que eles
usam mudaria a rotina das 32 lojas e o contrato do aplicativo — é decisão sua,
e está no D219.

## 24/09/2026, manhã — arrastar o bloco abre a janela da remarcação, já preenchida

### O que mudou para quem usa

- **Com o mouse, o agendamento se arrasta**, nas colunas e na linha do tempo.
  Enquanto se arrasta, uma sombra mostra onde ele cairia, com o período escrito
  — "10:20–11:00", e "· 40 min" quando a duração muda na agenda de outro
  colaborador. Onde não cabe, a sombra diz "não cabe".
- **Soltar abre a janela do "Remarcar"** com o colaborador e o horário já
  escolhidos: a mesma do teclado, que avisa a duração nova e mostra a recusa do
  servidor. Um clique em "Remarcar" confirma; "voltar" ou Esc desistem.
- **Soltar fora da agenda não faz nada** — sobre o nome no topo da coluna, na
  régua das horas, no nome fixo da linha do tempo, fora da janela. Esc no meio
  do gesto também desiste.
- **O clique que treme continua clique.** O gesto só começa depois de 6px, e o
  bloco que volta ao próprio lugar abre o painel, como um clique.
- **No toque, nada muda**: tocar abre o painel, e arrastar o dedo rola a agenda.
  O painel diz, a quem tem mouse, que o bloco se arrasta.
- **A recusa relê a agenda.** Quando o servidor diz que o horário foi tomado ou
  que alguém chegou, a grade é pedida de novo. Fora do dia de hoje ela não se
  relia sozinha, e a janela seguia oferecendo o horário recusado.

### Por que soltar não grava

O desenho move o agendamento arrastando, e a primeira versão deste plano
gravava ao soltar. **Uma revisão adversarial do desenho, antes de uma linha de
código** — três revisores e um cético por achado, 21 agentes, nenhum morto —
mostrou o preço:

- gravar ao soltar é a **decisão 7, sua**, e este roadmap prometia, em 23/09,
  "atalho para a mesma janela";
- o bloco encostado na borda da coluna trocaria de barbeiro num tremor do
  touchpad, e não há "desfazer": a remarcação recria as linhas;
- a duração que muda de uma agenda para outra não seria dita, e a recusa do
  servidor não teria onde aparecer.

A janela resolve os três. Se você decidir que soltar grava, a mudança é pequena:
a trava e o pedido já são os mesmos da janela.

E a revisão achou um defeito do plano que nenhum teste de tela acharia: **prender
o ponto à trilha mais próxima fazia soltar sobre o nome preso no topo da coluna
— o gesto de desistir — mirar um horário escondido debaixo dele.** O alvo agora
é o que está visível sob o mouse.

### Provado

- **439 testes no front** (28 novos). O gesto é medido na tela pura com a
  geometria dublada — 1px por minuto, e o nome da coluna acima da trilha —, e na
  tela ligada: soltar abre a janela, e o "Remarcar" leva o mesmo pedido do
  teclado.
- **Mutações**: as 26 peças do arrasto, uma a uma, e as 29 da remarcação de
  novo. Uma do arrasto sobreviveu na primeira rodada — a família com chegada se
  deixava arrastar, e o caso ficava verde porque a minha geometria dublada media
  um dia de 180 minutos numa grade de 100, e soltava num horário ocupado. A
  geometria passou a ler o dia de cada trilha, e o caso passou a olhar a sombra.
  A que o ambiente de teste não reproduz continua a mesma de antes (o foco
  guardado no bloco novo).
- **No Chrome**, contra o banco de contrato: arrastar o serviço das 09:00 para as
  10:00 abriu a janela preenchida, o clique do gesto não abriu o painel, nada de
  texto ficou selecionado; "Remarcar" gravou as 10:00 no banco, arquivou a linha
  velha, e o foco seguiu o bloco novo. Com a grade rolada, o nome preso no topo
  cobria horários da trilha: sob ele não houve sombra, e soltar ali não abriu
  nada. O mesmo na linha do tempo, sobre o nome fixo. E o Chrome achou uma coisa
  que os testes não acham: deitada, a sombra de 20 minutos tem 52px, e o período
  saía cortado em "10:40–1" — agora ele sai da sombra, legível.

### A revisão adversarial do código

Depois do commit, uma segunda revisão adversarial leu o código — três revisores
e um cético por achado, 14 agentes, nenhum morto — e confirmou 10 achados,
refutando 1. **O mais sério era meu, e um teste meu o escondia: o clique que
tremeu não abria o painel no navegador.** Com o gesto começado, os blocos param
de receber o mouse; o botão sobe sobre a trilha, e o navegador entrega o clique
a ela, e não ao bloco. O caso de teste clicava direto no bloco, o que o
navegador não faz. Agora o painel abre no próprio soltar, e o clique do
navegador é engolido. **Conferido no Chrome nos dois sentidos**: um arrasto de
13px que volta ao lugar abre o painel, uma vez; com essa linha arrancada, o
mesmo gesto não abre nada.

Os outros nove, todos consertados:

- a recarga de minuto que chegava com o mouse parado não relia o alvo, e a
  sombra dizia "cabe" num horário já tomado;
- depois de remarcar pelo arrasto, o foco voltava ao bloco do lugar velho;
- a recusa que tira o "Remarcar" do painel (alguém chegou) deixava o foco no
  `body` ao desistir — justamente no caminho do teclado;
- soltar um bloco apagava a recusa da chegada no painel de outro agendamento;
- o painel ensinava o arrasto na forma Agora, que é uma lista;
- e quatro testes meus mediam menos do que diziam: o `elementFromPoint` dublado
  não distinguia "visível sob o mouse" de "dentro da caixa da trilha", o dos
  ouvintes só conferia os nomes, ninguém soltava noutro colaborador no mesmo
  horário, e o do clique que tremeu clicava onde o navegador não clica.

Cada conserto ganhou o caso que fica vermelho sem ele: **447 testes no front**,
e **38 mutantes do arrasto**, todos vermelhos.

### O que continua

- **A decisão 7 continua sua**: soltar abre a janela (hoje) ou grava direto; e
  se haverá "desfazer".
- Arrastar não rola a agenda sozinho perto da borda: para um horário fora da
  vista, rola-se com a roda do mouse, e a sombra acompanha.
- Remarcar para outro dia continua fora (decisão 1).

## 24/09/2026, manhã — "Sair" passou a sair, e o login a trocar o id da sessão (D222)

### O que mudou para quem usa

- **"Sair" encerra a sessão de verdade.** Até hoje a página do ERP voltava a
  pedir a senha, e a API nova — a das telas novas — seguia respondendo como
  quem tinha saído. No computador do balcão, a tela nova aberta noutra aba
  continuava agindo com o usuário anterior.
- **O login troca o id da sessão.** O id que existia antes do login virava a
  sessão logada: quem pusesse um id conhecido no navegador de outra pessoa
  entraria como ela depois do login dela.
- **"Sair" invalida o "lembrar de mim" daquela sessão.** O `token_web`, que
  entra sem senha, saía só do navegador; no banco seguia valendo.

Nada muda para quem só entra e trabalha. Achado por acaso: limpando a sessão que
eu tinha posto no navegador para conferir o arrastar, a API respondeu depois
do "Sair".

### Provado

Um ensaio novo por HTTP, `ensaiar-sessao-do-erp.php`, com 6 casos — três deles
com controle, para não ficar verde com uma API que recusa tudo: **2 de 6 antes
do conserto, 6 de 6 depois**; e as seis peças do conserto, arrancadas uma a uma,
deixam o ensaio vermelho. A minha primeira medição da fixação errou: o id que
eu escolhi derrubava o PHP com erro fatal, e o erro saía com HTTP 200 — a cara
de uma sessão logada. Medida de novo, com o instrumento corrigido, ela é real.

### O que continua

O "um dispositivo por vez" não desloga quem foi expulso: o navegador vai para
a tela de entrar, e a sessão dele segue logada (D223, medido). O conserto mexe
na mensagem que explica a expulsão, e ficou anotado com a direção.

## 24/09/2026, manhã — a grade diz o tamanho de cada agendamento (D216)

### O que mudou para quem usa

- **Remarcar o agendamento marcado noutro passo passou a funcionar.** O
  colaborador que atendia de 20 em 20 e hoje atende de 30 em 30 tem cortes
  marcados às 09:00 e 09:20; a grade de hoje só desenha as 09:00. A tela contava
  um horário só, o pedido levava um, e o servidor recusava ("o serviço ocupa 2
  horários"). Agora a grade diz as horas do agendamento inteiro, e a janela e o
  arrasto levam as duas.
- **A duração de agora é a verdadeira**: "Marcado com Joaquim, às 09:00 (40 min,
  2 horários)" — e, levado para uma agenda de 30 em 30, a janela avisa que o
  atendimento fica com 60 minutos.

### Provado

A grade ganhou 2 casos (a família com um horário que a grade não desenha, e a
coluna montada sem o serviço, que não inventa família), e a tela 10: as horas e
a duração no modelo, o campo que chega pela API, a janela, e o arrasto — a
sombra e os destinos. **Mutações: 4 no servidor e 10 na tela, todas
vermelhas.** O que se escreve na resposta é aditivo: quem não lê o campo novo
não perde nada. **Uma revisão adversarial** da mudança confirmou 1 achado: o
corte das 23:30 que passa da meia-noite aparecia "às 00:00 (2820 min)" —
consertado. E anotou um limite anterior a ela: a família cujo horário fora do
passo já teve a chegada registrada continua recebendo o "Remarcar", e o
servidor recusa (D216).

## 24/09/2026, manhã — desmarcar pela agenda nova, e quem já chegou não se desmarca

### O que mudou para quem usa

- **O painel do agendamento ganhou "Desmarcar"**, ao lado do "Remarcar" e com a
  mesma regra: quem pode mexer na agenda, e o agendamento inteiro ainda marcado.
  Ele abre uma pergunta que diz quem, o quê, com quem e das quantas às quantas —
  o agendamento inteiro, com os horários que a grade não desenha (D216) e o
  corte que passa da meia-noite.
- **O foco abre no "voltar"**: o Enter distraído não apaga. O botão que apaga é
  o vermelho do que não volta atrás; no ERP de hoje ele é o mesmo verde do
  Salvar.
- **Depois de desmarcar**, a pergunta e o painel fecham, o aviso diz
  "Agendamento de Rita das 09:00 desmarcado.", a grade relê e o foco vai ao
  horário que ficou livre. O agendamento vai para o arquivo de exclusões, com
  quem desmarcou, como na tela de hoje.
- **Quem já chegou não se desmarca pela tela nova — nem quando a chegada foi
  registrada por outro caixa com a pergunta aberta.** O servidor relê o
  agendamento na hora de apagar e recusa; a pergunta fica com a frase dele, e a
  grade relê. A tela antiga e o aplicativo continuam desmarcando quem chegou.

### O que estava errado, e a revisão achou antes de chegar a você

A primeira versão conferia a chegada só na grade que a tela tinha — e a grade
pode ser de um minuto atrás. Com a pergunta aberta, outro caixa registrava a
chegada, e o "Desmarcar" apagava o cliente que estava na cadeira, com a chegada
junto. A remarcação já recusava isso no servidor; o desmarcar da API, não.
Agora recusa (409), relido depois da trava. A mesma revisão achou mais três:
o corte das 23:30 que acabava "às 24:30", o aviso "das 09:00 das 09:00" quando
o nome do cliente vem vazio, e um caso que não distinguia a família marcada
noutro passo. Os quatro estão consertados, cada um com o seu teste.

### Provado

- **A tela**: 16 casos novos (a pergunta, o foco que abre no "voltar", o 200, o
  404, o 409, a rede caída, o Esc, o envio no ar, o arrasto com a pergunta
  aberta, a família noutro passo, a meia-noite, o nome vazio) — a suíte foi de
  457 para 473. **22 mutações, todas vermelhas**, cada uma pelo caso que a
  guarda promete.
- **O servidor**: 2 casos na suíte, nos dois bancos (quem chegou — e só o
  horário que a grade não desenha — e o atendimento finalizado, com a frase do
  fim); **3 mutações, todas vermelhas**, inclusive a de ler a chegada da
  memória e não do banco. E 2 casos no ensaio HTTP das recusas: o 409, e o
  controle que desmarca de verdade pelo horário filho. Arrancado o 409 do
  controller, a suíte fica verde e só o ensaio pega: a resposta vira 200, sem
  nada apagado — um sucesso falso.
- **No navegador**, contra o banco do contrato: a linha saiu da tabela, a cópia
  foi para `agendas_exclusoes` com quem desmarcou, o aviso apareceu e o foco foi
  ao horário livre. E, com a pergunta aberta, a chegada registrada por baixo: a
  pergunta ficou com a frase do servidor, a grade relida mostrou "em
  atendimento" sem o Desmarcar, o Esc levou o foco ao título do painel, e o
  agendamento continuou no banco, sem nada no arquivo.
- **Uma revisão adversarial** com três lentes (estado, foco, testes): 12
  achados, 8 confirmados — os quatro defeitos acima, alguns achados mais de uma
  vez —, 4 refutados; dois dos refutados eram lacunas de teste, e viraram teste.

### Decisões que ficam com você

13. **Desmarcar quem já chegou.** A tela nova não oferece, e a API passou a
    recusar; a tela antiga e o aplicativo deixam, e a chegada vai junto para o
    arquivo. O cliente que chegou e foi embora sem ser atendido, hoje, só sai
    pela tela antiga. Se quiser pela nova, há dois caminhos: deixar desmarcar
    com a chegada (e dizer isso na pergunta), ou um "desfazer a chegada" antes
    — que a API nova também não tem.

## 24/09/2026, tarde — marcar pela API recusa o que a grade não oferece (D224, primeira parte)

### O que mudou para quem usa

Nada ainda na tela: é o primeiro passo do "Marcar" na agenda nova. O endereço
que a tela vai chamar — `POST /adm/api/agendamentos` — existia desde a segunda
fatia, e nenhuma tela o chamava. Lido antes de ligar a tela, ele aceitava o que
a grade não oferece: o horário com a agenda fechada, o dia de férias, o dia sem
agenda, o horário fora da grade; e "9h" era erro 500. Agora ele recusa cada um,
com a mesma frase da remarcação.

### Como o desenho foi revisado antes do código

Três leitores mapearam o servidor, a tela antiga e as peças da tela nova, e três
críticos tentaram derrubar o desenho antes de ele virar código. Eles mudaram o
plano em pontos que só a medição mostrava: as fixtures abrem os sete dias iguais,
e um dia lido no formato errado passaria em qualquer teste comum; dois casos
existentes mudavam de sentido, e não só de cor; e o balcão encadeia serviços
para o mesmo cliente em cerca de 15% das marcações — o que entra na fatia da
tela. O trabalho ficou em fatias, e esta é a primeira.

### Provado

- `EscritasDaAgendaTest` foi de 22 para 27 casos, nos dois bancos: fechado,
  férias, só a quinta sem agenda, a quinta abrindo às 10:00 com os outros dias às
  08:00, e "9h", "10:10" e "25:00" recusados sem erro.
- 8 mutações no serviço: 6 vermelhas, cada uma pelo caso da sua guarda — entre elas a grade e o dia aberto lidos em ISO —, e 2 equivalentes, escritas no código: as férias comparam as datas com `strtotime`, que lê os dois formatos, e os ids de antes, no marcar, ficaram defesa (nenhuma linha no horário pedido passa das conferências).

### O que continua (a fatia seguinte)

Cliente e serviço que não existem ainda são 500; item que não é serviço, serviço
inativo ou de outra unidade e colaborador fora da agenda ainda passam; hora
repetida num serviço de dois horários grava duas linhas. Estão no D224, e vêm no
controller, com um caso por guarda no ensaio por HTTP.

## 25/09/2026 — marcar pela API recusa o pedido malformado (D224, segunda parte)

### O que mudou para quem usa

Ainda nada na tela. O endereço que o "Marcar" vai chamar —
`POST /adm/api/agendamentos` — agora recusa, com a frase de cada campo, tudo o
que antes virava erro 500 ou se gravava sem poder: cliente ou serviço que não
existem, um item do bar, um serviço inativo ou de outra unidade, um
colaborador fora da agenda ou sem tempo de atendimento, hora repetida,
observação longa demais e emoji na observação. O emoji e o colaborador sem
tempo de atendimento também eram 500 na remarcação, e deixaram de ser.

### Provado

- 76 casos nos quatro arquivos de teste da marcação e da remarcação, nos dois
  bancos; a suíte inteira, 828 verdes, no MySQL e no PostgreSQL.
- O ensaio por HTTP, 77 de 77, com 16 casos novos — um por guarda, e dois de
  controle que precisam passar: o cliente "sem cadastro" (inativo, e 12,9% das
  marcações do balcão) e o cliente de outra unidade (24% das marcações de 2026).
- 14 mutações, todas vermelhas: 11 nas regras e nos serviços, pelos testes, e 3
  na fiação do controller, pelo ensaio.

### O D224 fecha na API

O que falta do "Marcar" é a tela: o diálogo, o encaixe e o encadeamento de
serviços do balcão. E a decisão 2 continua com você: se a agenda deve recusar
um serviço que o colaborador não faz — hoje, nem a tela antiga nem a API
perguntam.

## 25/09/2026, noite -- a lista do que se marca (o Marcar, etapa B)

### O que mudou para quem usa

Ainda nada na tela. O diálogo do "Marcar" vai oferecer o serviço, e a lista
vinha em `<option>` montado no servidor pela tela antiga. Agora ela tem
endereço próprio -- `GET /adm/api/agendas/servicos?colaborador=<id>` --, com o
mesmo conteúdo da tela antiga: os serviços da unidade que aquele colaborador
atende, pela lista dele ou pelas áreas de atuação, com quantos horários cada um
ocupa e o preço.

### Provado

- A suíte inteira, 837 verdes, no MySQL e no PostgreSQL, com a lista medida
  sobre as fixtures (`ServicosDaAgendaTest`) e a forma sem banco.
- O ensaio por HTTP, 83 de 83: sem permissão de marcar, sem colaborador, de
  outra unidade, fora da agenda, e dois controles que mostram a lista mudando
  de um colaborador para outro.
- Três mutações na fiação, cada uma vermelha pelo caso da sua guarda.

### O que vem

A tela: o botão "Marcar" no horário livre, o diálogo (cliente, serviço, início
e observação) e a grade relida depois.

## 25/09/2026, noite -- o Marcar na tela (etapa C)

### O que mudou para quem usa

Clicar num horário livre abre o Marcar: busca o cliente (pelo nome, pelo
telefone -- com ou sem máscara -- ou pelo e-mail), ou escolhe "cliente sem
cadastro"; escolhe o serviço, e o início vem escolhido se o serviço cabe no
horário clicado; uma observação, se houver, e "Marcar". A janela fecha, o aviso
diz o que foi marcado, a grade é relida e o foco fica no agendamento novo. Se o
servidor recusa -- o horário foi tomado, o serviço não cabe --, a janela fica
com a frase dele e a grade é relida. Quem só lê a agenda continua vendo "Horário
livre.", sem o Marcar. Cliente que ainda não existe: a janela leva ao cadastro
do ERP, em outra aba.

### Provado

- A suíte do front, 506 verdes -- 33 casos novos: 14 da tela ligada, 12 da
  janela sozinha, 3 da API e 4 do termo da busca. `tsc -b` e o oxlint limpos.
- 20 mutações no código novo: 18 vermelhas, cada uma pelo caso da sua guarda. Três
  sobreviveram na primeira rodada -- o passo da agenda fixo em 30 minutos, a
  lista de serviços atrasada caindo na janela de outro colaborador e a
  observação sem aparar -- e ganharam caso. As 2 que seguem vivas são
  equivalentes: o bit de escrita conferido de novo dentro do `abrirMarcacao`
  (os dois chamadores já o conferem) e o `preventDefault` do Enter na busca (a
  busca só existe sem cliente, e sem cliente o formulário não envia).
- No navegador, contra o banco de contrato: marcar com cliente e com "sem
  cadastro", o foco no bloco novo, o Escape devolvendo o foco ao horário, o
  modo toque, o tema escuro e a largura de celular (375 px, sem rolagem de
  lado).

### O que a conferência no navegador achou

- **"Marcado pela web" no que o balcão acabou de marcar.** A origem gravada é
  "web", a mesma da tela antiga -- "web" é o ERP, e não o site (3 milhões de
  linhas). A legenda da grade diz "marcado pela web". **Pergunta:** a legenda
  deve dizer "marcado no ERP" (ou "no balcão") para "web"?
- **Quem só marca não busca cliente (D226).** A busca da API nova abre a quem
  cuida do cadastro ou abre comanda. Dos 180 que marcam na agenda, 9 não têm
  nenhuma das duas -- 7 ativos nos últimos 90 dias. E a tela antiga tem o mesmo
  buraco desde o D138 (08/09): o `autoComplite` que o Marcar dela chama passou
  a exigir o privilégio do PDV. Em produção ainda não, porque nada foi
  implantado; na migração, 10 perderiam a busca lá. É o próximo passo.
- **O cabeçalho da coluna sem cor é transparente.** Rolando a grade, os blocos
  aparecem através dele. Só as duas fixtures não têm cor -- os 177
  colaboradores reais têm --, mas um colaborador novo sem cor teria o mesmo
  cabeçalho vazado.

### O que vem

A porta da agenda na busca de cliente (D226), e depois a etapa C2: encadear
serviços para o mesmo cliente e o aviso de cadastro desatualizado.

## 25/09/2026, noite -- quem marca na agenda busca cliente (D226)

### O que mudou para quem usa

Quem marca na agenda e não abre comanda nem cuida do cadastro de clientes
passou a buscar o cliente -- nas duas telas. Na nova, o Marcar deixava essa
pessoa só com o "cliente sem cadastro"; na antiga, a busca do Marcar ficava
calada desde o D138 (08/09), que a guardou pelo privilégio do PDV sem ver que a
agenda também a usa. Medido na cópia: 9 dos 180 que marcam, 7 deles ativos nos
últimos 90 dias. A inclusão na blacklist do aplicativo, que usa a mesma busca,
voltou junto (2 pessoas).

O que continua fechado a quem só marca: o cadastro inteiro do cliente (CPF,
endereço, anotações), na API e no ERP. A busca devolve o nome, dos clientes
ativos, e pede um termo.

### Provado

- Um ensaio novo, `scripts/ensaiar-busca-de-cliente-da-agenda.php`, 14 de 14: o
  colaborador da fixture entra sem privilégio nenhum (as portas recusam), só
  com a agenda e só com a blacklist -- a concessão antes de cada login e fora
  logo depois. Ele entrou no CI e na lacuna declarada.
- Quatro mutações, uma por porta arrancada, todas vermelhas no ensaio. E os
  que já existiam seguem verdes: permissão, 40 de 40; recusas, sem falha.
- Os portões do corredor, sem falha nova. O da lacuna do CI mentia: escolhia o
  primeiro contêiner com "app" no nome, e com a pilha do ensaio do deploy de
  teste de pé pegava a dela -- outro código, sem o ensaio novo -- e dava
  vermelho a dois ensaios que nem tinham rodado. Passou a usar o app deste
  projeto, e ganhou a declaração que faltava (a do ensaio da sessão, D222).

### Um beco da tela antiga, anotado

Na unidade que pede a atualização do cadastro, escolher um cliente
desatualizado fecha o Marcar antigo para abrir o cadastro -- e quem só marca não
abre cadastro. Fica anotado no D226; a tela nova não o herda.

## 25/09/2026, noite -- o Marcar depois da revisão adversarial

### O que mudou para quem usa

- Corrigir o nome depois de buscar desfaz a busca: a tela não diz mais "nenhum
  cliente com João Silva" sobre uma busca feita com "Joao".
- Marcado um horário, os vizinhos não o oferecem de novo enquanto a agenda é
  relida -- o pedido que voltaria recusado depois de preenchido.
- Quem usa leitor de tela ouve o resultado da busca.
- Abrir o Marcar não apaga mais o aviso do painel de outro agendamento, e o
  foco volta ao lugar certo quando esse painel fecha.

### Provado

- A suíte do front, 516 verdes (10 casos novos); `tsc -b` e oxlint limpos.
- Dez mutações nos consertos, cada uma vermelha pelo caso dela.

## 25/09/2026, noite -- o deploy de teste mede em vez de marcar (v5)

### O que mudou para quem opera

Nada foi ao servidor: o deploy continua só no repositório, esperando a sua
ordem. Uma terceira revisão adversarial leu a v4 e confirmou 18 achados; o
maior era de desenho, e o conserto também.

- **A cópia sai selada, e a base diz de que código é.** O exportador grava na
  primeira linha do dump o sha256 dos cinco arquivos que dão forma à cópia. O
  `base` só carrega a cópia do selo daquela árvore e grava o selo no banco, e o
  site só volta com o selo da base igual ao do código que está no clone. A v4
  marcava uma pendência, e o código que chegava por outro caminho -- um `git
  pull` à mão, um sinal no meio do merge -- religava o site com a base velha; e
  a cópia antiga, que fica no `$HOME` com o mesmo nome, apagava a pendência.
- **O `tirar` não depende do resto do `.env`, e fica.** Ele recusava justamente
  quando o `.env` acendia. Agora tira pelo projeto e pelo domínio, diz depois o
  que acende, e o `atualizar` e o `voltar` não religam: só o `publicar`.
- **O `conferir` mede o endereço**, e não o link em disco: um vhost ainda
  carregado, ligado como arquivo ou com outro nome passava por "fora do ar".
- **O freio que falha fica escrito** -- na queda do ssh, o `sudo` sem terminal
  deixava o vhost ligado, e o aviso ia para um terminal morto.
- **O que vai ao ar é o que foi conferido**: o nginx sobe sem mexer no app, e o
  app que diverge do compose é recriado ANTES das conferências.
- O cron é procurado pela porta, e não pelo nome do host (0.0.0.0 passava); a
  extração do dump mede o próprio status; e o estado vive por clone, em
  `.deploy-teste/`.
- **No endurecimento, 654 motivos de desconto digitados pela recepção** passavam
  como texto do sistema: o "Convênio <nome>" era casado por `LIKE`, que ignora
  caixa e acento. Agora pelo prefixo exato. E a régua ganhou dois contadores
  (o painel de BI, o título das mensagens) -- 21 números.

### Provado

- Um arnês com o script de verdade num clone falso, com dublês de docker, sudo,
  curl e git (o dos revisores, estendido): um cenário por achado, 63 checagens,
  e a base, o `tirar` e o `publicar` de ponta a ponta. E dez mutações -- cada
  guarda nova arrancada --, todas vermelhas pelo cenário dela.
- As duas funções de selo -- a do deploy e a do exportador -- dão o mesmo
  número, também com o arquivo em CRLF.
- A régua roda no MySQL e sai com 21 números; o prefixo exato mede 654 motivos
  a mais que o `LIKE`.

### O que o arnês não mede

O `nginx -T` e o `docker compose config --hash` de verdade, e a recusa da
primeira gravação do domínio (ela lê o `/etc/nginx` real, sem dublê). Os três
só se provam no BVIP.

### Continua de pé

**Não rode o deploy do commit `d391e43`** (o primeiro, já no GitHub). Os
commits desta noite estão só nesta máquina, e o push é seu.

## 26/09/2026, madrugada -- marcar e continuar (o Marcar, etapa C2)

### O que mudou para quem usa

O balcão marca mais de um serviço para o mesmo cliente em cerca de 15% das
vezes. A janela do Marcar ganhou **"Marcar e continuar"**: marca, fica com o
cliente, e o próximo serviço começa na hora em que o anterior acaba -- como na
tela antiga, onde o modal fica aberto depois de marcar. O "Marcar" continua
fechando, e quem marca um serviço só não ganha clique nenhum. A janela diz o que
acabou de marcar, e o foco volta ao serviço.

### Provado

- A suíte do front, 520 verdes (4 casos novos); `tsc -b` e oxlint limpos.
- Cinco mutações, todas vermelhas -- entre elas a hora seguinte contada do
  primeiro horário do serviço, e não do último: o caso marca um serviço de dois
  horários para pegar isso.
- No navegador, contra o banco de contrato: a janela ficou com o "sem cadastro",
  disse "Marcado: Barba Teste, 09:00 às 09:20", e o próximo veio às 09:20.

### O que falta da C2

O aviso de cadastro desatualizado, que a tela antiga dá ao escolher o cliente.

## 26/09/2026, manhã -- o aviso de cadastro desatualizado (etapa C2, fim)

### O que mudou para quem usa

A tela antiga, ao escolher o cliente no Marcar, abre o cadastro por cima quando
ele está desatualizado -- nunca alterado, ou alterado há tantos dias quantos a
unidade pede. A janela nova agora **diz** isso: abaixo do cliente aparece
"Cadastro desatualizado: confirme o telefone e o e-mail com o cliente, e
atualize no ERP.", e o aviso descreve o campo do serviço, que é onde o foco chega
depois da escolha. A marcação segue sem trava.

29 das 36 unidades ativas pedem o prazo (365 ou 730 dias), e 89% dos clientes
ativos passam de um ano sem alteração: o aviso vai aparecer em quase toda
marcação -- como o modal antigo abre em quase toda.

### Provado

- A suíte do front, 523 verdes (3 casos novos); `tsc -b` limpo, oxlint com os
  2 avisos que já havia.
- `ClienteApiTest` nos dois bancos, 10 de 10: a regra pelo relógio único da
  suíte -- nunca alterado, no prazo exato, um dia antes, a data em texto como o
  PostgreSQL devolve.
- Os ensaios HTTP contra o banco de contrato: as recusas da API (o caso 30 fixa
  as chaves da forma mínima, agora com `cadastroDesatualizado`) e a busca da
  agenda, 17 de 17 -- com uma fase nova, `prazo`, que mede o FIO: grava a data
  de alteração do cliente e o prazo das unidades, pede a busca, e devolve os
  dois no `finally`. As fixtures não têm nem um nem outro, e sem essa fase o
  controller podia esquecer o prazo da unidade com tudo verde.
- Dois mutantes, os dois vermelhos: `>=` virando `>` na regra (o caso "no prazo
  exato" do teste) e o controller passando `null` no lugar do prazo (o caso
  "alterado há 400 dias, a unidade pede 365" do ensaio).
- No PostgreSQL também, que é onde o CI roda os ensaios HTTP: a busca da agenda,
  17 de 17 com a fase `prazo`, num banco de contrato montado do zero; e o
  medidor da API pediu `GET /adm/api/clientes` -- a consulta com a coluna nova
  no `GROUP BY` -- e recebeu 200 sem exceção no corpo. Na primeira corrida,
  logo depois de a máquina reiniciar, cinco ensaios saíram com 2 ("não
  consegui medir": o pedido estourou o tempo com o MySQL ainda aquecendo); só a
  busca foi repetida, e passou.

### Para você decidir

**O aviso deve travar a marcação?** A tela antiga abre o cadastro e só volta ao
Marcar depois de salvar (fechar sem salvar perde a marcação em curso). A nova
diz e deixa seguir, porque quem marca pode não ter o privilégio do cadastro
(D226), e uma trava ali seria um passo a mais em quase toda marcação. Se a regra
da rede é obrigar a confirmação, a janela pode pedir um "confirmei com o
cliente" antes do Marcar.

## 26/09/2026, manhã -- o cabeçalho da coluna sem cor

### O que mudou para quem usa

O cabeçalho da coluna de quem não tem cor cadastrada era transparente: ele fica
parado no topo enquanto a grade rola, e os blocos apareciam através dele (visto
no navegador em 25/09). Agora, sem cor, ele usa o fundo e a letra da casa -- os
dois juntos, porque a letra da pessoa sobre o fundo da casa podia ser branco
sobre branco. Hoje só as duas fixtures não têm cor; os 177 colaboradores reais
têm, e para eles nada muda.

### Provado

- Um caso novo na grade (524 verdes): sem cor, o cabeçalho leva os tokens; com
  cor, a da pessoa. Ele ficou vermelho antes do conserto, pelo motivo certo (só
  a letra branca, sem fundo). `tsc -b` limpo, oxlint com os 2 avisos de antes.
- O caso mora no componente, e não no CSS: medido, o vitest entrega o CSS vazio
  pelo `?raw`, e um caso que lesse a regra ficaria verde com qualquer regra.
- No navegador, contra o banco de contrato, com a grade rolada: os dois
  cabeçalhos das fixtures opacos por cima da trilha (a hachura da coluna
  fechada não passa), branco com letra quase-preta no tema claro e a superfície
  escura com letra clara no escuro.

## 26/09/2026, manhã -- reabrir a agenda pela tela nova

### O que mudou para quem usa

O horário fechado, na agenda nova, agora se reabre: o painel dele oferece
**Reabrir** a quem pode fechar a agenda. Antes de reabrir, a tela pergunta com o
que vai voltar a ficar livre -- "De 01/10/2026 às 08:00 até 15/10/2026 às 20:00,
para todos os colaboradores. O período inteiro volta a ficar livre, e não só
este horário." --, porque reabrir apaga o fechamento inteiro, e não um horário
só. Como a tela antiga, que consulta e pergunta pelo período. Reabriu, a
agenda relê e o foco vai ao horário que ficou livre.

### Provado

- O servidor: o retrato do fechamento nos dois bancos (`FecharAgendaTest`, 7 de
  7 no MySQL e no PostgreSQL); por HTTP, o GET devolve o período e o alcance, e
  depois de reabrir o GET e o DELETE dão 404, sem nenhum bloqueio sobrando (o
  `ON DELETE CASCADE`); sem cookie, 401; sem o privilégio, 403 no ensaio de
  permissão.
- A tela: 13 casos novos (537 verdes) -- o botão só para quem escreve, a
  pergunta que consulta, o travado enquanto consulta, o que reabre, os dois 404
  e a recusa que fica. Sete mutações, todas vermelhas pelo caso da sua guarda.
- No navegador, contra o banco de contrato: um fechamento criado pela API
  (hoje, 14:00 às 15:00, os dois colaboradores), a pergunta com o período e
  "para todos os colaboradores", o foco no "voltar", o Reabrir clicado, o aviso
  "Agenda reaberta: …", a grade sem bloco fechado e o foco em "14:00, livre".
  O banco de contrato foi remontado depois.

## 26/09/2026, manhã -- o deploy de teste v6 e v7 (quarta e quinta revisão)

### O que mudou para quem opera

Nada foi ao servidor: o deploy continua só no repositório. Duas revisões
adversariais seguidas, cada uma com quatro lentes (o selo, o "fora do ar", a
mecânica do shell e a doc) e um cético por achado.

**A quarta leu a v5** e confirmou 23 de 25 achados; a v6 (`2ca56e4`) os corrigiu.
O maior, que QUATRO lentes acharam sozinhas: o `voltar` para um commit com o
script da v4 religava o site -- até um site tirado de propósito --, porque é o
script de lá que decide, e o de lá não mede o selo. E mais: o `base` gravava o
selo da árvore, e não o do dump; o "fora do ar" do `conferir` era deduzido de
qualquer falha do curl (certificado vencido incluído); o `publicar` com o
domínio de outro site desligava o vhost DELE; o `| tee` com Ctrl-C matava a
saída antes do freio; o exportador lia o endurecimento depois de calcular o
selo.

**A quinta leu a v6** e confirmou as correções da quarta, todas, pelas próprias
reproduções. Achou 18 novos, quase todos baixos; os dois médios foram o mesmo
padrão um andar abaixo -- da v6, o `voltar` aceitava um commit com o script da
v5, e de lá um segundo `voltar` chegava à v4 -- e o cano que morre sem mandar
sinal (o `ssh` sem terminal que cai), que matava o script sem armar o freio. A
v7 os corrigiu:

- **A versão das garantias** (`VERSAO_DO_DEPLOY=7`): o `voltar` e o `atualizar`
  só aceitam alvo cujo script diga pelo menos a mínima -- e, como o de lá confere
  o mesmo, a recusa vale também para o voltar seguinte. Para desfazer uma
  mudança do aplicativo: reverter num commit novo e `atualizar`.
- **O `tirar` não recusa antes de frear**: com outro `DOMINIO`, tira o do `.env`;
  sem sudo, tira a pilha e diz o resto; sem conseguir gravar a intenção, freia
  do mesmo jeito; e o freio que falha fica escrito.
- **O `conferir` diz o que mediu**: "fora" com prova, "no ar", ou "não medi"
  (o `nginx -T` reprovado por outro site); o 502 só é "meio fora" sem o nginx da
  pilha; e um site marcado como tirado que responde reprova.
- O selo lido só do stdout (um aviso do docker entrava no valor e o `base`
  apagava a base boa); a base que a limpeza não apagou tem marca própria; o cron
  lê também as unidades de usuário e transitórias do `systemd-run --user`, e os
  homes com espaço; o `atualizar` reconhece o `git pull` feito à mão; e todo
  subcomando adota o domínio do `.env`.

### Provado

- Um arnês com o script de verdade num clone falso e dublês de docker, sudo,
  curl, git, getent, crontab e flock (o openssl é o de verdade): **128
  checagens** nos cenários V1 a V28 (um por achado das duas revisões) e as
  **63 da v5**, verdes. (Corrigido depois pela sexta revisão: a lista dizia
  "openssl" entre os dublês; e a mensagem nova do doc6-03, "que religa o
  site", não tinha checagem -- ganhou uma no V14, e um mutante.)
- **37 mutações** -- cada correção desfeita --, todas vermelhas pelo cenário
  dela: 21 da v6 (o M09 sobreviveu à primeira rodada, e o cenário dele foi
  reforçado) e 16 da v7. E duas do exportador, com o exportador real e a
  árvore trocando de ramo no meio da cópia.
- `conferir-tudo` verde.

### O que o arnês não mede

O `nginx -T`, o `docker compose config --hash`, o certbot e o systemd de
verdade -- só no BVIP. A lente do selo da quinta revisão foi interrompida três
vezes pelo próprio motor e recomeçou; o que ela achou entrou na v8 (abaixo).

### Continua de pé

**Não rode o deploy do commit `d391e43`** (o primeiro, já no GitHub). Os
commits desta sessão estão só nesta máquina, e o push é seu.

## 26/09/2026, manhã -- o deploy de teste v8 (a lente do selo, que chegou por último)

A lente do selo da quinta revisão terminou depois da v7. Confirmou, com
reproduções próprias, as cinco correções que a v6 fez aos achados dela na
quarta, e achou três defeitos novos, os três baixos. A v8 os corrige, e o freio não muda: a
`VERSAO_DO_DEPLOY` segue 7.

- **O exportador congela também o anonimizador.** O endurecimento e a régua já
  rodavam de cópias tiradas no começo; o anonimizador e o modelo dele, não. Quem
  trocava de ramo durante a cópia da origem (o passo longo) e voltava antes do
  fim exportava, com o selo de A, uma cópia anonimizada pelo código de outro
  ramo -- e a conferência do fim, que olha só a árvore, passava. Agora os quatro
  saem de cópias, e o selo é calculado DELAS (e da cópia do próprio exportador):
  o selo é o do que roda. O `anonimizar.sh` aceita a raiz e o modelo de quem o
  chama (`ANONIMIZAR_RAIZ`, `ANONIMIZAR_MODELO`); sem elas, o uso de
  desenvolvimento é o de sempre, e o modelo ausente agora recusa em vez de
  mandar nada ao mysql com saída zero.
- **O `conferir` com o mysql parado** dizia "vazio depois de um 'base' que
  falhou?" -- o conserto da quarta revisão tinha ido só ao ajudante do selo, e
  o irmão, a primeira leitura do `conferir_base`, ficou. Agora só a tabela
  ausente é esse palpite; o resto diz que não leu, com o erro do mysql e o
  comando que o sobe.
- **A recusa da base de outro código nomeia o exportador**, que era o único dos
  cinco arquivos do selo que tinha mudado da v5 para a v6.

**Provado.** No arnês do deploy, os cenários V29 e V30 (9 checagens) e três
mutantes do deploy, todos vermelhos. No arnês do exportador, com o
exportador e o anonimizador reais: 26 checagens em oito modos (trocar de ramo e
voltar, editar e desfazer, trocar e ficar, o anonimizador sozinho de dois
diretórios, o modelo ausente), e seis mutantes, todos vermelhos -- e a v7 fica
vermelha nos três modos do achado (trocar e voltar, editar e desfazer, trocar e
ficar) e no do modelo ausente, que é a guarda nova. O arnês ganhou duas coisas
para isso:
no ramo `outro`, o `anonimizar.sh` também muda, e o dublê do docker recusa
quem o chama fora da raiz do clone.

**Muda o selo**: o exportador e o anonimizador estão entre os cinco arquivos,
então a cópia exportada pela v7 não entra num servidor na v8. Nenhuma foi ao
servidor.

## 26/09/2026, manhã -- a edição de usuário grava só o que a tela mostra (D204, e o D202 em parte)

### O que mudou para quem usa

Nada, para quem usa as telas como elas são. A tela de editar usuário mostra 23
campos, e o servidor gravava mais que isso: qualquer campo que chegasse no
pedido, na linha escolhida pelo campo oculto. Agora ela grava os 23, no usuário
da página; a lista de grupos e a de unidades, que só a tela restringia, o
servidor também impõe; e quem não é administrador não age sobre um
administrador em nenhuma ação da tela de usuários -- a listagem já os escondia,
e as outras ações, pela rota, não.

Medido na cópia, só leitura: 8 dos 10 administradores ativos estão numa unidade
com alguém que edita usuário. O cadastro de usuário também passou a gravar só
os campos da tela.

### Provado

- `UsuarioDadosTest`, 5 casos na caracterização, e quatro mutações, cada uma
  vermelha pelo caso dela; uma sobreviveu na primeira rodada e o caso ganhou a
  chave que a pegou.
- O portão do recorte por unidade passou a vigiar o teto: 56 métodos, e o teto
  arrancado o deixa vermelho.

### O que não foi medido

A fiação do controller por pedido HTTP: está conferida por leitura. E o D202
segue aberto em cinco telas -- produtos, serviços, pacotes, etiquetas e VIP
Class --, que continuam gravando na linha do campo oculto.

## 26/09/2026, manhã -- as outras cinco telas de editar (D202 fecha)

### O que mudou para quem usa

Nada, para quem usa as telas: produtos, serviços, pacotes, etiquetas e VIP Class
gravam o que gravavam. O que mudou é qual linha e quais campos. Cada tela
conferia a franquia da linha da página e gravava a do campo oculto; agora grava
a da página. E produto, serviço e etiqueta, que passavam o pedido inteiro ao
hidratador, passaram a gravar só os campos que a tela mostra -- com os mesmos
valores, pelos mesmos setters.

### Provado

- `EdicaoDoCatalogoTest`, 4 casos: produto, serviço com a linha de outra
  franquia no pedido, pacote e etiqueta.
- Três mutações vermelhas pelo caso de cada uma. Uma quarta -- a lista de campos
  arrancada na etiqueta -- passa verde e é equivalente: a etiqueta só tem nome e
  status.

### O que não foi medido

O VIP Class, que não tem fixture; e a fiação dos cinco controllers por pedido
HTTP, conferida por leitura.

## 26/09/2026, tarde -- o deploy de teste v9 (sexta revisão)

### O que mudou para quem opera

Nada foi ao servidor. A sexta revisão -- quatro lentes e um cético, sobre a v7 e
a v8 -- confirmou, pelas próprias reproduções, que os 21 achados da quinta
estão corrigidos, e achou 20 novos, todos baixos (o único médio foi rebaixado
pelo cético), em oito temas. A v9 os corrige:

- **O `tirar` não escreve nada antes do freio**: não copia a si mesmo para o
  `/tmp`, e sem a trava no `$HOME` (disco cheio, só-leitura) tira sem ela.
- **O `tirar` arma o freio da saída**: o `ssh` sem terminal que cai, ou um HUP
  no meio dele, deixavam o site no ar só com a marca.
- **O `tirar` não recusa antes de frear**: a intenção e a metade da pilha vêm
  antes das guardas; o `.env` é lido como o compose o lê (aspas, comentário);
  com o `DOMINIO` em maiúscula, vale o do `.env`; como root, segue com aviso. E
  o cenário novo achou um defeito da própria v9 antes do commit: a pasta da tela
  nova ficava com o domínio ignorado, e as guardas paravam o freio do vhost.
- **O `conferir`** reprova o tirado com o vhost do host ainda ligado, e diz
  "parado" do nginx da pilha que existe parado, em vez de culpar o php-fpm.
- **O `git pull` à mão é reconhecido pelo último commit aplicado**, e não pelo
  selo: depois do `ACEITAR_BASE=sim atualizar` da doc, o atualizar seguinte
  acusava um pull que não houve. A base sem selo com o site no ar também é dita,
  e o esquema é comparado desde o último commit aplicado.
- **Os sinais sem captura** (USR1, ALRM, o `ulimit -f`) passam a armar o freio.
- O `nginx -t` reprovado por outro site não grava mais "talvez no ar" de um site
  que já estava fora.

A `VERSAO_DO_DEPLOY` vai a 8, porque o freio mudou; a mínima exigida do alvo
continua 7.

### Provado

- Os cenários V31 a V41 (41 checagens), um por achado, e 13 mutantes -- cada
  correção desfeita --, todos vermelhos pelo cenário dela.
- A regressão inteira: as 63 da v5 e as 138 da v6 a v8 (a mais é a do V14, que a
  revisão cobrou).
- E o roadmap corrigido nos três pontos que a revisão apontou na prova da v7/v8.

### O que o arnês não mede

O `nginx`, o `sudo`, o `docker` e o `systemd` de verdade -- só no BVIP.

## 26/09/2026, tarde -- a tela do caixa (a quarta tela nova, com o D227 e o D228)

### O que mudou para quem usa

"Abrir/Fechar Caixa", no menu da tela nova, deixou de abrir a tela genérica. É
a quarta tela migrada, depois da agenda e dos dois caminhos da comanda -- a que
o PDV pedia, porque sem caixa aberto a comanda não finaliza. Ela:

- sem caixa aberto, pede o valor inicial (vírgula ou ponto, sem ponto de milhar)
  e abre;
- com caixa aberto, mostra o valor inicial, o recebido **por forma de
  pagamento**, as retiradas -- a cancelada fica, marcada, em vez de sumir como na
  tela antiga -- e o saldo, com o rótulo dizendo que ele **soma todas as formas,
  e não só o dinheiro da gaveta**;
- registra retirada, com motivo obrigatório e, desligada por padrão, a conta
  paga no financeiro;
- fecha em duas etapas: o botão só abre a confirmação, e o "confirmar" é o
  vermelho;
- **com o caixa de outro dia**, avisa e só oferece fechar. Quem diz que é de
  outro dia é o servidor, pela regra que recusa a venda (D28).

E a comanda: quando a finalização é recusada por falta de caixa, ou por caixa de
outro dia, ela oferece "ir ao caixa" -- só a quem pode abri-lo.

**O que ela não faz:** cancelar retirada. Continua na tela antiga até o D199
ganhar a conferência das linhas dentro de transação.

### O que mudou no servidor, para ela

- **A guarda da API do caixa passou a ser a do ERP** (`vendas/nova`), e não
  `vendas/caixa`, um bit que não governa tela nenhuma (D227). Medido na cópia:
  2 pessoas que abrem caixa no ERP levariam 403 na tela nova; ninguém que opera
  o caixa perde. Um portão novo vigia as três portas -- ERP, API e menu.
- **A retirada e o fechamento gravam sob a trava da linha do caixa** (D228),
  nas duas telas. Duas retiradas de sessões diferentes do mesmo operador já não
  passam as duas sobre o mesmo saldo.
- O recurso do caixa ganhou o recebido por forma, o aviso de outro dia e a conta
  a pagar da retirada; o 409 da finalização, o motivo em `detalhes`.

### A revisão adversarial, e o que ela mudou

Quatro revisores só de leitura -- servidor e concorrência, contrato e guarda,
comportamento do front, testes e texto -- e um verificador por dimensão tentando
refutar cada achado: **22 achados, os 22 confirmados**. Todos consertados, menos
um, que é decisão (a venda grava o saldo sem a trava -- ver o D228):

- **no servidor**, a trava chegou às escritas que tinham ficado de fora: o
  recálculo e o fechamento da tela antiga, o cancelamento de retirada (que passou
  a recusar caixa fechado) e a abertura (que não abre mais dois caixas). A
  comparação do saldo passou a ser em centavos, e o valor ganhou teto;
- **na tela**, uma recusa que diz que o caixa mudou por fora -- 409 ou 404 --
  faz a tela reler o caixa, em vez de insistir no de antes; "já há caixa aberto"
  diz que o valor digitado não foi usado; as recusas são anunciadas ao leitor de
  tela; a confirmação de fechar diz que o saldo é o da última leitura, e o aviso
  mostra o que o servidor carimbou; saiu a linha "Troco devolvido", que sairia
  sempre zero (a finalização zera o troco quando ele vira gorjeta ou crédito);
- **no portão**, o nível (`ler`/`escrever`) passou a ser conferido, os
  argumentos são lidos por token, e o menu sem os comentários; o "ir ao caixa"
  lê o privilégio do próprio item do menu, em vez de repeti-lo.

### Provado

- **Caracterização**, nos dois bancos: **883 casos** verdes, 31 deles do
  caixa novos (24 no `CaixaSobTravaTest`, 7 no `CaixaApiTest`). A trava é medida
  de verdade -- outra conexão prende a linha e a escrita desiste com erro de
  trava --, e um caso roda FORA da transação do teste: grava, e a outra conexão
  vê que a outra sessão foi relida e que a trava soltou.
- **Quinze mutantes do servidor**, todos vermelhos -- um sobreviveu na primeira
  rodada, e o defeito era do caso (o ruído do `double` sumia antes de chegar ao
  banco).
- **O portão** `conferir-guarda-do-caixa.php`, falsificado em treze formas.
- **Os seis ensaios HTTP** verdes contra o banco de contrato; o de recusas ganhou
  três casos -- o caixa de ontem se anunciando de outro dia, cancelar retirada de
  caixa fechado e o teto do valor --, e conferiu 86 de 86.
- **As suítes de contrato** do aplicativo, nos dois bancos: 67 de 67.
- **O front:** 583 testes, build e lint; **vinte e três mutantes do front**,
  todos vermelhos -- um sobreviveu na primeira rodada, e ganhou o caso que faltava
  (a releitura atrasada não traz de volta o caixa de antes).
- **No navegador**, contra o banco de contrato: ver o caixa das fixtures (as
  contas batem com o banco), retirar R$ 50,00 (`valor_final` 419,80 no banco,
  recalculado sob a trava), retirar acima do saldo (a frase do servidor, e o que
  se digitou fica), fechar (carimbado 419,80), abrir com "150,00" (150 no banco,
  e não 15.000), e o aviso de outro dia com o caixa recuado um dia. O navegador
  achou dois defeitos que os testes não acharam: a caixa de marcar do financeiro
  empilhada acima da frase, e o título "Abrir o caixa" repetido no título da
  página e no da seção. Os dois estão corrigidos.

### Desvio do desenho

Não há desenho desta tela no Claude Design -- o canvas trata tudo fora da
moldura, da agenda e da comanda como tela genérica. Ela usa as peças da comanda
(cartão, totais, o formulário de lançar, a confirmação em duas etapas) e não
inventa forma nova. Se o desenho vier depois, é ele que manda.

### O que não foi medido

A corrida de verdade, com dois pedidos HTTP de sessões diferentes ao mesmo
tempo -- a trava presa por outra conexão é o que se mede no lugar dela. E o
"ir ao caixa" com uma comanda de verdade no navegador: está provado com a API
dublada no front e com a resposta real no ensaio de recusas.

### Decisões que continuam suas

- Tirar o bit `Vendas caixa` da tela de permissões (D227).
- Levar a trava à finalização da venda, que ainda grava o saldo sem ela -- e,
  se o caixa fechar no meio, chama método em nulo depois de debitar crédito
  (D228, o resto do D164).
- O cancelamento de retirada apaga a conta a pagar quitada (D199, D133) -- é o
  que falta para ele entrar na tela nova.

## 26/09/2026, noite -- a tela de vendas realizadas (a quinta tela nova, com o D229 e o D231)

### O que mudou para quem usa

"Vendas Realizadas", no menu da tela nova, deixou de abrir a tela genérica. É a
lista do que foi vendido -- filtros de período, situação, código, cliente,
pesquisa NPS e desconto --, com o total vendido e o de descontos da consulta
inteira, páginas, e o detalhe de cada venda: itens com o motivo do desconto,
pagamentos, operador, caixa, NPS, e o motivo do cancelamento quando há. Só
leitura: cancelar venda fica para depois (o cancelamento tira do caixa da venda
sem olhar se ele já fechou -- é pergunta sua antes de virar botão).

**Quem é do caixa vê só as concluídas de hoje, e a tela diz isso.** A tela antiga
deixava os campos cinza e o servidor reescrevia o pedido em silêncio; a nova
recebe do servidor o recorte que ele aplicou, desliga o período e a situação e
escreve por quê. E o dia padrão é o do servidor, e não o do computador do balcão.

### O que mudou no servidor, para ela

- **"Com desconto: Não" trazia as vendas COM desconto** (D229), na tela antiga
  também: as duas respostas davam a mesma lista. Em 2025 foram 218.999 vendas sem
  desconto e 42.263 com -- quem pedia as primeiras recebia as segundas. O nulo
  (536.783 vendas de 2015 a 2020, de antes da coluna) conta como "sem".
- **O "Total descontos" com filtro de colaborador ou de forma de pagamento
  contava a venda uma vez por item ou por pagamento** (D231): dois serviços do
  mesmo barbeiro numa venda com R$ 10,00 de desconto davam R$ 20,00 no MySQL, e a
  consulta quebrava no PostgreSQL. Achado pela revisão adversarial; a tela antiga
  tem os dois filtros, então o número inflado estava em produção.
- As duas regras da tela saíram do controller para uma regra com nome, e a
  resposta passou a dizer o recorte aplicado. O código, a forma de pagamento, o
  colaborador e a unidade só aceitam número -- um código com letra casava com
  outra venda no MySQL e quebrava no PostgreSQL.
- **Anotado (D230):** a exportação em XLS da tela antiga ignora os filtros de
  desconto e de colaborador.

### A revisão adversarial

Três revisores sobre uma cópia congelada da fatia e um cético por achado: **29
achados, 22 confirmados e consertados**, nenhum agente morto. Além do D231, o
maior era meu: **o controle por HTTP do D229 não media nada** -- o gerente de
fixture é do caixa, a lista chegava vazia, e o controle passava com qualquer
filtro. Os outros: a tela destravando o formulário do caixa durante a carga, a
releitura apagando o que se digitava, estados sem saída durante a releitura, o
foco caindo no vazio, e três frases minhas que não batiam com o código.

### Provado

- **Caracterização**, nos dois bancos: **904 casos**, 21 deles novos.
- **Dezenove mutantes do servidor, quatro do controller pelo HTTP e trinta e cinco
  do front**, todos vermelhos.
- **Os ensaios HTTP**: o de recusas com 77 recusas e 15 controles, seis casos
  novos; o do fluxo com 22 passos -- a venda que a comanda finaliza aparece na
  listagem pelo código, e um código que não é o dela não traz nada.
- **O front:** 616 testes, build e lint.
- **No navegador**, contra o banco de contrato: as vendas do dia com o total que o
  banco confirma, o detalhe, o foco indo e voltando, o filtro por código e o
  código zero recusado. O navegador achou dois defeitos de desenho que os testes
  não acham -- o filtro "Desconto" sozinho numa segunda linha em 1366 px e o nome
  do cliente espremido em quatro linhas no painel estreito --, os dois corrigidos.

### Desvio do desenho

Não há desenho desta tela no Claude Design; ela usa as peças da comanda e do
caixa. Se o desenho vier, é ele que manda.

### Decisões que continuam suas

- Cancelar venda pela tela nova: o cancelamento subtrai do caixa da venda sem
  olhar se ele fechou.
- A exportação em XLS (D230) migra com a tela, ou conserta-se a antiga antes?

## 27/09/2026, madrugada -- o fiado a receber (a sexta tela nova, com o D232)

### O que mudou para quem usa

O fiado a receber chegou à tela nova. Como no ERP, não há item de menu para ele:
abre-se pelo caixa, no botão "receber fiado", porque é ali que o dinheiro entra.
É a lista das dívidas em aberto -- busca por cliente ou pelo número da venda,
páginas --, e o recebimento em duas etapas: escolher a forma e confirmar o valor,
com o foco no "voltar" para que um Enter distraído não receba. A dívida é quitada
inteira, e o valor entra no caixa de quem recebe.

**O botão só aparece a quem vê e recebe o fiado**, como no ERP. Sem receber, cada
"receber" da lista terminaria num "sem permissão", depois de escolher a forma e
confirmar o valor.

**As recusas dizem o que fazer:** sem caixa aberto, ou com o caixa de outro dia, a
tela oferece o caixa; se a dívida já estava quitada, a lista é relida e a tela
diz que nada foi recebido agora. E se o servidor não responder, a tela diz que o
recebimento **pode** ter entrado e relê a lista -- em vez de mandar tentar de
novo.

### O que mudou no servidor, para ela (D232)

- **Quitar fiado não conferia se a dívida já estava quitada, e gravava sem
  transação nem trava.** Na cópia, **16 débitos foram quitados duas vezes** de 2018
  a 2023 -- R$ 838,98 somados de novo em caixas. Agora é uma transação com trava,
  **para as duas telas**, e quitar de novo é recusado.
- **Crédito do cliente, fidelidade e pacote quitavam dívida sem dinheiro nenhum
  entrar.** A tela antiga não os oferece; a API aceitava. Agora o serviço recusa,
  para as duas.
- **O caixa de outro dia é recusado na tela nova**, como a finalização da comanda
  faz. A tela antiga continua aceitando -- é decisão sua (154 das 971 quitações do
  passado entraram num caixa aberto em dia anterior).

### A revisão adversarial

26 revisores sobre uma cópia congelada da fatia, nenhum morto: **23 achados, 18
confirmados e consertados**. O maior derrubou o que eu tinha escrito no D232: **a
releitura do caixa perdia dinheiro no MySQL** -- ela lia a foto da transação,
tirada antes de esperar a trava do caixa, e a soma apagava a retirada que outro
balcão acabara de gravar. Meu teste gravava pela mesma conexão e nunca veria.
Também: a ordem das travas se cruzava com a finalização da comanda (virou cliente
-> débito -> caixa, a ordem que a finalização terá de seguir quando ganhar trava),
a tela antiga contava a outra franquia se a dívida dela foi paga, e o portão de
recorte por unidade saía verde com a guarda arrancada -- aqui e numa guarda do
financeiro, desde sempre. Na tela: a recusa sumia se o operador mexesse na lista
com o recebimento no ar, o painel renascia sozinho, o foco caía no vazio, e no
telefone o botão ficava fora da vista.

### Provado

- **Caracterização**, nos dois bancos: **919 casos**, 15 deles novos.
- **Treze mutantes do servidor e trinta e três do front**, todos vermelhos -- entre
  eles a releitura do caixa sem trava, que só o teste da foto mata.
- **Contrato**: 67 nos dois bancos.
- **Os ensaios HTTP**: os seis verdes; o de recusas com 81 recusas e 18 controles,
  sete casos novos (9k a 9q), um deles uma quitação de verdade conferida no banco;
  e **quatro mutantes do controller pelo HTTP**, cada um derrubado pelo caso escrito
  para ele.
- **O front:** 643 testes, build e lint.
- **No navegador**, contra o banco de contrato: do caixa ao fiado, receber R$ 50,00 em
  dinheiro -- o banco confirma o débito quitado, o caixa em R$ 50,00 e uma venda só;
  o foco no título ao abrir, no "voltar" ao confirmar, na linha ao desistir e no
  aviso ao receber; a lista travada com o recebimento no ar; em 375 px o botão
  inteiro na primeira coluna, com a tabela rolando dentro da própria caixa; e o
  débito quitado "noutro balcão" no meio da confirmação -- a tela diz que já estava
  quitado, relê, e o caixa continua em R$ 50,00 com uma venda.

### Desvio do desenho

Não há desenho desta tela no Claude Design, nem destino no menu; ela usa as peças
do caixa e das vendas realizadas, e abre pelo caixa. Se o desenho vier, é ele que
manda.

### Decisões que continuam suas

- O caixa de outro dia continua recebendo fiado na tela antiga (o D28 só na nova).
- As 16 quitações duplicadas do passado continuam nos caixas onde entraram.

## 27/09/2026, manhã -- os clientes (a sétima tela nova, só leitura, com o D233)

### O que mudou para quem usa

"Clientes", no menu da tela nova, deixou de abrir a tela genérica. É a lista de
clientes da rede -- busca por nome, telefone ou e-mail, com ou sem a máscara do
telefone; ativos ou inativos; páginas de 20 -- e o cadastro de cada um em
leitura: contato, cadastro, endereço e histórico. O cadastro marcado como
desatualizado e o "cadastro do sistema" (o avulso do balcão) aparecem marcados.

**Cadastrar e editar continuam na tela antiga**, em outra aba, e só para quem
tem o privilégio de escrever -- como a lista do ERP. O cadastro aberto tem
"atualizar", para quem salvou lá e voltou.

**O que ela não mostra, de propósito:** o crédito e a pontuação da listagem, que
são somas da rede inteira (o crédito se usa por unidade, e a soma lida como
saldo engana), e o CPF, o RG e as anotações, que chegam com a edição.

### Por que ainda não edita (D233)

Lendo o caminho de gravação antes de oferecer "editar": **o `PUT` da API de
clientes apaga o que o corpo omite** -- e-mail, CPF, origem, endereço, convênio
--, grava o logradouro com um prefixo `' -- '`, e deixa a origem virar nula, o
que desliga a trava da primeira visita do aplicativo. Nada usa esse `PUT` hoje
(só um passo do ensaio), então ninguém foi ferido; a fatia 2, a edição, conserta
o servidor antes.

### Provado

- **A revisão adversarial**: 24 revisores, nenhum morto; 21 achados, 14
  confirmados (dois repetidos) e todos consertados. O maior era do meu teste: ele
  só passava num banco só de fixtures, e contra a cópia da produção quatro dos
  seis casos caíam sem se declarar pulados. Na tela: a resposta atrasada do mesmo
  cliente trocava o cadastro, e voltar depois de "atualizar" mostrava a linha
  velha.
- **Caracterização**, nos dois bancos: 925 casos, 6 novos -- a consulta da
  lista, que a tela antiga já usava e nada provava.
- **Cinco mutantes da consulta** (o "SEM CADASTRO" aparecendo, o desempate, o
  filtro virando OU, o estado ignorado, a caixa do termo -- este só o PostgreSQL
  mata), **dois do controller pelo HTTP e trinta e nove do front**, todos vermelhos.
- **Os ensaios HTTP**: os seis verdes; o de recusas com 85 recusas e 20
  controles, seis casos novos da lista e do cadastro.
- **O front:** 672 testes, build e lint.
- **No navegador**, contra o banco de contrato: a lista, a busca, o cadastro com o foco indo e
  voltando, o CPF e o RG do banco fora da tela, e o telefone de 375 px -- onde o
  navegador achou três defeitos que testes, mutantes e revisão não acharam: a
  página inteira rolava de lado 32 px por causa de um cabeçalho escondido, o
  e-mail virava uma tira, e o telefone quebrava no meio. Os três corrigidos.

### Desvio do desenho

Não há desenho desta tela no Claude Design; ela usa as peças das vendas
realizadas. Se o desenho vier, é ele que manda.

### Decisões que continuam suas

- Ativar e desativar cliente pela tela nova: a API deixa desativar qualquer
  cliente da rede, e a tela antiga só oferecia para a própria unidade ou para o
  administrador.
- Quem só tem o privilégio de ler vê o cadastro inteiro pela API (CPF e RG
  inclusive); na tela antiga, a única tela de detalhe pede escrever. Hoje ninguém
  tem só um dos dois.

## 27/09/2026, manhã -- a edição do cadastro de clientes (fatia 2, com o D233 consertado)

### O que mudou para quem usa

O cadastro aberto na tela de clientes tem **editar**, para quem tem o privilégio
de escrever -- e não no cadastro do sistema. O formulário parte do que o
cadastro tem: nome, nascimento, como chegou, CPF, RG, convênio, profissão e
anotações; DDI, telefone, e-mail e "aceita mensagens"; e o endereço. Os cinco
campos que o sistema exige aparecem marcados; o que não serve é marcado no
campo, com o foco no primeiro, antes de enviar ou quando o servidor recusa -- o
e-mail ou o telefone que já são de outro cliente inclusive. Salvar mostra
"Cadastro salvo." e o cadastro como ficou; voltar relê a lista. O telefone
brasileiro vai na forma da tela antiga, "(47) 99999-0001", qualquer que seja o
jeito de digitar.

**Cadastrar cliente novo continua na tela antiga**: o telefone repetido que
vira dependente pede uma pergunta ao operador, e é a fatia seguinte.

### O conserto antes da tela (D233)

O `PUT` da API **apagava o que o corpo omitia**. Agora ele exige o cadastro
inteiro -- a chave ausente é recusada com o campo nomeado, e só o `null`
explícito limpa --, a origem não vira nula, o logradouro não ganha prefixo, o
e-mail repetido é comparado em minúsculas (no PostgreSQL a caixa contava), e as
recusas dizem o motivo. A prova é a volta: abrir o cadastro e salvar sem mexer o
deixa como estava, pelo serviço de verdade, nos dois bancos -- e o dependente
continua dependente.

### Provado

- **A revisão adversarial**: 30 agentes, nenhum morto; 26 achados, 15
  confirmados e todos consertados. Entre eles: o cadastro novo sem rua imprimia
  um aviso antes da resposta; um valor que não é texto apagava o campo com 200;
  o texto maior que a coluna dava erro 500 em vez de recusa; e, na tela, o foco
  caía no vazio quando a lista relida não tinha mais o cliente.
- **96 mutantes** -- 30 do servidor, 12 do controller pelo HTTP (um deles só o
  PostgreSQL mata) e 54 do front --, todos mortos, cada um pelo caso que devia.
- **Caracterização**, nos dois bancos: 965 casos, 40 novos. **Contrato do
  aplicativo**: 67 nos dois. **Ensaios HTTP**: os seis no MySQL e os nove no
  PostgreSQL, o de recusas em 122 de 122 (17 novos). **Portões**: 48 verdes e o
  vermelho conhecido (D50). **Front**: 728 testes, de 672, build e lint.
- **No navegador**, contra o banco de contrato: salvar, salvar sem mexer (o
  mesmo cadastro de volta), voltar com o foco na linha, o telefone de 375 px e os
  dois temas. O navegador achou buracos na grade do formulário, em duas colunas,
  que nenhum teste via; a ordem dos campos mudou e a grade fecha nas duas
  larguras.

### Decisões que continuam suas

- **"Aplicativo" está entre as origens** que o formulário oferece, como na tela
  antiga -- e escolhê-la liga a trava da primeira visita do aplicativo para esse
  cliente.
- **O último a salvar vence**: se dois operadores editam o mesmo cliente, o
  segundo sobrescreve o primeiro -- como na tela antiga. Conferir a versão
  (recusar o salvamento feito sobre um cadastro que mudou) é trabalho novo.
- As duas desta tela que já estavam aqui: ativar e desativar pela tela nova, e o
  que quem só lê enxerga.

## 27/09/2026, tarde -- cadastrar cliente novo (fatia 3, com o D234)

### O que mudou para quem usa

A lista de clientes tem **cadastrar cliente novo**, para quem escreve no
cadastro, e ele abre o mesmo formulário da edição, vazio, com o DDI da unidade e
"aceita mensagens" ligado. Cadastrado, o cliente abre no detalhe com "Cliente
cadastrado."; desistir volta à lista.

**O telefone que já é de alguém**, a pergunta que a tela antiga fazia: o
formulário marca o campo com o nome do dono -- "Este telefone já é de Ana
Cliente." -- e oferece **cadastrar como dependente de Ana Cliente**, como quando
pais e filhos dividem o número. A oferta vale para o número recusado; mudou o
número, ela sai. Com isso, **o cadastro de clientes inteiro está na tela nova**:
consultar, editar e cadastrar.

### O conserto antes da tela (D234)

O cadastro novo pela API aceitava **qualquer responsável**: um id que não existe
dava erro 500, e um id qualquer dispensava a conferência do telefone repetido.
Agora o responsável tem de existir, estar ativo, não ser cadastro do sistema e
ter o mesmo telefone; e o dono apontado na recusa nunca é o "Cliente Avulso". A
tela antiga continua como era.

### Provado

- **A revisão adversarial**: 14 agentes, nenhum morto; 10 achados, 5 confirmados
  e todos consertados -- entre eles, a máscara brasileira aplicada ao telefone de
  cliente de outro país, que o ERP cortaria ao reabrir.
- **132 mutantes** (36 novos), todos mortos, cada um pelo caso que devia.
- **Caracterização**: 968 nos dois bancos. **Contrato do aplicativo**: 67 nos
  dois. **Ensaios HTTP**: os oito no MySQL e os nove no PostgreSQL, o de recusas
  em 129 de 129 (7 novos). **Portões**: 48 verdes e o vermelho conhecido (D50).
  **Front**: 754 testes, de 728, build e lint.
- **No navegador**, contra o banco de contrato: um cliente novo, outro com o
  mesmo número recusado com o nome do dono, e o dependente cadastrado pela
  oferta; o foco em cada passo; 375 px e o tema escuro.

### O que a verificação achou fora desta fatia

- A primeira rodada do contrato no PostgreSQL falhou com a máquina carregada, e
  passou com ela parada: anotado no **D66**, como ocorrência de outra espécie.
- Dois testes de arrastar da agenda falham às vezes, sob carga, e passam
  sozinhos. Nada da agenda mudou; ficou como tarefa separada.

### Decisão que continua sua

- **A tela antiga ainda aceita qualquer responsável** no cadastro novo; o
  conserto do D234 é só da API. Levar a mesma conferência à tela antiga muda o
  que ela aceita hoje.

## 27/09/2026, noite -- o catálogo de produtos e serviços (a oitava tela nova, só leitura, com o D235, o D238 e o D239)

### O que mudou para quem usa

**Produtos** e **Serviços**, no menu de Configurações, abrem a tela nova -- as duas
entradas, a mesma tela, cada uma no seu catálogo. A lista traz os ativos, com busca
por nome, tipo, categoria e situação, e nos produtos o "estoque menor que" da tela
antiga, com as mesmas opções. Cada item abre o cadastro em leitura: o preço, o tipo,
a categoria, a unidade; nos serviços os períodos, o "visível no aplicativo", a área
e a etiqueta; nos produtos o estoque, a linha VIP e o insumo. O preço de compra
aparece só para quem escreve no catálogo, como na tela antiga.

**Cadastrar, alterar e movimentar o estoque continuam na tela antiga** nesta fatia,
em outra aba, para quem escreve.

### O que mudou no servidor, para ela

- **D235** -- o `PUT` de produtos e o de pacotes chamavam um método que o D202 tinha
  tirado: erro fatal. Consertados, com o corpo conferido campo a campo: o que não
  cabe é recusado com o nome do campo, e não aceito sem gravar.
- **D239** -- o cadastro pela API nunca tinha gravado: o item nascia sem a situação,
  que a coluna exige. Nasce ativo, como no formulário.
- **D238** -- a API dizia o estoque de serviço, e o PDV o mostrava ao lado de um
  corte. O tipo decide, pela regra da entidade.
- **D236** -- as listas de produtos e de serviços da tela antiga aceitavam a unidade
  de outra franquia pela URL. Consertadas -- e o portão que devia vigiá-las, que
  ficava verde com a guarda arrancada em seis entradas, passou a exigir a marca uma
  vez só.

### Provado

- **A revisão adversarial**: 28 agentes, nenhum morto; 23 achados, todos
  consertados, um deles virado débito (D240).
- **89 mutantes**, todos mortos: 8 do portão, 33 do servidor, 16 dos controllers
  pelo HTTP, 32 do front.
- **Caracterização**: 985 nos dois bancos. **Contrato do aplicativo**: 67 nos dois.
  **Ensaios HTTP**: os oito no MySQL e os nove no PostgreSQL, o de recusas em 147 de
  147 (18 novos) -- e duas rodadas seguidas, com o catálogo igual antes e depois.
  **Portões**: 48 verdes e o vermelho conhecido (D50). **Front**: 799 testes, build e lint.
- **No navegador**, contra o banco de contrato: as duas entradas do menu, a busca,
  o cadastro de quem escreve e de quem só lê, o foco na volta; 375 px e o tema
  escuro.

### O que a verificação achou fora desta fatia

- **D240** -- o link de paginação das listagens da tela antiga repete a busca sem
  escape (38 telas).
- **D241** -- o cadastro de serviço pela API nasce sem área e sem etiqueta, e serviço
  sem área some da agenda e do aplicativo. Sem estrago ainda: a tela nova só lê. É o
  primeiro passo da fatia seguinte.
- **D242** -- a cor dos rótulos pequenos (`--tinta-3`) fica abaixo do contraste
  mínimo nos dois temas; é a cor de 51 regras.
- O ambiente parou pedidos triviais por até 19 s durante as medições; o ensaio de
  recusas ganhou o limite ajustável e a rodada parcial para os mutantes.

### Decisão que continua sua

- **O administrador**, que na lista antiga escolhe a unidade, na nova vê a da
  sessão -- como todas as telas novas até aqui.

## 28/09/2026, madrugada -- o catálogo passa a escrever: cadastrar, alterar, desativar e o estoque (com o D241, o D243, o D244, o D245 e o D246)

### O que mudou para quem usa

Quem escreve no catálogo **cadastra, altera, desativa e ativa** produtos e serviços
na tela nova, e **movimenta o estoque** do produto: a entrada, a saída e o
cancelamento de uma movimentação, com o saldo e a lista de movimentações -- no lugar
da tela antiga `produtos/estoque`. O formulário segue o do ERP campo a campo: o tipo
só se escolhe no cadastro, e não muda depois; o serviço exige área e etiqueta; o
produto começa com estoque 0, e depois o estoque muda só pela movimentação, que deixa
rastro. Cancelar uma movimentação pergunta no próprio botão, e o duplo clique não
confirma.

### O que mudou no servidor, para ela

- **D241** -- o serviço cadastrado pela API nascia sem área e sem etiqueta, e sumia
  da agenda e do aplicativo. As duas são exigidas, conferidas contra as ativas.
- **D243** -- toda movimentação de estoque saía da API sem data, desde a sétima fatia.
- **D244** -- o estoque abria para quem só lê o catálogo; a tela do ERP exige escrever.
- **D245** -- o lançamento podia devolver outra movimentação como a criada.
- **D246** -- o valor do pacote aceitava um número de 310 dígitos; o catálogo e o
  pacote têm agora o teto do campo do ERP, 999.999,99.

### Provado

- **A revisão adversarial**: 43 achados, 42 confirmados e consertados, 1 refutado.
- **108 mutantes**, todos mortos, cada um com o caso que o matou: 80 do front, 16 do
  servidor pela caracterização, 12 dos controllers pelo HTTP.
- **Caracterização**: 990 nos dois bancos. **Contrato do aplicativo**: 67 nos dois.
  **Ensaios HTTP**: os oito no MySQL e os nove no PostgreSQL, o de recusas em 160 de
  160 (13 novos). **Portões**: 48 verdes e o vermelho conhecido (D50). **Front**: 918
  testes, build e lint.
- **No navegador**, contra o banco de contrato: o cadastro do serviço com as marcas
  e o foco, o estoque com o lançamento, o "não", o duplo clique, o cancelamento e a
  volta ao cadastro; 375 px nos dois temas. E ele achou um defeito que nenhum teste
  via: o produto nunca contado virava "0" só de abrir o estoque.

### O que a verificação achou fora desta fatia

- **A lista que encolhe ao virar a página** -- outra aba desativou um item -- tirava
  a paginação com o botão apertado dentro, e o foco caía no vazio. Consertada no
  catálogo e no estoque, e, num passo próprio, nas clientes, nas vendas realizadas
  e nos devedores.
- O mapa da fase seguinte (pacotes, convênios, cupons) já achou, pela leitura e com
  um cético para cada achado, o que consertar antes das telas: o cadastro de convênio
  que sempre termina em 500 (o mesmo padrão do D239), a
  alteração de convênio que apaga as unidades dele, a campanha de cupom vencida que a
  rotina não desativa, e o item do pacote que aceita qualquer produto.

### E, na mesma madrugada, três de fora da fatia

- **D240** -- o link de paginação das listagens do ERP repetia a busca sem escape: uma
  aspa fechava o atributo, em 38 telas. O parcial passou a escapar, e o "Exportar"
  escondido da lista de pacotes também; um caso renderiza o parcial de verdade.
- **D242** -- o cinza dos rótulos pequenos (`--tinta-3`) passou do contraste mínimo nos
  dois temas: `#70695A` no claro e `#918871` no escuro, de 4,65:1 a 5,44:1. **Esta era
  uma decisão sua** -- a página dizia que mudar o token mudava o desenho --, e eu a
  tomei: está num commit só (`6dad5a3`), que se desfaz com um revert.
- **A lista que encolhe ao virar a página**, nas clientes, nas vendas realizadas e nos
  devedores, como no catálogo.

### Decisões que continuam suas

- **O administrador**, que na lista antiga escolhe a unidade, na nova vê a da sessão.
- **O push**: os 34 commits desde `d391e43` estão só nesta máquina.
- **O `--tinta-3`** (acima): fica, ou volta o desenho de antes.

## 28/09/2026, manhã -- a tela nova dos pacotes (com o D247)

### O que mudou para quem usa

"Pacotes", em Configurações, abre a tela nova. Quem lê vê a lista, com busca por nome
e situação, e cada pacote com o valor, a validade, a unidade e os serviços -- cada um
com as sessões e o valor de cada sessão --, com a soma das sessões ao lado do valor
do pacote. Quem escreve cadastra, altera, desativa e ativa, e **compõe o pacote**:
acrescenta um serviço (da mesma lista que a tela antiga oferece) e remove um, com a
pergunta no próprio botão. O pacote nasce vazio, como no ERP, e abre avisando que os
serviços entram agora -- o PDV recusa o pacote vazio.

### O que mudou no servidor, para ela

- **D247** -- o item do pacote aceitava qualquer produto de qualquer franquia pela
  tela antiga, e qualquer produto da unidade pela API; a quantidade e o valor não
  tinham regra. Agora o item é um dos serviços ativos da unidade do pacote, nos dois
  caminhos, com a quantidade inteira e o valor de duas casas.
- A lista do `<select>` virou recurso (`GET /adm/api/pacotes/:id/servicos`); o item
  sai com o id do serviço e na ordem em que entrou; e a lista e a tabela de serviços
  da tela antiga escapam o nome (D203).

### Provado

- **A revisão adversarial**: 30 achados, 28 confirmados e consertados, 2 refutados.
- **128 mutantes**, todos mortos, cada um com o caso que o matou: 84 do front, 38 do
  servidor pela caracterização, 6 do controller pelo HTTP.
- **Caracterização**: 1014 nos dois bancos. **Contrato do aplicativo**: 67 nos dois.
  **Ensaios HTTP**: os oito no MySQL e os nove no PostgreSQL, o de recusas em 177 de
  177 nos dois (17 novos) -- no PostgreSQL, depois de consertar um caso meu que lia
  o `false` da coluna como linha apagada. **Portões**: 48 verdes e o vermelho conhecido (D50). **Front**: 1027 testes,
  build e lint.
- **No navegador**, contra o banco de contrato: compor, remover, alterar, desativar,
  cadastrar; 375 px no tema escuro. E ele achou o que nenhum teste via: o duplo clique
  em "remover" caía no "não", porque a coluna crescia e empurrava o botão.

### Decisões que continuam suas

- **O administrador**, que na lista antiga escolhe a unidade, na nova vê a da sessão
  -- vale para o catálogo e para os pacotes.
- **O push**: os commits desde `d391e43` estão só nesta máquina.
- **O `--tinta-3`**: fica, ou volta o desenho de antes (`6dad5a3` se desfaz com um
  revert).



## 28/09/2026, tarde -- a tela nova dos convênios (com o D248 ao D261)

### O que mudou para quem usa

"Convênios", em Configurações, abre a tela nova. A lista tem busca por nome, situação e
"vale na unidade", e diz de cada convênio o desconto, **onde vale** ("todas" ou "N
unidades"), os clientes, a última venda e o contrato -- com o selo "vencido" só quando o
servidor marca. Cada convênio abre o cadastro: o desconto, onde vale por extenso (as
unidades inativas ligadas aparecem marcadas, e o convênio ligado só a inativas diz que
hoje não dá desconto em lugar nenhum), o contato, o contrato, o uso -- quantos clientes
ativos o têm -- e o histórico. As vendas e os clientes do convênio continuam na tela
antiga, com um link.

**Só o administrador altera convênio**: cadastra, edita, desativa e ativa aqui mesmo.
Quem tem o privilégio de escrever e não é administrador lê, e a tela diz por quê.
Desativar pergunta no próprio botão, dizendo quantas unidades ativas deixam de dar o
desconto. No formulário, **"onde vale" é escolha obrigatória**: todas as unidades,
inclusive as que abrirem depois, ou só as marcadas.

### O que mudou no servidor, para ela

- **D248** -- o cadastro pela API respondia 500, sempre.
- **D249** -- a alteração pela API apagava as unidades do convênio, e o convênio de uma
  franquia passava a valer na rede inteira.
- **D250** -- a API aceitava porcentagem zero e fracionária, e texto acima da coluna.
- **D251** -- o filtro "vigente" sumia, e o vencido não era recalculado ao gravar.
- **D252** -- filtrada por uma unidade, a lista contava uma unidade; e o D129 ganhou guarda.
- **D253** -- o nome de 92 caracteres ou mais derrubava a venda (há um assim, ativo).
- **D254** -- a lista antiga imprimia o nome cru.
- **D255** -- quem tinha "escrever" alterava o convênio da rede (medido: ninguém além dos
  administradores tinha).
- **D256** -- **salvar pela tela antiga um dos 260 convênios ligados só a unidades inativas
  o abria para a rede inteira.**
- **D257** -- o formulário antigo gravava o POST cru, sem as regras.
- **D258** -- o cadastro e a alteração não eram atômicos nem travados.
- **D259** -- salvar pela tela antiga desfazia a ativação ou a desativação feita na nova.
- **D260** -- o ativar e o desativar da tela antiga eram um link que alternava.

### Provado

- **A revisão adversarial**: 33 achados, todos confirmados e consertados -- 12 no front,
  3 de comportamento no servidor (o D259, o emoji do D257 e o D260) e 18 lacunas de teste,
  cada uma fechada por um caso que fica vermelho sem a linha.
- **176 mutantes, todos mortos**: 100 do front, 59 do servidor e 7 do controller pelo HTTP
  -- um deles mostrou que tirar a unidade de um convênio a tirava de todos os outros que
  valiam lá, sem nenhum teste ver, e ganhou o caso --, e os 10 do `useFocoDaLista`, que a
  verificação no navegador pediu.
- **Caracterização**: 1058 nos dois bancos, e o contrato do aplicativo em 67 de 67 nos
  dois. **Ensaios HTTP**: os oito verdes nos dois bancos, o de recusas em 200 de 200 (144
  recusas e 56 controles). **Portões**: 48 verdes e o vermelho conhecido do D50 -- quatro
  linhas de base acompanharam a fatia, e a dos pares ganhou a lista das coincidências:
  `UnidadeRepository::listagemIndex` ia sair dos falsos positivos só porque um teste de
  convênio cita "Unidade", sem teste nenhum dela. **Front**: 1184 testes, de 1027; build e
  lint.
- **No navegador**, contra o banco de contrato com um administrador temporário (apagado
  depois, e o banco remontado): a lista, o cadastro, desativar e ativar, e um convênio
  cadastrado numa unidade só -- a 375 px e nos dois temas. **Ele achou dois defeitos que os
  testes, a revisão e os mutantes não acharam:** a busca desalinhada (a nota do filtro
  erguia o campo), e **o foco no `body` durante a releitura** depois de voltar do detalhe
  -- 4 s no navegador, e nas três telas de lista com detalhe: catálogo, pacotes e convênios
  (o **D261**). O teste do catálogo que dizia "a linha velha recebe o foco" não o conferia.
  O molde saiu das três cópias para `useFocoDaLista`, e o navegador mediu de novo: o foco
  na linha durante a releitura, e no título quando ela saiu da lista.

### Decisões que tomei, e que continuam suas

As sete perguntas do desenho dos convênios eram suas; eu as decidi para seguir, e todas
se desfazem sem perder nada:

1. **Só o administrador escreve convênio** (o D255). A volta: tirar a guarda das seis
   ações e das dez entradas do portão -- mas aí o gerente volta a apagar as ligações das
   outras franquias, e a alternativa desenhada é a regra "alterável": ele só mexe no
   convênio que vale só na unidade dele.
2. **O motivo do desconto na venda é cortado em 100** (o D253).
3. **Sem filtro de contrato na tela** até o crontab do D50 voltar a marcar o vencido.
4. **As vendas e os clientes do convênio ficam na tela antiga**, com um link. Quando
   virarem recurso, a recomendação é recortá-los pela unidade para quem não é
   administrador.
5. **Sem ordenação por coluna.**
6. **A porcentagem é um inteiro de 1 a 100** -- os 638 da base já são.
7. **"Todas as unidades" continua opção, sem padrão**: a escolha é obrigatória.

E seguem suas as de antes: o administrador que na lista antiga escolhe a unidade e na
nova vê a da sessão; o push dos commits desde `d391e43`; e o `--tinta-3` (`6dad5a3` se
desfaz com um revert).

## 28/09/2026, noite -- a tela nova dos cupons (com o D262 ao D279)

### O que mudou para quem usa

"Cupons", em Marketing, abre a tela nova. A lista tem busca por nome, situação e momento
-- e, só para o administrador, a unidade, com "todas as unidades" --, e diz de cada
campanha, numa linha, o que ela faz ("comprou «Corte» no dia → 20% em «Barba»"), quantos
dias o cupom vale, o fim, e **um selo que diz se ela emite de verdade**: `emitindo`,
`parada` (a de check-in, a da unidade desativada, a de "comprou" um produto desativado),
`encerrada` (o fim passou) ou `desativada`. A tela antiga mostrava todas como "Ativo".

Cada campanha abre com o que ela faz por extenso, **os avisos de por que não emite** ou
do que acontece com os cupons já dados, os emitidos em números (válidos, vencidos,
usados, e até quando o último vale) e "como funciona". Quem escreve cadastra, edita,
desativa -- a pergunta no próprio botão diz quantos cupons válidos param de dar desconto
-- e ativa, e vê os cupons emitidos, um por cliente, com a situação e a venda. No
formulário, **a prévia diz a campanha por extenso antes de salvar**, e mudar o desconto
ou o produto de uma campanha com cupons válidos avisa que eles mudam junto.

**A tela não promete desconto no caixa novo:** a comanda nova emite o cupom e ainda não o
aplica (o D276, pergunta sua).

### O que mudou no servidor, para ela

- **D262** -- o nome de campanha acima de 75 derrubava o PDV (o motivo não cabia no item).
- **D263** -- ativar e desativar na tela antiga eram um link que alternava.
- **D264** a **D270** -- a campanha encerrada ficava "Ativo" para sempre; o produto podia
  ser de outra franquia, e a campanha mudava de franquia; o filtro "usado" da API não
  filtrava, e os emitidos sem venda sumiam; o corpo da API não tinha teto (500 na coluna);
  o formulário antigo gravava o POST cru, e o `id` oculto escolhia QUAL campanha gravar;
  nomes crus nas telas; e o check-in, que não emite desde 29/11/2025, era aceito.
- **D271** a **D277** -- registrados: o desconto novo alcança os cupons já dados; a
  carência de três dias que a rotina joga fora; os emitidos abertos a quem só lê; a
  campanha de produto desativado que segue emitindo (a #45, 9.750 cupons); o resgate que
  não confere a unidade; a comanda nova que emite e não resgata; o convênio que apaga o
  motivo do cupom.
- **D278** e **D279** -- achados pela revisão: a página 2 das telas antigas perdia os
  filtros, e os filtros crus davam 500 no PostgreSQL.

### Provado

- **Duas revisões adversariais**, cada achado com um cético: a do servidor, 14 achados,
  todos consertados (o D278 e o D279 saíram dela); a da tela, 25 achados, 24 confirmados
  e consertados -- a campanha de check-in que prometia emitir, a desativada e encerrada
  que dizia duas coisas, **o selo da lista que dizia "emitindo" da campanha que o
  detalhe mostra parada** (o servidor passou a mandar o estado da unidade e dos dois
  produtos na linha), o foco que pulava a validade, e o `--atencao` do tema claro, que
  ficava em 4,32:1 e virou `#96610F`.
- **37 mutantes, todos mortos**: 13 do servidor, 21 do front e 3 do selo da lista -- o
  terceiro sobreviveu à primeira versão do caso, que nunca desligava o produto
  oferecido.
- **Caracterização**: 1105 nos dois bancos, mais o caso do selo; o contrato do
  aplicativo em 67 de 67 nos dois. **Ensaios HTTP**: os nove verdes nos dois bancos, o
  de recusas em 200 de 200 e o dos cupons em 59 de 59 (33 recusas e 26 controles).
  **Portões**: 48 verdes e o vermelho conhecido do D50. **Front**: 1297 testes, build e
  lint -- um caso da agenda falhou uma vez com o host carregado e passou três vezes
  sozinho.
- **No navegador**, contra o banco de contrato com um administrador temporário, na mesma
  rodada da fidelidade: a lista com os quatro selos, o detalhe com o foco no título, a
  prévia do formulário, desativar com a pergunta no botão, o "não desativar" com o foco
  de volta, ativar, e os emitidos -- a 375 px (a tabela rola no próprio quadro, a página
  não) e nos dois temas. Nada de novo.

### Decisões que tomei, e que continuam suas

As sete perguntas do desenho dos cupons eram suas; eu as decidi para seguir, e todas se
desfazem sem perder nada:

1. **A rotina não fecha sozinha a campanha encerrada**: a tela a mostra `encerrada`,
   pelo relógio do servidor, e a lista antiga continua virando o estado quando alguém a
   abre. A volta: fazer a rotina da noite desativá-la -- mas ela roda em produção.
2. **O desconto e o produto não travam com cupons válidos** (o D271): a tela avisa
   quantos mudam. Travar congelaria a #121, que emite sem parar.
3. **O check-in é recusado** na campanha nova e na troca (o D270). Religar a emissão no
   check-in é sua.
4. **A campanha aceita todo produto ativo da unidade**, de qualquer tipo, como o ERP.
5. **A guarda dos emitidos na API continua "ler"** (o D273), e a tela só os mostra a quem
   escreve -- medido: ninguém ativo tem ler sem escrever.
6. **A campanha de produto desativado só ganha aviso** (o D274); desativar a #45 é sua.
7. **A unidade da campanha não muda.**

E uma pergunta nova, do crítico do desenho: **resgatar o cupom na comanda nova** (o D276)
-- hoje o cliente ganha lá e só usa se o caixa abrir o PDV antigo.

## 28/09/2026, noite -- a fidelidade no detalhe do cliente (com o D280 ao D287)

### O que mudou para quem usa

O crédito, os pontos e os pacotes do cliente moram agora **no detalhe do cliente**, abaixo
do cadastro. No ERP antigo eram quatro lugares -- o saldo num modal da lista, comprar e
devolver em páginas próprias, os pacotes numa aba -- e o resgate **só existia no PDV**.

- **Nesta unidade**, com os botões: o crédito em dinheiro, o crédito do programa (o que os
  pontos resgatados viraram) e os pontos com quanto valem. As outras unidades aparecem numa
  tabela de leitura, só as que têm alguma coisa.
- **Comprar crédito** confirma em duas etapas, com o foco no "voltar": a compra vira uma
  venda e entra no caixa. Sem caixa aberto, a tela leva ao caixa.
- **Devolver** pede a credencial de um gerente e diz que o dinheiro **não sai do caixa**.
- **Resgatar** manda o código ao telefone do cliente e diz por onde; "o cliente não recebeu
  o código" leva ao gerente, e fica registrado quem autorizou. O resgate espera a adesão ao
  programa, como no PDV.
- **Os pacotes** abrem nos que têm sessão; cancelar é do gerente, e pergunta no botão.
- **O histórico dos pontos**, a pedido, com até quando os que valem valem.

### O que mudou no servidor, para ela

- **D280** -- a compra aceitava pagar crédito com crédito, sem teto, no caixa de ontem, e
  não travava o caixa: a retirada feita no meio sumia do saldo.
- **D281** -- o cancelamento de pacote alcançava o pacote de outra unidade, e qualquer um
  com o cadastro.
- **D282** -- o código dizia "enviado" sem enviar, o push sem modelo dava 500, a mensagem
  do resgate saía com 0 pontos, e o aplicativo recebia o código de quem nunca o abriu.
- **D283** -- devolver o saldo inteiro dava 409 nos saldos com resíduo de `double`.
- **D287** -- o resgate gravava em três passos soltos.
- **O resgate e o código recalculam a pontuação antes**, como o PDV fazia: a ficha pagava a
  coluna velha (7 clientes na cópia).
- **D284**, **D285** e **D286** -- registrados: o cancelamento de venda que estorna reais da
  pontuação, as escritas de dinheiro sem idempotência, e o código sem validade nem limite
  de tentativas.

### Provado

- **A revisão adversarial**: 38 achados, 34 confirmados e consertados -- entre eles o
  D287 (o resgate em três passos soltos), a pontuação que o resgate e o código pagavam
  pela coluna velha, o canal do código que dizia "enviado" sem enviar, e a devolução do
  saldo inteiro que dava 409 (o D283).
- **48 mutantes, todos mortos**: 20 do front, 15 do servidor e 13 do controller pelo HTTP.
- **Caracterização**: 1128 nos dois bancos (13 pulados, os de sempre), com os 24 casos
  novos da fidelidade -- a regra pura, a tela nova e o tudo-ou-nada do resgate; o
  contrato do aplicativo em 67 de 67 nos dois. **Ensaios HTTP**: os dez verdes nos dois
  bancos -- o de recusas em 200 de 200, o dos cupons em 59 de 59 e o novo da fidelidade
  em 24 de 24 (14 recusas e 10 controles); o do fluxo no PostgreSQL caiu uma vez **pela
  virada do dia** (o banco montado às 23h com o caixa "de hoje", o ensaio às 00h13) e
  passou montado de novo. **Portões**: 48 verdes e o vermelho conhecido do D50 -- a
  contagem de chamadas do ISCadastro acompanhou (111 para 113). **Front**: 1342 testes,
  build e lint no piso de dois avisos.
- **No navegador**, contra o banco de contrato com um administrador temporário (apagado
  depois, e o banco remontado), a 375 px e nos dois temas: a ficha com o crédito, os
  pontos (a soma do histórico) e os pacotes; comprar até a recusa de "sem caixa", com o
  "ir ao caixa"; o resgate até o pedido da credencial do gerente -- **sem digitar senha
  nenhuma**, então a devolução e o resgate autorizado ficam provados pela caracterização
  --; cancelar o pacote até a pergunta, e o "não"; o histórico; e os focos de cada um.
  **Ele achou o que nada mais achou:** o "Ação" escondido do cabeçalho dos pacotes era
  `position: absolute` sem ancestral posicionado dentro da rolagem, ficava na altura 1066
  de uma janela de 533, e **a página inteira rolava por fora, levando a moldura junto**.
  Os cartões da fidelidade passaram a ser posicionados, como a `.clientes-lista` já era;
  medido de novo, a página tem a altura da janela. As outras telas com cabeçalho escondido
  (pacotes e estoque) já estavam numa rolagem posicionada.

### Decisões que tomei, e que continuam suas

1. **O resgate entrou na ficha**, e não na comanda nova -- que é a próxima entrega.
2. **O resgate espera a adesão**, só na tela, como o PDV; 17.930 linhas estão acima do piso
   e só 5.641 aderiram. O servidor continua sem conferir.
3. **Comprar e devolver pedem também `clientes/credito/escrever`**, a porta do menu antigo.
4. **A compra recusa o caixa de outro dia** (o D28), como a comanda e o fiado.
5. **Cancelar pacote é do gerente**, também no servidor.
6. **O histórico de consumo não entrou**: as vendas realizadas já têm tela.

## 29/09/2026, madrugada -- usuários e privilégios pela tela nova (com o D288 ao D293)

### O que mudou para quem usa

"Usuários", em Configurações, abre a tela nova. A lista busca por nome, e-mail ou login,
grupo e situação -- e, **só para o administrador, a unidade**, com "todas as unidades"
(a API mostrava ao administrador só a unidade em que ele entrou). Cada usuário abre em
duas vistas: **Dados** -- o acesso, o grupo e os papéis, onde a pessoa aparece, o tempo
de atendimento, as áreas, as férias -- e **Privilégios**.

**Privilégios é o catálogo inteiro, uma linha por funcionalidade**, com "ler" e
"escrever" em caixas que gravam sozinhas. A tela não oferece o que o servidor
recusaria, e diz por quê: ninguém altera os próprios privilégios; quem não é
administrador só liga o que ele mesmo tem, no mesmo nível, e nunca uma funcionalidade
de rede (marcada "de rede"). Desligar sempre pode, menos em si mesmo. Antes de gravar,
a tela diz que **vale no próximo login da pessoa**; o administrador aberto aparece como
é -- com tudo, com linha ou sem --, sem caixas. O cadastro, a senha, a foto, a agenda e a
comissão continuam na tela antiga, com um link.

### O que mudou no servidor, para ela

- **D288** -- **quem administrava usuários concedia a si mesmo qualquer funcionalidade,
  inclusive as que abrem a rede**, pela API e pela janela do ERP; e dava a colegas o que
  não tinha. A regra vale agora nas três portas: a API, a janela do ERP, e a troca de
  grupo (que reescreve os privilégios pelo molde).
- **D289** -- a gravação mexia numa linha e deixava a gêmea concedendo: há 117 pares
  com mais de uma linha, e o controle de acesso soma. A leitura soma; a gravação deixa
  uma; a exclusão tira o par.
- **D290** -- `/adm/api` não tinha barreira contra pedido de outra origem.
- **D293** -- gravar numa tela velha desfazia, em silêncio, o que outra aba tinha
  gravado: agora é 409.
- **D292** -- o administrador via só a unidade da sessão.
- **D291** -- registrado: o privilégio tirado só sai no próximo login.

### Provado

- **A revisão adversarial**: 24 achados, 16 confirmados e todos consertados. Os
  maiores: **a troca de grupo, terceira porta** que reescrevia os privilégios sem a
  regra; a janela do ERP que mostrava a linha e passou a gravar o par (salvar sem mexer
  tirava o "escrever" da gêmea); a tela velha que desfazia a outra aba (o D293); o
  administrador aberto que aparecia "sem privilégio nenhum"; e **a fiação da regra nos
  controllers, que só tinha portão textual** -- apagar o `return` da recusa deixava tudo
  verde. Três dos refutados eram anteriores à fatia e ficaram registrados (a propagação
  do molde com gêmeas, a corrida sem índice único, e a janela do ERP sem proteção contra
  pedido forjado, no D198).
- **Os controllers testados em processo** (`PrivilegiosPelosControllersTest`, 14 casos):
  as duas portas despachadas de verdade, sem HTTP e sem sessão -- nenhum roteiro HTTP
  escreve privilégio --, cada recusa conferida também no banco.
- **44 mutantes**: os 25 do servidor mortos (um sobreviveu à primeira rodada -- o efetivo
  que lia só a última linha -- e ganhou o caso que o mata); 18 dos 19 do front mortos, e
  o que sobra é equivalente (o foco do "tentar de novo" da lista, que o `useFocoDaLista`
  já garante).
- **Caracterização**: 1163 nos dois bancos (13 pulados, os de sempre); o contrato do
  aplicativo em 67 de 67 nos dois. **Ensaios HTTP**: os onze verdes no MySQL e os doze no
  PostgreSQL -- o novo, de usuários e da barreira de origem, em 15 de 15 (7 recusas e 8
  controles), e agora **no CI**; o de permissão em 51 de 51, com as três rotas novas.
  **Portões**: 48 verdes e o vermelho conhecido do D50. **Front**: 1377 testes, de 1342;
  build e lint no piso de dois avisos.
- **No navegador**, contra o banco de contrato, como o gerente e como um administrador
  temporário (apagado depois, e o banco remontado): a lista de cada um, a unidade
  "todas", as gêmeas somadas que viraram uma linha no banco ao gravar, o concedido, a
  travada, os próprios privilégios, o administrador aberto -- a 375 px e nos dois temas.
  **Ele achou o que nada mais achou:** a caixa travada ficava apagada sem dizer por quê a
  quem enxerga, e o clique nela não fazia nada. A tela ganhou a legenda.

### Decisões que tomei, e que continuam suas

1. **As três regras de quem concede o quê** (o D288) -- ninguém altera os próprios
   privilégios; quem não é administrador só concede o que tem, no mesmo nível; e nunca
   as nove de rede. Medido antes: ninguém perde acesso que tem. A volta: uma classe pura.
2. **Na troca de grupo não vale a regra 2**: 99 dos 107 gerentes não poderiam mais
   cadastrar um colaborador. O molde é decisão de quem administra os grupos.
3. **O par vira uma linha só** na primeira gravação; as 117 duplicatas de hoje saem de
   uma vez na migração final.
4. **A barreira de origem deixa passar quem não manda os cabeçalhos** (o `curl`, o
   aplicativo): pedido forjado só existe dentro de um navegador.
5. **O administrador aberto não tem caixas**: ele passa por tudo sem linha nenhuma.
6. **O privilégio vale no próximo login** -- a versão do controle de acesso fica no D291.

E uma pergunta nova: **a janela de privilégios do ERP continua sem proteção contra
pedido forjado** (anotado no D198) -- com a sessão de um administrador, é a porta mais
sensível do ERP.


## 29/09/2026, madrugada -- ativar e desativar usuário pela tela nova (D294, D295)

A fatia 2a de usuários: **desativar e reativar**, pela tela nova e pela antiga, com a
mesma regra. Das telas da ordem, usuários e privilégios fecham aqui; o cadastro (e-mail,
login, senha, campos pessoais) e a agenda e a comissão do colaborador continuam na tela
antiga -- cada um pede decisões suas (ver abaixo).

### O que mudou

- **D294** -- desativar era um `GET` que ALTERNAVA (o link aberto por quem escreve
  desativava alguém; a pré-carga e o duplo clique se desfaziam); ninguém impedia
  desativar a si mesmo; o login ganhava `_old` a cada vez e não o perdia na reativação;
  **a sessão aberta de quem era desativado seguia valendo** até a pessoa sair; e o campo
  "Status" da tela de editar era uma segunda porta, crua. Agora:
  - `POST /adm/api/usuarios/:id/ativacao` e `/desativacao`, com 409 para o que já está
    no estado pedido; a tela antiga manda POST com o estado pedido;
  - ninguém ativa nem desativa a si mesmo -- pelo botão, pela API ou pela edição;
  - o sufixo entra uma vez (`_old`, ou `_old2` se o `_old` for de outra pessoa) e sai
    inteiro na reativação, quando o login original está livre;
  - **quem é desativado sai no pedido seguinte**: a guarda de login do ERP confere o
    `status`; a rota do ERP cai no "entrar" e a API responde 401. O token do cookie sai,
    e a entrada pela senha recusa o desativado (antes, a senha certa dava 500).
- **D295** -- registrado: o login e o e-mail não têm índice único no banco. Fica para a
  migração final.

### Provado

- **A revisão adversarial**: 17 achados, 13 confirmados e todos consertados. O maior
  foi **o desativado seguir logado** -- a primeira versão da fatia só valia para o
  próximo login. Os outros: a edição que gravava o estado cru, o `_old` repetido entre
  duas pessoas, o sufixo com número que a reativação não tirava, e oito lacunas de
  teste (o administrador da mesma unidade, a falta do `escrever`, o POST sem estado, o
  Enter seguro, o botão ocupado, a pergunta que o "atualizar" desfaz, o 403 que não
  relê, a frase do 409). Os quatro refutados seguem o molde dos convênios ou são
  anteriores à fatia.
- **Testes em processo**: `UsuarioAtivacaoTest` (14) -- a API, a tela antiga e a edição
  despachadas de verdade, a guarda de login chamada como o evento de despacho a chama, e
  o `credential_callable` da própria configuração; `RegraDoUsuarioTest` (9), com o padrão
  do sufixo lido do arquivo do front.
- **38 mutantes, todos mortos**: 21 do servidor (a sessão, o token, a entrada pela senha,
  a porta da edição, o sufixo com número, os tetos) e 17 do front (o Enter seguro, o botão
  ocupado, o 403 que não relê, a frase do 409, a pergunta que o "atualizar" desfaz). Um
  sobreviveu à primeira rodada -- o GET sem estado no corpo, que a falta do estado já
  recusava -- e ganhou o caso que o mata.
- **Caracterização**: 1186 nos dois bancos (13 pulados, os de sempre); o contrato do
  aplicativo em 67 de 67 nos dois. **Ensaios HTTP**: os onze verdes no MySQL e os doze no
  PostgreSQL -- o de usuários em 19 de 19, o de permissão em 51 de 51, o da sessão do ERP
  em 6 de 6. **Portões**: verdes, e o vermelho conhecido do D50 -- um deles acusou uma
  homonímia (o teste novo cita o `popularCombobox` de Grupo num comentário e usa a
  entidade `Usuario`), registrada como coincidência, e não creditada como cobertura.
  **Front**: 1394 testes, de 1377; build e lint no piso de dois avisos.
- **No navegador**, contra o banco de contrato, como o gerente: desativar pergunta com o
  alcance, desativa, e o login vira `colab.teste_old`; ativar o devolve. **E a sessão
  aberta do colaborador, de verdade**, conferida por HTTP enquanto o gerente desativava no
  navegador: antes, 200; depois da desativação, a API respondeu
  401 e mandou expirar o cookie, e o ERP mostrou a página de entrada -- o que o teste em
  processo não alcança, porque o PHP de linha de comando não abre sessão. Na lista
  antiga, desativar a si mesmo diz a frase, e desativar o colaborador vai por POST; na
  edição antiga, "Status: Ativo" reativou e tirou o sufixo. A 375 px e no tema claro, sem
  rolagem lateral. **Ele achou o que nada mais achou:** a lista antiga dizia "Excluído com
  sucesso." de quem só tinha sido desativado -- agora diz "Desativado com sucesso.".

### Decisões que tomei, e que continuam suas

1. **A reativação devolve o login original quando ele está livre**; se outra pessoa o
   pegou, fica o `_old` e a tela diz. A volta: uma função pura (`RegraDoUsuario`).
2. **Quem é desativado perde a sessão no pedido seguinte.** A revisão classificou como
   alta; a volta é uma linha na guarda de login.
3. **Ninguém desativa a si mesmo, nem o administrador.**
4. **A tela antiga manda o estado pedido**, e o `GET` que alternava deixou de mudar
   qualquer coisa -- a trava do `conferir-remocao-por-post.php` desceu de 17 para 16.

## 29/09/2026, tarde e noite -- relatórios: o levantamento, e o painel do gerente como home (com o D306 ao D308)

Você mandou começar os relatórios lendo tudo o que existe antes de desenhar os novos. O
levantamento está em `docs/f3-relatorios.md`: os 24 itens do menu, uns 20 relatórios fora
dele, o Yungas, as APIs V1 a V3 e as 12 rotinas noturnas que pré-calculam os painéis
antigos -- com **35 sobreposições**, cada uma com veredito. Depois das suas decisões, a
primeira tela nova é **o painel do mês, a home do gerente de unidade**.

### O que mudou

- **O menu Relatórios da tela nova tem os doze itens, na sua ordem** -- Atendimentos,
  Caixa, Programa de Indicação, Profissional Parceiro, Descontos, NPS, Produtos/Serviços,
  Retirada de Produtos, Royalties (só administrador), Vendas (a tela nova de Vendas
  Realizadas), Vendas Canceladas e Cota-parte. **O que ficou fora é o D306**, e volta à
  mesa antes de a etapa fechar.
- **O D307**: cinco pontos em que as rotinas noturnas dos painéis antigos gravam número
  errado (o dezembro fantasma, o 31/12 que sai do ano, o Fluxo de Movimento que tira
  média de cópias, o mês do Dash. Colaboradores que não fecha, o crescimento que zera).
  O painel novo não as usa: calcula na hora, das vendas.
- **O painel** (`GET /adm/api/painel` e `/adm/api/painel/clientes`), com as definições
  que você escolheu -- faturamento líquido, cliente novo na rede, as três taxas de
  retorno: os dez números do mês contra o anterior (no mês em andamento, contra os
  mesmos dias), os clientes novos e as três taxas, a evolução diária, os serviços, as
  formas de pagamento e todos os colaboradores, do maior faturamento para o menor.
  **É a home de quem é gerente e não é administrador**; a agenda continua no menu, e
  para os outros a home continua a agenda.
- **O D308**: o índice que faria da pergunta "este cliente já comprou?" uma leitura só
  de índice. Fica para a migração final.

### Provado

- **Duas revisões adversariais**, com um cético por dimensão e nenhum agente morto. A
  primeira achou 25: 18 confirmados (16 distintos -- o "ano zero" apareceu três vezes) e
  7 refutados, dos quais quatro viraram as perguntas abaixo. A segunda leu os consertos e
  achou 15, todos confirmados ou plausíveis, e todos consertados. Os maiores: **o MySQL
  lia todas as vendas de cada cliente** para responder "comprou antes?"; **a trava da
  sessão punha os dois pedidos do painel em fila**; nada prendia o privilégio que a tela e
  o servidor repetem; a navegação sumia com um mês que falhava; e o painel **puxava o foco
  ao abrir o sistema**.
- **O custo, medido na sessão do banco** e variando a memória: com o buffer pool de
  fábrica, 27 a 41 s antes e 5 a 8 s depois, na unidade 1; com as vendas na memória, 37 a
  74 ms por lote nas duas formas. Um teste lê o plano nos dois bancos, porque o resultado é
  o mesmo com ou sem o conserto.
- **51 mutantes**, todos mortos -- nas regras, nas contas, no controller, na consulta, na
  sessão e na tela. Cinco sobreviveram à primeira rodada e oito à revisão, e cada um
  ganhou o caso que o mata.
- **Caracterização**: 1286 nos dois bancos (13 pulados, os de sempre); o contrato do
  aplicativo, 67 de 67 nos dois; os onze ensaios por HTTP no MySQL e os doze no
  PostgreSQL, o de permissão em 53 de 53 com as quatro rotas do painel. **Portões**:
  verdes, com o D50 de sempre; o dos booleanos acusou três literais novos da Fase 0 do
  marketing, que viraram parâmetro. **Front**: 1436 testes, build e lint no piso de dois
  avisos. O teste de arrastar da agenda falhou 4 vezes em 14 rodadas da suíte inteira --
  a mesma causa de 27/09, ficou como tarefa à parte.
- **No navegador**, contra o banco de contrato com um mês semeado: o gerente abre no
  painel sem o foco sair do lugar; "Painel" é botão, sem endereço no ERP; o "‹" fica
  ocupado enquanto o mês carrega, o duplo clique pede um mês só, e o foco fica no botão;
  os clientes só são pedidos depois dos números, com o mês deles; e a 375 px não há
  rolagem lateral.
- **E a verificação achou o que não era do painel**: doze arquivos que eram LF tinham
  virado CRLF inteiros pelas minhas edições -- voltaram --, e os testes da Fase 0 do
  marketing nunca tinham rodado no PostgreSQL: 23 erros de booleano, consertados.

### Decisões que tomei, e que continuam suas

1. **"Faturamento bruto" dos colaboradores** foi lido como o total que cada um gerou,
   depois do desconto (e não a cota-parte). Antes do desconto, a ordem muda em 2 de 27
   unidades em julho.
2. **O retorno em até 45 dias olha uma janela que já se fechou no fim do mês**, e não o
   mês anterior a partir da última visita: o número de um mês não muda depois que ele
   acaba. O 57% da tabela que você viu era a outra definição; na que ficou, a unidade 1
   em julho dá 58,7%.
3. **A API do painel não recusa o administrador** (o "Gerente geral" também é gerente);
   a home e o menu dele continuam a agenda.
4. **As formas de pagamento são barras, e não a rosca do mockup** -- o sistema de desenho
   não tem paleta de categorias.
5. **Mês antes de 2000 é recusado** (422).

**Perguntas que ficaram para você** (em `docs/f3-relatorios.md`): a cortesia de R$ 0,00
conta como produto vendido (na unidade 39, 568 dos 609 de julho); a comanda só de
cortesia é atendimento; quem estreou comprando crédito não é cliente novo no mês do
primeiro corte; os itens do D306; e a autorização para criar o índice do D308 na cópia
local, se quiser medir.

## 29 a 30/09/2026, madrugada -- o painel com as suas quatro respostas, o índice do D308 na cópia, e calcular na hora ou pré-calcular

Você respondeu as quatro perguntas que a revisão do painel tinha deixado, autorizou o índice
do D308 na cópia local e perguntou se, com isso, os relatórios deviam ser pré-calculados.
As respostas, nas suas palavras, e o que cada uma virou estão em `docs/f3-relatorios.md`,
seção 2; a pergunta do pré-cálculo virou a seção 4.

### O que mudou

- **Cortesias é um número à parte**, no mês e por colaborador, e **produtos vendidos não a
  contam**. Medido antes de decidir: na cópia, os itens de preço zero que não são de pacote
  são de dois tipos -- os de produto (o café, a água, a cerveja "free") e os de serviço, quase
  todos do "CLUBE DA VIP", que é assinatura. Por isso cortesia é **o produto lançado a preço
  zero na comanda**; o serviço de preço zero continua serviço. Na unidade 39, em julho: 41
  produtos vendidos e 568 cortesias, onde antes apareciam 609 produtos.
- **A comanda só de cortesia continua atendimento**, como você disse.
- **Descontos concedidos** entra no mês e por colaborador; o faturamento continua depois do
  desconto. Na unidade 1, em julho: R$ 21.198,60. A sessão de pacote não é desconto (ela tem
  preço e sai a zero sem desconto nenhum -- derivar o desconto da diferença a contaria).
- **Cliente novo é quem teve o primeiro serviço ou a primeira compra de crédito na rede no
  período**, o que vier primeiro. Duas consequências que a sua resposta não cita, e que
  ficaram escritas: a venda só de produto não faz ninguém novo nem antigo; e quem estreia
  comprando crédito é novo sem ser cliente atendido naquele mês. Na unidade 1, julho passa de
  55 para 57 clientes novos -- conferido por um SQL independente.
- **O índice do D308 foi criado na cópia**, nos dois bancos, com o mesmo nome (18 s no MySQL,
  14 s no PostgreSQL). Os DDLs do repositório continuam com o antigo: produção muda na
  migração final.
- **Pré-calcular ou calcular na hora** (a seção 4): o mês de uma unidade se calcula na hora
  -- menos de meio segundo mesmo com a memória de fábrica. O que pesa é o histórico: a
  estreia de cada cliente (8 a 9 s com 128 MB, 0,3 s com as vendas na memória) e as séries
  longas da rede (12 meses, 5 a 8 s; 36 meses, 14 a 21 s). A recomendação é calcular na hora,
  guardar a estreia do cliente só se produção se mostrar lenta, guardar o mês fechado quando
  o primeiro relatório de série longa for construído, e aposentar cada rotina noturna (D307)
  quando o relatório que a lê for refeito.

### Provado

- **Duas conferências, nenhum agente morto.** A do contrato entre servidor e tela não achou
  divergência de chave nem de tipo, e achou sete frases imprecisas (comentários e a nota da
  tela), corrigidas. A revisão adversarial -- quatro revisores (contas, consultas, testes,
  tela) e um verificador por achado -- achou **10, e os 10 se confirmaram**; todos foram
  consertados. O maior: **o retorno dos clientes novos contava a partir de uma visita
  anterior à estreia** -- quem levou uma pomada e dias depois fez o primeiro corte tinha o
  próprio corte contado como a volta. Em julho, na rede, não havia caso; a regra estava
  errada mesmo assim, e agora a volta conta a partir da estreia.
- **18 mutantes da revisão, todos mortos** -- a compra de crédito tirada dos candidatos, a
  cortesia lida pelo total em vez do preço, o pacote aceito como serviço, o desconto sem
  arredondar, o limite da faixa das compras de crédito --, cada um aplicado sozinho e
  devolvido com o sha1 conferido.
- **Os 57 clientes novos da unidade 1 em julho** (55 pela régua de antes) conferidos por um
  SQL escrito do zero, fora do código.
- **Caracterização**: 1298 nos dois bancos (13 pulados, os de sempre). **Contrato** do
  aplicativo: 67 de 67 nos dois. **Ensaios** por HTTP: os onze no MySQL e os doze no
  PostgreSQL. **Portões**: verdes, com o D50 de sempre. **Front**: 1440 testes, build e lint
  no piso de dois avisos.
- **No navegador**, contra o banco de contrato com setembro semeado (58 cortesias, 23 itens
  com desconto): os doze cartões, 5 + 5 + 2 a 1280 px e uma coluna a 375 px, sem rolagem
  lateral; descontos e cortesias mostram a seta e o sinal sem a cor de bom ou ruim; a lista
  de colaboradores soma o total dos dois; e a nota "como os números são contados" diz as
  regras novas. Depois, a sessão de teste foi apagada e o banco de contrato remontado.
- **As medições** foram feitas dentro da sessão do banco, repetidas com a máquina sem outra
  carga: as primeiras, com os agentes rodando suítes ao lado, chegaram a dar o dobro.

### O que o índice não resolveu, escrito

A sua regra nova do cliente novo pergunta, de cada venda antiga, se ela teve serviço -- e
isso é ler os itens dela. O índice tira da tabela a busca das vendas, mas não a dos itens:
com pouca memória ele corta um terço do tempo (13-14 s para 8-9 s na unidade 1); com as
vendas na memória, quase nada. Fazer da pergunta uma leitura só de índice pediria mais dois
índices, que não criei -- a autorização foi para este.

### Falta, e é seu

- **A sincronização com o Claude Design**: a skill só roda quando você a chama -- digite
  `/design-sync` aqui. O projeto já está vinculado (o do console), e o painel e o menu
  Relatórios entram nessa rodada.
- **Por onde começar os onze relatórios do menu** (a sugestão continua o Profissional
  Parceiro), e o D306 depois deles, como você disse.

## 30/09/2026 -- o Profissional Parceiro: o levantamento, as suas respostas e as duas primeiras fatias

Você mandou continuar pelo Profissional Parceiro (o Dash. Colaboradores). O levantamento, as
suas 13 respostas, as duas que vieram depois do desenho e as fatias estão em
`docs/f3-relatorios.md`, seção 5.

### O que o levantamento achou na tela antiga

Ela não calcula nada: lê uma tabela gravada às 23:30, só para o grupo 1. **Esconde quem não
tem a área "Barbeiro" no cadastro** -- 30 linhas e R$ 119.152,26 na rede em julho, 26 delas de
cadastros sem área nenhuma --; **deixa fora os gerentes que atendem** (R$ 43.164,40 e 482
serviços em julho); o mês não fecha de três jeitos; o NPS mostra 0 para quem não tem resposta;
e o "melhor da rede" em consumo por cliente tinha 3 clientes. E um vazamento pequeno, o D309:
o barbeiro lia o extrato do plano de carreira de um colega.

### O que mudou

- **No painel (F0)**, o cliente novo conta, por colaborador, para quem fez o serviço no dia da
  estreia. Na unidade 39, um caixa que só serviu café "ganhava" 23 clientes novos; agora, zero.
- **O Profissional Parceiro novo (F1)**, a lista do mês: só quem fez serviço, com faturamento
  e **produção** (a venda do pacote para quem vendeu e cada sessão pelo valor para quem
  executou, como você decidiu), **gorjetas** à parte, descontos, cortesias, as médias por
  cliente e o **NPS com o número de respostas** ("sem pesquisa" no lugar do 0). O
  administrador escolhe a unidade ou a rede; o gerente vê a unidade; o colaborador vê só a si
  mesmo, com o faturamento. O "ver detalhes" do painel abre o relatório no mês do painel.
- **O acesso sai das marcas do cadastro**, numa função só, que decide a API nova, o menu da
  tela nova, o menu da tela antiga e a tela antiga -- nunca pelo número do grupo (D124). A
  marca "Acesso dashboard" passou a valer no servidor: 27 colaboradores que abriam a tela
  antiga pelo endereço deixam de abrir. **O D309 foi consertado** nas duas partes que eram
  acesso.
- **A cota-parte do pacote (D310)**: conferi o PDV, e duas das três partes do que você disse
  já valem -- a venda do pacote não gera cota-parte (585 vendas em 2026, todas com zero), e a
  sessão gera na execução. Falta onde configurar o percentual no pacote; fica para a
  Cota-parte, ou antes, se você pedir.

### Provado

- **O desenho** teve quatro desenhistas e um crítico, só lendo e medindo; o crítico decidiu,
  com o porquê escrito, o que as suas respostas já decidiam, e sobraram duas perguntas, que
  você respondeu.
- **A revisão adversarial** -- cinco revisores e um verificador por achado, nenhum agente
  morto -- achou **9, os 9 confirmados e consertados**. O maior: as médias por cliente somavam
  o que foi feito para o avulso e o "sem cadastro" e dividiam só pelos clientes com cadastro
  (um colaborador aparecia com R$ 515,00 de consumo por cliente; o real era R$ 280,00). Um
  segundo verificador por achado conferiu os consertos, e as quatro pontas que ele achou
  também foram fechadas -- entre elas, o extrato que no MySQL aceitava "90001003abc" como o id
  de um colega.
- **Mutantes**: todo teste novo ficou vermelho com o defeito de volta, cada um aplicado sozinho
  e devolvido com o sha1 conferido.
- **Caracterização**: 1346 nos dois bancos (13 pulados, os de sempre). **Contrato**: 67 de 67
  nos dois. **Ensaios** por HTTP: os onze no MySQL e os doze no PostgreSQL, o de permissão com
  as rotas novas e o colaborador sem a marca. **Portões**: verdes, com o D50 de sempre.
  **Front**: 1474 testes, build e lint no piso de dois avisos -- o teste de arrastar da agenda
  falhou uma vez no ciclo e passou na rodada seguinte, a mesma instabilidade de antes.
- **No navegador**, contra o banco de contrato com agosto e setembro semeados: o "ver
  detalhes" do painel em agosto abriu o relatório em agosto; setembro mostrou a produção com
  as sessões (R$ 5.095,00 contra R$ 4.945,00 de faturamento), a venda do pacote na produção de
  quem vendeu, as gorjetas (R$ 15,50 e R$ 7,00) e o NPS (9,50 com 2 respostas; 10,00 com 1); a
  375 px, uma coluna, a posição na linha do nome e nada rolando de lado. Depois, a sessão de
  teste foi apagada e o banco de contrato remontado.

### Falta

- **As fatias seguintes do Profissional Parceiro**: F2 (os clientes novos e a fidelização com
  a janela de 45 dias, e o melhor da rede), F3 (o histórico de 12 meses e o completo), F4 (a
  agenda: os agendamentos, a vigência do horário semanal e a ocupação sem os bloqueados).
- **Um detalhe visto no navegador**: o cabeçalho do relatório mostra a unidade no formato do
  seletor ("BRASIL - SC - FLORIANOPOLIS - CENTRO"), e o painel, pelo nome ("UNIDADE TESTE
  CENTRO"). Não muda número; entra na F2.
- **A sincronização com o Claude Design**, que só roda quando você a chama (`/design-sync`).
- **O push** deste commit.

## 30/09/2026, noite -- o Profissional Parceiro, fatia F2: os clientes novos, a fidelização de 45 dias e o melhor da rede (com o D311)

Você mandou continuar, e a ordem era a F2 (`docs/f3-relatorios.md`, seção 5, "A F2").

### O que mudou

- **Cada colaborador ganhou os clientes novos e a fidelização**, num pedido à parte que a
  lista não espera. O cliente novo conta para quem fez o serviço da estreia -- a regra do
  painel. A fidelização segue a sua resposta 8: dos clientes que ele atendeu no mês, quantos
  tiveram a visita anterior, em qualquer unidade, também com ele, até 45 dias antes. A tela
  deixa escolher 30, 45, 60 ou 90 dias, e explica os que não se mantiveram: vieram de outro
  colaborador, eram novos, ou não tinham visita dentro da janela.
- **O melhor da rede voltou, com volume mínimo** (30 atendimentos no mês), nos dez números
  da lista. O valor vai para todos; quem é o melhor, só o administrador vê. Quem vê só a si
  mesmo tem o próprio número ao lado.
- **O cabeçalho do relatório mostra o nome da unidade como o painel** -- o do cadastro. Na
  cópia, 4 das 36 unidades ativas têm o nome diferente do rótulo da lista suspensa.
- **O índice do D308 entrou no gerador do PostgreSQL** -- sem ele, o corte levaria o índice de
  uma coluna, e a fidelização leria a tabela. O `postgres/schema.sql` foi regenerado e levou
  junto a linha do D189, que estava atrasada desde 21/09 sem ninguém ver (o CI está parado).

### Medido na cópia, julho de 2026

- Unidade 1: fidelização de 44,8% em 2.449 pares colaborador-cliente -- o número do desenho,
  reproduzido; o colaborador 200 em 57,1% ("323 de 566"). 57 clientes novos. 13 s, dos quais
  10 s são a pergunta "teve serviço ou crédito antes?", a mesma do painel.
- A rede: 41,9%, 1.403 clientes novos. **Cerca de 70 s e 122 MB** no MySQL com 128 MB -- o
  **D311**: nada limita pedidos iguais em paralelo, e a medida no PostgreSQL decide o que fazer.
- O melhor da rede: 130 concorrentes de 143, em 1,7 s; os vencedores são os do crítico.

### Provado

- **A revisão adversarial** -- três revisores, só lendo, com o sha1 de cada arquivo conferido
  depois -- não achou vazamento entre franquias nem entre colaboradores. Achou onze pontas, todas
  consertadas ou registradas: entre elas, o foco que caía no vazio no "tentar os clientes de
  novo", a frase da unidade que chamava pares de "clientes", a venda sem cliente que virava o
  "cliente 0" na base, o ruído do `double` que desempatava o melhor da rede, e cinco mutantes da
  tela que sobreviviam. O custo da rede virou o D311.
- **39 mutantes, todos mortos** -- 26 no servidor e 13 na tela --, cada um aplicado sozinho e
  devolvido com o sha1 conferido.
- **O ciclo inteiro, em série**: caracterização em **1374 nos dois bancos**; contrato, 67 de 67
  nos dois; os ensaios nos dois bancos, o de permissão com as rotas novas e olhando o corpo;
  portões verdes com o D50 de sempre; **1492 testes no front**, build e lint no piso. A suíte da
  tela, com todos os processos que o Windows dá, estoura o tempo de 5 s em um ou dois casos
  diferentes a cada rodada, em telas que a F2 não tocou; com quatro processos, passa inteira.
- **No navegador**, contra o banco de contrato semeado: **um oráculo em Python sobre as linhas
  cruas do banco, sem o código da aplicação, deu os mesmos números da tela**, na unidade (3 de 10
  fidelizados, 30,0%) e na rede (3 de 11, 27,3%), colaborador a colaborador, com a mesma
  explicação. A janela de 30 dias pediu só os clientes; o administrador viu o nome do melhor, o
  gerente só o valor; a 375 px, nada rolou de lado. Depois, a sessão de teste foi apagada e o
  banco de contrato remontado.

### Falta

- **F3** (o histórico de 12 meses e o completo), **F4** (a agenda), e depois os outros dez
  relatórios e o D306.
- **O D311**: medir os clientes da rede no PostgreSQL antes do corte.
- **O commit desta fatia**, que espera o seu pedido, e **o push** -- o da F1 também está só aqui.
- **A sincronização com o Claude Design** (`/design-sync`).

## 30/09 a 01/10/2026, madrugada -- o Profissional Parceiro, fatia F3: o histórico de um colaborador

Você mandou continuar, e depois, antes de dormir: "aguarde finalizar e continue de forma autonoma
até que seja necessário uma decisão. Lance agentes paralelos para resolver débitos tecnicos que
não dependem de decisão do usuário. Faça commit e push e atualize o roadmap artefato". A F2 foi
commitada (555e072), e a F3 é esta (`docs/f3-relatorios.md`, seção 5, "A F3").

### O que mudou

- **Cada cartão da lista tem "ver o histórico"** -- o do próprio colaborador também. O histórico
  abre no lugar da lista, com o mês, a unidade e a janela dela; a volta não pede a lista de novo.
- **Os 12 meses que terminam no mês da lista**, numa tabela do mais recente para o mais antigo,
  com o total do período. Cada mês é a linha que ele teria na lista daquele mês -- as mesmas
  contas, sem regra escrita duas vezes. Os meses sem atendimento aparecem com zero (férias e
  afastamento ficam à vista), e o mês em que ele só vendeu produto vem marcado "sem serviço". O
  total conta cada cliente uma vez no período, e por isso não é a soma dos meses.
- **O histórico completo, desde o cadastro** (a sua resposta 12): a tela pede os períodos de 12
  meses um depois do outro, até o mês do cadastro, e mostra o progresso. Os clientes novos e a
  fidelização entram também, período a período.
- **Quem vê**: o colaborador, só o próprio; o gerente, quem atendeu na unidade dele, e só o que
  foi feito nela -- o de outra franquia é o mesmo "não encontrado" de quem não existe; o
  administrador, qualquer um.

### Medido na cópia, julho de 2026

- O colaborador 200, o maior da rede: cerca de 10 s os números e 17 s os clientes, no MySQL com
  128 MB; o histórico completo dele são 12 períodos, de 2015 a 2026. A mediana da lista, cerca de
  7 e 6 s.
- Julho do histórico é a linha da lista de julho e os clientes da F2, nos dois colaboradores.

### Provado

- **A revisão adversarial** -- cinco revisores e um verificador por achado -- achou sete pontas, as
  sete consertadas: a mais séria, a guarda do gerente, que lia a história inteira do colaborador
  com a sessão presa (5 a 6 s parado); ela passou a responder pelos itens do próprio período e a
  rodar com a sessão solta. O que o tempo do "não encontrado" ainda conta -- que o id é de alguém
  -- ficou registrado no documento.
- **39 mutantes, todos mortos** (14 no servidor, 25 na tela).
- **O ciclo inteiro**: caracterização em **1398 nos dois bancos**, contrato 67 de 67 nos dois, os
  ensaios nos dois, os portões com o D50, **1514 testes no front**.
- **No navegador**, contra o banco de contrato semeado: o histórico bateu mês a mês com uma conta
  direta no banco, os clientes da série com os de cada mês, o completo encadeou três períodos até o
  cadastro, e a 375 px a tabela rolou dentro do cartão sem a página rolar de lado.

### Falta

- **F4** (a agenda), e depois os outros dez relatórios e o D306.
- **Os débitos sem decisão**, em agentes paralelos depois deste commit -- o próximo registro diz
  quais e como ficaram.
- **O D311**: a medida no PostgreSQL.
- **A sincronização com o Claude Design** (`/design-sync`).

## 01/10/2026, madrugada e manhã -- os débitos que não pediam decisão sua, em agentes paralelos

Depois do push da F3, como você pediu: agentes em paralelo nos débitos que não dependiam de
decisão sua. Antes, a triagem: dos 101 títulos abertos, a maior parte espera a migração, o
servidor de produção ou uma escolha sua (o WhatsApp, as parcelas do dia 31, a exportação em
XLS, o motivo do convênio, a remarcação com a comanda aberta). Ficaram cinco frentes, cada uma
num agente, com um verificador cético depois de cada uma, e uma trava para o `local.php` entre
eles -- as suítes trocam o banco, e duas ao mesmo tempo dão falso vermelho. Duas rodadas: os
verificadores da primeira acharam o que faltava, e a segunda fechou.

### O que foi consertado

- **D237 -- as ações do ERP antigo que aceitavam o id de outra franquia.** O item e o
  pagamento do PDV (o do pagamento conferia a unidade DEPOIS de apagar; o do item devolvia a
  venda alheia pelo segundo campo do formulário), o débito, o pacote, e na agenda: marcar, os
  horários livres, o destino da remarcação e o fechamento -- que gravava o bloqueio de outra
  franquia, visível na tela dela e impossível de remover lá. A verificação achou a mesma
  brecha no fechamento da API nova, também fechada. E o relatório da VIP Class. Ficam as três
  rotas antigas do aplicativo -- a pergunta abaixo.
- **D176** -- a consolidação do NPS dividia por zero numa pesquisa sem item e travava em laço.
- **D284** -- cancelar uma venda tirava REAIS dos pontos do cliente (a venda de R$ 90 com
  cashback 10 dá 900 pontos, e o estorno tirava 90). Agora o saldo volta ao que o histórico diz,
  inclusive o de quem indicou.
- **D198** -- o cookie da sessão saía sem `HttpOnly` e sem `SameSite`: a configuração existia e
  ninguém a aplicava. Agora vale, e também nos cookies do login, que entram sem senha; o token
  saiu do campo oculto de toda página.
- **D223** -- "um dispositivo por vez": quem era expulso por um login em outro aparelho
  continuava logado. Agora cai no clique seguinte, inclusive na tela nova.
- **D311, medido no PostgreSQL**: os clientes da rede no Profissional Parceiro levam 25 a 49 s
  com o disco frio e 7 a 8 s quente -- os mesmos números do MySQL, conferidos pela resposta
  inteira. O "3 a 6 s" do desenho não se confirmou.

### Registrados (achados no caminho)

- **D312** -- os pontos da indicação vão para a unidade de quem indicou, e a linha do histórico
  para a da venda (pergunta abaixo).
- **D313** -- o PDV antigo ainda tira item e pagamento de venda concluída, dentro da unidade (o
  conserto é o da API nova, sem decisão: fica para a próxima rodada).
- **D314** -- o cancelamento de pacote antigo aceita quem não é gerente, e por GET (idem).
- **D315** -- o administrador que assume outra pessoa e clica "Sair" derruba o aparelho dela.

### Provado

- **Cada conserto com o caso que falha sem ele**: 58 casos novos na caracterização, e os mutantes e
  as falsificações dos portões restaurados pelo sha1. Duas rodadas de verificação.
- **O ciclo inteiro, em série**: caracterização em **1456 nos dois bancos** (13 pulados por
  dependerem da cópia da produção), contrato 67 de 67 nos dois, os 11 ensaios nos dois bancos (o
  da sessão com 15 casos) e a medida da API nova no PostgreSQL, os portões com o D50, **1514
  testes no front**, o build e o lint.
- **Dois achados do próprio ciclo, consertados.** Uma bomba-relógio: o teste do relatório de
  atendimentos tinha setembro de 2026 escrito como constante, e a linha do cenário muda de mês com
  o relógio do banco -- no primeiro dia de outubro ficou vermelho sem nada mudar; agora lê o mês da
  própria linha. E três vermelhos que só a rodada inteira mostrava: dois casos novos que herdavam a
  sessão deixada por outro arquivo, e o portão do front, que no Windows caía por carga em casos
  diferentes a cada rodada -- passou a rodar com quatro processos, como o ciclo.

### Perguntas que esperam você

1. **As três rotas antigas do aplicativo** (`/api/agendas/cancelar`, `/api/v2` e `/api/v3/agendas/agendar`):
   hoje só uma sessão do ERP as alcança, e agem em qualquer unidade. (A) recortar pela unidade
   da sessão, como a agenda do ERP; (B) fechá-las até aposentar; (C) deixar como estão.
   **Recomendo (A).**
2. **O D311**: manter até o corte; um freio de um pedido da rede por pessoa; uma fila na tela; ou
   gravar a estreia do cliente na venda. E se vale também para o "melhor da rede", que todo
   gerente dispara. **Recomendo manter até o corte** e decidir com a memória do servidor de
   produção -- que só você pode ler (só ler).
3. **O D312**: os pontos da indicação são da unidade de quem indicou ou da unidade da venda?
4. **O D284, o que sobrou**: os pontos já resgatados de uma venda cancelada (hoje o valor fica com
   o cliente), e se as colunas de pontuação que já divergem do histórico devem ser corrigidas em
   massa (25 na cópia de produção).

### Falta

- **D313 e D314**, que não pedem decisão: a próxima rodada. O D315 depois deles.
- **F4** (a agenda), e depois os outros dez relatórios e o D306.
- **A sincronização com o Claude Design** (`/design-sync`).

## 01/10/2026, manhã -- a terceira rodada: o PDV antigo deixou de mexer em venda fechada, e o pacote só sai pelo gerente

Os dois débitos que a rodada anterior registrou sem pedir decisão, um agente para cada, com um
verificador cético depois de cada um. Os dois verificadores confirmaram, sem defeito.

### O que foi consertado

- **D313 -- o PDV antigo tirava item e pagamento de venda já concluída.** Um POST escrito à mão
  tirava o item -- e, com ele, todos os pagamentos -- ou o pagamento de uma venda que já tinha
  entrado no caixa e no relatório. Agora só a venda em andamento e a comanda aberta, os dois lugares
  onde a tela mostra o botão; a concluída e a cancelada são recusadas com uma frase que diz por quê.
  A regra mora num lugar só (`ISVenda\Regra\RegraDaVendaNoPdv`), e é a da comanda nova (o D163).
  Na cópia: 2,2 milhões de vendas concluídas, 62 mil canceladas, 16 em andamento e 1 comanda aberta.
- **D314 -- o cancelamento de pacote do ERP antigo aceitava quem não é gerente, e por GET.** Agora
  exige o gerente ou o administrador, como a API nova (o D281), e vai por POST, com a mesma pergunta
  "Deseja realmente remover?". A regra de quem cancela passou a morar num lugar só, chamada pela API,
  pela ação do ERP e pelo X da tela. Quem vê o X não mudou (os grupos 3, 4 e 7); quem perdeu foi
  quem digitava o endereço sem ser gerente.

### Registrado

- **D316** -- as outras escritas do PDV antigo também não leem a situação da venda: acrescentar item
  (que apaga todos os pagamentos dela), lançar pagamento, finalizar de novo (que debita outra vez o
  crédito do cliente), somar débitos e autorizar o fiado numa venda concluída. O conserto é o do
  D313, sem decisão: a próxima rodada.
- A aba de pacotes imprime cru o nome do pacote e o do serviço -- entrou no D203, que já é a classe
  disso.

### Provado

- **D313**: 11 casos novos, nos dois bancos; cada guarda arrancada, a regra devolvendo `true` e a regra
  sem o estado "em andamento" ficam vermelhos, no teste e no portão. **D314**: 9 casos novos, nos dois
  bancos, e sete mutantes, todos mortos; o portão da remoção por POST desceu de 14 para 13.
- **No navegador**, contra o banco de contrato, como o gerente de fixture: a aba de pacotes mostra o
  X, o X abre a pergunta, o "Sim" vai por POST, e o pacote some. A sessão injetada caiu no login 48 s
  depois de aberta -- era o `validarHash` levando o cookie anônimo, porque desde o D198 o cookie da
  sessão é HttpOnly e o script não o substitui; a nota ficou na memória da casa.
- **O ciclo inteiro, em série**: caracterização em **1476 nos dois bancos** (13 pulados por
  dependerem da cópia da produção); o contrato do aplicativo, 67 de 67 nos dois; os 11 ensaios nos dois
  bancos -- o da fidelidade, que cobre a guarda da API do D281, inclusive -- e a medida da API nova no
  PostgreSQL; os portões com o D50; **1514 testes no front** e o build.

### Falta

- **O D316**, na próxima rodada, e o **D315** depois dele (da família do D127, que espera você).
- **As quatro perguntas** da entrada anterior continuam esperando você.
- **F4** (a agenda), e depois os outros dez relatórios e o D306; e o `/design-sync`.

## 01/10/2026, tarde -- o D316: o PDV antigo deixou de mexer em venda fechada em todas as escritas, e a tela finalizava vendas duas vezes

Como você pediu ("continue com o D316"): um agente mapeou, só lendo, todas as escritas do PDV antigo
-- das rotas, dos serviços e do JavaScript, e não de memória --, outro consertou, e três verificadores
independentes tentaram derrubar o conserto, cada um por um lado.

### O que foi consertado

- **D316 -- as outras cinco escritas do PDV antigo agora recusam a venda concluída ou cancelada**:
  acrescentar item (que apagava todos os pagamentos da venda), lançar pagamento, finalizar de novo
  (que debitava outra vez o crédito do cliente, somava ao caixa e regravava a data), somar débitos e
  autorizar o fiado. A mesma regra do D313. O finalizar e a autorização do fiado respondem *"Esta
  venda já foi finalizada ou cancelada, código: N"*. O cancelamento da venda concluída continua
  livre -- é o fluxo da tela.

### O que o mapa achou, e é maior que o débito

- **A própria tela finalizava a mesma venda de novo.** Na cópia, **1.154 vendas concluídas foram
  finalizadas de 2 a 15 vezes**, de novembro/2025 a abril/2026 sobretudo; **52 ficaram com o débito
  do fiado em dobro (R$ 3.435,80), e 20 desses débitos continuam abertos (R$ 1.109,50): o cliente deve
  duas vezes a mesma venda.** A guarda de hoje recusa a segunda finalização. O que ela não resolve --
  por que a tela devolvia erro, a corrida de dois cliques quase simultâneos, e o dado já estragado --
  virou o **D321**, com três perguntas para você.

### Registrados

- **D317** -- o cancelamento do ERP recancela a venda já cancelada (devolve estoque, pacote e crédito
  outra vez); a API nova já recusa.
- **D318** -- a autorização do fiado da API nova não lê a situação da comanda.
- **D319** -- o "Quitar" do PDV antigo soma ao total o valor que vem no pedido.
- **D320** -- finalizar uma venda que não existe dá erro fatal.
- **D321** -- o finalizar repetido pela tela, com as perguntas.

### Provado

- **28 casos novos**, nos dois bancos, com o retrato do banco inteiro em cada recusa; os mutantes de
  cada guarda vermelhos. Os verificadores acharam dois pontos, consertados: a frase do fiado sem o
  código (o reclique do fiado entra pela autorização), e a ordem da guarda do item antes da gravação
  do cliente, que nenhum teste fixava.
- **No navegador**, pela tela do PDV, contra o banco de contrato: dois itens, o pagamento e a
  finalização -- a venda fechou com o caixa somado uma vez; o mesmo "finalizar" mandado de novo
  voltou com a frase e o código, e o banco ficou como estava.
- **O ciclo inteiro, em série**: caracterização em **1504 nos dois bancos**; o contrato do aplicativo,
  67 de 67 nos dois; os 11 ensaios nos dois bancos e a medida da API nova no PostgreSQL; os portões com
  o D50; **1514 testes no front**, o build e o lint. Dois vermelhos foram da máquina, carregada pela
  sincronização do Claude Design ao mesmo tempo, e foram repetidos com ela calma: 10 testes do front que
  estouraram 5 s (os três arquivos, depois, 343 de 343) e a medida da API nova, que não respondeu a 7
  endpoints e, repetida depois do ensaio do fluxo como manda a ordem, não achou nenhum quebrado.

### Perguntas que esperam você (o D321)

1. **A recusa "já finalizada" na tela:** deixar o aviso; ou o JavaScript tratá-la como sucesso e ir à
   venda nova; e achar o erro que levava ao reclique, que pede o log do servidor de produção (só
   ler). **Recomendo as duas últimas.**
2. **A corrida de dois cliques:** uma trava na linha da venda no finalizar (que a API nova também
   usa); só no JavaScript, o "Confimar" do fiado voltar apenas no retorno; ou os dois. **Recomendo
   os dois.**
3. **O dado já estragado:** medir na produção, só lendo, e começar pelos 20 débitos abertos em dobro.
   A correção em massa é escrita na produção, e só com a sua ordem.

## 01/10/2026, tarde e noite -- as telas da Fase 3 no Claude Design: 59 componentes, cada um com prévia

Como você pediu ("use o /design-sync e exporte as novas telas montadas para o claude design"), com
as suas quatro respostas no meio: prévia de todas, consertar o CSS dos campos, consertar os cortes
da agenda (sabendo que mexe nas medidas que o `templates/agenda/` usa) e trocar o exemplo do
cabeçalho. Projeto: "Barbearia VIP — Console (Fase 3)",
`https://claude.ai/design/p/2f95aaac-dd59-496f-9da1-7c0400013999`.

### O que foi para o projeto

- **De 17 componentes para 59**: as 25 telas puras que entraram depois de 15/09 (caixa, catálogo,
  clientes, console, convênios, cupons, devedores, estoque, fidelidade, pacotes, painel, Profissional
  Parceiro e o histórico, usuários, vendas realizadas e as peças deles), 10 peças novas da agenda
  (a grade em blocos, a linha do tempo, a forma "agora" e as janelas de marcar, remarcar, desmarcar
  e reabrir) e 8 da comanda. **Cada um com prévia de dados realistas** -- os números fecham entre as
  telas -- **fotografada e lida célula a célula: 237 células, todas aprovadas.**
- **O `LancarItem` saiu do projeto**: ele foi dividido em `LancarPelaBusca` e `LancarPeloCatalogo`. O
  template de comanda que você montou lá não o usa (usa o receber, o finalizar, o fiado e o aviso
  dos pagamentos, que continuam). Os templates e o material de handoff não foram tocados.
- **O texto que o agente de design lê** foi atualizado: o exemplo do cabeçalho com o
  `LancarPelaBusca`, o menu de hoje (15 grupos, 51 destinos, 13 em Relatórios) e as 16 peças da agenda.

### O que a sincronização achou, e foi consertado

- **O pacote não carregava no navegador**: uma expressão regular escrita com os caracteres da faixa,
  que o navegador lia em Latin-1 -- os 60 componentes sumiam do projeto. Trocada por escapes.
- **O texto de cada componente descrevia a célula errada desde 13/09**: o comentário de uma célula ia
  pendurado no exemplo anterior, e o da primeira se perdia. Dois ajustes no conversor (guardados em
  `frontend/.design-sync/overrides/`) consertam, e o comentário de abertura de cada prévia -- os
  avisos que valem para a tela inteira -- passou a chegar ao agente.
- **Na tela**, sem decisão nova: os campos de busca e de filtro crus do navegador em oito telas; caixa
  e rádio no azul do navegador; a faixa da linha do tempo, que cortava o serviço ao meio, e o bloco de
  15 minutos, que cortava o nome; a Home dizia "não carregou" de quem estava de férias; o cabeçalho da
  coluna sozinha colava o nome nos números; o telefone do resumo saía na borda; a fidelização do
  Profissional Parceiro invadia o rótulo vizinho (agora quebra só no "·"); "Aberta às" desalinhado; a
  movimentação cancelada do estoque sem a cor mais fraca; e o "o seu: R$" do melhor da rede partido.
- **O gerente dos dados de exemplo tinha o seu nome**, e o Console, a moldura e a Home o mostravam.
  Passou a fictício.

### Registrado

- **D322** -- os defeitos de tela que as prévias acharam e ficaram: cerca de quarenta sem decisão
  (frases, alinhamentos, um "tentar de novo" num 403, a Home que passa da tela...) e as perguntas de
  desenho abaixo.

### Provado

- **Três rodadas de nota, oito agentes cada**, mais a minha nos cartões que mudaram depois: cada
  folha lida, cada número conferido, cada descrição comparada com o que a célula mostra. A checagem
  de render: 59 cartões, nenhum quebrado nem vazio; o único aviso é conhecido e benigno (as duas
  células da escolha da janela só mudam o valor do seletor).
- **1514 testes no front, o build e o lint**; os portões dos débitos e dos links. Os 18 telefones
  das prévias não existem na cópia do banco.

### Perguntas que esperam você (o D322)

1. **Dois botões em ouro na mesma tela** -- editar e acrescentar nos pacotes; cadastrar e buscar nos
   convênios e cupons. A regra da casa é um por tela.
2. **"Dias trabalhados" em queda sai em vermelho** no painel, e a queda é só do calendário.
3. **O ticket de quem não faz serviço** (a caixa que lança o café) -- se o painel o mostra.
4. **A fidelização no cartão**: hoje ela quebra em duas linhas a 1280px. Mostrar a conta inteira ou
   só a porcentagem?
5. **Os estados que abrem por clique** (receber o fiado, comprar e devolver crédito, cancelar pacote,
   a oferta de dependente, os resultados da nova comanda) não têm cartão no Claude Design. Uma prop
   opcional de estado inicial os deixaria desenhar.

## 01/10/2026, noite -- o D315: o administrador que personifica deixou de derrubar o aparelho da pessoa assumida; e as suas 24 respostas

Como você pediu ("ataque D315 e depois F4", e depois "continue de forma autonoma a criação das telas
faltantes"): dois agentes mapearam o caminho da personificação e as saídas, só lendo; um desenhou;
outro consertou com os testes; três verificadores tentaram derrubar o conserto, cada um por um lado
(segurança, fluxo legítimo e provas); e uma rodada de correção fechou o que eles acharam.

### O que foi consertado

- **D315 -- a sessão de personificação passou a guardar o token de quem entrou**, e não o da pessoa
  assumida. O "Sair" do administrador apaga o token DELE (antes apagava o da pessoa assumida, e o
  aparelho dela saía pedindo login); a regra do um aparelho por vez confere o administrador (antes o
  login da pessoa assumida derrubava o administrador, e o login do administrador noutro lugar não
  derrubava a personificação); e a volta usa o token guardado, e não o do banco naquele momento.
- **A personificação aberta antes do conserto** sai uma vez, pedindo login de novo, em vez de acusar
  um "usada em outro local" que ninguém fez.
- **O D127 ficou como estava**, e é decisão sua: a volta para um id diferente de quem entrou. A
  revisão achou que ele não exige endereço editado -- personificando um administrador, o "Autenticar
  como" aparece em cada linha -- e que três navegações vindas de outro site escolhem quem sai (o
  "Sair" é GET). Está escrito no D315, com um caso que o caracteriza sem decidir.

### Provado

- **15 casos novos** (20 execuções), em processo e sem HTTP, nos dois bancos; com o código de antes,
  15 das 20 ficam vermelhas, e as 5 verdes são os controles. Cada mudança desfeita à mão deixou o
  seu caso vermelho.
- **O ciclo inteiro, em série**: caracterização em **1524 nos dois bancos**; o contrato do
  aplicativo, 67 de 67 nos dois; os 11 ensaios nos dois bancos e a medida da API nova no PostgreSQL;
  os portões; o front. O processo do ciclo bateu no limite de tempo no meio dos ensaios do
  PostgreSQL e deixou o local.php no banco de contrato e um ensaio órfão no contêiner: o órfão foi
  morto e o local.php devolvido pelo sha1 antes de qualquer outra coisa. Três ensaios ficaram
  vermelhos porque o banco foi montado antes da meia-noite e os ensaios rodaram depois (o caixa
  "de hoje" das fixtures virou o de ontem); remontado, os três passaram.

### As suas respostas (24), registradas em `docs/f3-relatorios.md`, seção 6

- **Os dez relatórios restantes, 16 perguntas e duas a mais**: o período (o mês do painel, com
  quatro exceções); o PDV passa a gravar quem cancelou e quem deu o desconto manual; quem não é gerente
  vê só a si mesmo; o caixa vê só o dia; Descontos e Canceladas viram análise, com a lista em Vendas
  Realizadas; o Atendimentos absorve o Dash. Faturamento; a Cota-parte do menu recalculada e uma
  **Cota-parte v2** só do administrador com a gravada e o aviso; o NPS em três médias de 0 a 10; a
  retirada na comanda e o motivo no estoque; a exportação como hoje; as categorias todas, mais "sem
  categoria"; o royalty faturado e o devido lado a lado; as faturas dos dois lados; a indicação pelo
  aplicativo, pelo formulário e pela origem; a gaveta com o valor contado; a cota do pacote por
  sessão, para quem vendeu; o ciclo da cota-parte por unidade; e a gorjeta na gaveta.
- **A F4**: a tabela nova e o índice nas duas cópias locais (autorizado); a ocupação de todos com
  agenda, como o Dash; todo bloqueio fora do denominador, com os motivos ao lado; e o mês antes do
  horário da época pelo retrato antigo do Dash.

**A seguir, sem esperar você:** a F4 (quatro fatias, cada uma com verificadores) e, enquanto ela
roda, as especificações dos dez relatórios com as suas respostas aplicadas; depois, os relatórios,
começando pela parte comum. **As mudanças do PDV que criam colunas** (o cancelamento e o desconto
com autor, a gaveta contada, a cota do pacote) ficam prontas e esperam a sua autorização para as
cópias locais.

## 02/10/2026 -- o Profissional Parceiro, fatia F4: a agenda -- os agendamentos, a vigência do horário semanal e a ocupação sem os bloqueados (com o D323 ao D325)

Como você pediu ("ataque D315 e depois F4"), com as suas respostas de 01/10: quatro leitores e três
desenhistas levantaram e desenharam; um juiz escolheu; e quatro fatias, cada uma com um implementador,
dois verificadores que tentaram derrubá-la e uma rodada de correção. Os 31 achados confirmados (nenhum
de gravidade alta) foram todos consertados, cada um com o caso que o pega.

### O que mudou

- **A vigência do horário semanal (F4.1).** Uma tabela nova, `usuarios_horarios`, guarda o retrato do
  horário de cada pessoa a cada mudança, com quem gravou (o administrador que entrou, pelo D315) e em
  nome de quem. As quatro portas que mudam o horário gravam dentro da transação delas, e uma falha da
  vigência desfaz o salvamento; um portão novo vigia que nenhum outro escritor apareça. Com a sua
  autorização, a tabela, a semente (2.432 linhas) e o índice do D189 foram criados nas duas cópias
  locais; a produção recebe a migração no passo 0 do corte.
- **A régua (F4.2).** O agendamento é a família, pela origem gravada (aplicativo ou recepção); a
  capacidade sai da grade da época; todo bloqueio sai do denominador, com os cinco motivos ao lado; o
  total da unidade é de **todos com agenda**, como o Dash (a sua B4); e o mês sem o horário da época
  usa **o retrato antigo do Dash menos os bloqueados**, marcado "régua antiga" (a sua B6).
- **A tela (F4.3 e F4.4).** O cartão de cada colaborador ganhou agendamentos, ocupação e bloqueados, num
  pedido à parte que a lista não espera; o rodapé da unidade diz a taxa de cada régua; e o histórico de
  12 meses ganhou as três colunas, com o mês do histórico igual ao do mês por construção.

### Medido

- **Na cópia, julho de 2026:** a unidade 1 em 2 a 4 s nos dois bancos; a rede em 3 a 6 s e 116 MB. O
  colaborador 200 tem os mesmos 894 horários e 822 agendamentos que o Dash gravou; na régua antiga, os
  mesmos agendamentos sobre a base do Dash sem os bloqueados. Um oráculo em Python, sobre as linhas
  cruas, deu 0 divergências em cinco recortes.

### Registrados

- **D323** -- o passo 0 do corte não cobre todas as mudanças de esquema feitas desde o dump de produção.
- **D324** -- duas "ocupações da agenda" com regras opostas: a agenda do dia conta o bloqueado como tomado.
- **D325** -- reabrir a agenda apaga os bloqueios sem rastro, e a ocupação de um mês passado muda.

### Uma pergunta nova (a T8)

Quando algum colaborador da unidade está sem o horário da época (nem vigência, nem linha no Dash), o
total da unidade **mostra a taxa dos que têm e diz quantos ficaram de fora**, ou **some**? As fatias
seguiram a primeira; a outra é só na tela.

### Provado

- Cada fatia com os casos que falham sem ela e os mutantes aplicados um a um, todos mortos; o portão
  dos escritores do horário falsificado arrancando cada porta.
- **O ciclo inteiro, em série e em etapas** (o processo do D315 tinha batido no limite de tempo):
  caracterização em **1593 nos dois bancos**; o contrato do aplicativo, 67 de 67 nos dois; os 11 ensaios
  nos dois bancos e a medida da API nova no PostgreSQL; os portões; **1548 testes no front**, o build e o
  lint.

### O que não foi provado

- A tela com sessão contra o banco de contrato: os agentes não entraram no ERP com senha. A tela real
  foi montada pelo Vite com respostas escritas à mão, a 375 px, sem rolar de lado.
- O limite de memória da produção, que a rede (116 MB) precisa caber.

## 03/10/2026 -- SEGREDOS: a chave do Asaas e o token das rotinas saíram do código, e o portão de segredos passou a vê-los

Um dos débitos que não precisam de você, da lista de 03/10.

### O que mudou

- **A chave de produção do Asaas não está mais no código.** A url e a chave vêm da seção `asaas` do
  `local.php` (`api_key`, e `url`, que vazia vale a de produção). Sem chave, o cliente do Asaas não
  nasce: a exceção vem antes de qualquer chamada. A máquina de desenvolvimento, o CI e o ambiente de
  teste deixaram de falar com a conta de produção do Asaas (D3).
- **O token das rotinas por URL não está mais no `CronJobController`.** Vem do `cron.token` do
  `local.php`, e a decisão mora num método só, que **recusa o token configurado vazio** antes de
  comparar -- sem isso, um `local.php` sem o token abriria as 26 rotinas para quem chamasse sem token
  nenhum. A recusa responde o que sempre respondeu (`{"sucesso": true}`); o 401 e o cabeçalho são o
  próximo item (D4).
- **O portão de segredos enxerga os dois, pelo nome e pelo valor.** Antes, o do CI e o do `pre-push`
  saíam verdes com os dois literais no código. Agora são um roteiro só (`scripts/conferir-segredos.sh`),
  que ignora maiúscula, conhece `tokenPadrao`, lê o valor que começa com `$`, declara as exceções com o
  motivo e não imprime o valor que acha. E procura os dois segredos que estavam no código **pelo
  valor**, em qualquer forma: a chave do Asaas pelo prefixo fixo, e o token pelo valor que o
  `VisagismoController` ainda traz, que não pode aparecer em outro arquivo. Entrou também no
  `conferir-tudo.sh`.
- **O `pre-push` confere cada commit que o push leva**, e não só o HEAD: o literal posto num commit e
  tirado no seguinte não passa mais.
- **O `verificar-local-php.php` conhece as duas chaves** (e as três do Yungas): faltando, aparecem como
  `grave`.

### O que a produção precisa, na migração final

O `local.php` de produção precisa de `asaas.api_key` -- a chave **rotacionada** (D3) -- e de
`cron.token` com **o mesmo valor que o crontab manda**: o de hoje, ou o novo, se a linha 9 do passo 5
do dia 0 já o rotacionou (crontab e `local.php` no mesmo passo). Sem a chave, as rotinas de faturas
lançam e nenhuma fatura sai; sem o token, as 26 rotinas param **em silêncio**. Está escrito no passo 3
do dia 0 e no passo 7 do corte.

### Registrado

- **D329** -- o passo 7 do corte troca o `local.php` de produção pelo modelo de desenvolvimento do
  PostgreSQL: liga o rastro de pilha e perde as credenciais. O conserto proposto é só de documento.
- **D330** -- o `conferir` do deploy de teste não exige a chave do Asaas nem o token vazios, e três
  comentários do ambiente de teste ainda os dão como escritos no código. Os arquivos estão congelados
  enquanto a cópia sobe no BVIP.

### Uma pergunta nova

O token do visagismo, que fica no código por sua decisão, **tem o mesmo valor do token das rotinas**.
Recomendo deixá-lo e dar ao `cron.token` de produção um valor novo na rotação; as opções estão no lote.
A rotação do token das rotinas já entrou no dia 0 como **linha 9 do passo 5** -- o valor está também
no histórico do git, então ela vale em qualquer das três opções; se preferir não fazê-la, é tirar a
linha.

### Provado

- `SegredosForaDoCodigoTest`, 11 casos nos dois bancos; com os vizinhos do `CronJobService`, do
  controlador e do `FranquiaService`, 224 casos (1 pulado, declarado), iguais nos dois -- rodados de
  novo depois que a máquina reiniciou sozinha no meio deste trabalho, e depois da revisão. O controlador
  nasce pela fábrica de verdade com espiões no lugar do serviço: nenhuma rotina de verdade roda, nem
  sob mutante, e as de faturas não são chamadas em caso nenhum.
- Doze mutantes no código, todos mortos -- cinco deles da revisão: o valor-padrão na fábrica (`??` e
  `?:`), que os casos de antes deixavam passar, o token só de espaços, e o verificador sem o `trim` e
  sem o `cron.token`; o portão de segredos falsificado devolvendo cada literal e arrancando cada peça
  dele, e a busca pelo valor sobre 13 formas inventadas (0 de 13 só pelo nome, 13 de 13 com ela); o
  `pre-push` novo num repositório de rascunho (o literal posto e tirado passava, e agora bloqueia); o
  `conferir-resposta-cron.php`, que a mudança deixava cego e calado, ensinado e falsificado; o
  verificador falsificado com as chaves vazias.
- Os portões do `conferir-tudo.sh`, com o vermelho conhecido de sempre (D50).

### O que não foi provado

- As rotinas de faturas com o `local.php` sem chave: a prova para no método que constrói o cliente,
  porque essas rotinas não se rodam aqui.
- O passo do CI que sorteia o token: o CI está parado desde 13/09; o passo foi simulado numa cópia.
- O medidor das rotinas e o `medir-telas-no-postgres.sh` contra a aplicação: não rodei, porque o
  medidor chama `relatoriosRoyaltiesAtualizar`. A revisão achou que o medir-telas morria no passo do
  cron (nenhum `local.php` dele tinha o token) e que o medidor saía 0 com o token errado; os dois
  consertos foram provados contra um dublê da aplicação, dentro do contêiner.
- O valor do literal saiu da árvore, não do histórico do git: a rotação é sua (D3).
- As rotinas por HTTP, na aplicação de pé: de propósito. Com a guarda caída, o pedido rodaria a rotina
  no banco de desenvolvimento; a prova é o controlador da fábrica de verdade, despachado em processo.
- Ficou de fora (D330): o `conferir` do `scripts/deploy-teste.sh` não exige a chave e o token vazios, e
  o cabeçalho dele, o `docker-compose.teste.yml` e o `teste-host.conf.modelo` ainda dizem que o código
  os carrega. Os três estão congelados enquanto a cópia do teste sobe no BVIP; o `ambiente-teste.md`
  já diz o que mudou.

## 03/10/2026 -- D4: as rotinas por URL respondem 401 sem o token certo, e aceitam o token pelo cabeçalho

Um dos débitos que não precisam de você, da lista de 03/10. É o passo 7 do dia 0, pronto para a
migração final.

### O que mudou

- **A rotina que recusa o token responde 401**, em JSON, e não mais `{"sucesso": true}`. Até hoje um
  crontab com o token errado -- ou um `local.php` sem `cron.token` -- parava as 26 rotinas
  (royalties, dashboards, NPS, envios) e cada chamada seguia respondendo sucesso. Agora o log de
  acesso do nginx mostra o 401. **O sucesso não mudou.**
- **O token vale pelo cabeçalho** `Authorization: Bearer <token>`, o mesmo que o `/api/v4` usa, **e
  continua valendo pela query** (`?token=`), que é o que o crontab de produção manda hoje: nada no
  crontab precisa mudar para o corte.
- **A guarda é a primeira coisa de cada rotina**, e o portão `conferir-resposta-cron.php` passou a
  exigir isso de todas -- a lista das 26 que podiam responder sucesso foi a zero, e uma rotina nova
  sem guarda nenhuma, que antes passava, agora reprova.
- O medidor das rotinas manda o token no cabeçalho e sai 2 no primeiro 401; o `coletar-producao.sh`
  redige também o `Bearer`; o passo 7 do dia 0 e os passos 7 e 9 do corte dizem o que conferir.

### Duas perguntas novas, no lote

- **O crontab de produção**: deixar como está (token na query, resposta ignorada), ou trocar as 25
  linhas no corte por `curl -fsS -H "Authorization: Bearer ..."` -- o token sai da URL e do log, e o
  cron passa a avisar do 401 (D331). Recomendo trocar, junto com a rotação do token.
- **As três exportações de vendas por hash** (`unidadeVendas*`) seguem respondendo "Acesso negado."
  com 200; passá-las a 401 muda o contrato com quem consome o faturamento (D9). Recomendo esperar o
  inventário do D9.

### Provado

- `SegredosForaDoCodigoTest`, 16 casos e 5.016 asserções, iguais nos dois bancos: as 26 rotinas,
  despachadas pela fábrica de verdade contra espiões, recusam com 401 em dez formas de não trazer o
  token certo e com o token do servidor vazio, sem rodar nada; o `Bearer` certo abre e tem
  precedência sobre a query; a query certa segue abrindo.
- Oito mutantes no controlador, todos mortos, um de cada vez e restaurados; as rotinas que cobram ou
  enviam ficam por último no teste e não foram despachadas por mutante nenhum.
- O portão falsificado com dez variantes do controlador; a leitura sem comentários é carregante.
- O medidor contra um dublê da aplicação: com o token certo, 0 de 20 pedidos com o token na URL (o
  de antes: 20 de 20); uma rotina recusada no meio sai 2 (o de antes saía 0).
- Por HTTP, na aplicação de pé, só sem token ou com token errado: 401 em JSON com
  `WWW-Authenticate`, também com o cabeçalho ilegível; nenhuma rotina escreveu no log.
- Os portões do `conferir-tudo.sh`, com o vermelho conhecido de sempre (D50).

### O que não foi provado

- A rotina aberta pelo cabeçalho **por HTTP**: de propósito -- o pedido com o token certo roda a
  rotina. A abertura está provada em processo; que o nginx e o `php -S` do CI entregam o
  `Authorization` ao PHP já era provado pelo `ApiV4Test`, e o `php -S` foi medido.
- O medidor contra as rotinas de verdade: ele chama `relatoriosRoyaltiesAtualizar`, que não se roda
  aqui; foi provado contra o dublê. O CI segue parado desde 13/09.
- A produção: nada foi aplicado lá, por decisão sua de 11/09.

### A revisão, no mesmo dia: sete achados, todos consertados

Uma revisão adversarial achou sete coisas verdadeiras. Nenhuma abria rotina a quem não tinha o
token; duas eram a conferência do corte dizendo "tudo bem" com as rotinas paradas.

- **A conta de 401 no log, do passo 9 do corte e do passo 7 do dia 0, dava 0 com as 25 rotinas
  recusadas**: lia só o `access.log`, e o site tem log próprio por vhost (D36). Agora o passo 9 marca
  os logs antes de religar o cron e, 10 minutos depois, mostra o código HTTP de cada pedido do
  crontab em todos os logs: tem de mostrar só 200, e mais de zero; nada impresso quer dizer "não
  siga". Ensaiada com logs inventados.
- **`Bearer` vazio caía para a query**, contra o "uma credencial por pedido". Agora o esquema é a
  primeira palavra do cabeçalho, e `Bearer` sem valor recusa.
- **O portão das rotinas não via ação declarada fora do molde** (`static`, `final`, sem visibilidade)
  **e isentava quem só citasse a guarda da unidade.** Agora lê os métodos pelo tokenizador; das 20
  variantes da falsificação, o portão de antes deixava passar 9.
- **A página `/api/docs/cronjobs` não falava do cabeçalho nem do 401**: agora fala, nas 26.
- **A abertura com o token certo só era provada numa rotina**: agora nas 22 que não cobram nem
  enviam, pela query e pelo cabeçalho.
- Uma frase do D4 que dava a falta de `cron.token` no ambiente como proteção estava invertida, e foi
  reescrita.

**Provado:** 18 casos e 6.256 asserções no `SegredosForaDoCodigoTest`, e 225 casos com os vizinhos,
iguais nos dois bancos; seis mutantes novos, todos mortos.

**Achado de passagem: o D332.** A apuração do incidente LGPD (passo 2 do dia 0 e
`remediacao-dia0.sh`) procura só no `access.log*`, e escreve "nenhum acesso externo" quando não
acha nada. Com o log do site em outro arquivo, esse "nenhum" pode ser o arquivo errado -- e é ele
que sustenta não notificar a ANPD. Ficou registrado, sem conserto: está fora deste item.

## 03/10/2026 -- SPECS-NO-REPO: os desenhos dos relatórios e das telas que faltam saíram do rascunho para o repositório, e os defeitos graves deles ganharam número (D333 a D377)

**O que muda para quem usa:** nada na tela -- é documentação. Muda onde estão as coisas. Os desenhos
que sustentam as suas respostas de 01/10 e o plano das próximas telas viviam no rascunho de uma
sessão, e sumiriam com ela. Agora estão no repositório:

- `docs/f3-relatorios/`: as onze especificações dos relatórios, a ordem final (com a fatia do PDV, que
  espera a sua autorização para as cópias locais), as perguntas de 01/10 como foram feitas, o juiz da
  F4 e o oráculo da F4.3;
- `docs/f3-telas-restantes/`: os catorze desenhos das telas que ainda abrem a `TelaNaoMigrada` e o
  consolidado, com as perguntas P1 a P16 -- ainda sem resposta. **A P1 tem prazo:** se houver sorteio
  de Dia das Crianças em 2026, os quatro de antes começaram entre 01 e 07/10, e o cadastrado depois do
  início perde as vendas dos dias anteriores (D371); criar é escrita na produção, e por isso foi ao
  seu lote de perguntas;
- um `README.md` em cada pasta diz o que é cada arquivo, como ler as citações e o que ficou no
  rascunho, e o das telas traz a tabela de cada rótulo provisório para o número do débito.

As suas respostas de 01/10 continuam num lugar só, a seção 6 de `docs/f3-relatorios.md`, que passou a
apontar para as pastas.

**45 débitos novos, do D333 ao D377:** os defeitos graves que os desenhos acharam e que não tinham
número, cada um com a consulta ou o arquivo e a linha que o mediu e a fatia que o conserta. Os que mais
pesam: a API de unidades que grava 35% no lugar de 3,5% de royalty e apaga o ID da Yungas (D333); um
GET que tira uma funcionalidade de 177 pessoas (D335); a etapa 5 da adesão, que se desliga ou trava para
a rede inteira (D338 e D339); 194 pessoas abrindo assistentes de fora do seu grupo (D340); quem digita
o login com maiúscula não entra no PostgreSQL -- risco do corte (D349); a versão do aplicativo que se
publica como 0.0.0 (D354); os arquivos da biblioteca entregues sem sessão (D355); e o mural da agenda,
impresso cru para cerca de 364 pessoas (D356). Os rótulos que colidiam entre desenhos -- o Dα dos
banners e o dos assistentes, os dois Dnovo-1 -- viraram números diferentes, e os que eram o mesmo
defeito visto de dois lados viraram um só. Os desenhos mandavam registrar os 160 rótulos de uma vez;
entraram com número os graves, os medidos que mexem com dinheiro, acesso, dado pessoal ou o corte, e --
juntos no D377 -- os que nenhuma fatia conserta. Os menores, cada um com a fatia que o conserta, ficam
listados nos desenhos e ganham número no commit dela: é escolha desta versão, e cada linha dos
desenhos que manda registrar ganhou uma nota que aponta para a tabela de rótulos.

**Provado:**

- cada um dos 45 conferido de pé no código de hoje, nas linhas citadas;
- os 31 arquivos copiados com o mesmo número de linhas; 25 linhas trocadas nos relatórios e 15 com
  nota nos desenhos, todas listadas nos dois `README.md` -- citações de arquivo que não vieram junto,
  dois comentários do oráculo que prometiam "nenhum nome" e "só ids e contagens", e as instruções de
  registro, que agora dizem o número que o rótulo já tem;
- nenhum CR; nenhum e-mail, CPF ou telefone (os 17 números com essa forma são ids e exemplos); e as
  duas credenciais tiradas do código hoje, procuradas pelo valor sem imprimi-lo, com controle positivo:
  zero;
- o oráculo da F4.3, rodado do lugar novo contra o código de hoje: 0 divergência na unidade 1 em julho,
  e 1 com a resposta perturbada;
- os portões de referências da documentação, débitos repetidos, links mortos, bytes de controle e
  segredos. O de bytes de controle lê só o que o git já versiona: os 33 arquivos novos foram
  conferidos à parte, pela mesma expressão -- nenhum.

**Não provado:** o portão de referências lê só `docs/*.md`, e não as pastas novas -- de propósito: os
desenhos citam 217 vezes arquivos que as fatias vão criar. E o oráculo foi rodado só no MySQL e num
recorte.

**Na verificação, no mesmo dia,** sete achados, todos de documentação:

- **doze débitos a mais, do D366 ao D377**, que os desenhos mediram e mandavam registrar: o
  faturamento corrigido que nunca chega ao Asaas (D366); a unidade desativada que some do relatório
  de royalties e pula a fatura (D367); a taxa de royalty sem vigência (D368); o CPF do sócio sem uso
  (D369); o cupom de sorteio em dobro (D370) e o sorteio tardio que perde vendas (D371); as 191
  conclusões repetidas da VIP Class (D372) e a nota 100 do curso sem pergunta (D373); o molde de
  privilégio repetido, que dá 500 no PostgreSQL (D374); a sangria desfeita no financeiro e viva no
  caixa, R$ 5.635,00 (D375); o furo da URL da Cota-parte antiga (D376); e os menores sem fatia (D377);
- **o D36 e o D57 ganharam uma nota de correção:** sem o `app.json`, as duas telas dão 404, e ninguém
  o refaz pela tela (o D354 ao lado); e o vínculo vazio dos assistentes quer dizer "todas as unidades",
  e não "desligado". O resto dessa correção de texto -- o `docs/roadmap.md` antigo e o
  `docs/funcionalidades-sem-uso.md` -- continua no D343;
- **as instruções de registro** das especificações e dos desenhos -- 23 linhas -- passaram a dizer o
  número que o rótulo já tem, para ninguém o registrar de novo;
- **o `README.md` dos relatórios** diz quanto a seção 6 do `f3-relatorios.md` andou desde que as
  especificações a citaram (864 linhas), e que `f3-relatorios.md` solto é o de `docs/`;
- **o comentário do oráculo** deixou de prometer "só ids e contagens": quando um motivo diverge, ele
  imprime o texto dos motivos, que na cópia de desenvolvimento traz nome de funcionário. Medido com
  um motivo-marcador: a linha da divergência saiu com o texto;
- **a P1** foi ao seu lote de perguntas, com o prazo.

**Provado na verificação:** um roteiro com 64 casos, um por afirmação dos sete achados, saiu 64
vermelhos antes e 64 verdes depois; oito mutantes -- um por achado, desfeito numa cópia dos
documentos, e o lote sem a P1 -- deixaram vermelhos só os casos do achado deles; os títulos novos
passaram pelo simulador da regra de repetidos e pelo portão; e os cinco portões da documentação saíram
com 0. Nenhum código, nenhuma suíte.

## 03/10/2026 -- CORTE-ROTEIRO: o passo 0 do corte ganhou as migrações que faltavam e um portão, o passo 8 deixou de mandar rodar a suíte na produção, e os logins com maiúscula têm conta certa (D323, D378, D379)

Um dos débitos que não precisam de você, da lista de 03/10. Nada rodou em produção: é o roteiro, e
as migrações que você aplica no corte.

### O que mudou

- **O passo 0 do corte passou de três linhas a nove** (`docs/procedimento-de-corte.md`, "O que hoje
  bloqueia"). Três migrações novas, para o que entrou no `mysql.sql` depois do dump de 16/08 sem
  nenhuma: `clientes_sessoes` (o login do aplicativo por código), `pontuacoes_historicos.autorizado_por`
  (o resgate autorizado pelo gerente) e as três `unidades.hash_anterior*`. Sem elas em produção antes
  do passo 1, **toda** leitura de unidade e do histórico de pontos falharia no PostgreSQL -- as
  entidades mapeiam as colunas. Entrou também a do Líder (D124), que nunca tinha aparecido no
  procedimento -- **e quando aplicá-la é pergunta sua** (abaixo). O laço aplica as seis de estrutura,
  com o aviso de que as duas com chave estrangeira copiam a tabela e pedem horário sem movimento.
  `sql/sessoes.mysql.sql` tem uma linha que diz **não aplicar**, e por quê (a revisão, abaixo).
- **Um portão novo, `conferir-passo-zero-do-corte.php`, no `conferir-tudo.sh`:** compara o `mysql.sql`
  de hoje com o do dump de produção e exige, para cada diferença, uma migração do laço do passo 0 que
  a faça; que o laço seja a lista das linhas "Aplicar"; que nada do laço leve ao `mysql.sql` o que o de
  hoje não tem; e que toda migração do repositório esteja citada.
- **O passo 8 não roda mais a suíte de caracterização na produção.** Ela exige as fixtures, e a
  mensagem com que para manda carregá-las -- seguida na janela, gravaria unidades, clientes e vendas
  inventados no MySQL de produção, a base da volta atrás. O aviso de **nunca** carregar fixtures na
  produção está lá; o que entra no lugar é pergunta sua (D378).
- **Os logins com maiúscula:** a consulta que o D349 mandava rodar dá **zero no MySQL** com qualquer
  número de maiúsculas -- a collation ignora a caixa. `scripts/medir-logins-com-maiuscula.sql` conta
  byte a byte, sem imprimir login nem e-mail, e traz comentado o `UPDATE` que baixa a caixa de quem
  não tem par; ele entra no passo 3 da janela, e só junto com o código do D349 -- que virou o 6 de "O
  que hoje bloqueia": sem ele, o teste manual do passo 8 reprova por construção.
- **O 7c, a passada das tags e só depois a chave `busca.tags_sem_acento`** (D119), e de onde vem o
  dicionário de CEP (D120): do MySQL de produção como estiver, com as 709.440 linhas, a menos que você
  decida esvaziá-lo antes do dump.
- **O passo 4 roda os dois geradores**, e o `migrar-dados.sh` foi regerado: o do repositório era de
  27/08 e não copiava as colunas e tabelas novas (D379).
- Os números velhos do procedimento e do ensaio geral foram remedidos: 1.611 casos de caracterização
  (eram 109), 67 de contrato (eram 35), 143 colunas de data (133), 76 políticas de RLS (75), 86
  colunas `double` (85), seis provas dos papéis (o ensaio ainda dizia dezesseis).
- **Para o deploy de teste:** o próximo `atualizar` vai recusar por causa das três migrações novas, e
  `ACEITAR_ESQUEMA=sim` é o certo, sem ALTER nenhum -- a base do teste veio da `franquiabv_dev`, que
  já tem a tabela e as colunas (`docs/ambiente-teste.md`).

### A revisão do mesmo dia: o laço tinha a `sessoes`, e ela parava a janela no passo 4

Uma revisão adversarial confirmou dez achados (dois eram o mesmo); todos conferidos no código e
consertados, menos uma escolha que é sua.

- **O mais grave:** com `sql/sessoes.mysql.sql` aplicada em produção, o passo 1 levaria a tabela ao
  `mysql.sql`, o gerador a emitiria duas vezes -- a segunda com RLS, que impede o login --, e o `psql`
  do passo 4 pararia em "already exists", **com a aplicação já parada**. Medido num banco descartável.
  Agora a linha diz "não aplicar", a conferência da carga aceita a `sessoes` só no PostgreSQL (é ele
  que a cria), e o gerador não a emite mais duas vezes, mesmo que ela venha no dump.
- **O portão tinha três furos, e os três davam verde sobre defeito:** lia como texto o comentário
  escrito dentro do `ALTER` entre aspas (que o MySQL ignora ao executar), conferia índice e chave
  estrangeira só pelo nome, e não lia o laço que o operador roda. Os três fechados; 17 mutantes novos,
  todos verdes no portão de antes, saem vermelhos no novo.
- **O Líder (D124):** aplicado antes da janela, quem o lê é o código antigo, e nele o papel de gerente
  autoriza fiado sem olhar a unidade, devolve crédito de cliente e cancela venda -- não só o relatório.
  Saiu do laço até a sua resposta (a quarta pergunta, abaixo).
- **O passo 8 voltou a ter regra de falha** ("qualquer falha no teste manual é volta atrás"), e o
  código do D349 entrou em "O que hoje bloqueia".
- **Correções de texto:** a `iframe_bi` **é** copiada pela carga (inofensiva); a produção é MySQL
  8.4.7, e não 8.0 como esta entrada dizia; a cópia do desenvolvimento tem 5 logradouros, e não 3.

### Quatro perguntas novas, no lote

- O que entra no lugar da suíte no passo 8 (e nas duas fases do ensaio geral).
- O que fazer com os logins e e-mails que só diferem pela caixa (D31, D295).
- Esvaziar ou não o dicionário de CEP em produção antes do dump.
- **Quando aplicar a migração do Líder:** antes da janela, com o código antigo no ar, ou no passo 3
  da janela (a da revisão).

### Provado

- Num MySQL descartável montado do dump de produção, as seis migrações do laço aplicadas **duas
  vezes**, no **8.4** (a versão da produção) e no **8.0.46** (a das réplicas do BVIP, num contêiner
  descartável sem rede): a segunda não muda nada, e depois delas a estrutura -- colunas na ordem,
  tipos, padrões, comentários, índices, chaves estrangeiras, motores -- é idêntica à de um banco montado
  do `mysql.sql` de hoje, menos a `iframe_bi` (1.390 linhas de retrato). A do Líder, à parte: `gerente =
  1`, e nada na segunda vez.
- O passo 1 e o passo 5, simulados: o schema gerado daquela estrutura tem as mesmas linhas que o
  `postgres/schema.sql`, menos a `iframe_bi`; num PostgreSQL descartável, a conferência da carga
  antiga reprovou com as cinco colunas de `sessoes`, e a nova passou as seis provas. Com a `sessoes`
  também no MySQL: o gerador antigo fez o `psql` parar; o novo deu o mesmo schema, byte a byte, e as
  seis provas passaram.
- O `UPDATE` dos logins num cenário inventado, no 8.4 e no 8.0.46: 3 logins e 1 e-mail na primeira
  vez, nada na segunda; o par, o de acento e o e-mail com par ficaram como estavam. A consulta do D349,
  no mesmo banco: 0.
- O portão com 39 mutantes, cada um numa cópia da árvore (nada no repositório foi mexido): dos 17
  novos, 15 saem com 1 e 2 (o laço ilegível) com 2; os 16 da primeira rodada, com 1; 4 "não consegui
  medir", com 2; e os dois controles, com 0.
- Os bancos descartáveis e o contêiner do 8.0 foram apagados no fim, e conferidos.

### O que não foi provado

- O tempo do `ALTER` das chaves estrangeiras sobre milhões de linhas: os bancos do ensaio eram
  vazios.
- A carga regerada: `migrar-dados.sh` não roda aqui. Pela leitura dele, uma coluna que falte no MySQL
  vira uma tabela carregada vazia sem erro, e só a primeira prova da conferência acusa (D379).
- O tempo da passada das tags (o 7c).

## 03/10/2026 -- PDV-D317-D320: o cancelamento não recancela, o fiado da API não se autoriza em comanda fechada, o "Quitar" soma o devido, e a venda que não existe não derruba o finalizar

### O que muda para quem usa

- **Cancelar venda (ERP):** só a venda concluída e a comanda aberta -- os dois lugares onde a tela
  mostra o botão. A já cancelada responde *"Esta venda já está cancelada."*, e o estoque, as sessões
  de pacote e o crédito do cliente não voltam de novo; a venda em andamento não se cancela (D317).
- **Autorizar o fiado (API nova):** 409 fora da comanda aberta; quem autorizou de verdade fica (D318).
- **"Quitar" no PDV antigo:** o total cresce pelos débitos abertos do cliente na unidade, calculados
  no servidor -- as mesmas linhas que o finalizar baixa --, e não pelo número do pedido. Sem débito
  aberto, *"Este cliente não tem débitos nesta unidade."* Para quem usa a tela, nada muda: ela já
  mandava a soma que mostrava (D319).
- **Finalizar uma venda que não existe** responde *"Não foi possível localizar a venda."*, e não erro
  fatal (D320).

### Provado

- `D317A320RestosDoD316Test`, 19 casos em processo, nos dois bancos: cada recusa com o retrato do banco
  intacto (a venda, o estoque, as sessões do pacote, o crédito, o caixa, os débitos, os pontos), e os
  controles mostrando que o retrato mudaria. Um caso finaliza de verdade depois do "Quitar": o
  finalizar baixa exatamente as linhas somadas, e o débito da outra franquia fica aberto. O
  `D316EscritasEmVendaFechadaTest` ganhou o débito nos dois controles e nas duas recusas do "Quitar".
- Sete mutantes, um de cada vez, todos vermelhos no teste, e os quatro que o portão vigia também
  nele: as guardas do D317 e do D318 arrancadas, a do D317 aceitando o nulo, o total pelo valor do
  pedido, a soma sem a unidade, a recusa do zero arrancada e a frase do D320 de volta à lista (o erro
  fatal reapareceu). Restaurados, conferidos por sha1.
- **A revisão achou dois vãos nos testes, e eles foram fechados.** O caso da concordância lia as
  linhas pelo repositório em vez de finalizar, e o finalizar baixando sem a unidade deixava tudo verde.
  Agora ele finaliza, e esse mutante dá um vermelho. As recusas do "Quitar" na venda concluída e na
  cancelada não tinham débito: sem a guarda do D316, o D319 recusava com o total igual e só a frase
  ficava vermelha. Agora a guarda arrancada leva o total de 100,00 a 130,00, e o retrato acusa nas
  duas. Mais dois mutantes, restaurados e conferidos por sha1.
- A caracterização inteira nos dois bancos: 1.630 testes, 13 pulados, nenhum vermelho.
- `conferir-recorte-por-unidade.php` com 164 entradas (4 novas); `conferir-debitos-repetidos.php`
  com dois pares novos explicados (o radical de estado saiu dos títulos corrigidos);
  `conferir-cobertura-por-par.php` com o ISVenda de 100 para 101 chamadas (o par do devido já era
  vigiado, e o sha1 ficou); e o inventário de ações regerado (o `pagarDebitos` passou a ler
  `ClienteDebito`).

### O que ficou

- **D380, com pergunta no lote:** o "Quitar" não deixa marca na venda -- na comanda reaberta os
  débitos aparecem de novo e somam em dobro (ou ficam abertos com o cliente pagando), o duplo clique
  soma duas vezes, e o finalizar do ERP aceita `pagarDebitos` sem "Quitar" (a metade do D159).
- Não provado no navegador: as quatro mudanças foram provadas em processo, contra o banco de teste.

## 03/10/2026 -- TRAVA-POST-PAIS: a devolução de crédito confere o saldo sob trava, o "Autenticar como" só por POST, e o país acompanha o DDI na edição de clientes (D285 parcial, D233)

Três consertos pequenos, cada um com o molde já decidido, feitos um de cada vez.

### O que muda para quem usa

- **Devolver crédito (API e ERP, D285):** duas devoluções ao mesmo tempo -- dois balcões, duas abas
  -- já não passam as duas sobre o mesmo saldo: a segunda espera a primeira, relê o saldo e é
  recusada com a frase de sempre se ele não cobre mais. No ERP, devolver o saldo inteiro que a tela
  mostra passa a funcionar também quando o banco o guarda um fio abaixo (0,80 contra 0,7999...), como
  na API desde o D283. A chave de idempotência (o pedido repetido que cabe no saldo) continua
  decisão do dono.
- **"Autenticar como" e a volta (ERP e tela nova):** o cadeado da barra, o botão da lista de usuários
  e o "voltar a ser eu" da tela nova mandam POST; para quem clica, nada muda. Um link ou uma
  navegação vinda de outro site já não começa uma personificação nem troca de pessoa -- recebe 404 --,
  e o POST de outro site ou de outro subdomínio recebe 403 (a barreira de origem da API nova, na
  revisão). Também da revisão: um link de outro site para a página de entrada já não encerra a
  personificação. O D127 (a volta para outro id, a marca e o rastro) continua com o dono.
- **Editar cliente na tela nova (D233):** o formulário ganhou o "País", logo depois do telefone, e
  salvar grava o país junto com o DDI, como a tela antiga. O país é obrigatório na edição, como lá
  (revisão): o cliente gravado sem país escolhe um para salvar. O cadastro novo continua recebendo o
  país da unidade. O "último a salvar vence" continua nas decisões.

### Provado

- **D285:** `DevolucaoDeCreditoSobTravaTest`, 10 casos verdes no MySQL e no PostgreSQL das
  fixtures -- a trava do cliente antes da leitura do saldo, a espera pela trava de outra conexão, a
  releitura (inclusive com a foto do MySQL tirada antes, fora da transação do teste), o saldo exato
  com o ruído do `double`, e as duas portas (API e ERP) despachadas em processo. Sete mutantes, cada
  um vermelho nos dois bancos.
- **Personificação:** `D315PersonificacaoTest`, 32 execuções verdes nos dois bancos (o GET recusado
  na entrada e na volta, o POST aceito, e os casos do D315 e do D127 por POST; da revisão, o POST de
  outra origem recusado na entrada e na volta, o da mesma origem aceito, e o GET do `entrar` com o
  cookie do administrador sem encerrar a personificação). Com a guarda
  arrancada, os dois casos do GET ficam vermelhos nos dois bancos; o `conferir-remocao-por-post.php`
  sai 1 em cada um dos cinco mutantes (a guarda arrancada ou depois da porta, o cadeado de volta a
  link ou sem `method="post"`, o botão da lista de volta a link). No vitest, a volta por POST e a ida
  ao ERP por formulário POST, com os dois mutantes vermelhos. Da revisão, quatro mutantes vermelhos
  nos dois bancos: a ação sem a barreira de origem, a leitura dos cabeçalhos sempre deixando passar
  (pega também na barreira da API, que passou a ler pelo mesmo lugar), a leitura sem o `Origin`, e o
  `entrar` sem a guarda da personificação.
- **D233:** `CadastroDeClienteTest` com o `ClienteApiTest`, 62 execuções verdes nos dois bancos; no
  vitest, o corpo, a máscara, o seletor e, da revisão, o país obrigatório na edição. Seis mutantes no
  servidor e nove no front, vermelhos; e o catálogo sem `paises`, pego pelo caso 35p do ensaio de
  recusas.
- **Os vizinhos:** 431 testes de caracterização (crédito, fidelidade, caixa, fiado, sessão,
  personificação, usuários, clientes) verdes nos dois bancos; depois da revisão, a suíte inteira,
  1.660 execuções (13 puladas), verde nos dois bancos; o vitest inteiro, 1.560 casos em 42 arquivos
  (`--maxWorkers=4`); `npm run build` e `npm run lint` sem erro.
- **Os ensaios HTTP, nos dois bancos de contrato:** recusas 201 de 201 depois da revisão (o caso novo
  é o `PUT` com o país nulo), fluxo 22 passos, fidelidade 24 de 24, sessão do ERP 15 de 15; e, da
  revisão, usuários 19 de 19 e permissão 61 de 61 (a barreira de origem da API, que passou a ler os
  cabeçalhos por `OrigemDoPedido::recusaDoPedido`).
- **Os portões:** remoção por POST (com a ação em `ACOES_SO_POST`, uma linha por tela), guarda do
  ERP, recorte por unidade, autorização privilegiada, links mortos, débitos repetidos (com o par
  D381|D382 explicado, e na revisão os pares D381|D383 e D166|D383), referências, cobertura por par
  (que desceu de 152 para 151 na revisão: o `selecionarPorTokenWeb` ganhou chamada no teste do D315),
  rotas sem login, privilégio da API nova, cobertura do contrato e mais 24 estáticos, todos com saída
  0; o inventário de ações regerado (o `autenticarComo` passou a receber POST).

### O que ficou

- **D381 (novo):** a compra de crédito pelo ERP grava sem trava -- o conserto é chamar
  `comprarCreditoTravado`; o caixa de outro dia continua decisão (D280).
- **D382 (novo):** a devolução pelo ERP aceita valor negativo, que sobe o crédito (pede a senha do
  gerente; a API recusa).
- **D383 (novo, da revisão):** o finalizar do PDV (o crédito usado e o troco), a comissão de indicação
  e o cancelamento de venda regravam o saldo de crédito sem a trava do cliente, e uma devolução
  confirmada no meio deles é sobrescrita -- o caso mais comum é a venda paga com crédito ao mesmo
  tempo que uma devolução. Lido no código; o conserto é o molde do D285 em cada escrita.
- O "Sair" por GET é outro item (SESSAO-SENHA-EMAIL). As formas POST do ERP não levam token de
  formulário: a defesa contra outro subdomínio, que o `SameSite` não dá, é a barreira de origem. Se
  os botões do ERP ganham token também é pergunta no lote do dono, com a recomendação de não.
- Não visto no navegador: o cadeado, o botão da lista e o seletor de país. O que é em processo é a
  ação da personificação; as duas telas do ERP só são lidas pelo portão textual (o
  `conferir-remocao-por-post.php`), e o clique que envia o formulário escondido do cadeado não é
  exercido por nada. O seletor de país foi provado no vitest, e o `PUT` nos casos e nos ensaios
  HTTP.

## 03/10/2026 -- VENDAS-CAIXA-CUPOM: o crédito sem linha recusa em vez de quebrar, a retirada negativa recusa, a exportação respeita o desconto, o PDV e a agenda conferem o que faltava, e o cupom é da unidade (D166, D208, D230 parcial, D237, D275)

### O que muda para quem usa

- **Finalizar venda ou comanda paga em crédito, com cliente sem crédito na unidade (D166):** em vez de
  erro, a frase *"O cliente não tem crédito nesta unidade para o pagamento em crédito desta venda.
  Remova esse pagamento e escolha outra forma."* -- na tela antiga e na API nova (409). O caminho pela
  tela existia: abrir a comanda de outro cliente com uma venda em andamento leva os pagamentos dela
  (o D384, novo, com pergunta no lote).
- **Retirada de caixa negativa (o resto do D208):** recusada com *"- Valor"*. Antes, "-50,00" somava
  R$ 50,00 ao caixa e, com "Criar saída no financeiro", gravava a conta a pagar de -50,00. A volta do
  valor na máscara, de que o levantamento suspeitava, sai certa, e não mudou; o zero continua aceito.
- **Exportar as vendas realizadas em XLS (D230):** o "Com desconto: Sim/Não" vale nas três abas, como
  na lista. O filtro de colaborador continua fora da exportação (pergunta no lote).
- **Acrescentar item no PDV antigo com a venda de outra franquia (D237):** *"Não foi possível localizar
  a venda."*, sem gravar o cliente nela; antes, a recusa vinha em branco.
- **Marcar na agenda do ERP (D237):** o serviço passa pela regra da tela nova -- da unidade do
  colaborador, do tipo serviço e ativo. A lista da tela já só oferece esses, então nada que ela mostra
  é recusado (0 das 194.968 linhas de 2026 com serviço, na cópia); o id que não existe deixou de
  derrubar a ação.
- **Remarcar na agenda do ERP para outro colaborador (D237, achado na verificação):** o serviço do
  agendamento tem de ser da unidade do colaborador de destino, com a mesma frase do marcar. Pela tela
  nada muda -- o modal só lista os colaboradores da unidade do agendamento --; fecha o POST escrito à
  mão de quem tem "Agendas unidades". No mesmo colaborador, remarcar continua como era.
- **Cupom (D275):** o desconto do PDV e a queima no fechamento só acham cupom de campanha da unidade.
  Na cópia, nenhum cupom válido de outra unidade: nada muda para quem usa.

### Provado

- **Casos novos, nos dois bancos:** `D166CreditoSemLinhaTest` (6), `D208RetiradaNoFinanceiroTest` (6),
  `D237RestosDoPdvEDaAgendaTest` (11, 3 deles da verificação), 3 no `ListagemDeVendasTest` e 5 no
  `CupomQueimadoTest` (1 da verificação). Antes de cada conserto, os casos de recusa ficaram vermelhos
  (o D166 com o erro fatal; os três casos da máscara do D208 já eram verdes -- a prova de que ela sai
  certa). **Os dois casos da comanda herdada do D166 entraram depois da rodada contra o código
  antigo**, e a verificação os rodou à parte, com os três arquivos do D166 de antes, nos dois bancos:
  os cinco casos de recusa em erro fatal, o controle verde. Mudaram dois casos antigos: o de
  outra franquia do `D316EscritasEmVendaFechadaTest` (a recusa muda virou a frase) e o do
  `CupomTest` (a consulta pede a unidade).
- **Treze mutantes, um de cada vez, todos vermelhos**, restaurados e conferidos por sha1 -- cada
  recusa arrancada em cada porta do D166 (o serviço, a tela e a API) e do D208 (o serviço e a tela),
  a pergunta do D166 invertida (pega o controle),
  o filtro tirado de cada consulta da exportação e o zero do D229 trocado, a unidade do PDV e o serviço
  da agenda arrancados, a unidade da sessão no lugar da do colaborador, e o recorte do cupom. **Mais
  cinco da verificação**, nos dois bancos, cada um vermelho só no caso que lhe toca: a guarda da remarcação
  arrancada, sem o "só quando o colaborador muda", com a unidade do colaborador de antes, recusando
  toda troca de colaborador; e o `pesquisarCupom` na unidade zero (o caso antigo do PDV fica verde com
  ele, e o controle novo fica vermelho).
- **A caracterização inteira:** 1.691 testes (1.687 do conserto e 4 da verificação), 13 pulados, verde
  no MySQL e no PostgreSQL.
- **Os portões:** `conferir-tudo.sh` com o vermelho conhecido de sempre (o D50) e mais nada;
  `conferir-recorte-por-unidade.php` com 172 entradas (8 novas, uma delas da remarcação; nove
  falsificações arrancando o código, todas vermelhas); `conferir-debitos-repetidos.php` com os pares que
  os títulos novos desfizeram retirados e dois novos explicados (D126|D275 e D132|D385); `conferir-cobertura-por-par.php` com os pares sem teste de 151 para
  149 (as duas consultas da exportação, conferido trocando o nome no teste) e o ISVenda de 101 para
  104 chamadas (as três do D166, a pares já vigiados).
- **Os ensaios HTTP, no banco de contrato (MySQL):** cupons 59 de 59 (o caso 44, o desconto
  do PDV por `pesquisarCupom`, continua achando o cupom da unidade), fluxo 22 passos (a comanda
  finaliza), recusas 201 de 201 (a primeira rodada saiu 2 porque rodei o fluxo antes no mesmo banco, e
  ele deixa outro caixa aberto; remontado, verde).

### O que ficou

- **D230:** o filtro de colaborador na exportação -- pergunta no lote, com a recomendação.
- **D384 (novo):** abrir a comanda de um cliente com uma venda em andamento de outro leva os pagamentos
  dela; com o segundo cliente COM linha de crédito, o finalizar debita dele o crédito conferido para o
  primeiro. Pergunta no lote.
- **D237:** as três rotas antigas do aplicativo continuam com o dono.
- **D385 (novo, da verificação):** a outra sobra da revisão do D208 -- excluir pelo ERP uma parcela ou
  despesa já excluída regrava a data e diz sucesso (a API já recusa com 409). Técnico, com molde; não
  feito aqui, porque o item pedia só a retirada.
- **Não visto no navegador:** as frases novas do PDV, da retirada e da agenda foram provadas em
  processo, contra o banco de teste; a tela nova não mudou, e a frase do 409 da comanda não foi vista
  nela.

### A verificação, e o que ela mudou

Sete achados dados como confirmados, conferidos de novo no código e na cópia, um a um; seis se
sustentaram:

- **A remarcação do ERP** era a outra porta do `agendar` e não passava pela regra do serviço --
  consertada (acima), com 3 casos, 4 mutantes e uma entrada no portão de recorte.
- **O título do D166** dizia que o D160 fechou o caminho da API, e a seção nova provava o contrário;
  **o do D208** dizia "o resto da revisão" com uma sobra aberta -- os dois títulos corrigidos, e a
  sobra virou o **D385**.
- **Os dois casos da comanda herdada do D166** nunca tinham rodado contra o código antigo -- rodados,
  vermelhos nos dois bancos.
- **O `pesquisarCupom` sem controle positivo na suíte** -- o caso entrou, com o mutante que o prova.
- **O denominador do D237** contava 131.447 horários fechados como marcações -- o registro passou a
  dizer 0 de 194.968 linhas com serviço.
- **Um achado não se sustentou e não mexeu em nada:** a "retirada com o pagamento gravado duas vezes"
  do D208 (a 11906, de 2022) tem um pagamento estornado (situação 0) e o outro ativo; nenhuma das 8.187
  retiradas tem mais de um pagamento ativo, e a soma dos ativos bate com a retirada em todas as que têm
  um (8.170; as outras 17 são os estornos que o registro já conta).

## 03/10/2026 -- SESSAO-SENHA-EMAIL: o "Sair" só por POST, a senha trocada pela própria pessoa derruba as outras sessões, a trava de si mesmo confere quem entrou, a janela de privilégios só da mesma origem, o reset na ordem certa e os e-mails em UTF-8 (D315, D347 parcial, D198, D172)

Sete consertos pequenos, sem decisão nova, cada um com o seu caso e o seu mutante.

### O que muda para quem usa

- **"Sair" (ERP):** o item do menu do usuário envia um formulário POST; para quem clica, nada muda. Um
  link ou uma imagem de outro site já não desloga ninguém (404), e o POST de outro subdomínio recebe
  403. A tela nova não tem "Sair".
- **Trocar a própria senha:** em "Meus dados", quem troca continua logado, e qualquer outra sessão com
  o login antigo -- inclusive a aberta pelo cookie de entrada automática -- cai no pedido seguinte, com
  "sua conta está sendo usada em outro local"; o cookie velho não entra mais (o deste aparelho também:
  quando a sessão expirar, entra-se com a senha nova). Pelo link de "Esqueci a senha", toda sessão
  aberta cai com "Atualizamos o sistema" -- menos a da própria pessoa no navegador que usou o link,
  que segue (a revisão, abaixo). A própria senha trocada na própria ficha ("Editar", aba "Senha")
  vale como "Meus dados" (a revisão). A senha que o gerente define para outra pessoa continua como
  era: é a P7.
- **Personificação:** quem entrou já não desativa a si mesmo assumindo outro administrador -- nem pelo
  botão da lista, nem pela edição, nem pela tela nova, que deixa de oferecer o botão.
- **Janela de privilégios (ERP):** o pedido de outro site ou de outro subdomínio recebe 403 sem gravar
  nem tirar nada.
- **"Esqueci a senha":** com o servidor de e-mail fora do ar, o link que a pessoa já tinha continua
  valendo (antes morria, e o novo não chegava).
- **E-mails (reset e SAC do aplicativo):** saem em UTF-8 e o dizem; o emoji do cliente chega como foi
  escrito (antes virava "?"). O SAC que falha responde com o CORS, e o aplicativo vê um erro, e não uma
  falha de rede.
- **Código morto:** saiu o `UsuarioService::autenticarComo`, sem chamador, que mandava a senha decifrada
  do `salt` como credencial; o docblock de `UsuarioJson` diz a razão do D78.

### Provado

- **A suíte inteira, 1.712 execuções (13 puladas), verde nos dois bancos** (MySQL das fixtures e
  PostgreSQL). As novas: `SenhaTrocadaTest` (4), `EmailsDoSistemaTest` (6), seis no
  `D315PersonificacaoTest` (o "Sair" por GET, por três origens de fora e pela mesma origem; a
  personificação de verdade recusando a desativação de quem entrou no botão e na API, e o `proprio`),
  uma no `UsuarioAtivacaoTest` (a edição) e quatro no `PrivilegiosPelosControllersTest` (as três
  origens de fora na janela, salvar e remover, e o controle da mesma origem).
- **21 mutantes do código, um de cada vez, todos vermelhos nos dois bancos, e com os mesmos casos:**
  o "Sair" sem a guarda de POST e sem a barreira de origem; `ehASiMesmo` só pelo `usuarioId`, e o
  botão, a edição, a API e o `proprio` cada um de volta ao `usuarioId`; a janela sem a barreira no
  salvar e no remover; "Meus dados" sem girar o token, sem regravar a sessão e regravando mesmo
  personificando; o link sem apagar o token; o reset gravando antes de enviar e sem o `refresh`; o
  CORS do SAC de volta ao fim; o `Mail` sem o charset; o texto do reset, o `<meta>` e a mensagem do SAC
  de volta a Windows-1252; o nome do reset sem escape. Os arquivos voltaram, conferidos pelo sha1.
- **O portão de remoção por POST** com o "Sair" em `ACOES_SO_POST` (a quinta coluna, para a rota
  chamada pelo nome): saída 0, e 1 em cada um dos quatro mutantes (sem a guarda, a guarda depois da
  barreira, o "Sair" de volta a link, o formulário sem `method="post"`).
- **O contrato do aplicativo, 67 de 67** no banco de contrato MySQL; e o SAC sem campos, por HTTP no
  ambiente local, segue respondendo 200 com `Access-Control-Allow-Origin: *` uma vez só (agora pela
  resposta do Laminas).
- **Os portões (`conferir-tudo.sh`)**: todos com saída 0, fora o vermelho conhecido do agendamento (D50).
  Dois pediram ajuste, os dois por causa do conserto: a cobertura por par (a contagem do
  `ISConfiguracao` desceu de 145 para 144 com o `autenticarComo` apagado -- uma chamada a
  `selecionarMenu`, par que segue chamado) e o inventário de ações, regerado (o `sair` passou a
  receber POST).
- **Os ensaios HTTP, nos dois bancos de contrato (MySQL e PostgreSQL):** sessão do ERP 17 de 17 (o 16:
  o GET é 404 e a API segue 200, o POST de outro subdomínio é 403 e a API segue 200, o da mesma origem
  é 302 e a API passa a 401; o 17: o cookie abre uma segunda sessão com o mesmo token, a troca em "Meus
  dados" na primeira a mantém e derruba a segunda, o cookie velho não entra, e a senha do contrato
  volta), busca de cliente da agenda 17 de 17 (que passou a sair por POST), usuários 19 de 19, rotas
  públicas 23. **Falsificados por HTTP**, um de cada vez no MySQL: sem a guarda de POST, o GET do
  "Sair" desloga e só o 16 fica vermelho; sem girar o token, a segunda sessão segue e o cookie velho
  entra, e só o 17 fica vermelho.
- **O reset no Mailpit, pelos bytes**, ao colaborador de fixture (`colab@teste.invalid`), nos dois
  bancos de contrato: a parte diz `charset=UTF-8` (transferência `8bit`), o `<meta>` diz `utf-8`, "solicitação"
  chega como `73 6f 6c 69 63 69 74 61 c3 a7 c3 a3 6f`, nenhum byte de Windows-1252, o corpo inteiro é
  UTF-8 válido, e o HTML que o Mailpit decodificou traz o texto certo. A caixa e o token do link
  voltaram ao que eram.

### O que ficou

- **D347, PARCIAL:** a senha que o gerente define para outra pessoa (`editarSenha`) troca a senha e
  deixa o token -- é a P7; e a sessão sem token do caminho do D127 não é derrubada pela troca.
- **D386 (novo):** a trava irmã do D288 -- ninguém altera os próprios privilégios nem o próprio grupo
  -- compara só com quem a sessão mostra; personificando um administrador, quem entrou altera os
  seus. O conserto é o mesmo `ehASiMesmo`, nas quatro portas.
- O SMTP caído continua virando 500 no reset e no SAC, e o envio continua síncrono (o D172).
- **Não visto no navegador:** o clique no "Sair" que envia o formulário escondido; a tela só é lida
  pelo portão textual, e o POST da mesma origem é o do ensaio. O SAC não foi mandado ao Mailpit (o
  destino dele é um endereço de verdade): os bytes dele foram medidos em processo, com o SMTP dublê.

### A revisão do mesmo item (04/10/2026)

Oito achados confirmados pela verificação, seis distintos (dois pares eram o mesmo). Nenhum pediu
decisão do dono.

- **A própria senha na própria ficha (D347):** quem pode editar usuários abria a aba "Senha" da
  própria ficha e trocava a senha sem girar o token -- uma terceira porta da própria pessoa, que a P7
  (a senha de OUTRA pessoa) não cobre. Agora, quando o alvo é quem entrou (`loginDaSessao`), a ficha
  gira o token e regrava a sessão, como "Meus dados"; a senha da pessoa assumida, personificando,
  continua sendo a P7.
- **O link de "Esqueci a senha" com a própria pessoa logada (D347):** derrubava também a sessão de quem
  trocou, no mesmo navegador. Agora o token gira e essa sessão o regrava; sem sessão, ou com outra
  pessoa logada (inclusive o administrador que personifica a pessoa do link), o token sai, como antes.
- **O 500 do SAC (D172):** o comentário e o registro diziam `ExceptionStrategy` e corpo HTML; quem
  responde é o `ErroJsonListener` do `ISApi`, em JSON, na mesma resposta que o controller recebeu. O
  texto foi corrigido, e um caso novo passa a exceção pelo `dispatch.error` da aplicação montada.
- **A janela de privilégios (D198):** "403, no JSON que a janela já lê" era falso -- o jQuery da janela
  trata o 403 no `error` e mostra a frase genérica. Nada muda para quem usa (a janela é da mesma origem);
  o docblock e o registro foram corrigidos.
- **O D315 se contradizia:** duas frases antigas e um docblock de teste ainda diziam que o "Sair" por
  GET continuava -- riscadas, com a nota.
- **Referências `arquivo:linha`:** as que o item deslocou foram realinhadas pelo conteúdo (o `difflib`
  entre o HEAD e a árvore, só nos blocos iguais): 94 com nome de arquivo e 36 continuações (`:N`), em
  28 documentos; e à mão as que citavam linhas que o item mudou ou apagou (o `autenticarComo` do
  serviço, o `senhaConfirmar`, o `senhaRedefinir`, o `editarSenha`, o `mudarEstado` da API) e as do
  `podeMexerEm` e do `senhaRedefinirAction` que já estavam velhas no HEAD. As demais velhas de antes
  ficaram: medido, os três commits anteriores deixaram 88 de 128, 115 de 254 e 11 de 31 referências a
  arquivos que mudaram apontando para outro conteúdo -- a casa trata `arquivo:linha` como retrato.

**Provado:** a suíte inteira, **1.719 execuções (13 puladas), verde nos dois bancos**; as novas são
seis no `SenhaTrocadaTest` (a própria ficha; personificando, a própria ficha e a da pessoa assumida; o
link com a pessoa logada; e o link com outra pessoa logada, duas execuções) e uma no
`EmailsDoSistemaTest` (o 500 pelo `dispatch.error`). O `oCookieEntra` do `SenhaTrocadaTest` media mal:
perguntava `hasIdentity()`, que no primeiro login pelo cookie do processo lê um armazenamento vazio, e
o "cookie velho não entra" passava sem medir conforme a ordem; passou a ler o redirecionamento. **Onze
mutantes novos, um de cada vez, vermelhos nos dois bancos com os mesmos casos** (os arquivos voltaram,
conferidos pelo sha1): a ficha sem o ramo da própria pessoa, sem regravar a sessão, pelo `usuarioId` e
pelo `ehASiMesmo`; o link sem passar a pessoa logada, sem regravar a sessão, com o serviço sempre
apagando, com a pessoa sempre "logada" e pelo `usuarioId`; o CORS do SAC fora do começo; e o ouvinte
montando uma resposta nova (só o caso novo fica vermelho: o antigo não via o ouvinte). Por HTTP, nos
dois bancos de contrato, a sessão do ERP segue 17 de 17 e usuários 19 de 19 -- nenhum dos dois passa
pela ficha nem pelo link, então nada novo foi provado por HTTP: as duas portas consertadas são vistas
só em processo. Os portões (`conferir-tudo.sh`): 51 ok e o vermelho conhecido do agendamento (D50).

## 04/10/2026 -- ESTOQUE: a retirada que não cabe é recusada nas duas portas, e cancelar uma movimentação desfaz uma vez só (D126, D199, D387)

### O que muda para quem usa

- **A aba "Estoque" do ERP para de errar a conta da retirada.** Ela comparava o texto do campo
  ("10,00") com o número do estoque, e o PHP compara isso como texto: a retirada de 10 passava com 5
  no estoque, e a de 2 com 10 era recusada. Agora a conta é numérica, a mesma da tela nova, e a
  recusa diz o estoque de agora: *"Quantidade solicitada é maior que a estocada! Estoque atual 5
  unidade(s)."*
- **Dois usuários cancelando a mesma movimentação ao mesmo tempo não a desfazem duas vezes** — nem o
  ERP e a tela nova. O segundo espera o primeiro, vê a movimentação já cancelada e lê *"Esta
  movimentação já estava cancelada."* (na tela nova, o 409 de sempre). Duas abas do mesmo ERP já iam
  em fila pela sessão, e nisso nada muda: a segunda continua recebendo "não encontrado".
- **Duas retiradas de usuários diferentes ao mesmo tempo não passam do estoque** — na aba "Estoque",
  no modal "Retirada" e na tela nova: a conta é feita sob a trava do produto, com o estoque relido.
- **As três portas fazem a mesma conta.** O modal "Retirada" conferia em `double` e a tela nova
  truncava o saldo, cada um antes do serviço; agora quem decide é só o serviço, em centavos. Na
  prática ninguém nota: nenhum produto de estoque da cópia tem saldo fracionário.
- **A retirada de um produto de outra franquia responde "Produto não encontrado."**, e não erro 500.

### Provado

- `EstoqueSobTravaTest`, novo: **31 execuções, verdes no MySQL e no PostgreSQL**; sobre o código de
  antes, **27 vermelhas nos dois** (as 4 que passavam são os caminhos bons). Entre elas o caso do
  registro — 10 com 5 no estoque —, a trava presa por outra conexão, o saldo e a movimentação relidos
  depois dela, e o cancelamento fora da transação do teste, gravando de verdade, com a foto do MySQL
  tirada antes.
- **21 mutantes, um de cada vez, vermelhos nos dois bancos:** o piso arrancado, em `double`, sem tirar
  a máscara e com o próprio D126 de volta dentro do serviço; a trava sem reler, a releitura sem
  trava, o serviço sem transação própria; o cancelamento sem reler, relendo sem trava e sem conferir
  o estado; a quantidade negativa e a operação desconhecida aceitas; a retirada sem piso e fora da
  trava; e as portas (ERP e API) ignorando cada recusa, o ERP comparando texto antes de chamar, a
  retirada sem frase e com o nulo de volta.
- **A suíte inteira, 1.750 execuções (13 puladas), verde nos dois bancos**; os vizinhos (o recurso de
  produtos da API, o relatório de retiradas, o catálogo, as travas do caixa e do crédito, os
  privilégios) também numa rodada só, 142 verdes nos dois, depois do caso que grava de verdade.
- **Os portões (`conferir-tudo.sh`):** todos ok, e o vermelho conhecido do agendamento (D50). O de
  débitos repetidos pediu dois ajustes, feitos: os três pares do D126 saíram (o título mudou e eles
  caíram abaixo do limiar) e o par D383/D388 entrou, com o motivo.
- **Não provado por HTTP:** no medidor da API no PostgreSQL as duas rotas de estoque param na guarda
  (403: a sessão dele não tem `configuracoes/produtos`), e nenhum ensaio leva o lançamento ou o
  cancelamento até o serviço. A fronteira real da transação foi medida em processo, fora da
  transação do teste.

### O que ficou

- **Pergunta ao dono:** cancelar uma ENTRADA continua podendo deixar o estoque negativo (D199).
- **D388 (novo), duas perguntas ao dono:** a venda e o cancelamento da venda gravam o estoque sem a
  trava, e podem apagar uma movimentação que termine no meio do pedido; e a edição do produto regrava
  o saldo do momento em que a página abriu -- salvar só um preço, minutos depois, desfaz as
  movimentações feitas nesse meio-tempo.
- Não visto no navegador: a frase nova do ERP e a do modal foram lidas em processo.

### A revisão do conserto (mesmo dia)

A verificação adversarial achou sete pontos, todos de gravidade baixa; detalhe no fim do D126.

- **O piso tinha teto:** os centésimos em `(int)` davam a volta acima do inteiro de 64 bits, e uma
  saída gigante escrita à mão ("92.233.720.368.547.758,08", "1e400") passava com 5 no estoque. Agora
  a conta fica em `float` arredondado, e a quantidade tem de ser finita.
- **As três portas fazem a mesma conta:** o modal "Retirada" (em `double`) e a tela nova (com o saldo
  truncado) conferiam antes do serviço e às vezes recusavam o que ele aceita. As duas conferências
  saíram.
- **Casos novos:** `EstoqueSobTravaTest` passa a **39 execuções, verdes nos dois bancos** — as saídas
  gigantes, a entrada infinita, o modal e a API tirando o saldo que a conta do serviço cobre, e a
  frase da aba "Estoque" com o saldo relido. O caso da trava na retirada passou a ter o saldo na
  memória, e o mutante "retirada fora da trava" fica vermelho pelo motivo certo.
- **26 mutantes, um de cada vez, vermelhos nos dois bancos** (os 21 de antes e 5 novos). Vizinhos,
  numa rodada só: **150 verdes nos dois**. **Suíte inteira: 1.758 execuções (13 puladas), verde no
  MySQL e no PostgreSQL.** Portões (`conferir-tudo.sh`): 51 ok e o vermelho conhecido do agendamento
  (D50).
- **Textos corrigidos:** a frase nova do cancelamento aparece entre sessões diferentes, e não entre
  duas abas do mesmo ERP (a sessão já as punha em fila); o D388 separa a venda (corrida dentro do
  pedido) da edição do produto (formulário velho, sem corrida), e o lote de perguntas também.

## 04/10/2026 -- FINANCEIRO-S1: os dez consertos da S1 de Contas a Pagar, e a despesa sob trava (D357, D358, D361 e D362 parciais, D385, D389 a D392; os restos do D199, do D205 e do D207)

### O que muda para quem usa

- **Na tela antiga de Contas a Pagar:**
  - **"Situação: Aberta" filtra.** Antes a lista vinha com as quitadas junto, porque o `0` do
    `<select>` era "vazio" para o PHP; e a página 2 perdia o filtro.
  - **Um filtro estranho na URL é ignorado**, e não devolve a lista vazia (nem erro, no PostgreSQL).
  - **A descrição de mais de 100 caracteres como fica gravada** (o sistema põe um espaço depois de
    cada ponto) é recusada no formulário, com o motivo, em vez de cair com erro ao salvar; e o tipo
    precisa ser um da lista.
  - **Excluir de novo** uma parcela ou uma despesa já excluída diz *"já estava excluída"*, em vez de
    "excluída com sucesso" com a data regravada.
  - **Excluir parcelas em lote** recarrega a tabela quando alguma saiu e diz quantas não saíram. Antes,
    se uma era recusada, o aviso saía vazio e a tabela seguia mostrando as excluídas.
  - **Lançar parcela numa despesa excluída** (aberta pela URL) diz que ela foi excluída, e não "confira o
    valor"; e a parcela com sobra de centavo de um estorno volta a mostrar Editar, Remover e a caixa do
    lote, que o sistema já deixava usar.
  - **A descrição aparece escapada** na lista, e a despesa só se exclui por POST (nenhum botão a chama
    hoje: um link deixou de excluir).
- **Na API de Contas a Pagar** (a tela nova ainda não existe): o extrato traz os estornos, marcados; a
  parcela quitada não "deve" nada, e o que a quitação dispensou vem à parte; pagar parcela quitada e
  alterar parcela com pagamento dão 409; a despesa excluída não muda nem recebe parcela (409); os
  filtros que não servem dão 422; a descrição tem o mesmo teto, e o tipo precisa estar ativo. O `PUT`
  parcial só grava o que veio (não desfaz o tipo que outra sessão gravou), e a 409 da quitada sem
  pagamento não manda mais estornar o que não existe.
- **Na retirada do caixa com "financeiro":** um motivo longo com pontos não derruba mais a retirada --
  a despesa vai com o texto cortado em 100, e o motivo da retirada fica inteiro.
- **Duas pessoas ao mesmo tempo** (o ERP numa aba e a tela nova noutra, ou dois usuários da franquia):
  excluir a despesa enquanto outra paga, estornar o mesmo pagamento duas vezes ou pagar a mesma
  parcela ao mesmo tempo passam a andar em fila, com o estado relido -- a exclusão vê o pagamento, o
  segundo estorno é recusado, e o pago soma os dois.

### Provado

- **Casos novos, nos dois bancos:** `SaidaApiEscritasTest` (21, a API despachada em processo -- a
  primeira vez que as escritas dela rodam num teste), `SaidasDoErpTest` (21, as portas da tela antiga,
  com a view renderizada de verdade), `SaidaSobTravaTest` (27 execuções: as oito escritas travam a
  despesa antes de ler e param na trava presa por outra conexão; quem esperou relê; e, fora da
  transação do teste, a exclusão vê o pagamento confirmado por outra conexão depois da foto do MySQL),
  `RegraDaSaidaTest` (8); e 12 a mais em `SaidaApiTest` (2) e `SaidaFinanceiroTest` (10). Com os da
  revisão, abaixo; a primeira versão desta entrada dizia "11 a mais" onde eram 10 e um nome trocado.
- **Vermelho antes, medido:** o tipo desativado gravava e o inexistente caía com erro na tela antiga;
  o mês cru da lista do ERP deu erro no PostgreSQL (o 500 do D361); os demais casos ficam vermelhos
  pelos mutantes abaixo, que devolvem o código de antes, um ponto de cada vez.
- **44 mutantes, um de cada vez, cada um vermelho pelo seu caso**, rodados de novo sobre o código
  final, no MySQL (e no PostgreSQL os que dependem do banco: o mês cru e três da trava), restaurados
  por sha1: a situação de volta ao `!empty` e o link sem o `0`; o extrato sem os estornos; a falta, o
  dispensado e a falta da despesa; as duas 409 do serviço; o teto em nove formas; os filtros crus em
  quatro; o tipo desativado, a despesa excluída e o formulário do ERP sem a lista; o escape dos dois
  ramos; o GET do `excluirSaida`; a reexclusão nas duas; o lote em três; a trava e a releitura em
  nove formas; a sobra do `double` em duas.
- **A suíte inteira: 1.838 execuções (13 puladas), verde no MySQL e no PostgreSQL;** e, depois da
  revisão abaixo, 1.847 (13 puladas), verde de novo nos dois.
- **O portão das remoções por POST** aceita a ação sem tela, e foi falsificado em quatro formas.
- **Os portões (`conferir-tudo.sh`):** 51 ok e o vermelho conhecido do agendamento (D50). O de
  débitos repetidos pediu dois pares, registrados com o motivo (D254/D391 e D134/D357); o inventário
  de ações foi regerado (o `excluirSaida` passou a receber POST).
- **Os ensaios HTTP, contra os bancos de contrato:** os 11 de MySQL e os 11 de PostgreSQL (com o
  medidor da API, que pede a lista de saídas), todos com saída 0. Nenhum chega a uma escrita de Contas a
  Pagar -- o usuário do contrato não tem o privilégio de saídas, e o ensaio de permissão para na
  guarda --: as escritas da API foram provadas em processo.

### A revisão (04/10/2026)

Nove achados confirmados, sete defeitos distintos (dois vieram em dobro), todos de gravidade baixa, todos
consertados, cada um com o seu caso e o seu mutante:

- **O `PUT` parcial da API desfazia o tipo que outra sessão gravou:** reenviava o tipo lido antes da
  trava. O serviço só troca o tipo quando ele vem; a API manda só o que veio (`SaidaSobTravaTest`).
- **Lançar parcela na despesa excluída, pelo ERP,** respondia com a frase de validação; agora diz que
  ela foi excluída (`SaidasDoErpTest`, com o controle da frase de validação na ativa).
- **A tela das parcelas tratava a sobra do `double` como dinheiro pago** (achado duas vezes): a caixa
  do lote, o Editar e o Remover perguntam `SaidaParcela::temValorPago()`, a regra do serviço
  (`SaidasDoErpTest`, a view renderizada, com o controle de um centavo).
- **A 409 de pagar a quitada mandava estornar** a quitada sem pagamento, que não tem o que estornar
  (achado duas vezes): o caminho só aparece quando há pagamento ativo (`SaidaApiEscritasTest`).
- **Com o `.min.js` velho em cache, o lote parcial mostrava a mensagem duas vezes:** a ação só a
  guarda quando a tela manda `recarrega`, e o `?cache=` do arquivo passou a 165 (`SaidasDoErpTest`).
- **O lote prometia recusar o id inexistente, e nenhum caso pedia um:** ganhou o caso; e, ao
  escrevê-lo, o id que nem é id derrubou o lote com 500 no PostgreSQL (22P02, medido), e passou a ser
  recusado sem ir ao banco (`SaidaFinanceiroTest`).
- **A documentação contava menos do que houve:** o D385 dizia dois vermelhos onde houve três (e três
  onde houve cinco), e esta entrada dizia "11 a mais" onde eram 10. Corrigidos aqui e no D385.

**13 mutantes novos, um de cada vez, no MySQL, restaurados por sha1:** 12 vermelhos pelo seu caso e 1
verde, que era o esperado -- o id cru ao banco, que no MySQL não erra; esse e mais dois rodaram no
PostgreSQL, e os três ficaram vermelhos (o do id cru, com o erro 22P02). **Os casos do item, nos dois
bancos:** 153 execuções, verdes; a suíte inteira, 1.847 (13 puladas), verde nos dois. Os portões: 51 ok
e o vermelho conhecido do D50. Dos ensaios HTTP, rodaram de novo os dois que abrem páginas do ERP com o
`layout.phtml` (`ensaiar-rotas-publicas-do-erp` e `ensaiar-sessao-do-erp`), no MySQL e no PostgreSQL,
com saída 0; nenhum chega a Contas a Pagar.

### O que ficou

- **D393 (novo):** a API do caixa não tem teto para o motivo da retirada -- lido, não exercitado.
- **D361 e D362, parciais:** as outras sete listas cruas; e o portão ainda não lê o JavaScript nem os
  `href`, nem conta sozinho a ação sem tela.
- **Quem excluiu a parcela** continua sem registro: pede coluna, como o motivo do estorno (a P5).
- **Com o dono, sem mudança:** o pagamento de R$ 0 (P1), o vencimento mensal do D209 (P3) e o total
  que conta as excluídas, o D135 (P4).
- **Não visto no navegador:** a tela antiga (o lote recarregando, as frases novas, os botões da parcela
  com sobra). O `?cache=` do `mod-financeiro.min.js` passou a 165 (a revisão), e o arquivo novo chega a
  quem tinha o velho em cache. Uma aba aberta antes da troca segue com o velho até recarregar: o lote
  parcial mostra o aviso, agora com texto, e a tabela fica velha -- como antes; a mensagem não sai de
  novo na página seguinte, porque a ação só a guarda quando a tela manda `recarrega`. A primeira versão
  desta linha dizia que o arquivo velho seguia "com o comportamento de antes", e não era exato: sem o
  `recarrega`, a mensagem saía duas vezes.
- **O id cru nas outras ações da tela antiga:** o lote recusa o que não é id sem ir ao banco, mas
  `parcelaQuitar`, `quitarSemPagamento`, `parcelaEditar` e `pagamentoEstornar` ainda fazem `find()` do
  `id` do POST -- no PostgreSQL, uma letra dá 500 (lido, pelo mesmo 22P02 medido no lote); é a forma de
  35 buscas `find($request[...])` em 16 arquivos do ERP (contadas por grep), a família do D361, e fica
  para as F0 de cada tela.

## 04/10/2026 -- ESCAPE-E-REMOCAO: o texto digitado sai escapado em mais 54 telas do ERP, e as onze remoções do cadastro só por POST (D199 e D203 parciais, D394)

### O que muda para quem usa

- **Nas listas antigas de clientes, produtos, serviços, pacotes, arquivos, assistentes, mensagens,
  etiquetas, pesquisas, unidades e VIP Class**, o X, a pergunta "Deseja realmente remover?" e o
  "Ativar" continuam no mesmo lugar e com a mesma cara -- mas enviam formulário, e não link. Um link
  aberto por engano (a pré-carga do navegador, uma página de fora, o histórico) já não desativa nem
  ativa nada, e o segundo clique não desfaz o primeiro: grava-se o que foi pedido, e não o contrário do
  que estava. A pesquisa do programa de fidelidade continua sem poder sair.
- **O texto digitado aparece como texto** -- nomes de cliente, colaborador, produto, serviço, unidade e
  convênio, e-mails e telefones, os motivos de desconto, de cancelamento e de bloqueio, as observações da
  agenda, os títulos de mensagens e notificações --, e um `<`, uma aspa ou um apóstrofo digitado já não
  vira pedaço da página. Para o texto comum nada muda; o balão do horário na agenda continua com os
  ícones e as quebras de linha. Entre as telas estão a página pública da pesquisa NPS e o topo de toda
  página (o primeiro nome de quem entrou).
- **Depois da verificação**, também os balões "MELHOR DA REDE" do painel de colaboradores, o link do
  WhatsApp no relatório do consultor visagista e as cores do colaborador na agenda. E um link com a
  data ou a ordenação adulterada (`?datasolicitada=` na agenda do dia, `?sort_nome=` nos convênios e no
  relatório de clientes) já não monta nada na página de quem o abre.

### O provado

- **As remoções** (`D199AtivarRemoverPorPostTest`, 57 execuções, 4 puladas): o GET e o POST sem estado
  dão 404 com a linha intacta; o POST desativa e o segundo não reativa; o "ativo" ativa; e cada lista,
  renderizada com o que a `indexAction` lhe passa, tem o X por POST, o "Sim" com o estado e o "Ativar"
  como formulário. O `conferir-remocao-por-post.php` desceu a trava de 13 para 2 e passou a ler os
  `href` da tela (o "Ativar" por link seria botão morto); falsificado nos dois sentidos.
- **O escape** (`D203TextoDigitadoNasTelasDoErpTest`, 19 execuções depois da verificação): as cinco
  telas apontadas e quatro da varredura, renderizadas com o texto hostil trocado no banco das fixtures,
  e uma marca por campo; o balão da agenda lido como o navegador o entrega. A régua nova de
  `$x['campo']`: 508 acessos crus em 64 telas no HEAD, 155 escapados (140 em 48 telas da varredura, 15
  nas cinco), 353 depois. **Das 508, só as 195 com cara de texto pelo nome do campo foram lidas uma a
  uma**; as outras 313 foram descartadas pelo nome, e a verificação achou texto digitado entre elas --
  os treze balões do painel de colaboradores, descartados pelo primeiro índice, com cara de soma -- e
  valores da URL (as ordenações). Uma régua pela última chave e outra pela variável simples acharam o
  resto; o que ficou de fora está no D203.
- **O balão é HTML só na grade da agenda**, e não em toda `.tooltips`: medido no Chrome com os scripts
  do layout, a que chega com a página sai como texto (o `App.init` do Metronic a inicializa antes do
  `html: true` do `geral.js`, e o Bootstrap 3.3.7 ignora a segunda chamada); a que chega por AJAX com
  `.tooltip({html: true})` monta o HTML. Por isso o painel leva um escape, e a agenda dois.
- **48 mutantes, um de cada vez, restaurados por sha1, todos vermelhos**: as onze ações aceitando GET,
  o X por link nas onze telas, o "Ativar" e o "Sim" por link, o "Sim" sem estado, o interruptor de
  volta, e dezesseis escapes arrancados. O portão ficou vermelho em todos os de tela e de GET, menos o "Sim" sem
  o estado -- esse só o caso vê.
- **Na verificação, mais 31 mutantes, um de cada vez, todos vermelhos no caso**: cada escape novo
  arrancado, o balão do painel com o escape duplo, a data do `onclick` só com `escapeHtmlAttr`, e os
  onze escapes da primeira rodada que nenhum caso via.
- **A suíte inteira, nos dois bancos:** 1.913 execuções (17 puladas), verdes; depois da verificação,
  1.923 (17 puladas), verdes nos dois. O inventário de ações foi
  regerado. **Nada foi visto no navegador**: as telas são antigas, e os casos as renderizam em processo.

### O que ficou

- **Por GET, duas:** o "Remover" dos banners (a ação não existe -- decisão) e o dos privilégios (o
  D-ACL-1).
- **Com o dono:** quem pode desativar um cliente -- a tela antiga só mostra o botão à unidade dele e ao
  administrador, e a ação e a API deixam qualquer franquia.
- **D394 (novo):** a ação antiga muda o estado dos dois clientes marcadores (avulso e "sem cadastro"),
  que a API recusa -- lido, e medido só que os dois estão inativos na cópia.
- **A forma do getter** fora das telas tocadas (108 em 33 telas, contados antes da verificação), dois
  textos digitados na forma da variável simples (a saudação do consultor visagista e o seletor do troco
  no PDV), o `javascript:` no link das notificações, o HTML montado no relatório de caixas e o PDF do
  histórico de consumo: anotados no D203.

## 04/10/2026 -- GRUPOS-F0: o molde de privilégio só se remove por POST, a propagação trata as gêmeas numa transação, a reativação da funcionalidade de rede é do administrador, e a personificação não contorna a regra do próprio (D335, D336, D386, D395 a D398; D289, D361 e D374 parciais)

A F0 do desenho de Grupos e privilégios (`docs/f3-telas-restantes/tela-acl.md`): os consertos no ERP
antigo que vêm antes de qualquer tela, com a resposta (a) da pergunta 1 -- só os moldes, nenhum grupo
editado. A F1 (a API), a F2 (a tela) e a F3 (a verificação) não entraram.

### O que muda para quem usa

- **Na lista antiga de privilégios** (Configurações > Privilégios), o "Remover" continua com a pergunta
  "Deseja realmente remover?" -- mas envia formulário, e não link. Um link aberto por engano já não tira
  uma funcionalidade do grupo inteiro (o molde de maior alcance, "Assistente IA - Usuários" no grupo
  Colaborador, alcança 177 pessoas ativas em 44 unidades). E só vale o envio da própria tela: o de outro
  site, ou de outro subdomínio do mesmo site, é recusado. Clicar duas vezes no "Sim" não dá mais o erro
  500: o molde sai uma vez, e o segundo envio, que o encontra já removido, mostra a página de "não
  encontrado".
- **O "Novo" só aparece a quem pode criar**, e o **editar muda só o nível** ("ler", "escrever"): o grupo
  e a funcionalidade aparecem, mas desligados. Criar um molde para um par que já tem molde é recusado com
  a frase *"Este grupo já tem um privilégio para esta funcionalidade: edite o que existe."*.
- **Mudar ou tirar um molde trata a pessoa pelo que ela tem de fato**: quem tinha duas linhas da mesma
  funcionalidade (no grupo 3, 60 pessoas ativas com duas linhas de "Yungas") fica com uma só, com o molde novo -- antes
  a outra continuava concedendo. E a gravação do molde e das pessoas vai junta: se falha no meio, nada
  muda.
- **Quem não é administrador não reativa** uma pessoa que tem uma funcionalidade que vale para a rede
  inteira (hoje, 3 inativos do grupo 5 com "Agendas unidades"): a tela nova, o botão da lista antiga e o
  campo "Status" da edição dizem *"Esta pessoa tem uma funcionalidade que vale para a rede inteira: só um
  administrador a reativa."*. Desativar continua igual, e o administrador reativa.
- **Na personificação**, o administrador que assume outro administrador não altera os próprios
  privilégios nem o próprio grupo -- nem pela tela nova, nem pela janela de privilégios do ERP, nem pela
  edição. A tela nova o mostra como "próprio".
- **Os filtros das listas de grupos e de privilégios** ignoram o que não está nas opções (um link
  adulterado mostra a lista inteira, e não um erro), e os nomes aparecem como texto.

### O provado

- **Nos dois bancos**, os testes do item e os vizinhos (o filtro de 16 classes, entre elas
  `RegraDoMoldeTest`, `PrivilegioPropagacaoTest`, `PrivilegiosDoErpTest`, `UsuarioAtivacaoTest`,
  `PrivilegiosPelosControllersTest`, `D315PersonificacaoTest`, `D199AtivarRemoverPorPostTest` e
  `D203TextoDigitadoNasTelasDoErpTest`): 252 execuções (4 puladas, que já eram puladas) no MySQL e as mesmas 252 no PostgreSQL, verdes. Os controllers
  despachados em processo e as listas renderizadas; nenhum ensaio HTTP escreve privilégio.
- **36 mutantes, um de cada vez, restaurados por sha1**, todos vermelhos ao fim -- o remover por
  GET, o X e o "Sim" por link, o `getReference`, o `id` do corpo, o par editável, o par repetido (com o
  500 do índice único medido no PostgreSQL), uma linha por pessoa, "em dia" pela última linha, sem a
  transação e sem o `rollback`, os campos e os filtros crus (o 500 do D361 medido no PostgreSQL), o
  "Novo" sem a guarda, os escapes, a reativação de rede em cada porta, e cada porta da personificação. Um
  sobreviveu na primeira rodada -- o grupo fora da lista, que o serviço recusava com a frase errada -- e
  o caso passou a conferir que quem recusa é o formulário, no campo; vermelho na segunda.
- **Os portões:** `conferir-remocao-por-post.php` com a trava de 2 para 1, falsificado devolvendo o GET,
  o X e o "Sim"; `conferir-autorizacao-privilegiada.php` vigiando a reativação nas três portas e o
  `ehASiMesmo` nas da personificação; `conferir-cobertura-por-par.php` com a contagem do módulo de 144 para 143 (o `propagar` passou a ser um só); e o `conferir-tudo.sh` inteiro, com 51 portões verdes e o vermelho conhecido de sempre (saída 0). O portão de autorização é textual: arrancar a chamada o deixa vermelho, mas um `false &&` na frente dela passa -- quem pega são os casos.
- **Os ensaios HTTP** `ensaiar-usuarios-adm-api` (a ativação e a leitura dos privilégios) e
  `ensaiar-permissao-adm-api`, nos bancos de contrato dos dois, com saída 0; nenhum escreve privilégio.
- **Não visto no navegador**: as telas são antigas, e os casos as renderizam em processo.

### A revisão

Seis achados da verificação, todos conferidos no código, cada um com o seu caso em `PrivilegiosDoErpTest`
e o seu mutante (seis, um de cada vez, restaurados por sha1, vermelhos):

- **`ler` e `escrever` só "0" ou "1", estritos** (o D361): o `InArray` do `Select` compara com `==`, e
  `"00"`, `"0.0"`, `"0e5"` e `"-0"` passavam como "Não" e eram gravados verdadeiros (medido no contêiner
  com o formulário de verdade). O `PrivilegioFilter` ganhou o `InArray` estrito nos dois campos.
- **O remover do molde é só da mesma origem** (o D335): a barreira de `/adm/api`, como na janela de
  privilégios da pessoa; o de outro subdomínio chegava com o cookie.
- **O editar como o navegador o envia** (sem grupo e funcionalidade, que vão desligados) e **o token no
  editar e no novo** (o D374): o código estava certo, faltavam os casos -- arrancar o grupo de validação de
  `paraEditar()`, ou tirar o `security` dele, deixava a suíte verde.
- **O grupo escapado no ramo de quem só lê** (o D398): o quarto `echo`, sem caso.
- **Esta entrada** dizia que o duplo clique não dava mais erro; dá a página de "não encontrado" no segundo
  envio, em vez do 500 -- corrigido acima.

Nos dois bancos, as 28 classes vizinhas (as 21 que usam `ControllerEmProcesso`, que ganhou os cabeçalhos
do pedido, e as do item): 488 execuções, 2337 asserções, 4 puladas (as de antes), verdes no MySQL e no
PostgreSQL.

### O que ficou

- **Com o dono:** a recusa da reativação pelo molde do grupo, que só protege se a reativação reaplicar o
  molde (a P13) -- pergunta no lote; o índice único de `privilegios` no MySQL (esquema, a P8).
- **Da tela:** a F1, a F2 e a F3 do desenho.

## 04/10/2026 -- MARKETING-CONSERTOS: a adesão ao programa não trava nem conta o voto em dobro, a pergunta sai escapada na página pública, a pesquisa da adesão só se troca por outra completa, e o mural da agenda grava pela regra, imprime pela lista fechada e só o administrador o escreve (D337, D338, D339, D356, D399 a D406; D342 e D361 parciais)

Pedido do dono: "Aguarde finalizar a lista e continue de forma autonoma para resolver os debitos tecnicos
que não precisam de intervenção. faça-os de forma sequencial, ficarei um tempo longe". As fatias A e B
de Pesquisas e a F0 de Mensagens dos desenhos de 02/10 (`docs/f3-telas-restantes/tela-pesquisas.md` e
`docs/f3-telas-restantes/tela-mensagens.md`), no ERP antigo; a remoção por POST das duas telas já tinha
vindo no `5ac901e`.

**O que muda para quem usa:**

- **Na adesão ao programa** (a página pública que o cliente abre pelo link): a etapa 5 só faz a pergunta
  que já tem duas opções ou mais -- a pergunta recém-criada na tela antiga, ainda sem opções, deixa de
  prender o cliente na etapa 5 em todas as unidades; o voto conta uma vez por adesão, numa opção ativa
  daquela pergunta, e não se perde quando duas adesões chegam juntas; o texto da pergunta e das opções
  sai escapado, ali e no link avulso -- e o primeiro nome do cliente no topo da página também (o D405); a
  mesma etapa enviada duas vezes ao mesmo tempo não dá mais 20 pontos (o D406).
- **Na tela antiga de pesquisas:** o "Não" na pesquisa da adesão, o "Sim" numa pesquisa nova, inativa ou
  incompleta, e excluir a última pergunta (ou a opção que a deixaria sem pergunta) são recusados com uma
  frase; trocar a pesquisa da adesão é uma transação que carimba só as duas; o título de mais de 100
  caracteres, com emoji, ou com `<` ou `>`, é recusado no campo; a pergunta e a opção com `<`, `>`,
  emoji, longas demais ou com ordem fora da lista voltam com a frase, sem 500 e sem o SQL na tela; a lista
  conta "pesquisas"; o filtro de situação não dá mais 500 no PostgreSQL.
- **Nos avisos da agenda (Configurações › Mensagens):** só o administrador cadastra, altera, ativa e
  desativa -- hoje já são só eles, então ninguém perde nada; a cor é uma das três, e o mural a imprime
  pela lista; o título longo, o texto acima de 500, o emoji e o dia que não existe são recusados no campo
  (o dia vazio continua aceito: a pergunta 4); o filtro por dia da lista passa a achar o aviso -- o dia que
  não existe é ignorado, e a lista volta sem o filtro (não um 422: o porquê está no D361); o editar grava o
  aviso da página, e não o do campo escondido; o texto com `<` ou `>` ("Estoque < 5") grava inteiro, e sai
  escapado; e quem não é administrador e só lê vê só os avisos que chegam à sua unidade (hoje, ninguém).

**O provado:**

- **Os casos novos**, nos dois bancos: `AdesaoComPesquisaTest` (a etapa 5 e a página da adesão despachadas
  em processo), `TelasDasPesquisasDoErpTest` (as quatro páginas com texto hostil, renderizadas),
  `PesquisasPelosControllersTest` (a tela antiga em processo, cada recusa conferida no banco),
  `RegraDaPesquisaTest`, `MensagensPeloControllerTest`, `TelasDasMensagensDoErpTest` (o mural sozinho e
  dentro da agenda), `MensagemAlvosTest` (a falha forçada no meio da gravação dos alvos) e
  `RegraDaMensagemTest`. O `PesquisaFidelidadeTest` continua, com a leitura do código atualizada para a
  lista nova.
- **Os números:** 102 execuções novas, em oito classes; a suíte inteira, verde nos dois bancos -- MySQL
  2069 testes e 15873 asserções, PostgreSQL 2069 e 15869, 17 pulados nos dois (os de antes).
- **Os mutantes:** 65, um de cada vez, restaurados por sha1: 61 vermelhos -- cada escape, cada recusa,
  o limite de duas opções, a validação, quem já aderiu, o `UPDATE` do voto, o `WHERE` das marcadas, os
  validadores, o POST cru, o id do corpo, as três guardas, os filtros, a transação dos alvos. Dois deles
  mediram o 500 do D361 no PostgreSQL: o `22P02` da situação das pesquisas e o `22008` do dia dos avisos.
  Quatro sobreviveram, e cada um tem motivo: a trava do cliente na adesão (provar pediria dois pedidos ao
  mesmo tempo), o repositório dos avisos sem converter o grupo (o controller já o confere), e cada uma
  das duas guardas da cor sozinha (as duas juntas: vermelho).
- **Os portões:** o `conferir-tudo.sh` inteiro, 51 ok e o vermelho conhecido do agendamento (D50), saída
  0 -- com o `conferir-recorte-por-unidade.php` ganhando as três guardas dos avisos (falsificado: a guarda
  arrancada e a guarda só num comentário, vermelhos), o `conferir-cobertura-por-par.php` de 311 para 314
  pares (quatro novos, um que saiu; o sha1 volta ao de 311 sem eles), o "sem menção" de 149 para 148 e os
  módulos de 113 para 115 e de 143 para 145, e o `conferir-booleanos-em-consulta.php` com as inofensivas
  de 10 para 9 (o `UPDATE` sem `WHERE` saiu). O inventário de ações foi regerado (duas linhas).
- **O ensaio HTTP** `ensaiar-rotas-publicas-do-erp`, no banco de contrato do MySQL: 23 casos, saída 0. O
  do PostgreSQL não rodou.
- **Não visto no navegador**, e nenhum ensaio HTTP novo: as telas são antigas, e os casos as renderizam em
  processo.

**O que ficou:**

- **Com o dono** (já no lote, nos desenhos): o link público avulso da pesquisa (a P5 -- a parte do D399
  que resta), a tela nova de pesquisas e de avisos (a P4 do consolidado), o aviso sem dia (a pergunta 4
  das mensagens).
- **Sem decisão, fora deste item:** nas pesquisas, o "Status" do editar que não grava (o DP-5), o alvo da
  pergunta nova pela sessão (o DP-9) e o carimbo das escritas de pergunta e opção (o DP-7); nos avisos, a
  edição que ainda repassa o estado do `<select>` (o outro lado do M5, o D259), a ligação inativa que o
  salvar tira (o D256, no D342) e o "só o que mudou" (o M9).

**A revisão do mesmo dia** (um verificador, dez achados confirmados; cada um conferido no código antes de
mexer):

- **O que mudou:**
  - o primeiro nome do cliente, cru no topo da adesão, escapado (o D405, novo);
  - as travas da adesão e da troca da pesquisa ganharam os casos que o relatório dizia impossíveis --
    a primeira leitura `FOR UPDATE`, a trava presa por outra conexão e o caso fora da transação do teste,
    o instrumento do `CaixaSobTravaTest`; o mutante A18, que sobrevivia, agora fica vermelho;
  - a pontuação da etapa da adesão passou para a mesma trava do cliente, num ajudante só
    (`FidelidadeService::comOClienteTravado`) -- o D406, novo; os pontos do indicador saem depois, na
    transação dele;
  - o `StripTags` saiu do título e do texto dos avisos (a decisão 7 do desenho: cortava "Estoque < 5" em
    "Estoque", calado), e o título da pesquisa recusa `<` e `>` com a frase (a seção 2.2 do desenho);
  - a alteração do aviso lê a linha com trava antes de apagar os alvos (o D258, na parte das mensagens do
    D342), e quem só lê vê só o que chega à própria unidade (o M8, no D404) -- a quarta linha dos avisos
    no `conferir-recorte-por-unidade.php`;
  - o título nas duas ramas da lista dos avisos ganhou o caso hostil;
  - o registro: o "Resta" do D399 diz tudo o que o link público ainda faz, o docblock de `contarVoto` não
    fala mais em "duas portas", o D361 escreve a escolha do dia ignorado (e não 422), e os dois desenhos
    ganharam a nota do que se fez e do que ficou.
- **Os números:** 13 execuções novas (115 nas oito classes do item); a suíte inteira, verde nos dois
  bancos -- MySQL 2082 testes e 15921 asserções, PostgreSQL 2082 e 15917, 17 pulados nos dois (os de
  antes); o `conferir-tudo.sh` com o vermelho conhecido do agendamento (D50) e o `conferir-debitos-repetidos.php`,
  que acusou o par D337/D405 (a mesma página, campos diferentes) -- registrado em `$conhecidos`, com o
  motivo; o `conferir-recorte-por-unidade.php` de 175 para 176 métodos (falsificado: a guarda do M8
  arrancada e só num comentário, vermelhos).
- **Os mutantes da revisão:** 27, um de cada vez, restaurados por sha1; 26 vermelhos -- o nome cru; o
  ajudante sem a trava e sem a transação (nos dois bancos); a adesão e a pontuação fora do ajudante; a
  pesquisa e as marcadas sem trava e a troca sem a transação; o `StripTags` de volta no aviso e no título
  da pesquisa, e o validador do `<` arrancado; o aviso de volta ao `getReference` (nos dois bancos); o
  recorte de quem só lê arrancado no controller (nos dois) e no repositório; os dois escapes da lista; e
  os quatro "POST cru" de antes, de novo. Um sobreviveu, e é equivalente: a regra dos avisos recebendo o
  POST cru no lugar do `getData()` -- sem o `StripTags`, o filtro só apara, e a regra apara e lê só as
  chaves que conhece.
- **Continua sem medir:** a corrida com dois pedidos HTTP ao mesmo tempo (a trava presa é o que se mede no
  lugar dela) e a ida ao navegador até a etapa 5.

## 04/10/2026 -- ASSISTENTES-F0-F1: o abrir dos assistentes tem o recorte da lista, a conversa só sai do molde do Zapier, remontada, e a administração antiga grava pela regra (D340, D341, D343, D407 a D413; D342 parcial)

Mais um dos débitos sem decisão: a F0 e a F1 do desenho dos Assistentes
(`docs/f3-telas-restantes/tela-assistentes.md`), sem a F1b -- o privilégio do uso, que muda quem abre (51
perdem, 60 ganham) e espera a P3 do dono.

**O que muda para quem usa:**

- **Quem usa assistentes** abre só o que a lista de cartões mostra: o ativo, do seu grupo e da sua
  unidade. Trocar o número na URL dava o assistente de outro grupo, de outra unidade, ou o inativo -- 194
  pessoas podiam, medido no desenho. Agora é 404, a mesma resposta para o que não existe (o D340).
- **A conversa** só aparece quando o HTML gravado é o molde de hoje do Zapier, e o que a página imprime é o
  molde remontado a partir do `chatbot-id`, nunca o texto gravado; o nome e a descrição saem escapados (o
  D341). Os 4 assistentes de hoje seguem o molde: conferido no `franquiabv_dev`, só com SELECT e sem
  imprimir os ids, a página imprime para eles os mesmos 285 bytes de antes.
- **Quem administra** (os 8 administradores) só consegue gravar o código de incorporação como o Zapier o
  entrega (o `<script>` e o componente com o `chatbot-id`); o resto é recusado com a frase. O nome tem teto
  de 100 e a descrição de 2.000, sem emoji (o D411); o grupo e a unidade só os da lista, e o assistente e os
  vínculos se gravam numa transação, com a linha travada (a parte dos assistentes do D342); o editar grava
  o assistente da rota, e não o do `id` escondido (o D408).
- **O estado** muda só pela lista: o campo "Status" do formulário saiu, e o inativo ganhou o "Ativar", por
  POST (o D410). A capa confere o token, aceita o JPEG por conteúdo -- o `.jpeg` passa a valer -- e diz
  quando falhou (o D407). O caminho de volta, o rótulo do filtro e a frase da lista vazia são dos
  assistentes, e não da VIP Class (o D412).
- **Dois achados no caminho:** desde 19/08/2026 a capa dos assistentes e a capa e o certificado da VIP
  Class eram guardados crus, sem o recorte -- o `Canvas` não gravava no `tempnam()` sem extensão (o D409,
  medido no contêiner, consertado nos três) --; e a lista da administração dava 500 no PostgreSQL com uma
  letra no filtro (o D413).
- **A documentação** deixou de dizer que vínculo vazio é "função desligada": notas de registro no D57, no
  roadmap e no `funcionalidades-sem-uso.md`, e o D57 com a consulta de quem tem cartão -- 215 pessoas, em 31
  unidades (o D343).
- **A fixture** ganhou quatro assistentes, um de cada forma do recorte, com `chatbot-id` falso.

**Provado:**

- **Os testes:** cinco classes novas -- `RegraDoAssistenteTest`, `AssistentesPelosControllersTest`,
  `TelasDosAssistentesDoErpTest`, `AssistenteVinculosTest` e `RecorteDaImagemTest` -- e o
  `D199AtivarRemoverPorPostTest`, que passou a tratar os assistentes como tela que ativa. Os do item e os
  vizinhos (as classes `Assistente*`, `D199AtivarRemover*`, `RecorteDaImagem*` e `VipClass*`): MySQL e
  PostgreSQL, 158 testes e 488 asserções cada, 4 pulados (os de antes). A suíte inteira, verde nos dois bancos: MySQL 2170 testes e 16173 asserções, PostgreSQL 2170 e 16169, 16
  pulados nos dois -- eram 2082 e 17: o "Ativar" dos assistentes passou a ser conferido no D199.
- **Os mutantes:** 40, um de cada vez, restaurados por sha1 -- todos vermelhos no MySQL, e os onze que
  dependem do banco (o recorte, a transação, a trava, o `alterStatus` e os filtros crus) também no
  PostgreSQL.
- **Os portões:** o `conferir-recorte-por-unidade.php` de 176 para 178 métodos (o abrir e a consulta dos
  cartões), falsificado arrancando o código e deixando o nome num comentário; o
  `conferir-cobertura-por-par.php` de 314 para 315 pares (o `disponivelPara`), com o "sem menção"
  em 148; o `auditar-group-by.php` com as duas consultas lidas (uma primeira versão, com o `FROM` num
  ajudante, ele não lia, e o de booleano não resolvia o apelido -- a consulta voltou a morar num método só);
  a marca das fixtures regravada; o `conferir-debitos-repetidos.php` com os pares D395/D408 e D403/D408
  (a mesma família do `id` do corpo, telas diferentes) registrados com o motivo; e o `conferir-tudo.sh`
  com só o vermelho conhecido do agendamento (o D50).
- **Não visto no navegador, de propósito:** abrir um assistente carrega o script do Zapier e chama a conta
  de produção, mesmo com o id falso da fixture. A capa por envio de verdade e a chamada do recorte nos três
  serviços também não se fazem em processo.

**Revisão (04/10/2026), depois da verificação:** nove achados confirmados, todos tratados.

- **O recorte nos três serviços ganhou caso** (o D409): o envio mora num passo só (`receberEnvio`), e o
  `RecorteNosServicosTest` troca só ele -- a capa do assistente e a do curso saem em 800x800 e o
  certificado em 2000x1414. Arrancar a chamada de qualquer um deixava a suíte verde; agora fica vermelha.
- **O envio de verdade foi medido por HTTP**, contra o banco de contrato, sem pedir o abrir: a capa do
  assistente, a do curso e o certificado guardados no tamanho certo, e o mesmo ensaio acusa os dois 300x200
  com o recorte arrancado. Pelo mesmo ensaio: a lista, o "Ativar" e o "Remover" por POST (e o GET
  recusado), o editar com a recusa do molde, o novo, a capa e os cartões -- nenhuma traz o script do
  Zapier. A lista e o editar recusado foram vistos no navegador, numa cópia sem scripts das páginas que o
  ensaio recebeu: o "Ativar" cabe na célula como o "Editar", e a frase sai legível sob o campo.
- **A fixture não carrega mais o Zapier:** o HTML dos quatro assistentes ficou vazio, e um caso confere
  que nenhum vira conversa.
- **A frase do campo "iFrame"** diz o formato aceito, atributo por atributo, e não mais "como ele vem" --
  o código do Zapier com `width='400px'`, que uma das páginas públicas usa, é recusado. Aceitar outros
  tamanhos ficou como pergunta ao dono.
- **O masculino na tela inteira** (o D412): "Novo", "Ativo"/"Inativo", "Ativos"/"Inativos" e
  "Ativado/Removido com sucesso.".
- **O registro corrigido:** a página não era parte do D413 (o caso que dizia provar passava com ou sem o
  conserto, e saiu); o porquê do recorte do certificado é a proporção da página, e não um tamanho em
  pixels (o D409); e o comentário que dizia que o certificado imprime a data (o D72) foi corrigido.
- **Os números, sobre a árvore final:** a suíte inteira, MySQL 2176 testes e 16214 asserções,
  PostgreSQL 2176 e 16210, 16 pulados nos dois; os do item e os vizinhos, 164 testes e 529 asserções em
  cada banco, 4 pulados. **Mutantes:** os 40 de antes e 10 novos, um de cada vez, todos vermelhos no
  MySQL; os 16 que dependem do banco, também no PostgreSQL; e a fixture com o molde de volta, vermelha
  nos dois.

## 04/10/2026 -- USUARIOS-F0: a tela nova diz onde a pessoa aparece, o login entra com maiúscula nos dois bancos, o gerente não alcança a conta de rede, a senha do cadastro grava como digitada, e a cota-parte extra só do que o cálculo lê (D345, D346, D348, D349, D414 a D416)

A F0 do desenho do resto de Usuários (`docs/f3-telas-restantes/tela-usuarios-resto.md`), sem a F1 a F3,
que são a tela nova. O D-B (o D347) já estava feito.

**O que mudou para quem usa:**

- **A tela nova de Usuários** diz por extenso onde a pessoa aparece -- "sim, na do ERP e no aplicativo",
  "sim, só na do ERP", "não: só no aplicativo, e o balcão não vê na grade o que ele marca", "não, em
  agenda nenhuma". Até aqui dizia "sim" de todo mundo, inclusive dos 191 ativos sem agenda (o D345; o
  `ext` é o D416).
- **O login com maiúscula entra** -- "Gerente.Silva", como o teclado do celular escreve -- também no
  PostgreSQL, onde não entrava; o mesmo no "Esqueci a senha" e na credencial do gerente (cancelar, fiado,
  devolução, resgate). Os campos de login não põem mais a maiúscula sozinhos. O cadastro recusa "USER1" ao
  lado de "user1". **Quem tem o mesmo login que outro cadastro ATIVO** passa a ver, com a senha certa, "Há
  mais de um cadastro ativo com este login. Peça ao administrador que o corrija antes de entrar." -- antes,
  o sistema entregava um dos dois --; e quem tem o mesmo E-MAIL que outro ativo não pede mais o link do
  "Esqueci a senha". Na produção de 19/08 eram zero os logins com mais de um ativo (o D31); os e-mails
  repetidos ninguém mediu. As duas contas estão no lote (o D349).
- **O gerente não alcança mais quem tem funcionalidade de rede** -- a conta "API Integração": não abre a
  ficha, não troca a senha, não desativa. Só o administrador (o D346). Se o gerente deve ao menos
  desativar, foi ao lote.
- **A senha escolhida no cadastro vale como foi digitada** -- com espaços ou `<` e `>`, que o filtro
  tirava, e a pessoa não entrava (o D348).
- **A janela "Cota-parte" do usuário** oferece só serviço, lavagem e produto ativos (o pacote, que o
  cálculo ignora, saiu), aceita de 1 a 100 e não cria uma segunda linha para o mesmo produto (o D414); os
  campos "Visível agenda/PDV/dashboard" e o percentual base só aceitam o que a lista oferece (o D415).

**Provado:**

- **Os testes:** quatro classes novas -- `TetoDeRedeTest`, `LoginSemCaixaTest`, `RegraDaCotaParteTest` e
  `CotaParteExtraDoErpTest` --, casos novos no `UsuarioApiTest` e no `UsuarioDadosTest`, o caso do D336 no
  `UsuarioAtivacaoTest` reescrito para o teto, e o `SenhaEAcessoTest` lendo a lista. Os do item e os
  vizinhos: 65 testes e 381 asserções em cada banco. A suíte inteira, verde nos dois bancos: MySQL 2202
  testes e 16402 asserções, PostgreSQL 2202 e 16398, 16 pulados nos dois -- eram 2176. O front: 1565
  testes, o build e o lint.
- **Os mutantes:** 27 no servidor, um de cada vez, restaurados por sha1. Vinte e quatro vermelhos nos dois
  bancos; os três da caixa -- o `LOWER` da coluna do login, o do e-mail e o e-mail sem o `semCaixa` --
  vermelhos SÓ no PostgreSQL, que é a prova de que os casos medem (no MySQL a collation já ignorava a
  caixa). No front, o `simOuNao` de volta: os cinco casos vermelhos.
- **Os ensaios por HTTP**, nos dois bancos de contrato: a sessão do ERP 18 de 18 (o caso 18, novo: o login
  com a primeira letra maiúscula entra, e a sessão é a do gerente do contrato; falsificado no PostgreSQL
  sem o `semCaixa`, só ele fica vermelho), a busca de cliente da agenda 17 de 17, os usuários da API 19 de
  19 e as permissões 61 de 61.
- **Os portões:** o `conferir-tudo.sh` com só o vermelho conhecido do agendamento (o D50); o
  `conferir-recorte-por-unidade.php` de 178 para 182 métodos (o teto de rede nas quatro guardas) e o
  `conferir-autorizacao-privilegiada.php` com a pergunta de rede no método novo -- os dois falsificados
  arrancando o código de cada guarda; o `conferir-linha-sorteada.php` de 6 para 3; o
  `conferir-cobertura-por-par.php` de 315 para 314 pares (saiu `ProdutoRepository::popularCombobox`, que
  ficou sem chamador; o sha1 conferido devolvendo-o); e o `conferir-debitos-repetidos.php` com o par
  D310/D414 registrado.
- **Medido na cópia** (só contagens): nenhum login ou e-mail com maiúscula ou em par -- a anonimização os
  reescreveu, e só a réplica responde --; 1 conta ativa não administradora com funcionalidade de rede; 12
  extras de pacote, todas de inativos; nenhum valor de "onde aparece" fora da lista.
- **Não visto no navegador:** a frase nova da aba Dados e as recusas da janela da cota-parte foram medidas
  pelo DOM do vitest e pelo JSON da ação, e não na tela. (A primeira versão dizia também que o par com um
  inativo de id menor, no MySQL, não tinha como ser montado com as fixtures; tinha, e a revisão o mede.)

**Foi ao lote:** se o gerente deve poder ao menos DESATIVAR a conta com funcionalidade de rede (o D336 o
deixava; o teto do D346 o tira), com a recomendação de manter; e, sem pergunta nova, o efeito do D349 no
dia em que subir -- quem está num par de dois ativos deixa de entrar --, com as contagens para a réplica.

### A revisão do mesmo dia (o USUARIOS-F0 depois da verificação)

A verificação achou quinze pontos; todos se confirmaram no código, e foram consertados ou corrigidos no
texto.

**O que mudou para quem usa:**

- **A ficha de quem está inativo** diz "não: está inativo (o cadastro marca ...)", e não mais "sim, na do
  ERP e no aplicativo": a grade só traz ativos (971 inativos com `ext_int` na cópia). A grade, a lista de
  colaboradores da agenda, quem recebe marcação e a API leem a mesma lista agora (o D345).
- **Quem tem funcionalidade de rede abre a própria ficha** -- o teto do D346 a fechava também (o D346).
- **O gerente num par de logins ouve que é o par** ao autorizar fiado, cancelamento, devolução ou resgate,
  e não "Login ou senha não conferem." com a senha certa; e troca a senha em "Meus dados". A frase do par
  só sai a quem digita a senha de um dos dois: com outra senha, a entrada diz "Dados inválidos.", como
  para o login que não existe (o D349).
- **O par que já existe não trava a edição da ficha** das duas pessoas: a unicidade só confere o login e
  o e-mail que mudam (o D349).
- **Os três campos de login de gerente que tinham ficado de fora** -- o cancelamento da venda e o resgate
  de pontos nas duas telas do PDV -- não põem mais a maiúscula sozinhos (o D349).

**Corrigido no texto:** o D336 dizia que o desativar passa e que as três portas recusam a reativação -- o
teto do D346 mudou as duas coisas, e a recusa do D336 ficou atrás dele, sem caso (agora medida com o teto
desligado); o docblock de `ProdutoRepository::popularCombobox` dizia que as telas de cupom o chamam (não
tem chamador); "a regra é uma só" era uma comparação da constante com ela mesma; o zero de 19/08 era dos
logins, e não dos e-mails; e o par com um inativo de id menor tinha como ser montado, e foi.

**Provado:**

- **Os testes:** casos novos em `LoginSemCaixaTest` (o par com a senha errada, o gêmeo inativo de id menor,
  a recusa da credencial, o fiado do ERP, a edição com o par que já existe -- com o "Editado com sucesso."
  no controle --, e os atributos dos campos de login, a tag dos modais lida sem PHP nem comentário), em
  `UsuarioApiTest` (a API contra a grade, valor a valor e nos dois estados), `TetoDeRedeTest` (a própria
  ficha), `UsuarioAtivacaoTest` (o D336 com o teto desligado), `SenhaTrocadaTest` ("Meus dados" com o par)
  e `D317A320RestosDoD316Test` (o fiado da API com o par). Os do item e os vizinhos: 506 testes e 3707
  asserções em cada banco. A suíte inteira, verde nos dois: MySQL 2212 testes e 16542 asserções,
  PostgreSQL 2212 e 16538, 16 pulados nos dois. O front: 1570 testes, o build e o lint.
- **Os ensaios por HTTP**, nos dois bancos de contrato: a sessão do ERP 18 de 18, a busca de cliente da
  agenda 17 de 17, os usuários da API 19 de 19, as permissões 61 de 61, a fidelidade 24 de 24 e as
  recusas 201 de 201.
- **Os mutantes:** 28 no servidor, um de cada vez, restaurados por sha1 -- todos vermelhos no MySQL; o da
  busca antiga do adaptador com o login já sem a caixa, só pelo caso novo do gêmeo de id menor, que era o
  que faltava (no PostgreSQL, pelo do login gravado com maiúscula). No front, 4: o inativo com a frase do
  ativo e o atributo de cada um dos três campos -- vermelhos.
- **Os portões:** o `conferir-autorizacao-privilegiada.php` passou a derivar do código as portas da
  credencial (todo método de `module/` que chama `->autenticacao(`, 8 hoje) e a exigir `recusaDaCredencial`
  em cada uma, e vigia `confereASenhaDe` e `parComEstaSenha`; falsificado arrancando o código de duas
  portas, e com o nome só num comentário -- vermelho. O `conferir-cobertura-por-par.php` de 314 para 317
  pares (os três novos, com teste), e `UsuarioRepository::popularCombobox` saiu dos falsos positivos,
  coberto de verdade. O `conferir-tudo.sh`, por último e sozinho: só o vermelho conhecido do D50.

**Foi ao lote:** o login com acento depois do corte (o MySQL ignora o acento, o PostgreSQL não), com a
medida antes de decidir; e a correção da entrada do par, com a conta dos e-mails repetidos entre ativos.

## 05/10/2026 -- CADASTRO-FIDELIDADE-ROTINAS: o nascimento sem a hora do relógio, o responsável e o dono do telefone no ERP pela regra da API, a devolução do ERP conferida, e a campanha cancelada que não sai (D194, D234, D283, D303; D417 e D418 registrados)

**O que mudou para quem usa:**

- **O nascimento do cliente e do sócio** é gravado como o dia, sem a hora em que se salvou: no dia do
  aniversário, o filtro por faixa etária conta a idade nova desde a meia-noite (D194).
- **O modal de cliente do ERP** (agenda, venda, comanda, visagismo) e a página "Novo cliente": o cadastro
  novo como dependente só aceita como responsável o dono do mesmo telefone, ativo e que não seja cadastro
  do sistema, com a frase da tela nova; o "Telefone já cadastrado" nunca oferece o "Cliente Avulso" como
  dono, e o número que só ele tem deixa de contar como repetido (D234).
- **As campanhas:** cancelar a que a rotina já assumiu diz "já começou a ser enviada", em vez de
  "Cancelado" com a campanha saindo inteira; a cancelada antes de a rotina chegar nela não sai e não vira
  "enviado"; e o que sai é o texto gravado na hora em que a rotina a assume (D303).
- **A devolução de crédito no ERP:** nada mudou -- o saldo inteiro com o resíduo do `double` já passava
  desde 03/10 (o D285); conferido e registrado no D283.

**Provado:** 16 casos novos (`NascimentoSemHoraTest`, `ResponsavelNoCadastroDoErpTest`,
`CampanhaCanceladaNaRodadaTest`), com os controllers do ERP despachados em processo, e os vizinhos: 741
testes em cada banco, verdes (MySQL 10264 asserções, PostgreSQL 10262; 9 pulados nos dois, os
declarados). 14 mutantes, um de cada vez e restaurados por sha256, todos vermelhos -- 5 deles também no
PostgreSQL --, entre eles a devolução do ERP comparando em `float`, que derruba os dois casos do D283.
Os portões: o `conferir-cobertura-por-par.php` de 317 para 320 pares (os três do repositório
das campanhas, com teste; tirando-os, o sha1 volta ao de antes), e o `conferir-debitos-repetidos.php` sem os
dois pares do D303 que o título novo desfez; o `conferir-tudo.sh`, por último e sozinho, só com o vermelho
conhecido do D50 (os testes do front inclusive). **Não provado rodando:** a fiação em `marketingEnvios` (assumir, reler, encerrar) --
a rotina manda WhatsApp e push de verdade e não roda em teste; foi lida. (A revisão, abaixo, a tirou daí.)

**Foi ao lote:** a alteração de cliente pelo ERP com o telefone de outro (D417) e o acerto, na produção,
das datas de nascimento que já têm hora (D194).

**A revisão (05/10/2026)** achou e consertou:

- **a campanha assumida que caísse antes do primeiro envio ficava presa** em "Enviando", sem sair e sem
  poder ser cancelada -- uma regressão do conserto do D303: o assumir não gravava o último envio, que é
  por onde a rodada seguinte retoma. Agora grava;
- **a campanha adiada depois que a rodada a leu saía na hora velha**: o assumir passou a conferir que ela
  ainda está devida;
- **a fiação da rotina ganhou caso:** o corpo do laço foi para `NotificacaoService::enviarCampanha`, com o
  envio de verdade numa função que a rotina passa; o teste passa uma que só anota;
- **o nascimento nos dias em que a meia-noite não existiu** em São Paulo (o início do horário de verão, 34
  dias de 1985 a 2018) ficava 01:00: o dia agora é criado em UTC, e o MySQL grava 00:00:00 sempre. O
  PostgreSQL (`timestamptz` no fuso de São Paulo) continua 01:00 nesses dias -- só uma coluna `date` o
  tiraria; foi ao lote como opção;
- **o id de dez dígitos do responsável** ganhou caso (a guarda do teto existia e nenhum caso a derrubava);
- o comentário da API que ainda dizia `stringToDate()` no setter do nascimento.

**Provado:** 26 casos no item (10 novos), verdes nos dois bancos, e o item com 57 vizinhos (60 arquivos;
fora o `RoyaltiesTest` e o `CronTravaSimultaneaTest`, que chamam a apuração de royalties): 754 testes em
cada banco, verdes (10132 asserções, 8 pulados, os declarados); 7 mutantes novos, um de cada vez e
restaurados por sha256, todos vermelhos -- 5 nos dois bancos, 1 (a data em UTC) nos dois com casos
diferentes, e 1 (o teto do id) só no PostgreSQL, o único banco em que a guarda muda a resposta. Os
portões: o `conferir-cobertura-por-par.php` com o ISApi de volta a 136 chamadas e o ISMarketing em 32 (o
assumir e o encerrar mudaram de módulo), e 147 pares sem menção em teste -- a leitura da rodada,
`selecionarEnviosCronJob`, ganhou chamada de verdade; trocando o nome no teste, volta a 148 --; o
`conferir-escritores-do-horario.php` com o receptor `$notificacao` no serviço e fora da rotina; e o
`conferir-tudo.sh`, por último e sozinho, só com o vermelho conhecido do D50. **Continua lido, e não
rodado:** a função de envio da rotina (as tags, o push e o WhatsApp, que só mudaram de lugar).

## 05/10/2026 -- AGENDA-TELA-ANTIGA: o motivo do fechamento cabe na coluna, o remarcar redesenha o dia que se vê e confere o token, e a tela nova não oferece mover quem já chegou (D111, D215 e D216 na tela antiga; D420 e D421 registrados)

**O que mudou para quem usa:**

- **Fechar a agenda (tela antiga):** o motivo vai aparado; só espaços é "falta o motivo"; acima de 100
  caracteres o modal recusa com aviso ("- Motivo com até 100 caracteres"), e o campo não deixa digitar
  mais que isso. Antes, cem letras com um espaço no fim davam erro no servidor depois de a tela dizer
  "pode fechar" (D215). O horário de início inválido diz "- Horário início inválido", e não mais
  "- Horário fim início" (D111).
- **Remarcar (tela antiga):** a grade que volta é a do dia que está aberto, e não a de hoje -- no modal
  e no arrastar. O modal e o arrastar passaram a mandar o token do formulário, e o servidor o confere
  nos dois (o arrastar entrou na verificação, abaixo); o token vale a sessão inteira, então a agenda
  aberta o dia todo continua remarcando (D216). O arrasto recusado devolve o bloco ao lugar.
- **A agenda nova:** o "Remarcar", o "Desmarcar" e o arrasto somem do agendamento em que alguém já
  chegou mesmo quando a chegada está num horário que a grade de hoje não desenha (as famílias de antes
  da troca de passo): antes, o botão aparecia e o servidor recusava (D216).
- **Por dentro:** a validação do fechamento virou `RegraDeFechamento` (D111), sem mudar o que diz.

**Provado:** 31 casos novos -- `FechamentoDaTelaAntigaTest` (15; os 9 de caracterização verdes contra
a função privada ANTES de ela mudar de lugar, nos dois bancos; dos 6 do conserto, 5 vermelhos contra o
código de antes e o de 100 caracteres como controle do limite), `RemarcacaoDaTelaAntigaTest` (11, com o relógio da sessão adiantado seis minutos e a
página `index.phtml` desenhada de verdade) e 5 em `GradeDaAgendaApiTest` --, e os vizinhos da agenda:
400 testes em cada banco, verdes (2781 asserções; 1 pulado, o declarado do `RecorteDeUnidadeNaAgendaTest`).
18 mutantes do servidor, um de cada vez e restaurados por sha256, todos vermelhos (no MySQL). Na tela
nova, 9 casos novos, 4 mutantes vermelhos, e 1579 testes verdes, com o build e o lint. O
`conferir-tudo.sh`, por último e sozinho, só com o vermelho conhecido do D50 -- depois de corrigida a
citação sem `.php` que o `conferir-referencias-docs.php` acusou no registro. O CI segue parado por
cobrança: os casos novos rodaram aqui, não lá.

**Foi ao lote:** consertar o `dataValida` no ajudante, o que muda a resposta do aplicativo a um
nascimento malformado (D420). **Registrado:** o motivo com emoji, que as duas portas deixam chegar ao
MySQL (D421). **Fica com o dono:** o D217 (o terceiro horário do arrastar) e o teto de dias do
fechamento na tela antiga. **Não visto no navegador**, nem a tela antiga nem a nova: o envio do JS foi
medido com um jQuery de mentira, e o resto em processo.

**A verificação (05/10/2026)** confirmou sete achados, todos baixos, e os sete foram tratados (detalhe no
D216): **o arrastar da tela antiga remarcava sem token** -- consertado, com o mesmo ajudante do modal
(`recusaDoTokenDoRemarcar`) e a grade de volta na recusa; **o bit `remarcavel` prometia mais do que
confere** -- é a pergunta do atendimento, e a família descompassada sai `true` e dá 409 de propósito; os
comentários dizem isso, e um caso prende a fronteira; **o caso "bloqueio e livre sem o bit" era vazio**
-- refeito com o mapa, e um caso novo pela receita com um fechamento de verdade; **nada prendia o que
o `mod-cadastros.js` manda** -- `remarcarDaTelaAntiga.teste.ts`, no front, roda as funções do arquivo
servido com um jQuery de mentira e confere o piso 165 da versão no layout; **a suíte tinha rodado só os
vizinhos** -- rodou inteira; e **o D420 contava linhas** -- são quinze chamadas em onze linhas.
**Os números, sobre a árvore final:** `RemarcacaoDaTelaAntigaTest` 13 casos e `GradeDaAgendaApiTest`
30 (43 e 171 asserções), verdes nos dois bancos; **a suíte inteira, verde nos dois: MySQL 2273 testes e
16796 asserções, PostgreSQL 2273 e 16792, 16 pulados nos dois**. Seis mutantes novos do servidor, um de
cada vez no MySQL, e cinco do JS e do layout, todos vermelhos, restaurados pelo sha256. O front: 1584
testes verdes (`--maxWorkers=4`; na primeira rodada, o caso "soltar abre a janela" da `AgendaLigada`
caiu, e caiu também sozinho duas vezes logo depois, com a máquina carregada; passou na rodada inteira
seguinte, no arquivo inteiro duas vezes, e sozinho três vezes alternado com a árvore do HEAD, que passou
nas quatro -- o arrasto instável sob carga, item 26 da fila, e não esta mudança, que no front só mexeu
em comentários e no caso novo), o build e o lint. O `conferir-tudo.sh`, por último e sozinho: saída 0,
51 portões ok e o vermelho conhecido do D50. **O que
fica:** as outras escritas da agenda antiga (marcar, desmarcar, a chegada, fechar, abrir, remover
bloqueio) seguem sem token nem barreira de origem, como os botões do D260.

## 05/10/2026 -- CONTAGEM-PELO-CORPO: os 421 débitos lidos um a um, 189 de pé, e 73 títulos que diziam outra coisa

**A pergunta do dono:** "quantos debitos ainda estão em aberto?". O `contar-debitos.php` lê só o título, e o
título atrasa: ele dava 133 de pé, e uma régua minha, também pelo título, 155. As duas erravam para menos.

**Como foi lido:** oito leitores, um por lote, leram o corpo inteiro de cada débito -- vale o acréscimo datado
mais recente -- e deram o estado e o primeiro bloqueio. Os 73 em que o contador lia outra coisa, mais cinco
fechados citados nos roteiros do corte e três achados por uma varredura dos fechados, passaram por uma
segunda leitura, independente, que tentou derrubar a primeira; nas sete divergências, um terceiro leitor
desempatou, e ficou com a segunda nas sete (o D1 e o D2 seguem ativos em produção até o passo do dia 0; o
D34 não é remissão; o D4 e o D194 são parciais; o D98 e o D161 só esperam a janela).

**O resultado, pelo corpo:**

| estado | débitos |
|---|---|
| fechado | 230 |
| pronto no código, à espera só do passo em produção na janela (PREPARADO) | 24 |
| parcial | 65 |
| aberto (com 3 medidos e 8 adiados por decisão do dono) | 100 |
| remissão, fora da conta (D30 e D83) | 2 |

**Os 189 de pé, pelo primeiro bloqueio:** decisão do dono, 92; dá para fazer sem o dono, 48; a janela da
migração final, 30; adiados pelo dono (OneSignal, visagismo, SMS, LGPD), 7; ação do dono (BVIP, contas
externas, CI), 6; uma fase que ainda não começou, 6. O bloqueio veio de um leitor só; o estado de 69 dos 189
passou pela segunda leitura.

**O que mudou no registro:** 73 títulos ganharam a marca depois de um travessão novo -- "PARCIAL, lido em
05/10/2026" (47), "PREPARADO" (20), "FECHADO" (3), "ABERTO" (2) e "REMISSÃO" (o D83) -- e uma nota logo
abaixo do título com o que o corpo diz e o que falta. Dos que o título dava como fechado, 47 não estavam: 36
parciais, 9 à espera só da janela, 1 aberto e 1 remissão. O desenho mais comum entre os parciais: a API e a
tela nova consertadas, e a tela velha como está, à espera de decisão (D165, D208, D212, D216, D228, D232). A
marca é curta de propósito: o `conferir-debitos-repetidos.php` mede radicais em comum entre títulos, e uma
frase mais longa repetida em 73 deles inventaria pares.

**Dois roteiros ajustados:** o `contar-debitos.php` ganhou o D83 na tabela de exceções -- é a decisão de
02/09 que manda cada pendência da F1.1 para o D1, o D2, o D3 e o D7, e o título terminava em "DECIDIDO", que
a régua lê como fechado. O `conferir-debitos-repetidos.php` passou a tratar `parcia` como marca de estado,
como já fazia com `prepar`: sem isso, oito pares novos só pela palavra PARCIAL. Com a palavra fora da conta,
quatro pares de assunto vizinho subiram ao limiar, e cada um foi lido e registrado com o motivo (D86|D318,
D131|D147, D229|D230, D285|D381).

**Provado:** o `contar-debitos.php`, pelo título, dá agora o que o corpo diz: 230 fechados, 24 preparados, 65
parciais, 100 entre abertos, medidos e adiados, e 2 remissões. O `conferir-debitos-repetidos.php` verde, com
80 pares tratados, e as duas mudanças nele falsificadas: sem `parcia`, os oito pares voltam; sem as quatro
entradas, os quatro. O `conferir-referencias-docs.php` verde com as notas novas. O `conferir-tudo.sh`, por último e sozinho:
51 portões ok e o vermelho conhecido do D50.

**O que não foi feito:** os corpos não foram reescritos -- a nota diz o estado, e o corpo continua sendo a
história. Nenhum débito foi consertado nesta rodada, e a fila dos que não pedem decisão segue parada, como o
dono pediu.

## 05/10/2026 -- WHATSAPP-W10: a máquina de quem programa deixa de alcançar os provedores de produção (D360 parcial)

**O que mudou para quem usa:**

- **No ambiente local,** Asaas, WhatsApp (Evolution e WAHA), OneSignal, SMS, Yungas, Zoomtec e o SMTP de
  produção resolvem para o loopback dentro do contêiner do PHP, no IPv4 e no IPv6 -- os nove nomes que o
  deploy de teste já cortava. A tela antiga do WhatsApp numa sessão das fixtures 90001150 e 90001200 chama
  o provedor e a chamada morre no contêiner: o cliente que ela usa volta "Erro de conexão" (medido na
  classe). O `marketingPush` no banco de contrato também não sai da máquina, mas não foi rodado, e no ramo
  do OneSignal a falha é muda: o `curl_exec` devolve `false`, sem mensagem. O ViaCEP segue alcançável.
- **Da raiz do repositório, sempre:** de `frontend/`, de `scripts/` ou de um worktree, o compose não lê o
  `.env` e o app sobe sem o corte (medido). Antes de usar as fixtures 90001150 e 90001200 no navegador,
  `docker compose exec -T app php scripts/conferir-provedores-no-loopback.php --conteiner` tem de sair com 0;
  sem isso, a restrição do D360 vale como antes.
- **Onde mora:** num arquivo novo, `docker-compose.desenvolvimento.yml`, que o compose lê pelas duas linhas
  `COMPOSE_` do `.env.example`. Quem já tem um `.env` copia as duas e recria o app (`docker compose up -d
  app`); o `setup-dev.sh` avisa quando faltam. A homologação e o teste do BVIP não o leem: cada um tem o seu
  `COMPOSE_FILE`, e o `docker-compose.yml` ficou como estava. Se a homologação também deve ser cortada, é a
  pergunta que foi ao lote.
- **Um portão novo,** `scripts/conferir-provedores-no-loopback.php`, duas vezes no `conferir-tudo.sh`:
  compara a lista com a do deploy de teste, procura no código um provedor que ela não tenha, confere quem
  carrega o arquivo e, com `--conteiner`, mede a resolução lá dentro.
- **Efeito colateral da recriação:** as sessões do navegador no ambiente local caíram (o PHP as guarda no
  `/tmp` do contêiner); basta entrar de novo.

**Provado:** antes de recriar, o portão com `--conteiner` ficou vermelho nos nove nomes, todos em endereços
reais; depois, verde, e o `getent` dá `::1` e `127.0.0.1` para cada um, o ViaCEP fora. O `EvolutionApi` e o
`WahaApi` (os clientes da tela), com as chaves de mentira das fixtures, voltam "Erro de conexão ... Could not
connect to server" -- medido na classe, e não na tela aberta no navegador. Pelo
`docker compose config`, a homologação (pelo `COMPOSE_FILE` do modelo e pelo `-f` da documentação) e o
laboratório seguem sem `extra_hosts`. Doze mutantes no portão, todos vermelhos, e o provedor escrito só em
comentário, verde. 66 testes vizinhos verdes nos dois bancos. O `conferir-tudo.sh`, por último e sozinho:
53 portões ok e o vermelho conhecido do D50.

**O que não foi feito:** a fixture com o WhatsApp ligado e sem chave, a segunda parte do D360 -- hoje nenhum
caminho a distingue (sem chave, a tela antiga chama o provedor do mesmo jeito), e ela entra com a F0 do
WhatsApp, que é quem a lê. Nada do BVIP foi tocado.

**Depois da revisão:** o "da raiz" entrou no cabeçalho do `docker-compose.desenvolvimento.yml`, no
`.env.example` e na `docs/ambiente-desenvolvimento.md`, e a restrição das duas unidades passou a depender do
`--conteiner` verde; o D360 registra a troca com o `docker-compose.override.yml`, medida num rascunho. A
pergunta do lote foi corrigida: as opções (b) e (c) mexem as duas numa das `CAMADAS` do `deploy-teste.sh`,
e só a (c) fecha o laboratório de migração do BVIP, que sobe só com o `docker-compose.yml`. O consolidado e
o desenho do WhatsApp ganharam a nota de onde o corte foi parar.

**O ciclo inteiro, depois da revisão e com o contêiner já recriado:** a caracterização em 2273 testes nos dois
bancos (16796 e 16792 asserções, 16 pulados), o contrato do aplicativo 67 de 67 nos dois, os 12 ensaios por
HTTP nos dois bancos, o `conferir-tudo.sh` com 53 portões ok e o vermelho conhecido do D50, e 1584 testes no
front. Nenhum teste dependia de alcançar um provedor de fora.

## 05/10/2026 -- TESTES-DOS-CONSERTOS-A: as provas que faltavam ao D10, D173, D184, D190 e D201

**O que mudou para quem usa:** nada na tela -- nenhum código de produção mudou. Cinco consertos já feitos,
que só tinham sido provados por leitura ou por um cenário que não os distinguia, passaram a ter caso que fica
vermelho sem eles:

- **D201, o catálogo na API nova:** as sete rotas de item (`ver`, `PUT`, estoque, movimentar, cancelar
  movimentação, ativação e desativação), pedidas para um produto de outra franquia, respondem 404 igual ao do
  id que não existe, e não mudam nada; o da minha unidade e o administrador continuam alcançando.
- **D190, a chegada na agenda antiga:** o check-in do cliente marcado em duas franquias no mesmo dia vira só o
  horário da minha unidade, e a grade que volta é a minha, mesmo pedindo a outra; com "Agendas unidades",
  os dois e a grade pedida -- o que acontece hoje, e não uma regra decidida (o D422, na verificação abaixo).
  E, desde a verificação, o "Abrir" do horário fechado (`abrirAction`, a ação que apaga o fechamento).
- **D184, a adesão pública:** o `POST /fidelidade/salvarDados` recusa o par id/hash mal formado e o errado (o
  hash de outro cliente inclusive) sem gravar nada, recusa o campo vazio da etapa, e com o par certo grava a
  etapa e os 10 pontos uma vez só -- o que só passou a ser verdade na verificação (o D423, abaixo).
- **D173, o autocompletar de rua:** o teste deixou de depender de o dicionário de endereços ser curto. As duas
  ruas do cenário terminam em "DA CARACTERIZACAO", e os termos também.
- **D10, o relatório de descontos:** uma venda de cenário com dois itens descontados (a 90001610) prova que a
  listagem tem uma linha por item, e não uma por venda.

**Provado:** cada caso, com o mutante que arranca o conserto, um por vez, restaurado byte a byte e conferido no
`git diff` -- o recorte de `produtoDaRota` (cinco rotas e a mesma resposta vermelhas, gravando de verdade) e o
de `mudarEstado` (ativação e desativação); o filtro da escrita do check-in e a unidade crua da grade; o hash
não conferido, a guarda do par e a validação da adesão; a raiz da listagem de descontos de volta na venda (no
MySQL o caso novo, 3 itens onde há 4; no PostgreSQL a consulta nem roda); o `LOWER()` da busca de rua (seis
vermelhos no PostgreSQL, nenhum no MySQL, como o arquivo diz) e o termo de antes. O D173 também contra o
dicionário real: num PostgreSQL descartável com as 709.440 ruas do `franquiabv_dev` (lidas por `COPY
(SELECT ...)`, e o banco apagado no fim), os oito casos verdes; com "presidente" de volta, cinco vermelhos.
A suíte inteira depois da fixture nova: 2302 testes nos dois bancos (16920 e 16916 asserções, 16 pulados);
o contrato do aplicativo 67 de 67 nos dois, depois de remontar o banco de contrato; o `conferir-tudo.sh`,
por último e sozinho, com 53 portões ok e o vermelho conhecido do D50 -- e o `conferir-cobertura-por-par`
desceu de 147 para 146 pares sem teste (`AgendaRepository::selecionarAgendaCliente`; a verificação achou que
a descida se apoiava num comentário, abaixo). Títulos: D173, D184, D190 e D201 CORRIGIDO; o D10 continua
PARCIAL.

**O que não foi feito:** o par `Visagismo::listagemIndexRelatorio` do D10, adiado pelo dono; o caso por HTTP
no banco de contrato do D201 e o de escrita do `salvarDados` no ensaio público do D184 (o despacho em processo
exercita as mesmas ações). O D202, o D204, o D206, o D207 e o D105 são a rodada seguinte.

**A verificação (05/10/2026)** confirmou nove achados (dois deles o mesmo); todos foram tratados, e um foi ao
dono:

- **A etapa da adesão pública não era conferida (D423, CORRIGIDO):** com o par certo, `-1`, `0` ou a etapa
  ausente rendiam 10 pontos cada, sem limite, e `04`, `4.0` ou ` 4` pontuavam a etapa 4 de novo; `abc` e a
  vazia davam 500. A etapa agora é um dos seis textos que a página manda, ou "Dados de segurança
  inválidos." antes de tocar no cliente; e a pontuação do serviço só conta as seis, com a descrição de
  sempre. Na cópia, nenhum dos 128.012 históricos de etapa tem descrição fora das seis.
- **O D173 tinha um sexto vermelho de pé:** o `BuscaDeClienteComAcentoTest` afirmava zero "jose" sem acento
  na tabela inteira, e a cópia do PostgreSQL tem 3.576. A conta passou a ser só do cliente do cenário, com um
  caso que põe um "jose" de fora na transação. O título fica CORRIGIDO, agora com a sexta prova.
- **O `abrirAction` do D190 não tinha caso,** e é a ação que apaga: dois casos novos, com um fechamento em
  cada franquia -- o da outra recebe a mesma recusa do que não existe e continua lá; o meu é apagado, e a
  grade que volta é a minha.
- **"Oito outros chamadores" era falso:** a consulta do check-in tem um chamador só. O comentário de produção
  e o teste dizem agora o porquê de verdade (a regra é do controller, e o repositório não lê sessão), e o
  teste ganhou a chamada direta da consulta -- a descida do `conferir-cobertura-por-par` para 146 deixou de
  depender do nome num comentário (sem ele no comentário, 146; sem ele também na chamada, 147).
- **Vai ao dono, o D422:** com "Agendas unidades" (e o administrador), o check-in da agenda antiga vira também o
  horário do cliente na outra franquia, e desfaz a chegada que ela registrou; a tela nova não faz isso. O caso
  que o travava como "o comportamento de antes, para quem pode" passou a dizê-lo como fato, com outro que
  mostra a chegada desfeita; a pergunta está no lote, com a recomendação de limitar à unidade do horário
  clicado.
- **Um comentário desatualizado:** o "35 contra 30" do `DescontosNoRelatorioTest` é 43 contra 38 desde a
  venda 90001610 (medido).

**Os números, sobre a árvore final:** `D184SalvarDadosDaAdesaoTest` 23 casos, `D190CheckinEmDuasFranquiasTest`
9 e `BuscaDeClienteComAcentoTest` 12, verdes nos dois bancos; **a suíte inteira, verde nos dois: MySQL 2318
testes e 16964 asserções, PostgreSQL 2318 e 16962, 16 pulados nos dois**. Sete mutantes novos, um de cada
vez e restaurados pelo sha256, todos vermelhos: a etapa sem a pergunta no controller (os dez casos da rota)
e com a guarda antiga no serviço (o caso do serviço); a conta do acento de volta na tabela inteira (nos
dois bancos); e no `abrirAction`, sem `fechamentoDaMinhaUnidade` (nos dois bancos), com `getReference()` e
com a `unidade` crua. O front não foi tocado. O `conferir-tudo.sh`, por último e sozinho: saída 0, 53
portões ok e o vermelho conhecido do D50; e depois da última edição dos documentos, `contar-debitos` (232
fechados, 89 abertos com o D422), `conferir-debitos-repetidos` (nenhum par novo) e
`conferir-referencias-docs`, todos com saída 0.

## 05/10/2026 -- TESTES-DOS-CONSERTOS-B: as provas que faltavam ao D202, D204, D206, D207 e D105

**O que mudou para quem usa:** uma coisa só. No relatório de **evasão**, exportar uma unidade sem os dados
da rotina (`relatoriosEvasaoAtualizar`) volta à tela do relatório e agora diz **"Dados indisponíveis."** -- a tela nunca mostrava
mensagem nenhuma, e quem clicava via a página voltar sem explicação (achado pelo ensaio do D105, abaixo). O
resto são provas de consertos já feitos, que estavam "conferidos por leitura":

- **D202, as cinco telas de editar do catálogo** (produtos, serviços, pacotes, etiquetas, VIP Class): o `id` do
  campo oculto não escolhe a linha -- a gravada é a da rota, e a de outra franquia (ou a outra etiqueta, o
  outro curso) fica como estava; a rota de outra franquia é 404; o administrador alcança.
- **D204, a tela de editar usuário:** o usuário do campo oculto não é o gravado, o nome vai como o formulário
  filtrou, a ficha de outra franquia e a de um administrador da própria unidade são 404 para quem não é
  administrador, e o grupo de administrador no POST dele volta com a página sem gravar.
- **D206, a identificação da despesa:** a despesa do campo oculto (de outra unidade) não muda, a situação não
  se troca por ali, e a descrição vai filtrada; a rota de outra unidade não alcança a despesa dela.
- **D207, o estorno:** o pagamento de outra unidade recebe a mesma resposta do que não existe, e nada muda.
- **D105, os cinco links consertados em 14/09:** medidos por HTTP no banco de contrato, nos dois bancos, com o
  ensaio novo `scripts/ensaiar-links-consertados-do-erp.php`.

**Provado:** quatro arquivos de caso novos, todos com o controller despachado em processo
(`ControllerEmProcesso`) e o formulário de verdade -- `D202EdicaoPelaRotaNoErpTest` (12),
`D204EdicaoDeUsuarioNoErpTest` (4), `D206D207SaidasPelaRotaNoErpTest` (5) e `D105CacheVazioDosRelatoriosTest`
(2), verdes nos dois bancos. **Vinte e cinco mutantes, um de cada vez, restaurados pelo sha1 e conferidos no
`git status`, todos vermelhos pelo caso deles:** no D202, a entidade do `id` do POST nas cinco telas e o
recorte arrancado de produtos, serviços e pacotes (8), e o filtro de `status` trocado nas duas listas da tela
de serviços (2, o caso das listas); no D204, o usuário do POST, o POST cru, a franquia, o
teto e o `select` de grupos de antes (5); no D206, a despesa do POST, o POST cru e o recorte de
`selecionarSaida` (3); no D207, a guarda do estorno (1); no D105, os cinco caminhos de antes, pelo ensaio (os
dos atendimentos e da evasão também pelo caso em processo), e o parcial das mensagens arrancado da evasão (6)
-- e o ensaio já tinha ficado vermelho antes do conserto da evasão. O ensaio do D105: 5 de 5 no MySQL de
contrato (montado antes, sozinho) e no PostgreSQL de contrato, resíduo zero, `local.php` de volta ao
`franquiabv_dev` depois de cada rodada; as sessões do ensaio saem pelo "Sair". **A suíte inteira, verde nos dois:
MySQL 2341 testes e 17051 asserções, PostgreSQL 2341 e 17049, 16 pulados nos dois.** Nenhuma linha entrou no
`fixtures.sql`: as linhas alheias nascem dentro da transação de cada caso. Títulos: D202, D204 e D206
CORRIGIDO; D105 e D207 continuam PARCIAL.

**Achados no caminho:**

- **O D105 escrevia o quinto morto errado:** `/mod-relatorios/vendas/index` não era "sem alias" nem 404 -- o
  alias `vendas` é o do PDV e vale em todos os prefixos, o controller dele não tem `indexAction`, e o padrão do
  Laminas pede um molde que não existe: **500**. É de uma classe, registrada como **D424** (15 controllers do
  ERP sem `indexAction`; medidos, dois caminhos).
- **Duas telas do grep de `->update(...)` com o POST** ficaram fora, porque a rota delas também não recorta e o
  campo oculto não alcança nada a mais: unidades (já desenhada como o U10) e clientes (o cliente é da rede).
- **O `conferir-cobertura-por-par` ficou vermelho com o caso novo:** a catraca dos falsos positivos desceu de 25
  para 23, porque o caso do D202 chama as listas de área e de etiqueta da tela de serviços. A chamada sozinha
  não era cobertura (os dois `select` não conferem a lista), e o caso ganhou um que afirma o que elas trazem,
  com um mutante em cada repositório; só então os dois pares saíram da lista, e a descida foi registrada no
  D177.

**O que não foi feito:** o motivo do estorno (o resto do D207) pede a coluna da P5 do desenho de Contas a
Pagar -- esquema e decisão do dono, já no lote; os seis links de `MORTOS_CONHECIDOS` (o resto do D105) esperam
decisão; o ensaio novo não entrou no CI (a recomendação é o job `caracterizacao-postgres`, ao lado do
`ensaiar-sessao-do-erp.php`); o D424 só foi registrado. O front não foi tocado.

**Os portões:** o `conferir-tudo.sh`, por último e sozinho, com saída 0 -- 53 portões ok (o `cobertura por par`
com a descida de 25 para 23, o `links mortos`, o `recorte por unidade` e os testes do front entre eles) e o
vermelho conhecido do D50; e, depois da última edição dos documentos, `contar-debitos` (232 fechados, 90
abertos com o D424), `conferir-debitos-repetidos` (nenhum par novo) e `conferir-referencias-docs`.

**A revisão da rodada achou três furos de prova, e os três foram fechados no mesmo dia:**

- **O ensaio do D105 só devolvia o cenário no caminho feliz.** `pedir()` e `entrar()` saíam por `exit(2)`
  dentro do `try`, depois das primeiras escritas, e em PHP o `exit` não roda o `finally`. Medido no MySQL de
  contrato, contra um endereço que não responde: ficaram os cinco privilégios do gerente (de 10 para 15) e a
  pesquisa 90008200. Agora as duas lançam, o fim sai com 2, e o banco volta antes do "Sair", que tem `try`
  próprio. Dois mutantes (o `exit` de volta em cada uma; a senha errada de propósito para o de `entrar()`)
  deixam o resíduo; com o conserto, saída 2 e resíduo zero. O ensaio inteiro de novo: 5 de 5, resíduo zero,
  nos dois bancos de contrato.
- **A lista de áreas da tela de serviços não distinguia a desativada:** o cenário não tinha nenhuma, e o filtro
  de `status` **arrancado** ficava verde. O caso ganhou a área desativada 90202006; o arrancado fica vermelho
  nas áreas e nas etiquetas, nos dois bancos (D177).
- **O ramo de cadastro da identificação (D206) não tinha caso que separasse o formulário filtrado do POST
  cru:** o POST cru deixava os 97 casos de saídas verdes. O segundo caso manda a descrição com marca e espaços
  e confere a filtrada; o mutante fica vermelho nos dois bancos.

Cinco mutantes novos, um de cada vez, restaurados pelo sha1, todos vermelhos. Os casos do item e os vizinhos:
139 verdes no MySQL e 139 no PostgreSQL. Só testes, o ensaio e um comentário do `conferir-cobertura-por-par`
mudaram; o código de produção e o `fixtures.sql`, não. O `conferir-tudo.sh` de novo, no host e sozinho: saída
0, 53 portões ok e o vermelho conhecido do D50; e `contar-debitos` (232 fechados, 90 abertos),
`conferir-debitos-repetidos` e `conferir-referencias-docs` com saída 0 depois desta última edição.

**O ciclo inteiro das duas rodadas (A e B), juntas:** a caracterização em 2341 testes nos dois bancos (17053 e
17051 asserções, 16 pulados), o contrato do aplicativo 67 de 67 nos dois, os 13 ensaios por HTTP nos dois bancos
-- o `ensaiar-links-consertados-do-erp.php` entrou na lista do ciclo --, o `conferir-tudo.sh` com 53 portões ok e o
vermelho conhecido do D50, e 1584 testes no front.

## 06/10/2026 -- POSTGRES-COMPAT: os seis `DATE()` da agenda por cliente viram faixa, e a varredura do ERP por booleano calculado em coluna inteira dá zero (D128 corrigido, D189 parcial, D425 corrigido, D426 ao lote, D427 medido)

**O que mudou para quem usa:** quase nada que se veja, e é de propósito. O check-in da agenda antiga, a
comanda aberta pela agenda, o fechamento do pagamento, os horários marcados do cliente e os próximos
agendamentos do aplicativo e da API perguntam o dia do cliente como faixa (`[00:00:00, 00:00:00 do dia
seguinte)`), e não mais como `DATE(data)` -- a mesma resposta, numa forma que o PostgreSQL pode servir por
índice. Uma diferença só: **uma data que não existe no calendário (31/02) volta a dar erro**, como dava
antes de 21/09; desde então, a grade e a agenda do aplicativo respondiam por 3 de março, em silêncio (D425).
E o portão do D82 passou a acusar o booleano **calculado** ligado a coluna inteira, o defeito do D128 que só o
CI tinha pego, e, na revisão, também o literal `true`/`false` escrito na DQL contra coluna que não é booleana.

**Provado:**

- **D189, a equivalência na cópia, só `SELECT`, nos dois bancos:** a tabela inteira (4.000.085 linhas no
  MySQL, 4.000.084 no PostgreSQL) sem nenhuma linha fora da faixa do próprio dia; 369 dias (viradas de mês de
  2023 a 2026, os 111 dias com linha à 00:00:00 e vizinhos, as trocas de horário de verão, 40 sorteados),
  4.599 pares (dia, unidade) e 148.981 linhas na forma antiga contra a nova, **zero divergências** nos dois;
  32 dias de "de tal dia em diante", zero; e os seis métodos de verdade, o arquivo do HEAD contra o de hoje,
  em 4.059 triplas (cliente, unidade, dia) e 24.790 chamadas -- 660.782 linhas no MySQL, 630.686 no
  PostgreSQL: os mesmos ids em todas; a mesma ordem em todas no MySQL, e no PostgreSQL em todas menos 36. A única diferença é de entrada: o texto vazio, que era erro e agora é vazio, e que nenhum
  chamador manda (registrado no D189, não ajustado).
- **D426, a ordem que mudou, medida até o fim e não ajustada:** 8 das 36 são empates do `ORDER BY age.data`;
  28 são da comanda aberta pela agenda (`servicosMarcadosPorClienteData`), que não tem `ORDER BY`. Cada forma é
  estável consigo mesma (28 de 28), e quem muda a ordem é o plano. O MySQL de produção dá a ordem do id em 845 de
  845 chamadas, e nenhuma das duas formas a reproduz no PostgreSQL. Como a ordem decide qual serviço gasta a
  sessão do pacote (e a comissão vem do item), a pergunta foi ao lote, com a recomendação de escrever
  `ORDER BY age.id` -- a produção de hoje.
- **D189, os casos:** `DiaDoClienteNaAgendaTest`, 10, com horário às 00:00:00 e às 23:59:59 na virada do mês,
  verdes nos dois bancos. **Oito mutantes, um de cada vez, restaurados pelo sha256, vermelhos no MySQL e no
  PostgreSQL** -- e o que devolve o `DATE()` a um método fica vermelho só no caso da FORMA, que lê o SQL que
  chega ao banco: os de resultado passam sobre o `DATE()`, porque é a equivalência.
- **D128:** o portão `conferir-booleano-em-coluna-inteira.php` julga a expressão ligada (comparação,
  lógica, `!`, `(bool)`, `empty`/`isset`/`is_*`, ternário de booleanos, variável, parâmetro `bool`), cruza com o
  tipo da coluna no `postgres/schema.sql` (517 ligações, zero discordâncias com o mapeamento) e segue um salto
  até quem chama, pela posição e pela chave. **Zero sítios no ERP.** Sete mutantes: os seis que deviam acusar
  acusam (o caso do D128 e o do D82 de volta entre eles) e o controle `(int) (...)` passa; o portão do HEAD,
  com o caso do D128 de volta, ficava verde. E os pontos cegos foram medidos: os 350 valores de quem chama que
  são retorno de método são `getId()`, `format()` e `fromQuery()`; o `pdo_pgsql` recusa `PARAM_BOOL` em coluna
  inteira e aceita `PARAM_INT` (o setter de entidade passa); as 7 chamadas de SQL nativo não ligam booleano.

**A suíte inteira:** MySQL 2351 testes e 17088 asserções, PostgreSQL 2351 e 17086, 16 pulados nos dois --
números da rodada da revisão (abaixo), a primeira do MySQL sobre a versão entregue do teste novo. **Os
portões:** o `conferir-tudo.sh`, por último e sozinho, com saída 0 -- 53 portões ok (o `booleano em coluna
inteira` estendido, o `cobertura por par` e os testes do front entre eles) e o vermelho conhecido do D50; e, depois
da última edição dos documentos, `contar-debitos` (234 fechados, 91 abertos com o D426),
`conferir-debitos-repetidos` (nenhum par novo) e `conferir-referencias-docs`, com saída 0. O `conferir-cobertura-por-par` desceu de 146 para 142 (os quatro métodos por cliente
ganharam caso; registrado no D177) e, na revisão, subiu para 148 por outro motivo: a medida passou a ler os
testes sem comentários. O front não foi tocado.

**O que não foi feito, e por quê:** o `ALTER` do índice `(colaborador, data)` no MySQL de produção, a leitura do
`innodb_buffer_pool_size` de lá e o índice de `filtrarClientesPrimeiraVisita` são da janela do corte (o resto do
D189). A borda das 23:59 só foi provada pelos casos: a cópia não tem nenhuma linha depois das 23:59. Os ensaios
por HTTP do ciclo não rodaram; o contrato do aplicativo, que passa pelos métodos trocados, rodou na revisão
(abaixo).

**A revisão de 06/10/2026, oito achados, todos de gravidade baixa e todos confirmados no código:**

- **O contrato passa pelos métodos trocados, e não tinha rodado.** A primeira versão deste registro dizia que
  nenhum teste por HTTP os alcançava. Errado: `ApiV4Test::testAgendaAutenticada` e
  `AplicativoApiTest::testClienteAgendaAutenticado` chamam `selecionarProximoAgendamentosCliente`, e
  `testAgendaSelecionar` e `testAgendaSelecionarV2ComDados` passam pelo `diaInteiro()` do D425. Rodado: 67 de 67
  no MySQL e no PostgreSQL; com o `diaInteiro()` recusando toda data (um mutante), os quatro ficam vermelhos nos
  dois bancos.
- **O irmão `MONTH()`/`YEAR()` da agenda virou o D427, medido:** `totalAgendamentoPorMesAnoColaborador`, na rotina
  do painel de colaboradores, lê no PostgreSQL o histórico inteiro do colaborador (28.664 páginas contra 246 da
  faixa, o mesmo total). Não foi trocado: no mesmo laço da rotina há outros cinco métodos com `MONTH()`/`YEAR()`, e
  a família tem 117 linhas no `module/`.
- **O portão do D82/D128 passou a ver o literal na DQL** (`ven.status = true`, que os dois portões deixavam
  passar): 31 literais, todos em coluna booleana; três mutantes vermelhos e o controle verde; o portão do HEAD e o
  irmão do D81, verdes com o primeiro.
- **O `conferir-cobertura-por-par` passou a ler os testes sem comentários.** O cenário da revisão -- as chamadas
  do teste novo trocadas, o docblock intacto -- deixava o portão verde; agora fica vermelho. Os quatro da agenda
  continuam mencionados pela chamada; seis outros pares só eram "mencionados" em comentário (142 -> 148), cinco
  falsos positivos entraram e dois saíram (23 -> 26), e uma coincidência sumiu.
- **Textos corrigidos:** o comentário do `diaInteiro()` (a data impossível É caminho, pelo `stringDate()`, e dá
  500 -- o erro de antes de 21/09); o D177, que dizia "na ordem" sobre o caso da comanda, que ordena antes de
  comparar; e o D425, que listava a linha 663 (data já gravada) e esquecia a grade.
- **A suíte inteira do MySQL rodou de novo,** depois da última edição do teste novo (o sha256 do arquivo igual
  antes e depois): 2351 testes, 17088 asserções; e o PostgreSQL, 2351 e 17086. Os 26 vizinhos (DiaDoCliente,
  DiaDaAgenda e D190), verdes nos dois.

**O ciclo inteiro, depois da revisão:** a caracterização em 2351 testes nos dois bancos (17088 e 17086 asserções,
16 pulados), o contrato do aplicativo 67 de 67 nos dois, os 13 ensaios por HTTP nos dois bancos, o
`conferir-tudo.sh` com 53 portões ok e o vermelho conhecido do D50, e 1584 testes no front.

## 06/10/2026 -- PORTOES-E-INSTRUMENTOS: o portão do desempate lê cada ramo e o termo dos auxiliares, o inventário segue a escrita até o serviço, o medidor de relatórios abre o XLS legado e faz os dois passos do TXT do NPS, e os ensaios irmãos devolvem o que o login grava (D47 e D316 corrigidos; D61, D113 e D199 parciais; D428 e D429 registrados)

Nada disto muda uma tela: são os instrumentos que dizem se as telas estão certas, e cada um enxergava menos
do que dizia.

- **D47 -- `conferir-ordenacao-empates.php`:** lia só o último `ORDER BY` de cada listagem paginada, e só
  até a primeira aspa; o ramo escrito com `Ordenacao::comoMysql()` ou `nulosPorUltimo()`, e os ramos de
  coluna clicável antes do último, passavam sem leitura. Agora lê cada `ORDER BY` pelo tokenizador,
  seguindo a concatenação, com o primeiro argumento dos dois auxiliares como o termo ordenado: 41 listagens,
  54 ramos, a árvore limpa verde. Numa cópia fora do repositório, cinco ramos sem desempate (dois
  `comoMysql`, a ordenação só pelo auxiliar, um `direcao()` no meio, um que termina em variável) ficam
  vermelhos no portão novo e verdes no de antes; e o código novo arrancado, um pedaço de cada vez, devolve
  o verde (só o último ramo) ou o falso vermelho na árvore limpa (sem seguir a concatenação).
- **D199 e D316 -- `inventariar-acoes.php`:** o `muta` ganhou o terceiro sinal -- a chamada ao serviço ou ao
  repositório que escreve, por ponto fixo -- e o `recebe_post` passou a ler a forma da guarda
  (`formaDaGuardaPost`, a terceira cópia, e o gerador recusa rodar se as três divergirem). **198 ações
  mutavam; agora 232.** As 34 novas, conferidas no código, gravam de verdade: 25 rotinas do cron e nove
  telas, entre elas `vendas/novo` e `vendas/comanda` (o resto do D316). `recebe_post` não mudou em linha
  nenhuma, e os dois medidores que leem o CSV escolhem as mesmas ações (31 e 295). Colunas novas no fim do
  CSV: `escreve_por_chamada` e `guarda_post`. Falsificado numa cópia: a remoção que volta a aceitar GET sai
  "muta" pelo serviço, e a guarda de sinal trocado sai "não recebe POST"; o gerador de antes errava as duas.
- **D428, novo:** o terceiro sinal achou a lista de vendas realizadas rodando a consolidação de NPS da
  rotina da noite a cada abertura -- para o administrador com a unidade em branco, a da rede inteira. A
  pergunta foi ao lote.
- **D113 -- `medir-relatorios-no-postgres.php`:** o XLS legado de atendimentos (OLE2) é aberto e lido célula
  a célula, e o TXT do NPS faz o primeiro passo (o detalhe da unidade, na mesma sessão) antes de ser pedido;
  o cenário ganhou uma pesquisa de NPS respondida (março de 2023, contabilizada), e a marca do
  `fixtures.sql` foi regravada. **Com marca de dado: de 23 para 25 de 30; sem prova, de 3 para 1 -- o
  Consultor Visagista, adiado.** Três mutantes vermelhos: o primeiro passo arrancado, o XLS procurado nos
  bytes, a pesquisa apagada do banco.
- **D61 -- os ensaios de recusas e de fluxo:** guardam o `token_web` dos usuários da fixture e o maior id de
  `usuarios_acessos` antes do primeiro login, e os devolvem num `finally`; o de fluxo deixou o `exit` pela
  exceção `FluxoParou` e passou a abrir o banco. Resíduo ZERO nos dois bancos, e no MySQL também com a
  senha errada, o servidor que não responde, a rodada parcial e o fluxo parado no passo 5 depois do login.
  Três mutantes: sem a devolução, cada um acusa o token e os acessos; com o `exit` de volta no passo, o
  resíduo fica calado no banco. Resta a variante `+DDI` da checagem de duplicata, fora deste item.

**Os testes:** a caracterização inteira, com a fixture nova, em 2351 testes nos dois bancos -- MySQL 17088
asserções, PostgreSQL 17086, 16 pulados --, os mesmos números de antes. **Os portões:** o `conferir-tudo.sh`,
por último e sozinho, saída 0 -- 53 ok e o vermelho conhecido do D50; e, depois da última edição do registro,
`contar-debitos` (233 fechados, 65 parciais, 92 abertos) e `conferir-debitos-repetidos` (nenhum par novo).
**O front não foi tocado.**

**O que não foi provado:** o D428 foi lido no código, e não medido por HTTP (nem o tempo do pedido com a
unidade em branco); o medidor de relatórios na ordem do CI rodou com a marca do XLS ainda por escolher (24 de
30), e a marca no lugar foi medida noutra montagem (a concessão, as rotinas e o medidor: 25 de 30); o
inventário não vê o serviço que chega por parâmetro (`conveniosAtualizarVencimento`) nem conta a escrita em
arquivo; e o ensaio de recusas interrompido DEPOIS dos logins só foi medido pela rodada parcial (saída 3) --
a senha errada e o servidor que não responde o param antes do primeiro login. *(Corrigido pela revisão,
abaixo: a rodada parcial não interrompe nada, e a interrupção depois dos logins foi medida agora.)*

### A revisão do item (06/10/2026)

A verificação confirmou onze achados, nove distintos (dois vieram em dobro); todos conferidos no código, e
todos tratados:

- **D47, um defeito vivo que o portão novo deixava passar:** ele aceitava o `.id` de qualquer alias, e a
  lista de devedores (`ClienteDebitoRepository::listagemIndex`, do ERP antigo e da API nova) desempatava por
  `ven.id` sobre a raiz `deb` -- no dev, 32 débitos empatados em 15 vendas, em unidades de mais de uma
  página. A listagem ganhou `, deb.id DESC`, com o caso `DevedorTest::testNaMesmaVendaODebitoMaisNovoVemPrimeiro`
  (vermelho sem o conserto nos dois bancos). O portão passou a exigir o `.id` **da raiz** (seguindo o auxiliar
  que monta o `FROM`), a montar o pedaço da concatenação sem comentário, e a ler todo `ORDER BY` de cada
  literal. Quatro mutantes de árvore e cinco do portão, numa cópia; árvore limpa verde.
- **D199, dois furos do inventário:** a guarda de sinal trocado tirava o segundo sinal do `muta` (agora ele é
  o texto, e a forma decide só o `recebe_post`); e as três cópias de `formaDaGuardaPost()` eram iguais no
  texto e diferentes no resultado, porque cada uma chamava o `semComentarios()` do seu arquivo. A função
  faz a forma canônica sozinha, o gerador recusa a cópia que chame função que não seja do PHP e roda seis
  amostras. O CSV e o `.md` regerados saíram idênticos byte a byte.
- **D61, o ensaio de recusas:** a rodada que limpa a sobra de uma rodada morta saía com 1 por culpar o token
  do administrador do ensaio (agora ele fica fora da foto); e a interrupção depois dos três logins foi medida
  de fato, com cópias do ensaio fora do repositório: exceção depois dos logins, saída 2 e ZERO; morta por
  SIGKILL e a rodada seguinte da 1ª versão, saída 1 falsa; a da revisão, ZERO. O registro passou a listar os
  dez roteiros que ainda entram no ERP sem devolver o que o login grava -- o "o que fica" dizia que só
  restava o `+DDI`.
- **D316:** o "O que ficou" tinha um item sem dono (duas abas do PDV, duas vendas temporárias), e o
  "CORRIGIDO" do título era desmentido pelo corpo. Virou o **D429**, registrado.

**Os testes da revisão:** a caracterização inteira, em série -- MySQL 2352 testes, 17089 asserções; PostgreSQL
2352, 17087; 16 pulados nos dois (um caso a mais, o dos devedores). O ensaio de recusas inteiro, com o banco
montado do zero: 201 de 201 e saída 0 no MySQL e no PostgreSQL, resíduo ZERO. **Os portões:** o
`conferir-tudo.sh`, sozinho e depois do banco de contrato remontado, saída 0 -- 53 ok e o vermelho conhecido do
D50; e, depois da última edição do registro, `conferir-referencias-docs`, `contar-debitos` (233 fechados, 65
parciais, 93 abertos) e `conferir-debitos-repetidos` (nenhum par novo). O front não foi tocado.

**O que a revisão não provou:** os dez roteiros que ainda não devolvem o que o login grava ficaram como
estavam (fora do item; listados no D61); a rodada morta por SIGKILL deixa o resíduo dela, que nenhuma rodada
seguinte sabe devolver -- remontar o banco segue sendo o que zera; e o D429 foi lido, não reproduzido.

**O ciclo inteiro, depois da revisão:** a caracterização em 2352 testes nos dois bancos (17089 e 17087 asserções,
16 pulados), o contrato do aplicativo 67 de 67 nos dois, os 13 ensaios por HTTP nos dois bancos -- os dois
ensaios mudados inclusive --, e 1584 testes no front. O `conferir-tudo.sh` da primeira rodada saiu com 1 só pelo
portão "testes do front", com o front intocado e a etapa do front, logo depois, verde nos 1584; repetido sozinho,
saiu com 0: 53 portões ok e o vermelho conhecido do D50. É o arrastar instável sob carga, item 26 da fila.

## 06/10/2026 -- DECISOES-NO-REGISTRO-E-NO-CORTE: as 29 respostas do dono no registro, e as da janela no roteiro do corte

**Para quem usa, nada muda hoje:** nenhum código da aplicação foi tocado, e nada roda na produção. O que
mudou é o que está escrito sobre o que vai acontecer.

- **O registro diz o que foi decidido.** Cada um dos 30 débitos que as 29 perguntas citam ganhou um
  parágrafo "Decidido pelo dono em 06/10/2026", com a opção escolhida e a observação dele quando houve
  (a 1, o sorteio, espera a F3 de Sorteios; a 10, o ViaCEP). As que pedem código (as 3, 4, 14, 15, 16,
  17, 20 a 24 e 27 a 29) dizem que a implementação está na fila, e o título não mudou; a 18 e a 25 ficam
  condicionadas à P5 de Contas a Pagar e à P13. Só dois títulos ganharam "DECIDIDO", os que não têm mais
  nada de pé: o D126 (cancelar entrada sem piso fica como está) e o D346 (o teto de rede vale para tudo).
- **O roteiro do corte leva as decisões da janela.** No passo 3, depois da limpeza e antes do dump: o
  inativo de cada par de login (e de e-mail) que só difere pela caixa ganha `.inativo<id>` e a caixa de
  todos baixa (a 8); as datas de nascimento com hora perdem a hora, se a réplica der mais que zero (a 12);
  a migração do Líder (a 11, e a linha do passo 0 deixou de dizer "espera a sua decisão"); e o dicionário
  de CEP é esvaziado (a 10). No passo 7 o `cron.token` ganha o valor novo da rotação, e no passo 9 o
  crontab volta com o token no cabeçalho, `curl -fsS -m` e `MAILTO`, por um roteiro que não imprime o
  token (a 5). O passo 8 diz que a suíte saiu da janela (a 7). Antes da janela, a lista do que se mede
  na réplica ganhou o 7 (as datas de nascimento) e diz que o login com acento se decide pela medida do 5
  (a 9).
- **Dois roteiros SQL:** `scripts/medir-logins-com-maiuscula.sql` ganhou os dois `UPDATE` do renomear e
  as listagens 4b (o nome novo cabe na coluna e não é de outro) e 4c (os ids de quem tem mais de um ativo
  no mesmo login ou e-mail); `scripts/medir-nascimento-com-hora.sql` é novo, só contagens, com os dois
  `UPDATE` comentados no fim.

**Provado:** os roteiros SQL da janela -- os quatro `UPDATE` dos logins, os dois do nascimento, o
`limpar-dicionario-cep.sql` e a migração do Líder --, tirados dos próprios arquivos, em bancos
descartáveis no MySQL 8.4.11 e no 8.0.46 (o da réplica), com dados inventados: as linhas certas na primeira
vez, nada na segunda, a mesma saída nos dois; o nome novo maior que a coluna e a data zero recusados
inteiros pelo modo estrito, e acusados antes pelas medidas. As duas medidas, só leitura, no
`franquiabv_dev`. O bloco do passo 9, tirado do procedimento, sobre um crontab inventado, com bash e gawk
e com dash e mawk: o mesmo crontab byte a byte, e a conferência acusando a linha fora do molde, o token
vazio e o `MAILTO` no molde; a conta do log, refeita sem o `token=` do filtro. O portão do passo 0, saída
0, e falsificado em duas cópias do procedimento, saída 1 nas duas. `contar-debitos` (233 fechados, 65
parciais, 93 abertos -- os mesmos), `conferir-debitos-repetidos` (nenhum par novo) e
`conferir-referencias-docs`.

**Não provado:** nada disto rodou na produção nem na réplica -- o passo 3 de verdade, a forma real das 25
linhas do crontab e a entrega do e-mail do cron são da janela. **Quatro dúvidas foram ao lote**, sem
segurar a janela: como o cache de CEP volta a encher (sob demanda, como hoje, ou pré-carga), para quem vai
o aviso do `MAILTO`, se o renomear alcança também os logins repetidos exatos (o índice único do D295), e a
conta de quem chama as três `unidadeVendas*`, que a resposta 6 espera e nenhum roteiro faz.

### A verificação, no mesmo dia: oito achados, e as provas saíram do rascunho

**Para quem usa, nada muda hoje** -- outra vez só roteiro e registro. Mas dois achados eram defeitos que
o passo 3 levaria à produção.

- **O passo 3 apagava as UFs do ERP antigo (D430).** `limpar-dicionario-cep.sql` apagava `estados`, e o
  select obrigatório "*Estado" do cadastro de cliente (e do PDV, da agenda, de fidelidade, de unidades,
  de usuário e do filtro dos relatórios) sai dela: no primeiro dia, o primeiro cliente de cada UF não se
  cadastrava pela tela antiga. O desenvolvimento tem 4 UFs desde 31/08. O roteiro passou a repor as 27
  de `RegraDoCadastroDeCliente::UFS`, a lista da tela nova -- e isso pede a confirmação do dono (lote,
  ponto 5): a outra saída é não apagar `estados`.
- **O crontab entrava com a conferência vermelha.** O bloco do passo 9 imprimia as contagens e instalava
  na linha seguinte: com o `cron.token` vazio, as 25 linhas iam com `Bearer` vazio. Agora são dois
  blocos, e o segundo só roda `crontab` se a conferência bater.
- **O renomear alcançava o par de acento.** Os `UPDATE` agrupavam pela collation, que iguala "paulo" e
  "páulo"; a decisão 8 é sobre a caixa. A chave passou ao minúsculo byte a byte.
- **A pergunta 9 tinha virado regra.** O registro e o roteiro davam como decidida a regra da
  recomendação ("poucos, aviso; muitos, sem acento"); a resposta foi só "decidir depois da medida".
  Corrigido, e o ponto 6 do lote pergunta se o dono quis dizer a regra.
- **O que ficou para trás:** os resumos do topo do D4, D74, D124, D194, D315 e D349 e as remissões do
  D189, D190 e D319 ainda davam como pendente o que estava decidido, e a P1 das telas não tinha a
  resposta (c); o ensaio geral não rodava nada do passo 3 novo, e o tempo do `DELETE` de 758 mil linhas
  não entrava na conta da janela; e nenhum portão ficava vermelho se uma decisão saísse do procedimento.

**Provado, agora no repositório:** `php scripts/ensaiar-sql-do-passo-3.php` monta um banco descartável
com as tabelas do `mysql.sql`, roda os quatro roteiros como o passo 3 os roda e confere cada resultado
(4, 1, 5 e 2 logins; 2 e 1 nascimentos; o Líder; 0, 0, 0 e as 27 UFs; nada na segunda vez), e confere
que o procedimento ainda manda rodá-los -- verde no 8.4.11 e no 8.0.46, e vermelho em dez cópias
mutadas (o dicionário de antes, a chave pela collation, sem o renomear, sem o `UPDATE` dos sócios, o
Líder comentado, o comando do dicionário tirado ou comentado, a linha do Líder de volta ao texto de
antes ou começando por "Aplicar", e o nascimento sem citação). `sh scripts/ensaiar-crontab-do-passo-9.sh`
roda os dois blocos do passo 9 sobre um crontab inventado: o certo instala, e o token vazio, o `MAILTO`
no molde, quatro formas fora do molde e o `token=` fora da URL não -- com o bloco de antes, três
instalavam; vermelho também com cada condição tirada, com o `crontab` fora do `if` e sem o `exit`. Os
dois entraram no `conferir-tudo.sh`. O ensaio geral ganhou a fase `passo3` (o dicionário e o Líder),
que não rodou num ensaio inteiro. O `DELETE` do dicionário no tamanho de produção, com linhas
inventadas: 3,7 s (8.4.11) e 5,2 s (8.0.46 em 4 CPUs) -- a ordem de grandeza, e não o número da
produção.

**Os portões, depois da revisão:** o item não toca a aplicação, os testes nem o front, e por isso rodou só o
`conferir-tudo.sh`, sozinho: 55 portões ok -- os dois ensaios novos do corte e os testes do front entre eles -- e o
vermelho conhecido do D50.

## 06/10/2026 -- PROVEDORES-HOMOLOGACAO: a homologação e o laboratório do BVIP também deixam de alcançar os provedores de produção (D360 parcial, D431 registrado)

**Para quem usa:** nada muda na máquina de quem programa nem no teste. No BVIP, a homologação e o
laboratório de migração passam a resolver Asaas, WhatsApp, OneSignal, SMS, Yungas, Zoomtec e o SMTP de
produção para o loopback dentro do contêiner do PHP (IPv4 e IPv6), como o dono decidiu na pergunta 4; o
ViaCEP segue alcançável, e o `docker-compose.yml`, que a produção talvez use, não mudou. Vale no próximo
`up` de cada um, que o dono faz: na homologação, `docker compose up -d app`, `docker compose restart nginx`
e o `conferir-provedores-no-loopback.php --conteiner` lá dentro; no laboratório, o comando novo da
`docs/ambiente-homologacao.md`, com o `-f docker-compose.desenvolvimento.yml`. Até a saída 0 lá, as
unidades 90001150 e 90001200 seguem fora de ensaio na homologação.

- **O que mudou:** os 18 `extra_hosts` no `app` do `docker-compose.homologacao.yml`; o `-f` do corte no
  comando do laboratório (e não o arquivo da homologação, que traria tetos e `restart: always`); o
  portão compara a lista nova com `HOSTS_MORTOS`, exige o `-f` em cada comando do laboratório e que o
  arquivo do desenvolvimento só corte, porque ele agora sobe num servidor de IP público.
- **Provado:** pelo `docker compose config`, homologação (pelos dois caminhos), teste e laboratório com os
  nove nomes no `127.0.0.1` e no `::1`, sem repetir entrada; a configuração do teste e a local idênticas às
  de antes (mesmo sha256); num app descartável, o `--conteiner` 0 com a configuração da homologação e com a
  do laboratório, e 1 com só o `docker-compose.yml`; dezenove mutantes com a saída esperada; os 66 vizinhos
  do D360 verdes nos dois bancos; `conferir-tudo.sh` com 55 portões ok e o vermelho conhecido do D50.
- **Achado no caminho (D431):** o laboratório, como a documentação o ensina, divide com a homologação as
  portas do `.env` e o `local.php`, e os comandos seguintes da seção vão à homologação. Registrado, não
  consertado.
- **A revisão:** o portão lia o `extra_hosts` do arquivo inteiro, e o bloco movido do `app` para o `nginx`
  da homologação ficava verde com o app sem corte -- agora as três listas são lidas do `app`, e só dele; e
  ele não exigia o `docker-compose.homologacao.yml` no `COMPOSE_FILE` do modelo do BVIP -- agora cada
  modelo tem a lista exata, na ordem. O título do D360 deixou de dizer "o BVIP cortado" (vale no próximo
  `up`), o cabeçalho do arquivo do desenvolvimento deixou de mandar mexer no `.env.example`, e três textos
  que ainda diziam que a homologação não corta foram reescritos (só comentário: as configurações dos
  quatro ambientes têm o mesmo sha256 de antes). Os mutantes novos, num espelho no rascunho: 0 antes do
  conserto e 1 depois, e cada parte do conserto é quem pega o seu.
- **Resta no D360:** a fixture com o WhatsApp ligado e sem chave, com a F0 do WhatsApp.

**Os portões, depois da revisão:** o item não toca a aplicação, os testes nem o front, e o ambiente local tem a
mesma configuração de antes (o mesmo sha256); rodou o `conferir-tudo.sh`, sozinho: 55 portões ok e o vermelho
conhecido do D50.

## 06/10/2026 -- ERP-BOTOES-E-DATAS: a barreira de origem nos quatro botões do ERP que escrevem por POST, e o `dataValida` que derrubava o PHP 8 consertado no ajudante (D420 corrigido, D315 parcial, D432 registrado)

**Para quem usa:** nada muda na tela. O ativar e o desativar dos convênios e das campanhas de cupom, o
desativar usuário da lista antiga e o desconectar o WhatsApp da unidade passam a recusar com 403 o POST que
vem de outro site ou de outro subdomínio do mesmo site, como o "Autenticar como" já fazia (a resposta 3 do
dono); o clique na tela é da mesma origem e passa. E a data com texto que não é número, num pedido montado à
mão, recebe a frase de cada tela -- no aplicativo, "data nascimento inválida" -- no lugar do 500 (a resposta
29); as datas de verdade respondem como antes.

- **O que mudou:** a `OrigemDoPedido` (o D290) em `ConveniosController::ativarRemoverAction`,
  `CuponsController::ativarRemoverAction`, `UsuariosController::ativarRemoverAction` e
  `WhatsAppController::desconectarAction`, depois da guarda de POST e antes de ler o alvo; e
  `DataHora::dataValida` com `ctype_digit` nas três partes e `is_string`, as quinze chamadas de uma vez.
  Quem chama os quatro botões é só a tela do ERP (procurado em `module/`, `public/`, `frontend/src` e
  `scripts/`); o ensaio dos cupons manda só o cookie e passa.
- **Provado:** `BotoesDoErpSoDaMesmaOrigemTest` (20) e `D420DataValidaTest` (27, com os dois da revisão),
  verdes nos dois bancos;
  os vizinhos, 922 execuções em 66 arquivos, verdes nos dois bancos (10 pulados por pedirem o histórico da
  produção); oito mutantes, um de cada vez, cada um com o seu vermelho (as quatro barreiras arrancadas, a
  barreira que só recusa `cross-site`, a do usuário depois do `mudarEstado`, o ajudante sem o `ctype_digit`
  -- 11, com o `TypeError` nas duas chamadas despachadas -- e sem o `is_string`); por HTTP, contra o banco de
  contrato, `/api/clientes/novo` com `aa/bb/cccc` responde 200 com a frase, e 500 com o ajudante de antes, e
  o ensaio dos cupons 59 de 59; a suíte de contrato 67 de 67 nos dois bancos, sem instantâneo mudado.
- **Não visto no navegador:** os quatro cliques (as telas não mudaram); o 403 por HTTP desses quatro também
  não foi medido -- é a leitura que o caso 16 do ensaio da sessão mede no "Sair".
- **Fica:** no D315, o caminho do D127, com o dono. E duas perguntas no lote: a barreira nas outras
  escritas por POST do ERP antigo, que a pergunta 3 não contava; e o `dataValida` conferir a forma
  `dd/mm/aaaa` (o D432).

**A revisão do item** (seis achados, todos confirmados no código):

- **As duas perguntas do lote contavam menos do que falta, e foram reescritas.** A da barreira levava só as
  19 da caixa de remover por POST e da despesa, e o próprio D315 dizia que as escritas da agenda antiga
  tinham ido junto. Lido o código dos 57 controllers do ERP antigo, 135 ações aceitam POST sem a barreira e
  sem o token, e perto de 90 escrevem (por heurística, pelo nome) -- entre elas a cota-parte de comissão, o
  sócio da unidade, as parcelas de contas a pagar, o PDV e as cinco da agenda, fora da caixa. A pergunta
  traz agora a classe inteira e a barreira num lugar só (a entrada do `CrudController`, com o módulo do
  aplicativo de fora: ele responde `Access-Control-Allow-Origin: *`). A do D432 falava só do cadastro de
  cliente: o fechamento da agenda antiga (as três formas, nos dois bancos) e o relatório de caixas (as
  três no PostgreSQL, `001/2/2026` no MySQL) também dão 500, medido em processo, cada forma num processo
  próprio -- a primeira redação dava as três como medidas, e só a primeira tinha sido.
- **O D315 e o D420 ainda diziam "a implementação está na fila"** e o token como pergunta: riscados, com a
  data do conserto.
- **O `D420DataValidaTest` não prendia o `ctype_digit` do mês nem o do ano:** nenhum caso tinha letra só
  num deles, e o `(int)` engolia o texto. Entraram `01/1a/2026` e `01/02/2a26`; os dois mutantes novos,
  sem só o do mês e sem só o do ano, deixam cada um o seu caso vermelho (eram verdes nos 25 de antes).

**O ciclo inteiro, depois da revisão:** a caracterização em 2399 testes nos dois bancos (17177 e 17175 asserções,
16 pulados), o contrato do aplicativo 67 de 67 nos dois, os 13 ensaios por HTTP nos dois bancos, o
`conferir-tudo.sh` com 55 portões ok e o vermelho conhecido do D50, e 1584 testes no front.

## 06/10/2026 -- PDV-QUITAR-E-COMANDA: o "Quitar" do PDV antigo passa a ter marca, e abrir a comanda de outro cliente com pagamento lançado é recusado (D384 corrigido; D380 corrigido no ERP e parcial pela tela nova; a metade do ERP do D159; D433 e D434 registrados)

**Para quem usa:** no PDV antigo, o "Quitar" fica desabilitado enquanto o pedido não volta, e um segundo
clique -- ou outra aba -- não soma os débitos de novo. A comanda reaberta depois do "Quitar" já vem com os
débitos somados: sem o bloco e com a baixa ligada; antes ela oferecia o "Quitar" outra vez, e finalizar
sem clicar cobrava os débitos no total e os deixava abertos. O finalizar recusa baixar débitos que a venda
não cobre, com a frase; a tela chega ali quando nasce um débito entre o "Quitar" e o finalizar (a outra
comanda do cliente fechada fiado em outro caixa), e a recusa mostra o "Quitar" de novo. E abrir a comanda de um cliente quando o
PDV tem uma venda em andamento com pagamento de outro cliente é recusado, no ERP e na tela nova, com a
frase dizendo para remover os pagamentos ou finalizar a venda antes (as respostas 14 e 15 do dono). Quem
usa a tela como sempre não é recusado: o "Abrir comanda" manda o cliente da própria venda, e a bandeira
da baixa só liga depois do "Quitar" -- com uma exceção achada na verificação, o "Sem Cadastro" cadastrado
no meio da venda, com pagamento lançado (o D434, pergunta ao dono). E a comanda com o "Quitar" do ERP,
fechada pela tela nova, ainda cobra os débitos no total sem baixá-los (o D380 ficou parcial; pergunta ao
dono).

- **O que mudou:** a marca é o que o total da venda aberta tem acima da soma dos itens (o proxy do D159),
  num lugar só (`ISVenda\Regra\RegraDosDebitosNaVenda`), lida pelo "Quitar" das duas portas, pelos dois
  finalizar e pela comanda reaberta do ERP; o finalizar da API passou a usá-la sem mudar a resposta. O
  débito que nasce depois do "Quitar" soma só ele. `VendaService::comandaAbrir` devolve a recusa, e as
  duas portas dizem a frase (o ERP `sucesso: false`, a API 409). O `mod-vendas.js` (servido agora com
  `?cache=165`) desabilita o "Quitar" e mostra a frase da recusa do "Abrir comanda".
- **Provado:** `D380QuitarComMarcaTest` (14) e `D384ComandaDeOutroClienteTest` (7), verdes nos dois
  bancos; contra o código de antes, 11 vermelhos e os 10 controles verdes. Vinte e dois mutantes, um de
  cada vez, cada um com o seu vermelho: treze no PHP (no MySQL), seis no JavaScript (o
  `quitarDaTelaAntiga.teste.ts`, que roda as funções do arquivo servido com um jQuery de mentira) e três no
  portão do recorte, que ganhou as entradas do D380 (o código arrancado, e não o comentário). A tela nova mostra a recusa e não abre
  (`ComandasLigada.teste.tsx`; é guarda, e não prova do conserto: ela já mostrava a frase de qualquer 409). O `D166CreditoSemLinhaTest` passou a gravar a comanda herdada em vez de
  montá-la por `comandaAbrir`, que agora a recusa.
- **Não visto no navegador:** o "Quitar" desabilitado, a comanda reaberta e a frase do "Abrir comanda".
- **Fica:** o D433 -- a venda em andamento com os débitos de um cliente somados, sem pagamento, ainda vira a
  comanda de outro com a dívida dentro --, com a pergunta no lote. E, da verificação: a comanda com o
  "Quitar" do ERP fechada pela tela nova (o D380, parcial), o "Sem Cadastro" cadastrado no meio da venda (o
  D434) e a confirmação de que o débito novo soma só ele -- as três no lote, com opções e recomendação.

**A verificação (06/10/2026)** achou, lido no código, quatro coisas e duas afirmações sem prova, e o que
era técnico foi consertado: a recusa do finalizar mandava clicar num "Quitar" escondido -- e no
`/vendas/novo` recarregar apaga a venda --, e agora traz o devido (`quitar_debitos`) e a tela mostra o bloco
de novo; a comanda reaberta deixou de mostrar a dívida em vermelho no painel do cliente; o `POST .../debitos`
da API que soma só o débito novo ganhou caso; os `detalhes` do 409 e a venda sem cliente também. O docblock
de `VendaService::comandaAbrir`, o `docs/f2-fatia-pdv.md` e o `api/comandas.ts` passaram a dizer o 409 e o
`POST .../debitos` que não soma de novo. Os 27 casos do item (18 e 9) rodaram contra o código de antes nos
dois bancos -- 15 vermelhos e 12 verdes em cada um, que era a metade que faltava --, e 12 mutantes de PHP
(6 novos e 6 do implementador) rodaram nos dois, cada um com o mesmo vermelho; no JavaScript, 4 novos.
**O ciclo da verificação:** a caracterização em 2426 testes nos dois bancos (17268 e 17266 asserções, 16
pulados), o `conferir-tudo.sh` sozinho (55 ok e o vermelho conhecido do D50), e 1595 testes no front, com o
build e o lint. O contrato do aplicativo e os ensaios por HTTP não foram repetidos: nada do aplicativo nem
da API nova mudou de código na verificação, só testes e comentários.

**O ciclo:** a caracterização em 2420 testes nos dois bancos (17243 e 17241 asserções, 16 pulados), o
contrato do aplicativo 67 de 67 nos dois, sem instantâneo mudado, os ensaios por HTTP das recusas (201 de
201, o caso 11 do D159 inclusive) e do fluxo (22 passos) nos dois bancos, o `conferir-tudo.sh` sozinho
(55 portões ok e o vermelho conhecido do D50; o da cobertura por par pediu a base
do ISVenda de 108 para 109 -- a chamada nova do finalizar, par já vigiado), e 1592 testes no front, com o build e o lint.

**O ciclo inteiro, depois da revisão:** a caracterização em 2426 testes nos dois bancos (17268 e 17266 asserções,
16 pulados), o contrato do aplicativo 67 de 67 nos dois, os 13 ensaios por HTTP nos dois bancos, o
`conferir-tudo.sh` com 55 portões ok e o vermelho conhecido do D50, e 1595 testes no front.

## 06/10/2026 -- VENDAS-EXPORTACAO-E-NPS: a exportação das vendas realizadas filtra pelo colaborador, e a lista de vendas não consolida mais o NPS da rede (D230 corrigido, D428 parcial; respostas 16 e 17 do dono)

**Para quem usa:** na tela antiga das vendas realizadas, quem filtra um colaborador e exporta recebe no
XLS as vendas que a tela mostrou -- as abas 0 e 1 com os itens dele (a aba 1 soma o "Total" do topo), a
aba 2 com os pagamentos das vendas em que ele tem item. A coluna de pagamento da aba 0 e a aba 2 trazem
o valor da venda inteira, sem rateio: é a regra (a) do dono. E abrir a lista consolida o NPS de uma
unidade só, nunca o da rede: o gerente continua vendo o da loja dele na hora; o administrador com a
unidade em branco consolida a dele (a da sessão), e não mais a rede inteira; o que escolhe outra loja
continua consolidando a escolhida, como antes -- esse caminho a resposta 17 não descrevia, e a pergunta
foi ao lote.

- **Provado:** 5 casos novos no `ListagemDeVendasTest` -- o quinto passa pelo caminho da tela antiga, a
  lista grava o pedido na sessão e a exportação, despachada depois com a mesma sessão, o lê e roda as
  três consultas -- e 5 no `D428NpsDaListaDeVendasTest` (a lista despachada em processo, como GET),
  verdes nos dois bancos. Dez mutantes, um de cada vez, cada um com o seu vermelho, restaurados e
  conferidos por sha1. Na cópia, só `SELECT`: nenhuma pesquisa pendente, e no pico a rede recebe 6 numa
  janela de dez minutos. A lista nunca segura a trava da rotina por mais tempo do que já segurava.
- **A verificação devolveu três coisas, e as três mudaram:** a primeira versão mandava a unidade da
  sessão também ao administrador que escolhe outra loja -- decidia pelo dono um caso fora da pergunta, e
  podia segurar a trava por mais tempo --, e marcava o D428 como corrigido; a prova do D230 dizia passar
  pelo pedido que a tela antiga guarda na sessão, e montava um equivalente pela regra da API nova; e a
  fatia F2 (`docs/f2-fatia-vendas.md`) ainda citava a chamada antiga do NPS.
- **Não visto:** o arquivo XLS que o `gerarXls` escreve (termina em `exit`), e o tempo do pedido com
  pendentes.
- **Para o dono:** o administrador que escolhe outra loja -- a escolhida, como está, ou sempre a dele.

**O ciclo inteiro, depois da revisão (começou à meia-noite, de propósito):** a caracterização em 2436 testes nos
dois bancos (17344 e 17342 asserções, 16 pulados), o contrato do aplicativo 67 de 67 nos dois, os 13 ensaios por
HTTP nos dois bancos, o `conferir-tudo.sh` com 55 portões ok e o vermelho conhecido do D50, e 1595 testes no front.

## 07/10/2026 -- ESTOQUE-DECISOES: a venda e o cancelamento da venda gravam o saldo por conta no banco, e a quantidade sai da edição do produto (D388 corrigido; respostas 20 e 21 do dono)

**Para quem usa:** finalizar e cancelar uma venda continuam baixando e devolvendo o estoque do mesmo
jeito -- o saldo final de uma venda sem ninguém mexendo no estoque ao mesmo tempo é o de antes, nos dois
bancos, sem piso, e o nulo continua valendo zero. A diferença é quando alguém movimenta o produto pela aba
"Estoque" enquanto a venda está em andamento: antes a venda gravava por cima o saldo que tinha lido no
começo e a movimentação sumia do saldo (continuava na lista); agora a venda faz a conta no banco
(`quantidade = quantidade - ?`, e `+ ?` no cancelamento) e as duas valem. Na tela de editar produto do ERP
o campo "Quantidade" fica só de leitura, com o saldo do banco, e salvar a edição não mexe mais no saldo --
o aviso em vermelho que mandava ajustar pela aba "Estoque" passa a ser a regra. O cadastro de produto novo
continua com a quantidade inicial. A API nova e a tela nova já não deixavam editar a quantidade, e não
mudaram.

- **Provado:** `D388SaldoPorContaNoBancoTest`, 10 casos verdes nos dois bancos (a movimentação que
  termina no meio da venda e no meio do cancelamento, o saldo de antes sem concorrência, o nulo, a
  entidade em memória, os dois métodos do repositório chamados direto, e a tela de editar despachada em
  processo, com o formulário velho, o POST com 999, o campo só de leitura no GET e na volta com erro, e o
  cadastro novo). 10 mutantes, um de cada vez, vermelhos nos dois bancos, restaurados e conferidos por
  sha1. A página inteira de editar, renderizada pela view da aplicação num caso temporário: o campo sai
  `readonly`.
- **Os vizinhos:** 802 execuções numa rodada (os 9 primeiros casos novos e os testes que passam pelo
  finalizar, pelo cancelamento, pelas portas da venda e do produto, o D316 a D320, o D380, o D384 e o
  `EstoqueSobTravaTest` do D126), verdes nos dois bancos, 4 puladas (as que pedem a cópia).
- **Os portões:** o `conferir-tudo.sh` com o vermelho conhecido do D50 e a cobertura por par, que pediu
  a base de 320 para 322 pares (os dois métodos novos do repositório, declarados) e a do ISVenda de 109
  para 111; depois disso, verde. O front não mudou.
- **Não visto:** a corrida com dois pedidos HTTP ao mesmo tempo, e a página aberta no navegador.

**A revisão do item** (quatro achados, todos de gravidade baixa e todos confirmados no código):

- **A prova da tela não passava pela tela:** o caso da edição montava o campo com o `FormText` à mão, e o
  do produto novo conferia um `new Form\Produto()`. Um `<input>` escrito à mão no `editar.phtml`, ou a
  função nova chamada também no `novoAction`, deixavam os 10 casos verdes (medido: o arquivo de antes,
  reconstruído no rascunho, fica verde contra os dois e contra o asterisco que ficou). Agora a edição e o
  novo são renderizados inteiros pelo `ViewRenderer` da aplicação, no GET e na volta com erro, e o novo
  ganhou caso próprio (`testATelaDoProdutoNovoMostraAQuantidadePreenchivel`): 11 casos.
- **O D388 dizia "As duas implementações estão na fila."** sob o CORRIGIDO, e **o D126 dizia no presente**
  que a venda e a edição apagam a movimentação do meio: os dois riscados, com a data.
- **Dois comentários vizinhos** (o `atualizarAction` da API de produto e o `EdicaoDoCatalogoTest`) diziam
  que o `atualizar()` de produto grava o estoque: corrigidos.
- **Depois da revisão:** o arquivo, 11 verdes nos dois bancos (85 asserções); sete mutantes, um de cada
  vez, vermelhos nos dois (o novo só de leitura no GET e na volta, só na volta, o campo escrito à mão na
  view, o asterisco que fica, e os três de antes da tela sobre a prova nova); os vizinhos, 804 execuções
  verdes nos dois bancos (4.042 e 4.041 asserções, 4 puladas); o `conferir-tudo.sh` com 55 portões ok e o
  vermelho conhecido do D50; `contar-debitos.php` e `conferir-debitos-repetidos.php` com saída 0.

**O ciclo inteiro, depois da revisão:** a caracterização em 2447 testes nos dois bancos (17429 e 17427 asserções,
16 pulados), o contrato do aplicativo 67 de 67 nos dois, os 13 ensaios por HTTP nos dois bancos, o
`conferir-tudo.sh` com 55 portões ok e o vermelho conhecido do D50, e 1595 testes no front.

## 07/10/2026 -- AGENDA-DECISOES: o check-in da agenda antiga mexe só na unidade do horário clicado, e a comanda aberta pela agenda recebe os serviços na ordem em que foram marcados (D422 corrigido, D426 parcial, D435 e D436 registrados, D437 parcial; respostas 22 e 23 do dono)

**Para quem usa:** na agenda antiga, quem enxerga a rede ("Agendas unidades", e o administrador) clica no
check-in de um horário e marca a chegada só nos horários do cliente, naquele dia, na unidade daquele
horário -- a chegada que outra franquia já registrou para o mesmo cliente fica como estava. Para marcar
na outra, abre-se a grade dela. Se o horário clicado sumiu enquanto a janela estava aberta -- remarcado
(pelo aplicativo, pelo arrastar ou por outro caixa) ou desmarcado --, o check-in de quem enxerga a rede
responde "Não foi possível localizar o horário: ele pode ter sido remarcado ou desmarcado. Atualize a
agenda e tente de novo." e não mexe em nada; antes, virava os horários do cliente na rede inteira. Para
quem não tem "Agendas unidades" nada muda, nem nesse caso: o check-in vira os horários do cliente naquele
dia na própria unidade, como sempre. O botão continua sendo um interruptor (desmarca quem já chegou). A
comanda aberta pela agenda recebe os serviços na ordem em que os horários foram marcados: é a ordem que o
MySQL já dava, agora escrita, e o PostgreSQL passou a dar a mesma. A tela nova e a API não mudaram.

- **Provado:** `D190CheckinEmDuasFranquiasTest` (15 casos: os dois que diziam o fato de antes mudaram; e
  entraram a unidade do clicado -- e não a da sessão nem a da grade aberta --, o horário remarcado com a
  janela aberta, para quem enxerga a rede e para quem não enxerga, o clique forjado no horário de outra
  franquia para quem não enxerga, e a escolha do dia do cenário) e `D426OrdemDosServicosDaComandaTest`
  (4 casos: a ordem da consulta, o primeiro item da comanda de verdade, as duas sessões e a forma do SQL),
  19 de 19 nos dois bancos. Mutantes, um de cada vez, vermelhos nos dois bancos e restaurados pelo sha256:
  oito do check-in e do dia do cenário (a tabela está no D422) e os dois da ordem da comanda (no MySQL,
  sem o `ORDER BY` só o caso da forma fica vermelho -- a ordem é a que ele já dava). Os vizinhos (agenda,
  check-in, comanda, cupom, remarcação, fechamento, recorte por unidade), 499 testes verdes nos dois
  bancos (2 pulados).
- **Medido na cópia, só `SELECT`:** a ordem da comanda não muda em nenhuma das 845 chamadas da amostra
  com dois serviços ou mais no MySQL, e o PostgreSQL dá a mesma em todas.
- **A verificação, no mesmo dia, desfez uma parte:** a primeira versão recusava o horário clicado que
  sumiu também para quem não tem "Agendas unidades" -- a resposta 22 tratou de quem enxerga a rede, o item
  mandava que os outros seguissem como hoje, e esta entrada dizia "nada muda" para eles sem que fosse
  verdade. Voltaram ao comportamento de antes, e dois casos o prendem. A tabela do D190 no registro, que
  ainda abria dizendo que os dois horários viravam "o fato de hoje", foi corrigida.
- **Ficou de fora, e foi ao lote:** o desempate pelo id nos quatro métodos que ordenam os horários do
  cliente só pela hora -- ele mudava a ordem de 136 chamadas no MySQL, e a resposta partia de "não muda
  nada"; o D435 -- com uma sessão no pacote, a comanda dá a sessão aos dois serviços iguais do dia (a
  premissa do D426 de que a ordem decidia isso não vale); e o D436 -- para quem enxerga a rede, na grade
  de outra unidade, a janela "HORÁRIOS MARCADOS" lista os horários da unidade da sessão e abre a comanda
  nela, e o check-in, desde a resposta 22, mexe na do horário clicado: o operador confirma olhando uma
  lista que não tem nada a ver com o que o clique muda. Junto, como ciência ao dono: a recusa do horário
  que sumiu.
- **Com prazo, D437:** cinco arquivos de teste da agenda montam o cenário "daqui a 45 dias", e em
  09/11/2026 esse dia cai no horário fixo da fixture (24/12/2026) -- medido com o relógio do PHP dos
  testes adiantado: 23 casos vermelhos que o controle, um dia antes, não tem. O do D190 escolhe o dia
  agora (15 de 15 com o relógio em 08/11/2026, 09/11/2026 e 26/11/2029); os outros quatro
  (`D237AgendaRecortePorUnidadeTest`, `D237RestosDoPdvEDaAgendaTest`, `FechamentoDaTelaAntigaTest`,
  `RemarcacaoDaTelaAntigaTest`, 22 casos) precisam do mesmo antes de 09/11/2026.
- **Não visto:** a tela da agenda aberta no navegador (o check-in escreveria no banco de desenvolvimento),
  a janela dos horários marcados na grade de outra unidade, e o finalizar levando o pacote a -1 (pela
  leitura). Se algum pedido real chega com o horário clicado já remarcado, não se sabe.

**O ciclo inteiro, depois da revisão:** a caracterização em 2457 testes nos dois bancos (17459 e 17457 asserções,
16 pulados), o contrato do aplicativo 67 de 67 nos dois, os 13 ensaios por HTTP nos dois bancos, o
`conferir-tudo.sh` com 55 portões ok e o vermelho conhecido do D50, e 1595 testes no front.

## 07/10/2026 -- CADASTRO-DECISOES: quem desativa cliente é a unidade dele ou o administrador, o telefone de outro na alteração segue a regra da API, e o código do Zapier entra com outro tamanho (D394, D417 e D438 corrigidos, D199 parcial, D145 e D341 com a decisão feita, D439 registrado; respostas 24, 27 e 28 do dono)

**Para quem usa:**

- **Desativar e ativar um cliente** é da unidade dele ou do administrador -- na lista antiga, que já só
  mostrava o botão a elas, na ação por trás dela e na API nova (`POST /adm/api/clientes/:id/desativacao` e
  `/ativacao`, que respondem 403 `outra_unidade` com a frase). Antes, qualquer franquia com o cadastro de
  clientes desativava qualquer cliente da rede pelo endereço. **Editar continua da rede.** O "Cliente
  Avulso" não tem mais o "Remover" na lista, e a ação antiga recusa os dois cadastros do sistema, como a
  API (o D394). A tela nova não tem o botão de desativar cliente; quando o ganhar, a recusa já está na API.
- **Trocar o telefone de um cliente pelo de outro** -- no modal das telas de agenda, venda, comanda e
  visagismo, e na página "Editar" -- é recusado com "Este telefone já é do cliente N.", e o modal não
  oferece mais "cadastrar como dependente" na edição: a oferta deixava o cliente com o número do outro e
  sem ser dependente de ninguém. O dependente e quem salva sem mexer no telefone passam; a página "Editar"
  barrava quem só trocava o nome de um cliente com número repetido de antes da regra. O cadastro novo
  continua oferecendo o dependente.
- **O código do Zapier** colado na administração dos assistentes pode vir com a largura, a altura e o
  popup escolhidos no painel dele (o da página do Motivacional VIP tem 400px). Grava-se o molde de hoje, e
  a conversa abre em 100% e 600px, sem popup -- a frase do campo o diz. Outro script, outro endereço ou
  outro atributo continuam recusados, e a página do assistente continua imprimindo só o molde remontado.

- **Provado:** `DesativarClienteSoDaUnidadeTest` (8 casos, novo), `D417TelefoneNaAlteracaoDoErpTest` (8,
  novo), `RegraDoAssistenteTest` (de 26 para 61), `AssistentesPelosControllersTest` (de 43 para 50) e
  `TelasDosAssistentesDoErpTest` (de 14 para 15): 142 verdes nos dois bancos. Os vizinhos (o cadastro, a
  API e a lista de clientes, a fidelidade, o D199, o D202, o D203, o D234, a barreira de origem, os
  assistentes), 491 execuções verdes nos dois bancos (3 puladas). 20 mutantes, um de cada vez, restaurados
  e conferidos por sha1, todos vermelhos (a tabela está no D199, no D417 e no D341).
- **Pelo aplicativo de verdade:** o ensaio de recusas ganhou o caso 35v2 (o gerente desativa o cliente da
  unidade 90001150: 403) e saiu 202 de 202; com a guarda da API arrancada, o caso sai 200 e o ensaio o
  aponta. O de permissão, 61 de 61; o do fluxo, 22 passos. O `PUT` da API, que passou a chamar a regra
  compartilhada do telefone, pelos casos 35l, 35m e 35s.
- **Os portões:** o `conferir-recorte-por-unidade.php` vigia a regra de quem desativa e as duas chamadas
  (187 métodos; cada guarda arrancada, um de cada vez, o deixa vermelho); o `conferir-tudo.sh` com 55
  portões ok e o vermelho conhecido do D50; `contar-debitos.php` e `conferir-debitos-repetidos.php` com
  saída 0 (nenhum par novo). O front não mudou.
- **Não visto no navegador:** a lista, o modal e a página de clientes foram despachados em processo; o
  assistente não se abre de propósito (chama a conta de produção do Zapier).

**A verificação (07/10/2026)** achou uma porta que as três de cima não fechavam, e uma prova que faltava:

- **A edição do cliente mudava o estado** -- e outras marcas -- de qualquer cliente da rede: a página
  "Editar" e o modal entregavam o pedido inteiro à gravação, e o `status` dele desativava o cliente de
  outra franquia, ou o reativava pelo campo escondido da página quando a unidade dele o tinha desativado
  nesse meio-tempo; pela mesma porta, a blacklist do aplicativo (sem o privilégio dela), a fidelidade e a
  restrição do cadastro, e o estado do avulso e do "Sem Cadastro". **Agora a edição grava só os campos da
  tela** (o D438), e o estado muda só pelos botões. Para quem usa a tela, nada muda: o campo do estado da
  página "Editar" sempre foi escondido.
- **O `PUT` da API**, que passou a usar a regra compartilhada do telefone, ganhou três casos em processo,
  nos dois bancos; antes só o ensaio HTTP no MySQL o conferia.
- **Registrado, sem conserto:** a edição antiga ainda reescreve o nome e o telefone do avulso e do "Sem
  Cadastro", que a API não deixa alterar (o D439) -- pela tela, só pelo "Editar" do avulso na lista dos
  inativos.
- **Provado:** `D438EdicaoDoClienteSoGravaOsCamposDaTelaTest` (4 casos, novo) e `D417TelefoneNaAlteracaoDoErpTest`
  (de 8 para 11); os testes do item, 149 verdes nos dois bancos, e os vizinhos, 478 (3 pulados). 6
  mutantes novos, um de cada vez, restaurados e conferidos por sha1, todos vermelhos -- entre eles o
  conserto mínimo, só o `status` fora, que deixava a blacklist aberta. Pelo aplicativo de verdade, os
  casos de clientes do ensaio de recusas (rodada parcial, 33 de 33) no MySQL e no PostgreSQL. O
  `conferir-tudo.sh` com 55 portões ok e o vermelho conhecido do D50; `contar-debitos.php` com 274 de 437
  fora de pé; `conferir-debitos-repetidos.php` sem par novo.

**O ciclo inteiro, depois da revisão:** a caracterização em 2523 testes nos dois bancos (17619 e 17617 asserções,
16 pulados), o contrato do aplicativo 67 de 67 nos dois, os 13 ensaios por HTTP nos dois bancos, o
`conferir-tudo.sh` com 55 portões ok e o vermelho conhecido do D50, e 1595 testes no front.

## 07/10/2026 -- D437-DATA-RELATIVA-NAS-FIXTURES: os quatro arquivos de teste da agenda que ficariam vermelhos em 09/11/2026 escolhem o dia do cenário, como o do D190 (D437 corrigido)

**Para quem usa:** nada muda na tela nem na API -- é a suíte de testes. Sem isto, a partir de 09/11/2026
ela sairia vermelha sem que o código mudasse (22 casos em quatro arquivos), e vermelho falso ensina a
ignorar o vermelho. O prazo era 08/11/2026.

- **O que mudou:** a escolha do dia que o `D190CheckinEmDuasFranquiasTest` fazia virou um ajudante comum,
  `tests/Caracterizacao/DiaLivreNaAgenda.php`: o primeiro dia, a partir do 45º, em que nenhum cliente e
  nenhum colaborador do cenário tem horário, sempre pelo `agora()`. `D237AgendaRecortePorUnidadeTest`,
  `D237RestosDoPdvEDaAgendaTest`, `FechamentoDaTelaAntigaTest` e `RemarcacaoDaTelaAntigaTest` o usam, e o
  D190 também. A fixture não mudou. Lido nela, um choque que a medida de origem não tinha visto: o
  colaborador 90001150 em 15/06/2030, alcançado por "+45 days" em 01/05/2030 -- os dois arquivos do D237
  o levam na lista.
- **Provado:** com o relógio de `agora()` adiantado -- agora por um `--bootstrap` no `/tmp` do contêiner,
  sem trocar linha de arquivo, calibrado contra a medida de origem --, os cinco arquivos em 08/11/2026,
  09/11/2026, 26/11/2029 e 01/05/2030, nos dois bancos, saem só com os 2 vermelhos do controle (o "hoje"
  da aplicação, que não se adianta); antes do conserto, 2, 24 e 25 nas três primeiras. Com o relógio de
  hoje, 77 de 77 nos dois bancos (o D190 com dois casos novos, um para cada metade da escolha), e os
  vizinhos da agenda, 430 verdes nos dois (2 pulados). 10 mutantes, um de cada vez, restaurados pelo sha256, vermelhos nos dois
  bancos (a tabela está no D437). O portão do relógio com saída 0; o `conferir-tudo.sh` com 55 portões ok
  e o vermelho conhecido do D50; `contar-debitos.php` com 275 de 437 fora de pé;
  `conferir-debitos-repetidos.php` sem par novo; o front, 1595 testes, sem mudança.
- **A varredura:** de 07/10/2026 a 31/12/2027, dia a dia (451 dias), no MySQL como o CI, os 31 arquivos de
  agenda, venda ou comanda com data relativa que o grep achou (476 testes por dia). Nenhum dia com uma
  data relativa caindo numa data fixa da fixture. O que saiu do controle é do instrumento: menos
  vermelhos nos primeiros 29 dias (o adiantamento ainda pequeno) e um caso da fidelização a mais de
  25/01 a 04/02/2027 (a janela de 90 dias do servidor, pelo relógio da aplicação, com o do teste 110 a
  120 dias à frente). Nenhum débito novo.
- **Não provado:** os 20 casos do controle ficam vermelhos em todo dia adiantado, e uma colisão dentro
  deles não aparece -- entre eles `DiaDoClienteNaAgendaTest::testOsHorariosMarcadosDeHojeEmDiante`. A
  varredura não rodou no PostgreSQL (lá, as quatro datas acima, iguais ao MySQL), nem nos 7 arquivos com
  data relativa que não são de agenda nem de venda.
- **A verificação (o mesmo dia), quatro achados:** (1) a contagem de casos do D190, no registro e no
  docblock, ficou velha -- corrigida; (2) os três casos da escolha deixavam passar um "+1" cego, a escolha
  que pula no máximo um dia e a que devolve o dia seguinte ao livre (17 de 17 verdes com cada uma; com o
  "+1" e o relógio em 08/11/2026, 30 vermelhos nos cinco arquivos) -- agora cada caso confere que a escolha
  deu o PRIMEIRO dia livre, e dois casos novos provam o próprio dia pedido quando livre e dois dias
  seguidos ocupados (o D190 tem 19 casos); (3) desfazer a escolha de um arquivo ficava verde com o relógio
  de hoje -- o portão novo `scripts/conferir-dia-do-cenario-da-agenda.php`, no `conferir-tudo.sh`, recusa
  pelos tokens o dia escrito como número (falsificado com 12 mutantes); (4) o portão do relógio não vê
  `new \DateTimeImmutable('+1 day')` -- registrado como D440, e o docblock do trait corrigido. Depois
  dela: os cinco arquivos 79 de 79 nos dois bancos com o relógio de hoje, e só os 2 do controle em
  08/11/2026, 09/11/2026, 26/11/2029 e 01/05/2030; os vizinhos da agenda, 432 verdes nos dois (2 pulados).

**O ciclo inteiro, depois da revisão:** a caracterização em 2527 testes nos dois bancos (17648 e 17646 asserções,
16 pulados), o contrato do aplicativo 67 de 67 nos dois, os 13 ensaios por HTTP nos dois bancos, o
`conferir-tudo.sh` com 56 portões ok (o novo do dia do cenário entre eles) e o vermelho conhecido do D50, e 1595
testes no front.

## 07/10/2026 -- CI-ESPELHO-E-PRE-PUSH: o `ci.yml` ganha os 28 portões que só o corredor rodava, a lacuna do CI confere as duas direções, e o `pre-push` não trava mais fora de terminal (D441 parcial, D442 corrigido)

**Para quem usa:** nada muda na tela nem na API. Muda para quem programa: portão novo posto no
`conferir-tudo.sh` sem passo no CI (nem motivo escrito para ficar fora) deixa o corredor vermelho --
inclusive o que não chama roteiro de `scripts/`, como os testes do front; e o `pre-push` chamado à mão,
de um agente ou de uma IDE, termina sem `< /dev/null` e avisa que não leu o stdin. O `git push` em si
nunca precisou do `< /dev/null`: o git dá ao hook um pipe próprio, que fecha.

- **O que mudou:** o `ci.yml` ganhou os passos de 28 roteiros que só o corredor rodava -- 23 textuais no
  job `verificacao`, `conferir-relacoes-doctrine`, `conferir-ordenacao-nulos` e o ensaio SQL do passo 3
  do corte no `contrato` (o ensaio pelo contêiner do serviço MySQL do job), e os dois irmãos do portão
  dos tipos no `caracterizacao-postgres` -- e um job novo, `front`, com os testes do front (Node 24,
  `npm ci`, o mesmo comando do corredor, no fuso de São Paulo). Os 8 que pedem a cópia, o docker de quem
  programa, o histórico do git ou o disco local ficam fora, cada um com o motivo, na tabela `FORA_DO_CI`
  do `scripts/conferir-lacuna-do-ci.sh`. O roteiro para também nessa direção e quando uma declaração
  envelhece, e lê os dois arquivos como shell: comentário (o `;#` inclusive), texto entre aspas, `echo`,
  o comando antes de `|| true`, `&&` ou pipe, condição de `if`, heredoc e passo com `continue-on-error`
  ou `if: false` não contam como "o CI roda"; o `rodar` depois de `&&` ou dentro de um `if` conta como
  "o corredor roda"; o `rodar` sem roteiro vale pelo comando. Com `--so-declaracao` ele roda no
  `conferir-tudo.sh`, e está escrito no `verificacao`. Dois ensaios novos, no corredor e no
  `verificacao`: `scripts/ensaiar-lacuna-do-ci.sh` (43 casos) e `scripts/ensaiar-pre-push.sh` (7). O
  `.githooks/pre-push` lê o stdin só quando recebe os dois argumentos que o git sempre passa, e sem eles
  diz no stderr que não leu. O cabeçalho do `conferir-ordenacao-nulos.php`, que dizia que ele não roda no
  CI e precisa da cópia, foi reescrito.
- **Provado:** os 23 textuais e a declaração da lacuna com saída 0 num checkout limpo no contêiner (PHP
  8.1, `dash`, `mawk`, sem `vendor/`, `local.php` nem `.env`); os 3 do MySQL e os 2 do PostgreSQL com 0
  contra bancos descartáveis montados como os dos jobs (e apagados no fim). A lacuna: os 43 casos do
  ensaio verdes no gawk do Git Bash e no mawk do contêiner, cada um conferindo a saída e o motivo
  impresso; contra a versão anterior do leitor, 29 deles fora do esperado; 28 mutantes do código, cada
  um morto por pelo menos um caso (a primeira rodada deixou dois vivos, e os casos foram apertados até
  matá-los). O `pre-push`: os 7 casos verdes no Git Bash e no `dash`; o hook do HEAD sai 124 com o stdin
  que não fecha e confere calado a faixa passada sem argumentos; o da primeira versão deste item cai nos
  dois pelo aviso que faltava; 3 mutantes do hook caem. A revisão mediu que um `git push` com o hook
  antigo não travava. Os 484 imports relativos do front batem com a grafia exata dos arquivos. O YAML
  carrega no PyYAML; o `conferir-tudo.sh`, rodado sozinho, com 59 portões ok (a lacuna e os dois ensaios
  entre eles) e o vermelho conhecido do D50; `contar-debitos.php` com 276 de 440 fora de pé;
  `conferir-debitos-repetidos.php` sem par novo.
- **Não provado:** NENHUM passo novo rodou no runner -- nem os 28 roteiros, nem o job `front`, nem a
  lacuna e os dois ensaios do `verificacao`: o CI não inicia desde 13/09/2026 (D187), e já houve ensaios
  escritos no `ci.yml` que nunca executaram. Os testes do front nunca rodaram no Linux (o
  `node_modules` daqui é o do Windows), e o job novo custa minutos de runner. Nem o MySQL 8.0 da
  matriz, nem o `docker exec` pelo `job.services.mysql.id`, nem o PHP do `setup-php`. A primeira
  execução verde é que fecha o D441.

## 07/10/2026 -- CODIGO-MORTO-E-DOCS: a raiz `/mod-vendas` leva à lista de vendas, saem dois métodos de consulta sem chamador e os arquivos de exemplo do recorte de imagem, e os documentos que diziam o contrário do código foram corrigidos (D14, D50 e D73 parciais; D188 e D443 corrigidos; D444 registrado)

Para quem usa: digitar `/mod-vendas` sem caminho leva à lista de vendas realizadas -- antes a tela
falhava ao desenhar, porque a raiz caía na ação de marcador do Laminas, cujo molde não existe. Quem não
enxerga a lista recebe na raiz o mesmo 404 que ela dá. Nada mais muda na tela. No ambiente de teste,
o modelo de `.env` publica o app e o Adminer em 8092 e 8093, e não mais em 8090 e 8091, o par que o
guia da homologação manda usar quando a 8080 está ocupada no BVIP; um `.env` gerado antes segue com
as antigas, e vale o que está nele.

- **O que mudou:** `VendasController::indexAction` redireciona para `indexVendas` pedindo o mesmo
  `vendas/realizadas/ler` da lista, e a raiz saiu das órfãs conhecidas do `conferir-rotas-orfas.php`
  (fica a V1 de agendamento, que espera os logs). Apagados `UnidadeRepository::popularComboboxPorColuna`
  e `UsuarioRepository::selecionarTempoAtendimento`, sem chamador, e a linha de base do
  `conferir-injecao-dql.php` desceu de 32 para 28. Saiu `public/global/plugins/jcrop/demos/` (11
  arquivos estáticos, citados por ninguém; a biblioteca fica). O inventário de ações foi regerado (360).
  Comentários: a regra do contrato vencido junto do campo, em `Convenio` (D50); o bit das ações no
  recurso da grade da agenda, que desde 21/09 é `gerenciar/escrever` (D195). Documentos: o D127 dizia
  que `setCookie()` não tinha chamador (os dois logins o chamam); o roadmap e o
  `funcionalidades-sem-uso.md` ainda diziam "funções desligadas" e "não há usuário" para a VIP Class
  (há 341 pessoas, e vincular unidade a um curso o fecha para as outras); a homologação contava 83
  tabelas (o `mysql.sql` tem 85, a cópia 87) e não listava `clientes_sessoes` entre as de PII; o guia
  do teste dava o `~` e a trava como um lugar só, e com o root eles são outros (D443). O docblock de
  `UsuarioJson` já tinha sido corrigido em 03/10 (SESSAO-SENHA-EMAIL), e ficou como estava.
- **O que o ciclo pegou:** a primeira versão da raiz fez o link morto `/mod-relatorios/vendas/index`, que o
  D105 tirou da tela inicial do grupo 9, redirecionar para a lista de vendas -- o apelido `vendas` vale em
  todas as curingas --, e o `ensaiar-links-consertados-do-erp.php` saiu com 1. A raiz agora só redireciona
  pelas rotas do próprio módulo (`module => ISVenda`); fora delas, o 404 de sempre. Dois casos novos no
  D14 (o roteador põe o `module`; a curinga dos relatórios dá 404), e o mutante sem a guarda fica vermelho.
- **O que a verificação do mesmo dia acrescentou:** a raiz era um caso de uma classe de URL -- as seis
  rotas curinga aceitam qualquer apelido de controller, e 12 apelidos ainda caem no `indexAction` de
  marcador (`/mod-vendas/caixas/index`, por exemplo, falha ao desenhar como a raiz falhava); medidos
  pelo `ControllerManager`, registrados no D14 (o título agora diz) e fixados por um quinto caso no teste,
  sem conserto, porque o que cada um mostra é escolha de tela. Os métodos `selecionarAniversariantes*`
  sem chamador são **quatro**, e não três (faltava o de `UnidadeSocioRepository`): 8 dos 28 sítios do
  portão; o título do D73 dizia que "os sítios sem chamador saíram", e saíram 4 dos 12. O D188 dizia que
  nenhum portão acusaria a pasta de volta, e o `conferir-referencias-docs.php` acusa (medido: recriada,
  saída 1). O docblock do contrato vencido ganhou os leitores que faltavam -- o selo "Vencido" e
  `ConvenioRepository::selecionarConvenios()`, sem chamador, que devolve os ativos **não** marcados, a
  forma da armadilha. O comentário da grade da agenda apontava como "o que o ERP pede há anos" o método
  da API nova; agora nomeia o da tela antiga. O guia de desenvolvimento ainda dizia 82 tabelas (são 85).
  O item do D127 na lista do D315 foi riscado. A correção da VIP Class no `funcionalidades-sem-uso.md`
  dizia que a prioridade baixa "não vale" -- isso seria decidir por você: agora diz que vale a sua ordem
  de 27/09 (VIP Class em 8º) até você responder, e a pergunta foi ao lote. E a trava do
  `deploy-teste.sh` tem no código o erro que o guia tinha (uma por usuário, e a recusa diz "neste
  servidor"): virou o **D444**, sem mexer no script enquanto a cópia sobe no BVIP.
- **Provado:** `D14RaizDoModuloDeVendasTest` (5 casos, em processo) e 14 classes vizinhas -- a lista
  de vendas, o NPS dela, as comandas, a guarda de login, a grade da agenda, os convênios, as APIs de
  unidades e usuários --, 178 testes e 985 asserções verdes no MySQL (como o CI) e no PostgreSQL (eram
  177 e 923 antes do quinto caso).
  Mutantes, um de cada vez e restaurados byte a byte: sem o `indexAction`, 4 vermelhos e o portão das
  órfãs com saída 1; sem a guarda, 2 vermelhos e o `conferir-guarda-do-erp.php` com 1 (35 sem guarda);
  com a guarda de outro privilégio, 2 vermelhos; o método de consulta devolvido, o
  `conferir-injecao-dql.php` com 1 ("SUBIU de 28 para 31"). O primeiro mutante pegou o próprio teste:
  `method_exists` passava com o método herdado, e o caso passou a conferir onde ele é declarado. Na
  verificação: com um `indexAction` declarado em `CaixasController`, o quinto caso fica vermelho
  (`caixas` sai da lista medida); sem o `indexAction` da raiz, agora os cinco; e a pasta `jcrop/demos` recriada, mesmo vazia, deixa o
  `conferir-referencias-docs.php` com 1. Por HTTP, só o anônimo: `/mod-vendas` sem sessão dá o
  formulário de entrada. Os portões do item com
  saída 0 (rotas órfãs, links mortos, referências dos documentos, injeção de DQL, guarda do ERP,
  proteção do webroot, provedores no loopback, guarda da agenda, autorização privilegiada, recorte por
  unidade, cobertura por par, inventário atual) e `conferir-debitos-repetidos.php` sem par novo.
- **A conta dos débitos andou para trás, e é de propósito:** `contar-debitos.php` dá **275 de 442 fora
  de pé**, e antes do item dava 276 de 440. O D73 ("FEITO em 01/09") e o D50 ("PREPARADO") desceram
  para parcial, porque o corpo de cada um diz o que resta -- o D73 já tinha "O que continua em aberto",
  e o D50 espera a linha do crontab em produção; o D14 passou de aberto a parcial, o que não muda a
  conta; o D443 entrou fechado e o D444 aberto; e o D188 já contava como fechado ("REMOVIDOS" desde
  14/09). Saem dois, entra um: 276 - 2 + 1 = 275.
- **Não provado:** a raiz com sessão, no navegador, e os 12 irmãos dela abertos de verdade (lidos no
  código e no roteador, não na tela); e, no BVIP, se o `.env` do teste tem 8090 e 8091 e se a
  homologação de lá usa esse par -- quem confere é quem está no servidor. Ficaram de fora, com registro
  no D73: **quatro** `selecionarAniversariantes*` -- de `UnidadeRepository`, `UnidadeSocioRepository` e
  `UsuarioRepository` (dois) --, também sem chamador, que não estavam no levantamento; e
  `ConvenioRepository::selecionarConvenios()`, sem chamador, registrado no D50.

## 08/10/2026 -- MEDICOES-LOCAIS-E-D220: desmarcar custa 2 s no MySQL mesmo com 2 GB de pool, e com o índice de `id_pai` 0,2 ms; a linha a mais das pesquisas no PostgreSQL é a pesquisa de teste de 2018; a etapa 6 repetida é coisa de poucos casos na cópia (D220 e D304 medidos, D106 explicado, D445 e D446 registrados)

Para quem usa: nada muda -- este item só mediu, na cópia local, sem tocar código, esquema nem
dado. O que ele traz são três números para decisões que são suas, e quatro perguntas no lote
(P8, WHATSAPP-P3, D445 e D446).

- **D220, a família do desmarcar.** A consulta que o código monta (`id = X OR id_pai = X`)
  examina as 4.000.085 linhas de `agendas` para devolver 3. No MySQL: mediana de 2,8 s com o
  pool de 128 MB e de 2,0 a 2,3 s com 2 GB e a tabela inteira na memória (nenhum byte do disco)
  -- **o pool não explica o custo**, ao contrário do D189: é a CPU varrendo 4 milhões de
  linhas. No PostgreSQL, mediana de 271 a 279 ms com a varredura em três processos, e de 0,4 a
  0,7 s num processo só -- o número que vale para uma VPS de 4 vCPU. Com o índice de
  `id_pai`, medido em bancos descartáveis só com `agendas` e sem dado pessoal (apagados no
  fim; a cópia do MySQL fica no binlog local até expirar, perto de 07/11): 0,2 ms no MySQL e de
  0,05 a 0,15 ms no PostgreSQL; 47,6 MB e 6 s para criar no MySQL nessa cópia estreita (na tabela
  inteira, o D189 mediu de 11 a 20 s), 29 MB e 2,7 s no PostgreSQL. O desmarcar roda umas 358
  vezes por dia (547 no pico), e o da tela nova varre duas vezes. O pool foi lido (128 MB), posto
  em 2 GB e devolvido (conferido). A pergunta do índice foi ao lote como **P8**, com o DDL escrito
  e sem rodar: o índice entra no MySQL pelo passo 0 do corte, e o PostgreSQL o herda do
  `mysql.sql`, sem linha à mão no gerador.
- **D106, a linha a mais.** O PostgreSQL tem a pesquisa id 1 e a pergunta dela, que o MySQL não
  tem: é a pesquisa de teste de 2018, apagada do MySQL local por
  `scripts/remover-pesquisa-teste-2018.sql` em 29/08, depois da carga de 16/08. O resto das
  três tabelas é igual nos dois. Não é defeito; a consulta que reproduz está no D106.
- **D304, a etapa 6.** Na cópia, 183 amigos ainda com a origem do indicador, de 183 indicadores
  (nenhum com dois); 493 clientes mandaram a etapa 6 desde 14/04 e ganharam os 10 pontos no próprio
  envio, com amigo ou sem -- 110 desses envios criaram um amigo, e nenhum amigo completou o
  cadastro --; o reenvio provável (outro cliente
  de indicação na mesma unidade em até 2 minutos) aparece 5 vezes, contra 1 a 2 do acaso. **Se
  foi para o mesmo telefone, a cópia não sabe:** a anonimização reescreveu os telefones pelo id.
  A conta que separa, só leitura, está no D304 para rodar na produção. O freio foi ao lote
  (**WHATSAPP-P3**).
- **D445, novo.** O PostgreSQL dos contêineres tem 64 MB de `/dev/shm`, e o `VACUUM` paralelo
  de uma tabela de 4 milhões de linhas falha por falta de memória compartilhada; nenhum compose
  define `shm_size`. Não mexido: os compose são de todos os ambientes, e a cópia sobe no BVIP. A
  escolha foi ao lote.
- **D446, novo (da verificação).** A página da adesão promete pontos "na primeira compra" do amigo
  indicado; o código dá 10 pontos a quem manda a etapa 6, no envio, com amigo ou sem, e a
  conversão do amigo não paga nada (acha o histórico do envio); a comissão da primeira compra só
  existe para o convite do aplicativo. Qual é a regra foi ao lote.
- **A conta dos débitos:** `contar-debitos.php` dá 274 de 444 fora de pé (era 275 de 442): o
  D220 saiu de "adiado" para "medido" -- ele esperava a medição, e o defeito continua --, o D304
  saiu de aberto para medido, e o D445 e o D446 entraram abertos. `conferir-debitos-repetidos.php`
  sem par novo.
- **A verificação (08/10/2026) corrigiu cinco coisas do texto, sem mudar a recomendação:** os 493
  da etapa 6 eram envios, e não conversões; a frequência do desmarcar contava duas vezes cada família
  com filhos (o `id` do arquivo de exclusões é dele, não da agenda: 358 por dia, e não 375); o
  PostgreSQL não é "dez vezes mais rápido porque reparte em três" -- os três processos rendem de 1,5
  a 2,5 vezes, e o resto é custo por linha; a opção recomendada da P8 punha o índice dos dois lados,
  e o gerador o emitiria duas vezes; e faltavam limites escritos (a versão, a data da cópia, o
  cronômetro de cada coluna, e o tempo de criar medido numa cópia estreita).
- **Não provado:** quanto custa na produção -- a CPU da VPS, o pool e o `shared_buffers` de lá
  nunca foram lidos, e a tabela cresce perto de 1% ao mês; o `shared_buffers` daqui não foi
  mudado (pede reinício); e a divisão "mesmo telefone / telefone novo" da etapa 6.

## 08/10/2026 -- DECISOES-07-10-NO-REGISTRO-E-NO-CORTE: as 18 respostas do dono de 07/10 (perguntas 30 a 47 da página do lote) estão no registro, e as do corte estão no roteiro -- o renomear do passo 3 alcança os repetidos exatos, a conta de quem chama as `unidadeVendas*` está pronta para o dia 0, o teste do e-mail do cron espera só o endereço, e o laboratório de migração ganhou um clone próprio, com blocos que param fora dele (D371 adiado por decisão; D31, D428 e D431 parciais; nenhum código da aplicação mudou)

- **O registro.** Cada débito citado ganhou o parágrafo "Decidido pelo dono em 07/10/2026 (pergunta N
  da página do lote)", com a opção e o texto dela: D4, D9, D31, D74, D120, D295, D315, D331, D349, D371,
  D380, D426, D428, D430, D431, D432, D433, D434, D435 e D436. Nas três em que o dono não seguiu a
  recomendação, o registro diz a escolha dele: na 35, a (b) -- vale a regra da recomendação quando o
  login com acento for medido (poucos, aviso a cada um; muitos, a comparação sem acento), e o número
  que separa os dois não foi dado: na fronteira, volta ao dono --; na 42, a (b) -- o "Sem Cadastro" vira
  venda sem dono --; na 47, a (b) -- a janela mostra os horários da unidade do clicado, e o "Abrir
  comanda" fica como hoje. Na 38, as duas partes: a barreira num lugar só, e, no fato, "Não, que eu
  saiba" -- nenhuma página de fora manda formulário para o ERP, que o dono saiba (o que ele sabe, e não
  uma medida). As implementações da 38 à 42 e da 45 à 47 estão na fila.
- **Fechadas pela decisão, sem nada a fazer:** a 30 (o cache de CEP enche sob demanda), a 34 (as 27
  UFs, como o roteiro já fazia), a 43 (o débito novo soma só ele) e a 44 (a loja escolhida). Os casos
  que já fixavam o "como está" rodaram verdes nos dois bancos, 28 de 28 (`EnderecoPorCepTest`,
  `D380QuitarComMarcaTest` e `D428NpsDaListaDeVendasTest`). O D371 ficou adiado (a 36: Sorteios segue na
  ordem das telas). O D428 segue PARCIAL só pelo texto: o comentário do `VendasController` e o do caso no
  teste ainda dizem que a escolha espera o dono, e este item não muda código.
- **A 32, no passo 3 do corte.** O inativo do login ou do e-mail repetido EXATO ganha `.inativo<id>`,
  como o do par de caixa; os dois ativos continuam com o dono (a 4c), e o e-mail vazio fica de fora. O
  ensaio do passo 3 ganhou dezoito usuários inventados, a conferência da 3b e da 4, e a prova de que,
  depois do roteiro, o índice único do D295 só esbarra nos ativos da 4c: 12, 4, 6 e 2 linhas, verde no MySQL 8.4.11 e no 8.0.46, e vermelho
  com o roteiro de antes e com cada um de quatro mutantes. Na cópia não há repetido exato -- a
  anonimização os apaga --; a produção diz na listagem nova, a 3b.
- **A 33, no dia 0.** `scripts/medir-chamadas-unidade-vendas.sh` conta por dia os pedidos às três
  `unidadeVendas*` no log do nginx, sem IP, sem `hash`, sem query e sem user-agent, com o controle
  `/api/versao/app` que impede o zero do log errado. O 9 de "O que hoje bloqueia" do procedimento diz
  onde fica o log, como rodar pelo `ssh` sem pôr arquivo na produção e o que fazer com o número (a
  pergunta 6 volta ao dono com ele). O ensaio, com um log inventado, roda no corredor e no CI, verde no
  Git Bash e no contêiner (bash e dash, gawk e mawk).
- **A 31, o aviso das rotinas.** O 8 de "O que hoje bloqueia" é o teste do e-mail do cron no dia 0.
  **O endereço falta:** o campo veio vazio, e o lugar dele está marcado no teste e no passo 9, que não
  instala o crontab sem ele.
- **A 37, o laboratório.** Um clone próprio (`~/franquiabv-lab`), o `.env.laboratorio.example` (o
  projeto `franquiabv-lab`, o corte pelo `COMPOSE_FILE`, as portas 8094, 8095, 8027, 3310, 5435, 9004 e
  9005, medidas ao lado das da homologação e do teste com `docker compose config`), a seção da
  homologação reescrita, e o portão `conferir-provedores-no-loopback.php` lendo o modelo e a seção (11
  mutantes, 1 vermelho cada, e o controle verde). Nada rodou no BVIP; se o laboratório já rodou do
  diretório da homologação, a seção diz como conferir o `local.php` dela.
- **A revisão (treze achados confirmados, dez distintos, todos consertados).** A conta da 33 jogava em "parecidos" o
  `/index.php/` na frente e a URL inteira no pedido, que chegam à exportação (200 no nginx daqui): as
  três sairiam em zero com alguém chamando; agora contam, e o ensaio também RODA o comando do `ssh` do
  procedimento sobre um diretório de logs inventado, em vez de achar o nome do roteiro na tabela. Os
  blocos do laboratório só avisavam (`|| echo "PARE"`) e seguiam no clone da homologação -- e o do
  ensaio geral do corte era um `cd ... && export PROJETO` solto; agora são subshells com `set -e`, e `scripts/ensaiar-blocos-do-laboratorio.sh` (novo, no corredor e no CI) roda
  cada um sem o clone, com o `.env` errado e com o certo. O teste do e-mail do cron mandava com o endereço
  no molde e não lia o syslog `.gz`; agora não manda e usa o `zgrep`, e o ensaio do passo 9 o roda. O
  ensaio do passo 3 passou a conferir a 3b e a 4. O D428 voltou a PARCIAL, o quadro do D349 ganhou a
  regra da 35, o D315 diz "que o dono saiba", o D31 e o D295 registram o docblock desatualizado do
  `UsuarioRepository`, e o cabeçalho do ensaio da conta cita o 9 de "O que hoje bloqueia", e não o 8. Cada conserto tem os seus mutantes, um por vez em cópias, todos vermelhos (um
  equivalente, verde, escrito no D331).
- **A conta dos débitos:** `contar-debitos.php` dá 275 de 444 fora de pé (eram 274): o D371 foi
  adiado, o D31 e o D431 passaram de abertos a parciais, e o D428 segue parcial. `conferir-debitos-repetidos.php` sem
  par novo. O `conferir-tudo.sh` passou, com o vermelho conhecido do D50.
- **Não provado:** nada do que roda na produção ou no BVIP -- o teste do e-mail, a conta do log, a 3b,
  o clone do laboratório --; o formato e os nomes dos logs da produção são deduzidos (o D332).

## 08/10/2026 -- ERP-BARREIRA-NA-ENTRADA: todo POST de outro site ou de outro subdomínio é 403 na entrada do ERP, e não só nos cinco botões; o aplicativo e a `/adm/api` ficam de fora, e um portão confere que nenhuma rota do ERP escapa (a pergunta 38 do lote; D315 parcial só pelo D127, D290 anotado)

- **Para quem usa, nada muda na tela:** o que o ERP manda para ele mesmo passa como antes (`Sec-Fetch-Site: same-origin`, ou o `Origin` do próprio endereço), e o `curl` do cron, das integrações e dos ensaios, que não manda esses cabeçalhos, também. O que muda é a página de OUTRO site ou de outro subdomínio do mesmo site, aberta com a sessão viva: ela não consegue mais fazer o navegador gravar nada no ERP antigo -- as cerca de 90 ações que escreviam por POST só com o `SameSite` (a caixa "Deseja realmente remover?", a agenda antiga, a cota-parte, o sócio, as parcelas, a retirada, os pagamentos e itens da venda, o fiado, o resgate de pontos), o login e o "Sair". A resposta é 403 com a frase de sempre, em texto, ou em JSON para o `$.ajax`.
- **Um lugar só, como o dono pediu:** o ouvinte `ISConfiguracao\Permissions\BarreiraDeOrigem`, acima da guarda de sessão, com a regra da `OrigemDoPedido` (sem cópia). As nove chamadas que os cinco botões tinham em cada ação saíram, e os testes delas passam pela barreira. De fora, só as rotas do `ISApi` (o aplicativo, a API v4, o cron, as exportações -- lidas do `module.config.php` dele), para o pedido sem o cookie da sessão do ERP, e as da `/adm/api`, que têm a sua -- as duas só onde a guarda de sessão não exige sessão. A lista de exceções (`rotas-de-outra-origem.php`) nasce vazia, pela resposta do dono ("Não, que eu saiba"); uma página de fora que ninguém conhecia passa a receber 403, e o remédio é uma linha ali.
- **O portão** `conferir-barreira-de-origem.php` (corredor e CI): o que fica de fora é derivado da configuração, e um POST de outro site a cada rota passa pela aplicação montada -- 233 rotas, 43 sob a barreira, 65 do aplicativo, 125 da `/adm/api`, 163 POSTs e mais 59 com o cookie da sessão às do aplicativo, EXIT 0 (os números depois da revisão, abaixo). Tirar o ouvinte, pô-lo depois da guarda de sessão, pôr uma rota do ERP na lista de fora, uma exceção para o curinga ou uma rota do `ISApi` que leve a um controller do ERP: cada um, EXIT 1.
- **Provado:** o `BarreiraDeOrigemNaEntradaTest` (233 execuções depois da revisão, pelo `Application::run()` de verdade, com uma sentinela no lugar do controller); 1192 execuções verdes no MySQL e no PostgreSQL (todos os testes que despacham pelo arnês em processo, e os vizinhos); os casos 19, 20 e 21 do `ensaiar-sessao-do-erp.php` por HTTP nos dois bancos de contrato, 21 de 21 -- o 19 (de outro site, de outro subdomínio e com o `Origin` de outro host, 403 e o cliente segue ativo; da mesma origem e sem cabeçalho, grava), e os outros onze ensaios do ciclo verdes nos dois; o proxy do Vite, pelo `npx vite` de verdade (a tela nova passa, de outra origem não); dez mutantes, todos vermelhos, e o ouvinte arrancado também por HTTP (os casos 16 e 19, 17 de 19).
- **A revisão, seis achados de um verificador, todos verdadeiros e consertados:** (1) quatro escritas do `ISApi` -- desmarcar, agendar v2 e v3, cadastrar indicação -- autorizam pela sessão do operador e ficavam de fora: a página de outro subdomínio as disparava; agora o pedido de outra origem que traz o cookie da sessão do ERP fica sob a barreira, e o aplicativo, sem ele, segue com o 401 (caso 20); (2) as oito `/api/docs*` exigem sessão e ficavam de fora: a guarda desviava o formulário de outro site para o `entrar`, e com o conserto arrancado ele ENTROU por HTTP; agora só fica de fora a rota que a guarda não desvia (caso 21); (3) o arnês em processo dava o curinga do módulo a todo controller, e agora deriva a rota do roteador (`PelaEntradaDoErpTest`, novo); (4) a janela de privilégios volta a recusar em JSON a qualquer pedido, porque a forma segue a ação; (5) o registro dizia que o caso 19 roda no corredor, e não roda -- é o ciclo e o job do PostgreSQL do CI; (6) o caso 19 deixava o cliente inativo se saísse no meio, e agora começa pondo-o ativo e o devolve no encerramento. Seis mutantes novos, todos vermelhos, quatro também por HTTP.
- **A conta dos débitos:** `contar-debitos.php` igual, 275 de 444 fora de pé -- o D315 segue parcial, agora só pelo caminho do D127 --; `conferir-debitos-repetidos.php` sem par novo.
- **Não provado:** nenhum clique no navegador; e o navegador sem `Sec-Fetch-Site` (o Safari antes do 16.4) compara o `Origin` com o `Host` -- atrás de um proxy que reescreva o `Host`, toda gravação dele seria 403. Os modelos de homologação e de teste passam o `Host`; o servidor da produção não está no repositório, e fica para conferir no primeiro deploy.

## 08/10/2026 -- DATA-NA-FORMA: o `dataValida` confere a forma `dd/mm/aaaa`, e `1/02/02026` e `001/2/2026` recebem a frase de cada tela no lugar do 500, as quinze chamadas de uma vez (a pergunta 39 do lote; D432 corrigido, D447, D448 e D449 registrados)

- **Para quem usa, nada muda na tela:** os seletores de data, a máscara e o aplicativo mandam `dd/mm/aaaa`, e
  essa forma passa como antes. O pedido montado à mão com a data fora da forma -- o dia de um ou três
  dígitos, o ano de cinco ou de dois -- recebe a frase de cada caminho ("- Data inválida" no cadastro de
  cliente do ERP, "data nascimento inválida" no aplicativo, as duas frases de data no fechamento da agenda
  antiga, a data de hoje nos relatórios e na lista de vendas) no lugar do 500 no cadastro e no fechamento, do
  500 do PostgreSQL nos relatórios e, no MySQL, do 1525 (`Incorrect DATE value`, com `001/2/2026`) ou dos
  caixas de outro dia (com `1/02/02026`). No aplicativo, contrato congelado, é a mudança que o dono aceitou
  na resposta 39, e só no pedido malformado.
- **Um lugar só:** `DataHora::dataValida` confere `is_string` e `~^([0-9]{2})/([0-9]{2})/([0-9]{4})\z~` (o `\z`,
  porque o `$` aceitaria o `\n` no fim) e passa as partes ao `checkdate`; a expressão toma o lugar do
  `strlen`, do `explode` e do `ctype_digit` do D420. As chamadas foram derivadas do grep: as mesmas quinze em
  onze linhas, mais o dia do remarcar, que já conferia a forma; nenhuma cópia do ajudante, nenhum
  equivalente em JavaScript.
- **Provado:** `D432DataNaFormaTest` (32 execuções, novo, verde no MySQL e no PostgreSQL) -- as quatro formas
  que passavam são falsas, as vizinhas que já eram falsas continuam (entre elas a data sem o zero e o ano de
  cinco dígitos), e quatro caminhos -- seis das quinze chamadas, cada uma recebendo as quatro formas --,
  despachados em processo, respondem a frase e não gravam: o modal de cliente do ERP, o cadastro do
  aplicativo, o fechamento da agenda antiga (as duas datas, nas duas ações) e o relatório de caixas (a torta
  só no início e só no fim; ele mostra hoje, e uma data na forma fica no filtro); 502 execuções vizinhas em
  40 arquivos, verdes nos dois bancos (3 pulados em cada). Oito mutantes, um por vez, todos vermelhos: o
  corpo do D420 de volta (20 de 59 nos dois bancos, com o 500 de cada caminho), o `checkdate` arrancado
  (9), o `$` no lugar do `\z` (1), sem o `^` (1), `.` no lugar de `[0-9]` (9, os do D420), `[0-9]{1,2}` no
  dia e no mês (3), `[0-9]{4,}` no ano (1) e, no `CaixasController`, a conferência do fim arrancada (4, no
  PostgreSQL).
- **A revisão, seis achados de um verificador, todos verdadeiros e tratados:** (1) a data sem o zero
  (`1/02/2026`, nove caracteres) não tinha caso, e o `[0-9]{1,2}` passava verde -- com ele o `stringDate` lê
  a posição 9 de um texto de nove, e o 500 volta; entraram quatro vizinhas (`1/02/2026`, `01/2/2026`,
  `1/2/2026`, `01/02/20260`) e dois mutantes novos; (2) o nascimento do sócio e o do usuário são gravados
  sem validação de data, e morrem no `flush` com a forma torta -- medido nos dois bancos, é o D448; (3) a
  lista de bloqueios da agenda e a de notificações leem a data da URL crua na consulta -- medidas (500 no
  PostgreSQL, 1525 no MySQL com duas formas), entraram no D447, que deixou de falar só de relatórios; a
  comanda aberta pela agenda já era do D425; (4) a página `/api/docs` manda o nascimento do aplicativo com
  traço, que o backend nunca aceitou -- é o D449, e o D432 o diz; (5) o registro dizia "quatro das quinze
  chamadas despachadas", e eram cinco recebendo a forma torta: o caso do relatório de caixas passou a
  mandá-la também só no fim, e são seis; (6) este roadmap omitia o 1525 do MySQL.
- **D447, novo:** filtros de tela que leem a data da URL sem o ajudante -- seis relatórios (descontos,
  vendas canceladas, consultor visagista, clientes, indicações e franquias) e, fora de `ISRelatorio`, a lista
  de bloqueios da agenda e a de notificações. Cinco medidos (três relatórios e as duas listas): 500 no
  PostgreSQL com as formas do D432, e o MySQL dá 1525 ou lê outro dia. Os outros chamadores do
  `stringDate` estão listados lá, não traçados.
- **D448, novo:** o nascimento do sócio da unidade e o do usuário (e a contratação) vão ao `flush` sem
  validação de data; com `1/02/02026`, a `ConversionException` do cadastro de cliente, nos dois bancos.
- **D449, novo:** a documentação embutida da API descreve o nascimento do cadastro do aplicativo como
  `dd-mm-aaaa`; o backend só aceita a barra, e o aplicativo publicado a manda (1.438 cadastros dele com
  nascimento na cópia de desenvolvimento -- dedução pela contagem, sem o código do aplicativo).
- Nenhum dos três foi mexido: o pedido era só o D432.
- **A conta dos débitos:** `contar-debitos.php` dá 276 de 447 fora de pé (eram 275 de 444): o D432 fechou e
  o D447, o D448 e o D449 nasceram abertos. `conferir-debitos-repetidos.php` sem par novo (o título do
  D448 foi escrito sem repetir o do D447 e o do D295, que o portão aproximava; são assuntos distintos).
- **Não provado:** nenhum pedido por HTTP nem clique no navegador neste item; o contrato e os ensaios do
  aplicativo ficam para o ciclo.

## 08/10/2026 -- PDV-QUITAR-SEGUNDA-RODADA: a venda com os débitos de outro cliente somados não vira comanda, o "Sem Cadastro" cadastrado no meio da venda abre a comanda, e a tela nova diz os débitos já somados e manda a bandeira que os quita (respostas 40 a 43 do dono; D433 e D434 corrigidos, D380 parcial com um caso ao lote)

- **No PDV antigo e na tela nova, abrir comanda:** a venda em andamento com os débitos de um cliente somados
  pelo "Quitar", sem pagamento, não vira mais a comanda de outro cliente com a dívida dentro (a 40). O ERP diz
  *"Há uma venda em andamento no PDV com os débitos de outro cliente somados ao total, código: N. Finalize a
  venda ou recarregue o PDV (recarregar descarta a venda em andamento) antes de abrir esta comanda."*, e a
  API, 409 com `motivo = venda_em_andamento_com_debitos`. Com pagamento e débitos ao mesmo tempo, a frase é
  esta. O mesmo cliente abre como antes.
- **O "Sem Cadastro" como venda sem dono (a 42, a opção "b", e não a recomendada):** "Cliente sem cadastro",
  item, pagamento, cadastrar a pessoa e "Abrir comanda" deixou de ser recusado -- a venda vira a comanda do
  cliente pedido. O pagamento: pelo ERP, a página da comanda o APAGA ao abrir, como antes do D384 (o operador
  recebe de novo; nada saiu do caixa); pela API, ele fica dentro da comanda -- o custo que a opção dizia. A
  venda sem cliente e o avulso continuam recusados; o "Sem Cadastro" com débitos somados também (a 40 não
  abre exceção). A instrução do item mandava o 2 seguir o caminho da venda sem cliente, que hoje é a recusa;
  segui a opção do dono, e o D434 diz isso.
- **Na tela nova, finalizar (a 41):** a comanda da API ganhou `debitosSomados` (em toda resposta, e só na
  aberta), com a mesma conta da comanda reaberta do ERP (`RegraDosDebitosNaVenda::jaSomados`). A tela mostra
  *"Débitos de R$ X já somados; serão quitados ao finalizar."* e manda `pagarDebitos: true`; o servidor
  confere pela marca. A API continua sem baixar sozinha.
- **A 43, nada a construir:** o segundo "Quitar" soma só o débito novo; os casos que o fixam seguem verdes.
- **Provado:** `D384ComandaDeOutroClienteTest` (18; eram 9) e `D380QuitarComMarcaTest` (26; eram 18), verdes
  no MySQL e no PostgreSQL; 27 arquivos vizinhos, 460 casos, verdes nos dois (2 pulados em cada). Quatorze
  mutantes PHP, um por vez, cada um com o mesmo vermelho nos dois bancos (a tabela no D433), e três no front.
  O portão do recorte ganhou a entrada do devido da comanda da API (vermelho com a unidade arrancada), e a
  contagem do `ISVenda` no de cobertura por par foi de 111 a 112. Front: `vitest` 1597 de 1597 (2 novos),
  `build` e `lint` com saída 0.
- **A conta dos débitos:** `contar-debitos.php` dá 278 de 447 fora de pé (eram 276): o D433 e o D434
  fecharam, e o D380 segue parcial, com a marca nova no fim do título. `conferir-debitos-repetidos.php` sem
  par novo.
- **Ao lote:** a comanda com um débito que nasceu depois do "Quitar" do ERP, fechada pela tela nova -- o
  total não cobre, a tela não avisa nem manda a bandeira, e os débitos ficam abertos com o cliente pagando a
  parte já somada. É o PARCIAL do D380; a recomendação é um aviso sem bandeira.
- **Não provado:** nada aberto no navegador (nem a frase no PDV antigo, nem o aviso na tela nova); nenhum
  ensaio HTTP; o contrato e os ensaios ficam para o ciclo.
- **A verificação do item (o mesmo dia), sete achados conferidos no código:**
  - **a corrida entre as duas telas:** a tela nova decidia a bandeira pela comanda que carregou, e a comanda
    aberta nela antes do "Quitar" do ERP saía sem a bandeira -- o cliente pagava os débitos e eles ficavam
    abertos. Agora o finalizar da API SEM a bandeira, com os débitos já somados, é 409
    `debitos_somados` e não grava nada; a tela relê a comanda, mostra o aviso e finaliza com a bandeira. Não
    é a opção (b) da 41: nada se baixa sem a bandeira;
  - **as seis escritas sem caso:** cada resposta da comanda aberta tem o seu caso que exige
    `debitosSomados`, e um caso lê o controller sem comentários e exige uma chamada só de
    `ComandaJson::completo`, dentro do `recurso`;
  - **o meio centavo do desconto** (5% de 45,90 grava 43,605) era lido como débito somado e recusava o abrir
    comanda -- também no caminho do "Sem Cadastro" da 42. A marca passou a somar os itens arredondados ao
    centavo, o que conserta também o irmão do `cobre`: dois itens arredondados para baixo faziam o finalizar
    recusar a bandeira depois do "Quitar". Medido na cópia, só leitura: 9 vendas com o excedente falso pela
    conta antiga, 3 pela nova -- essas 3 são o centavo que tirar um item deixa no total, registrado como
    **D450** e mandado ao lote;
  - **o `POST .../debitos` não revogava a autorização do fiado** (achado ao escrever o caso; é do D165):
    agora revoga quando soma;
  - **as prévias do `/design-sync`** ganharam o campo novo (cinco literais com TS2741 que o `build` não vê)
    e o estado do `Finalizar` com o aviso; **o `f2-fatia-pdv.md`** deixou de chamar o D380 de corrigido;
  - **o 422 que se repete** quando um débito nasce com a comanda aberta na tela nova: registrado, e uma
    linha a mais na pergunta do lote, sem mudar a tela.
- **Ao lote, da verificação:** o finalizar do ERP sem a bandeira, com os débitos somados (a mesma corrida
  entre duas abas; recomendação: recusar e mandar clicar em "Quitar", que não soma de novo), e o D450
  (recomendação: tirar e ajustar subtraem o item arredondado).
- **Provado na verificação:** `D380QuitarComMarcaTest` (39; eram 26) e `D384ComandaDeOutroClienteTest` (20;
  eram 18), verdes no MySQL e no PostgreSQL; 44 arquivos vizinhos (664 casos, 2 pulados), verdes nos dois;
  onze mutantes PHP (seis deles trocam o `recurso` numa escrita cada), um de cada vez, cada um com o mesmo
  vermelho nos dois bancos (a tabela no D380, "A verificação"), e três no front; `vitest` 1599 de 1599 (2
  novos), `build` e `lint` com saída 0; `conferir-tudo.sh` pelo host com saída 0 (62 ok e o vermelho conhecido
  do D50); `contar-debitos.php` dá 278 de 448 fora de pé (o D450 nasce aberto), e o
  `conferir-debitos-repetidos.php` ganhou o par D433|D450, com o motivo.
- **Não provado na verificação:** nada aberto no navegador, nenhum ensaio HTTP; o contrato e os ensaios
  ficam para o ciclo.

## 08/10/2026 -- A primeira versão de teste no servidor: o que falta, quem faz e quanto leva

O pedido do dono, textual: "atualize a estimativa e inclua no artefato para colocar a primeira versão de
teste no servidor". A estimativa foi publicada como uma seção nova do Artifact do roadmap ("A primeira
versão de teste no servidor · estimativa de 08/10"), depois de uma crítica que derrubou nove pontos dela.
Esta entrada é o conteúdo dela, no repositório, **com as correções que vieram depois da publicação** --
as da página "Subir" (SUBIR-COM-SENHA) e as da verificação dela: o "Antes de tudo", o cenário B, os
passos 2, 3, 6 e 8. A seção publicada ainda não as tem; o que nela ficou atrás está na entrada
SUBIR-COM-SENHA, abaixo. Nada aqui afirma o estado do BVIP: daqui ele não se vê.

- **O que sobe:** o ERP antigo em `https://sistema.franquiabv.xyz/` e a tela nova em `/novo/`, juntos.
  O mesmo `publicar` do `scripts/deploy-teste.sh` sobe os dois, e não há como subir um sem o outro: o
  `front_precisa_de_build` não tem chave para pular a build, e uma falha dela tira o site do ar (o freio
  armado antes). Os dois com a cópia anonimizada de 02/10 (selo `fca02f5a26f7…`), atrás da senha HTTP
  decidida em 02/10 (D326). A cópia de 02/10 vale para o código de hoje: o selo dela bate com o desta
  árvore, e os cinco arquivos que o formam não mudam desde 26/09 (ffbe670) -- não é preciso exportar de
  novo.
- **Antes de tudo:** no BVIP, `bash scripts/deploy-teste.sh conferir`, que só lê. Se a linha do endereço
  não for `público: senha HTTP na frente de / e /novo/ (401)` -- o ERP em `/` com 200 ou 302 (aberto), e
  também `fora do ar`, `MEIO FORA` ou sem certificado --, `bash scripts/deploy-teste.sh tirar` na hora,
  antes de qualquer outra coisa: a página "Subir" mandava publicar sem a senha HTTP (D451), a cópia tem
  os dados que o D326 mandou proteger, e um site fora **sem a marca do `tirar`** (o `base`, ou o freio de
  um passo que reprovou, tiram sem gravá-la) volta aberto no `atualizar` seguinte se o `.env` não tem
  `SENHA_HTTP=sim`. Se o `conferir` parar antes da linha, um `curl` de fora mede o código de `/`: fora o
  401, o `tirar`.
- **Do meu lado, de 2,6 a 5 h** sem o ensaio da build, e até 6,5 h com ele: fechar o R13, acertar a
  página "Subir" (de 1,6 a 3 h) e, opcional, ensaiar a build Linux da tela nova (de 0,5 a 1,5 h).
- **Cenário A -- nada publicado, ou parou no meio: de 3,25 a 9,5 h de relógio do dono**, das quais de
  2,25 a 6,5 h à mão. Sete passos: dizer onde parou, DNS e portas, pré-requisitos, levar a cópia, o clone
  no commit, `preparar` + senha HTTP + `base` + `publicar`, e conferir no navegador. A carga das 14,7
  milhões de linhas e a build da tela nova correm sozinhas, e ninguém mediu quanto duram no BVIP. Fora da
  conta: a propagação do DNS.
- **Cenário B -- a cópia de 02/10 já no ar, com a senha: de 1 a 2,8 h.** Três passos: dizer onde parou,
  o `atualizar` com `ACEITAR_ESQUEMA=sim` (as três migrações de 03/10 já estão na cópia; nenhum ALTER) e
  abrir as duas telas no navegador. O `atualizar` sem `tirar` antes: ele mesmo tira e religa, com a
  escolha do `.env` -- e só depois de `grep '^SENHA_HTTP=' .env | tail -n 1` imprimir `SENHA_HTTP=sim`.
  Com o `tirar` antes, o `atualizar` deixa o site fora, e falta o `SENHA_HTTP=sim bash
  scripts/deploy-teste.sh publicar`.

**O caminho, em ordem:**

1. **Dizer onde o BVIP parou** (o dono, de 15 a 30 min): do clone de lá, `git log -1 --oneline`; a
   linha das portas do `.env` (`grep -E '^(APP|ADMINER)_PORT=' .env`); só os nomes do que há em
   `.deploy-teste/`; a saída do `conferir`; e `ss -tln | grep -E ':(8090|8091|8092|8093)'`. Isso decide
   entre o A e o B, e diz se as portas colidem com a homologação.
2. **Acertar a página "Subir"** (eu, de 1,6 a 3 h): os comandos passam a criar o `htpasswd` e a publicar
   com `SENHA_HTTP=sim`; a tabela do topo deixa de dizer "sem senha HTTP"; entram o `apache2-utils`, o
   aviso do certbot (na primeira vez ele pergunta o e-mail e os termos), o clone que já rodou (pelo `git
   pull`, o `base` e o `publicar` quando o `base` não chegou ao fim; pelo `atualizar` só com o `.env`
   dizendo `SENHA_HTTP=sim`) e duas linhas em "O que fica de fora": as 4 UFs da cópia (D430) e o Líder
   sem a visão de gerente (D124). Só documento: a página não está no selo. É o único item meu que deve
   vir antes do `publicar`. Feito no mesmo dia (SUBIR-COM-SENHA, abaixo).
3. **Fechar o R13** (eu): as respostas 40 a 43 no PDV e na tela nova. Não bloqueia: sem ele sobe o commit
   anterior, e um `atualizar` depois o traz. Saiu no 7a4894d (08/10, 20:06), depois de a seção ser
   publicada.
4. **Ensaiar aqui a build Linux da tela nova** (eu, de 0,5 a 1,5 h, opcional): como o `publicar` a faz,
   num contêiner Node. A imagem já está nesta máquina; desceriam só os pacotes do npm, e isso pede a
   autorização do dono. O CI não faz build e está parado; no BVIP, uma falha dessa build tira o site do
   ar. Não há registro do resultado do ensaio de 25/09 (d391e43 o dava como em andamento). Feito em
   09/10, com a autorização da resposta 59 (BUILD-LINUX-DO-PUBLICAR, abaixo): verde no 2d3c6a0, a build
   de 35 a 53 s, e o ensaio fica no repositório (`scripts/ensaiar-build-do-publicar.sh`).
5. **DNS e portas 80/443** (o dono, de 15 min a 1 h, só no A): o registro A de `sistema.franquiabv.xyz`
   para o IP do BVIP, e as portas 80 e 443 chegando ao nginx do host; o certificado sai pela porta 80.
6. **Pré-requisitos do BVIP** (o dono, de 15 min a 1,5 h, só no A): docker compose v2, nginx, certbot,
   python3, flock, curl, openssl e git; o `sudo`, que o script chama mesmo como root (sem ele param o
   `conferir`, o `base`, o `publicar`, o `atualizar`, o `voltar` e o `_aplicar`); e o `htpasswd`, do
   pacote `apache2-utils`. Saída para a internet: o Docker Hub, os espelhos do apt do Debian, a imagem do
   composer, o phar do phpunit, o npm e o packagist.
7. **Levar a cópia de 02/10** (o dono, de 15 a 30 min, só no A): o `base-teste.sql.gz` (214 MB) por `scp`
   para o `$HOME` de quem roda o `base`.
8. **O clone no commit** (o dono, de 15 min a 1 h): reaproveitar o clone de 02/10, se existir -- um
   segundo clone dividiria os contêineres e o banco com ele (o `COMPOSE_PROJECT_NAME` é fixo). Com a
   base de 02/10 carregada até o fim: o `atualizar` com `ACEITAR_ESQUEMA=sim`, depois de `grep
   '^SENHA_HTTP=' .env | tail -n 1` imprimir `SENHA_HTTP=sim` -- ele tira o site e o religa com a escolha
   do `.env`; com o site tirado pelo "Antes de tudo", ele troca o código e o deixa fora, e falta o
   `SENHA_HTTP=sim bash scripts/deploy-teste.sh publicar`. Com o `base` que não chegou ao fim, ou que
   nunca rodou: o `tirar`, o `git pull --ff-only`, o `base` e o mesmo `publicar` com a senha. Pelo
   `atualizar` seria também com `ACEITAR_BASE=sim`, e depois de um `base` que morreu no meio da carga o
   banco fica sem tabela nenhuma e o `atualizar` para no meio, com o código já trocado. Os comandos estão
   na página ("Se o BVIP chegou a rodar uma versão anterior deste script").
9. **`preparar`, senha HTTP, `base` e `publicar`** (o dono, de 1,5 a 4 h de relógio, só no A): num
   `tmux`, o `preparar`; `sudo htpasswd -c /etc/nginx/.htpasswd-sistema.franquiabv.xyz <usuário>`; o
   `base ~/base-teste.sql.gz`; e `SENHA_HTTP=sim EMAIL_CERTBOT=<e-mail> bash scripts/deploy-teste.sh
   publicar`.
10. **Conferir e abrir no navegador** (o dono, de 30 min a 1 h): o `conferir`; depois, no navegador, a
    senha HTTP, o `user1` com a senha que o `base` gravou em `.deploy-teste/senha`, e o `/novo/`. À mão
    de propósito: com a senha HTTP ligada, o script só mede o 401, e o "Publicado" não prova que a tela
    nova carrega.

**O que bloqueia, e é do dono** (as perguntas 57 e 58 da página do lote):

- **Em que pé está o BVIP?** (a 57) É a dúvida que ficou da resposta 52a ("depois que a cópia estiver de
  pé no BVIP"): toda menção do repositório até 08/10 diz que a cópia "está subindo". Decide entre o A e
  o B, e destrava o que está congelado à espera da cópia.
- **O DNS de `sistema.franquiabv.xyz` já aponta para o BVIP, e o domínio está sob o controle do dono?**
  (a 58; só no cenário A) Sem isso, o certbot do `publicar` não emite o certificado.

Não bloqueiam: qual commit sobe (o `atualizar` pega o `main` da hora) e a autorização para o ensaio da
build (a 59).

**Depois da primeira versão, a fila que não bloqueia:**

- **Antes de chamar quem testa** (de 1 a 2 h, minhas): um roteiro de teste -- o `/novo/` depois do login
  do ERP, as 13 telas ligadas no menu, o aviso "ainda roda na versão antiga" nas outras, os dados até
  16/08, as quatro unidades de fixture, os 38 logins com caixa aberto de outro dia, e o Líder sem a visão
  de gerente.
- **As 27 UFs no banco do teste** (de 0,2 a 0,7 h, dos dois): um INSERT que pode rodar duas vezes sem
  estrago, rodado no BVIP; pôr no endurecimento mudaria o selo, que está congelado (D430).
- **As respostas que faltam** (de 7,5 a 15 h): o R14 (a agenda, 45 a 47), o R15 (o índice do Desmarcar,
  50) e o R16 (a etapa 6 da adesão, 51 e 53).
- **A fila dos débitos, itens 25 a 32** (de 24,5 a 49 h, pela soma das faixas de cada item): a F4 no
  navegador e os consertos de tela do D322 primeiro, porque o testador vai vê-los; depois o arrastar
  instável, o laminas-cache, a cobertura, os tipos do front, os consertos do deploy (D328, D444, e agora
  o D451) e o roteiro em dia.
- **Congelados até a cópia estar de pé:** o `shm_size` do PostgreSQL (D445, a resposta 52a), o `conferir`
  cobrar a chave do Asaas e o token vazios (D330), a sonda do `/novo/` com a senha ligada, e a cópia limpa
  (D327 e D326: de 4 a 8 h minhas e de 1,5 a 4 h do dono, porque o selo muda).

Somados, o R14 a R16 e os itens 25 a 32 dão de 32 a 64 h de item: de 1,6 a 3,2 dias de fila em série, no
ritmo medido, sem contar as pausas.

**A régua, medida no relógio e não estimada:** de 03/10 04:28 a 08/10 15:01, 36 itens da fila, em série,
levaram 107,9 h de parede em 130,5 h de calendário -- cerca de 20 h de item por dia. Item só de
documento: de 1,6 a 2,1 h (o DOCS-QUE-MENTEM, o análogo da página "Subir", levou 3 h). Item de código do
ERP: mediana de 3 h (de 1,7 a 7,2 h). Ciclo de testes: mediana de 0,7 h, mais lento em 08/10 (1,4 e 2,1
h). A régua antiga deste roteiro -- intervalos entre commits, com teto de 60 minutos -- deixou de medir
quando os itens passaram a rodar em agentes: para os mesmos commits, ela dá 8,8 vezes menos. As horas do
dono não vêm de medida: são as faixas lidas nos passos do script e da página.

**O que não se sabe:**

- o estado do BVIP: o commit do clone, se o `preparar`, o `base` e o `publicar` rodaram, se o site está
  no ar e com a senha, se o `htpasswd`, o DNS e o certificado existem;
- quanto o `preparar`, o `base` e o `publicar` demoram lá: ninguém mediu;
- se a build Linux da tela nova passa no BVIP (aqui passou em 09/10, no 2d3c6a0, como o `publicar` a
  faz -- BUILD-LINUX-DO-PUBLICAR, abaixo --; a imagem que o BVIP baixar pode ser outra), se o BVIP
  alcança os repositórios de imagens e de pacotes, e se tem disco para as imagens;
- se o certbot do BVIP já tem conta registrada; se não tiver, alguém precisa estar no terminal na
  primeira vez;
- se a barreira de origem de 08/10 e a sessão passam pelos dois nginx com a senha HTTP: lido no código e
  deduzido que sim, não medido.

A tabela por fases ("Quanto falta até o fim da Fase 3", acima) é de setembro e não foi recalculada.

## 08/10/2026 -- SUBIR-COM-SENHA: a página "Subir" do ambiente de teste publica com a senha HTTP, e um servidor que já rodou começa vendo se o site está aberto (D451 registrado)

- **Para quem sobe (o dono, no BVIP), em `docs/ambiente-teste.md`:** os comandos de "Subir" criam o
  `htpasswd` em `/etc/nginx/.htpasswd-sistema.franquiabv.xyz` (o caminho que o vhost espera) e publicam
  com `SENHA_HTTP=sim`, e terminam no `conferir`, que tem de dizer "senha HTTP na frente de / e /novo/
  (401)". A tabela do topo diz a decisão de 02/10 (senha HTTP, D326) no lugar do "sem senha HTTP" de
  24/09, e um parágrafo diz por que a senha não liga sozinha: sem a variável e sem a linha no `.env` --
  que o `preparar` não escreve --, o script assume `nao` e publica aberto (D451).
- **Antes de tudo, num servidor que já rodou:** o `conferir`, e toda linha do endereço que não seja a do
  401 manda o `tirar` na hora -- a do site aberto (`público: ERP em /, tela nova em /novo/`, e a recusa de
  `SENHA_HTTP=sim` com o vhost sem senha), e também `fora do ar`, `MEIO FORA` e sem certificado, porque
  um site fora sem a marca do `tirar` volta aberto no `atualizar` seguinte; se o `conferir` parar antes
  (sem sudo, sem `.env`, o root num clone de antes da v9), um `curl` de fora mede o código de `/`, e fora
  o 401 vem o `tirar`. Como root num clone de antes da 9ee7c144, nem o `tirar` roda: os dois comandos à
  mão.
- **O servidor que já rodou uma versão anterior:** reaproveitar o clone (o `COMPOSE_PROJECT_NAME` é fixo:
  um segundo clone toma os contêineres e o volume do banco); para um clone novo, `tirar` e `docker compose
  down -v` no antigo, porque o `down` sem o `-v` deixa o volume com a senha do `.env` antigo e o
  `preparar` do novo para em "a aplicacao nao entra no banco com o usuario dela" (assim, sem acento, como
  o script imprime). Dois caminhos, conforme o `conferir`: a base de 02/10 carregada até o fim ("Tudo
  conferido" com a linha `base (uso)`, e não `base: vazia`), pelo `ACEITAR_ESQUEMA=sim ... atualizar`,
  só depois de o `.env` dizer `SENHA_HTTP=sim` (e o `publicar` com a senha, se o site ficou tirado); o
  `base` que não chegou ao fim, pelo `git pull --ff-only`, o `base` e o `publicar` com a senha -- pelo
  `atualizar` seria `ACEITAR_BASE=sim ACEITAR_ESQUEMA=sim`, e depois de um `base` cortado o `_aplicar`
  para em "não tem a tabela usuarios" com o código já trocado. A cópia de 02/10 serve: não é preciso
  exportar de novo.
- **O que a página não citava:** o `sudo`, mesmo para o root (sem ele param o `conferir`, o `base`, o
  `publicar`, o `atualizar`, o `voltar` e o `_aplicar`; o `tirar` faz só a metade da pilha); o
  `htpasswd`, do `apache2-utils`; a saída para a internet inteira (o Docker Hub com as sete imagens, o
  apt do Debian, o `phar.phpunit.de`, o npm, o packagist e o GitHub, o Let's Encrypt); as portas 80 e
  443; o certbot que pergunta os termos e o e-mail na primeira vez (o script não passa
  `--non-interactive` nem `--agree-tos`); a conferência à mão no navegador (com a senha ligada, as sondas
  só medem o 401); e, em "O que fica de fora", as 4 UFs da cópia (D430) e o Líder sem a visão de gerente
  (D124, D327). A trava por usuário cita o D444.
- **D451 registrado:** o deploy sem a escolha explícita publica aberto -- o `publicar`, e o `atualizar` e
  o `voltar` pelo `_aplicar`, que religa pelo mesmo `instalar_vhost`. O conserto (fechado na falha:
  recusar sem a escolha, ou o padrão `sim` com a recusa sem o `htpasswd`, no ponto por onde os três
  passam) fica para o item 31 (DEPLOY-TESTE-V9), porque não há ensaio do script no repositório e a carga
  está congelada. O script não mudou.
- **Na verificação (onze achados confirmados, nove distintos, todos de documento):**
  - o caminho "base carregada até o fim" religava **aberto** um site fora sem a marca do `tirar` com o
    `.env` sem a linha: o `conferir` diz `fora do ar` e "Tudo conferido", o `atualizar` não acha a marca,
    acha o certificado e o selo igual, e o `por_no_ar` liga o vhost sem `auth_basic`. Agora o "Antes de
    tudo" manda o `tirar` em toda linha que não seja o 401, e o `atualizar` vem depois de `grep
    '^SENHA_HTTP=' .env | tail -n 1` dizer `SENHA_HTTP=sim` (também em "Atualizar, voltar, conferir");
  - três passagens mandavam um `publicar` sem `SENHA_HTTP=sim` justamente para servidores antigos, e o
    script imprime esse `publicar` no fim do `preparar`, do `base`, do `tirar`, do `aplicar` e nas recusas
    do selo: as passagens levam a senha, e a página avisa para não copiar o do script;
  - o D451 dava o defeito e o conserto (a) só ao `publicar`: o `atualizar` e o `voltar` publicam aberto
    pelo mesmo `instalar_vhost`, e o conserto vai para o ponto por onde os três passam;
  - "como root, num clone de antes da v9, só o `tirar` roda" valia só para a 9ee7c144: de 6bcfe44 a
    ffbe670 as guardas recusam o root até no `tirar` (lido no `git show` de cada um), e ali o site sai
    pelos dois comandos à mão;
  - o passo 8 desta estimativa, que o cenário B segue, mandava "primeiro o `tirar`; depois o `atualizar`",
    e com a marca o `atualizar` deixa o site fora, sem `publicar` nenhum no B: agora, no B, o `atualizar`
    sem o `tirar` (e com o `.env` conferido), ou o `publicar` com a senha depois;
  - "Tudo conferido" não separava a base carregada da vazia (o `conferir_base uso` aceita a vazia, e a
    conferência do selo é pulada com ela): o primeiro caminho pede agora a linha `base (uso)`, e não
    `base: vazia`;
  - a frase do `preparar` citada com acentos sai do PHP embutido sem eles;
  - esta estimativa se dizia "o mesmo conteúdo" da seção publicada, e não é (abaixo);
  - e, de passagem, fora dos achados: a página dizia que aspas ou comentário na linha `SENHA_HTTP` do
    `.env` reprovam; o `do_env` os tira (como o compose), e só outro valor reprova.
- **O Artifact ficou atrás desta entrada e da página:** a seção publicada ainda diz, no "Antes de tudo",
  que a página "Subir" de hoje manda publicar sem a senha, e só manda o `tirar` com o 200 ou o 302; no
  passo 8, que o cenário B segue, "primeiro o `tirar`; depois o `atualizar`" (o site fica fora) e o
  `ACEITAR_BASE=sim` para o `base` que não chegou ao fim (para no meio); no passo 6, o `sudo` sem o
  `voltar` e o `_aplicar`; e os passos 2 e 3 ainda em andamento. Falta republicar a seção com o que está
  aqui; não republiquei.
- **Provado:** cada comando da página conferido no `scripts/deploy-teste.sh` como está -- os subcomandos
  e a ordem do `publicar` (a escolha gravada no `conferir_env`, a build, o certificado e só então o
  `htpasswd` no `instalar_vhost`), o `HTPASSWD` do vhost, as recusas do `decidir_a_base` e do
  `conferir_base`, os `sudo -v` de cada subcomando e as frases citadas; o script não muda desde 6c2c807
  (02/10), então um clone de lá diz as mesmas frases. Medido em 08/10: o selo desta árvore dá
  `fca02f5a26f7`, o mesmo da primeira linha do `base-teste.sql.gz` local; os cinco arquivos do selo sem
  commit desde ffbe670; o `INSERT` de `estados` do dump com 4 linhas (SP, SC, RJ, ZY). Portões:
  `conferir-referencias-docs`, `conferir-links-mortos`, `conferir-bytes-de-controle`, `contar-debitos` e
  `conferir-debitos-repetidos`, todos com saída 0; `contar-debitos.php` dá 278 de 449 fora de pé (o D451
  nasce aberto), e o de repetidos, sem par novo. Depois da verificação, os cinco de novo com saída 0 e os
  mesmos números (416 chamadas; 451 débitos lidos; o título novo do D451 sem par), e um medidor de uma
  vez, no rascunho e fora do repositório (`medir-subir-pagina.py`): todo `publicar` dos blocos com a
  senha, todo subcomando e variável da página no script, toda linha de estado do endereço do `conferir`
  no "Antes de tudo", nenhuma frase do script citada com acento, e o `.env` conferido junto de todo
  `atualizar` -- 9 falhas na página do HEAD, 8 na do implementador, 0 na de agora, e os seis mutantes da
  página mortos.
- **Não provado:** nada rodou no BVIP, nem um ensaio do deploy aqui. As perguntas do certbot são o
  comportamento dele sem `--non-interactive`, lido, não medido; o `down -v` do clone novo e a parada do
  `_aplicar` depois de um `base` cortado foram lidos no código, não rodados; o `curl` de fora não rodou.
  Da verificação, também lidos e não rodados: o `atualizar` que religa aberto o site fora sem a marca (o
  `_aplicar`, o `aplicar` e o `por_no_ar`), e a recusa do root em todo subcomando de 6bcfe44 a ffbe670.

## 08/10/2026 -- AGENDA-SEGUNDA-RODADA: a sessão do pacote não se gasta duas vezes na mesma venda, dois horários do cliente no mesmo minuto saem na ordem em que foram marcados, e o modal "HORÁRIOS MARCADOS" lista os da unidade do horário clicado (respostas 45 a 47 do dono; D426, D435 e D436 corrigidos; D452 e D453 registrados com perguntas ao lote; D454 registrado)

- **Na comanda aberta pela agenda e no PDV (a 45):** com uma sessão no pacote e dois horários do mesmo serviço,
  só o marcado primeiro sai como sessão; o outro sai pelo preço (com o convênio, se houver), e o pacote termina
  em 0 depois do finalizar -- antes, os dois saíam de graça e o pacote ia a -1. Vale no PDV antigo e na API da
  tela nova (o mesmo serviço de lançamento): o segundo lançado sai pelo preço, e a sessão volta para o próximo
  lançamento quando o item dela sai da venda. Com dois pacotes do mesmo serviço, cada item vai para um. A
  comanda reaproveitada conta as sessões que já tinha. Os 17 pacotes já negativos ficam como estão. O item que
  já tinha saído pelo preço continua pelo preço quando o da sessão sai: isso a resposta não decide, e foi ao
  lote (o D453).
- **Na agenda, no aplicativo e no finalizar (a 46):** dois horários do cliente no mesmo minuto saem na ordem
  de quem foi marcado primeiro, nos dois bancos (`ORDER BY age.data, age.id` nos quatro métodos do cliente que
  ordenavam só pela hora; a lista saiu do `grep`, que acha nove `ORDER BY` sobre a agenda, e os dois da grade,
  que são do colaborador, não mudaram).
- **No modal "HORÁRIOS MARCADOS" da agenda antiga (a 47, a opção "b", e não a recomendada):** para quem tem
  "Agendas unidades", a lista é a dos horários do cliente na unidade do horário clicado -- os que o "Fazer
  check-in" alcança --, e o "Abrir comanda" continua na unidade de quem está logado, com os serviços dela. O
  desmarcar de dentro do modal o redesenha na mesma unidade. O horário clicado que sumiu (remarcado ou
  desmarcado com a grade aberta), inativo ou de outro cliente abre o modal como antes, com a lista da unidade
  da sessão e o "Abrir comanda" de sempre, e um aviso diz que a lista não é a da grade clicada; o check-in
  desse clique continua pedindo para atualizar a agenda. Para os outros, nada muda. `mod-cadastros.js?cache=166`.
- **Provado:** `D435SessaoDoPacoteNaMesmaVendaTest` (8), `D426DesempateDosHorariosDoClienteTest` (3),
  `D436ModalDosHorariosMarcadosTest` (10), `D452SessaoDoPacoteForaDaMesmaVendaTest` (3) e o
  `D426OrdemDosServicosDaComandaTest` (4, um caso mudado com a 45): 28 casos, verdes no MySQL e no PostgreSQL.
  No front, `remarcarDaTelaAntiga.teste.ts` roda o `agendaModalHorariosMarcados` do arquivo servido com um
  jQuery de mentira (5 casos novos: o POST leva o horário clicado, o aviso aparece com o modal aberto, a
  recusa mostra a frase do servidor) e confere o piso do `?cache=` em 166; o front inteiro com 1.604 casos
  verdes, o `build` e o `lint` com saída 0. A caracterização inteira, rodada de novo depois da última mudança
  do controller: 2.862 casos verdes no MySQL e 2.862 no PostgreSQL, 16 pulados em cada. Da primeira rodada
  (antes da verificação): o contrato 67 de 67 nos dois, e os ensaios `ensaiar-fluxo-adm-api` (22 passos),
  `ensaiar-escritas-do-aplicativo` (6) e `ensaiar-fidelidade-adm-api` (24 de 24) nos dois bancos. Mutantes, um por vez, cada um com o mesmo vermelho
  nos dois bancos, menos os do desempate, que no MySQL só o caso da forma pega (as tabelas no D426, no D435 e
  no D436); os nove do controller rodaram de novo contra o código final, e os quatro do JavaScript e do
  layout agora ficam vermelhos no front, e não num roteiro de rascunho. `conferir-tudo.sh`: saída 0, com 62 ok
  e o vermelho conhecido do D50.
- **Da verificação (nove achados confirmados):** a recusa do modal no horário que sumiu escondia o "Abrir
  comanda", que o dono quis "como hoje" -- virou o modal da sessão com o aviso; a prova do JavaScript entrou no
  front, com o piso do cache em 166 e o comentário do layout dizendo por quê; tirar da comanda o item da sessão
  deixa o outro pelo preço com a sessão livre (pior que antes do conserto, nesse caminho) -- é regra de dinheiro
  e foi ao lote como o D453, com o caso do fato; o item de duas sessões leva a -1, DENTRO da mesma venda, o
  pacote que a própria venda já gastou -- entrou no D452 e na pergunta dele (a opção "a" com o saldo da venda),
  com o caso do fato; o botão "Agendamentos" do modal de marcar manda sempre o cliente 7136, escrito no JS
  desde a importação -- registrado como o D454, sem conserto aqui, e o D436 deixou de citá-lo como caminho
  legítimo; o D426 dizia "oito" `ORDER BY`, e são nove; os casos de dois pacotes comparavam o dia móvel com a
  validade fixa da fixture (2030-12-31), e agora a põem um ano adiante (com a fixture vencendo em 10 dias, o
  arquivo velho fica vermelho e o novo, verde); e o "112 OK" valia para um controller anterior -- tudo rodou de
  novo.
- **A conta dos débitos:** `contar-debitos.php` dá 281 de 452 fora de pé (eram 278 de 449): o D426, o D435 e o
  D436 fecharam, e o D452, o D453 e o D454 nascem abertos. `conferir-debitos-repetidos.php` ganhou os pares
  D435|D452 e D435|D453, com o motivo, e perdeu o D435|D436, que deixou de aparecer.
- **Ao lote:** o D452 -- a sessão ainda vai abaixo de zero por duas vendas abertas do cliente, e pelo item de
  quantidade maior que o saldo do pacote que vence antes, também dentro da mesma venda; a recomendação é levar
  a regra da 45 a esses caminhos --, e o D453 -- ao tirar o item da sessão, ela passa ao item mais antigo do
  mesmo serviço que saiu pelo preço, recomendado --, provados como fato nos dois bancos.
- **Não provado:** nada aberto no navegador (nem o modal com a grade da outra unidade, nem o aviso, nem a
  comanda); o "pacote de outra unidade" foi lido no código, sem caso novo; a amostra da cópia não foi medida de
  novo com o desempate (os números são os de 07/10); o contrato e os ensaios HTTP não rodaram de novo depois da
  verificação (ela mudou o controller da agenda antiga e o JS, que eles não exercitam).

## 09/10/2026 -- INDICE-ID-PAI-NO-CORTE: o índice de `agendas.id_pai` entra no passo 0 do corte, e as respostas 48 a 53 do dono ficam registradas (D220 preparado para a janela)

- **O que muda para quem usa: nada hoje.** Na janela do corte, o Desmarcar -- a tela antiga, o aplicativo, a
  API, o remarcar da tela antiga e a tela nova, que busca duas vezes -- deixa de ler a agenda inteira para achar
  a família do horário: na cópia, 2,8 s por busca no MySQL; com o índice, 0,2 ms no MySQL e 0,14 ms no
  PostgreSQL. A sua resposta 50, a opção (a): `scripts/migracao-agendas-indice-id-pai.sql` (idempotente,
  `INPLACE` e `LOCK=NONE`, as escritas seguem) no laço do passo 0, a linha `| 0 | **Aplicar ...**` dela em
  `docs/procedimento-de-corte.md`, o `KEY IDX_AGENDAS_id_pai` no `mysql.sql`, e o `postgres/schema.sql`
  regerado -- o gerador o herda como `agendas_IDX_AGENDAS_id_pai (inquilino_id, id_pai)`, sem linha à mão.
  Os bancos de teste (o da caracterização e o do contrato) nascem com ele.
- **Uma linha de código, que a opção (a) dizia não precisar (o D455, corrigido):** com o índice, o PostgreSQL
  passou a devolver a família do horário com os filhos antes da raiz -- a consulta não tinha `ORDER BY`, e a
  ordem era a do plano -- e a caracterização caiu (`AgendamentoCriadoTest`, e numa outra rodada dois casos
  do Desmarcar da API nova). `selecionarHorariosFilhos` ganhou `ORDER BY age.id`: a raiz, depois os filhos,
  que é a ordem que o MySQL de produção já devolve e o desempate pelo id da sua resposta 46. Quem conta com
  ela: o push do check-in, que sai pela primeira linha e escreve as horas na ordem da lista, e as listas
  `familia` e `removidos` da API nova. No MySQL de hoje nada muda.
- **Provado:** o portão do passo 0 sai 0 com as sete migrações do laço e 1 em cada uma das cinco
  falsificações (o `KEY` sem a migração, a migração sem o `KEY`, a linha sem o laço, a cláusula em comentário
  dentro do literal, a coluna errada); num MySQL 8.4.11 descartável montado do dump de produção com 400 mil
  linhas inventadas, o laço inteiro duas vezes (a segunda não faz nada), a busca da família passou de ler a
  tabela (0,53 s) ao `index_merge` da `PRIMARY` com o índice (0,26 ms), e a `agendas` saiu igual à do
  `mysql.sql` de hoje; num PostgreSQL 16 descartável montado do `schema.sql` regerado, o índice uma vez, o
  `verificar-schema-postgres` com 0, e o `BitmapOr` como `bvip_app` com o RLS; e o achado da P8 reproduzido --
  com a linha à mão no gerador, a carga do `schema.sql` para em "already exists". Os bancos descartáveis
  foram apagados, e nada foi ao binlog. O caso novo, `DesmarcarAgendamentoTest::testAFamiliaDoHorarioSeAchaPeloIndiceDeIdPai`,
  verde nos dois bancos, e vermelho sem o `KEY` (nos dois) e com a consulta reescrita em `COALESCE` (no
  MySQL; no PostgreSQL as três linhas das fixtures não deixam o plano separar, e ele confere o índice no
  esquema -- a primeira versão, que lia o plano, passou com o mutante). O `ORDER BY` do D455: sem ele, três
  casos da agenda caem no PostgreSQL (no MySQL nenhum, e não há como -- lá a ordem do id sai do plano); com
  ele, o plano do descartável segue no índice (`index_merge` mais uma ordenação de 3 linhas, 0,14 ms no
  MySQL; `Sort` sobre o `BitmapOr`, 0,23 ms no PostgreSQL). A caracterização inteira, depois da última
  mudança de código: 2.863 casos verdes no MySQL e 2.863 no PostgreSQL, 16 pulados em cada; o contrato, 67
  de 67, num banco montado de novo; os ensaios `ensaiar-fluxo-adm-api` (22 passos) e
  `ensaiar-escritas-do-aplicativo` (6, o desmarcar do aplicativo junto) nos dois bancos; o front, 1.604
  casos (não mudou). `verificar-schema.php` e `verificar-schema-postgres.php` com 0 e
  `conferir-schema-tool.sh` com 4 (a linha de base) -- o que só diz que as colunas não mudaram: os três
  saem iguais com o índice e sem ele, e não são prova dele (corrigido na verificação, abaixo) --, o gerador
  igual ao `schema.sql`, e o `conferir-tudo.sh` com saída 0: 62 ok e o vermelho conhecido do D50.
- **O que fica com você:** aplicar, no passo 0 da janela. **E uma pergunta nova ao lote**: a cópia local e a
  base do teste no BVIP seguem sem o índice (a P8 oferecia criá-lo na cópia "só com a sua autorização", e a
  resposta 50 não falou disso); recomendo criá-lo na cópia local, nos dois bancos, como no D308. **Para o
  deploy do teste:** o próximo `atualizar` recusa pela migração nova, e `ACEITAR_ESQUEMA=sim` é o certo, sem
  ALTER -- mas, ao contrário das de 03/10, a base do teste NÃO tem o que esta migração escreve; nada depende
  dele (`docs/ambiente-teste.md`).
- **As respostas 48 a 53, registradas nos débitos:** a 48 (manter os testes do front no CI) no D441, que
  continua parcial até a primeira execução verde no runner; a 49 (a VIP Class em 8º) no D57 e no D343, e a
  resposta ao lado das duas correções de registro que diziam "até ele responder" (neste roteiro e no
  `funcionalidades-sem-uso.md`) -- se a administração dela e a dos assistentes entram na tela nova segue
  sendo a P14; a 50 no D220; a 51 (o freio da etapa 6, com o teto por indicador por dia, **cujo número veio
  vazio** -- a sugestão era 5) no D304; a 52 (`shm_size: 1g`) no D445, **não aplicada**: espera a cópia do teste de pé no BVIP, que é a
  pergunta 57; e a 53 (a comissão da primeira compra, como a página diz) no D446. A 51 e a 53 são
  implementadas pelo item R16.
- **A conta dos débitos:** `contar-debitos.php` dá 283 de 453 fora de pé (eram 281 de 452): o D220 passou
  de medido a preparado para a janela, e o D455 nasce corrigido. `conferir-debitos-repetidos.php`: nenhum
  par novo.
- **Não provado:** o tempo de criar o índice na tabela inteira de produção (o D189 mediu de 11 a 20 s para um
  parecido, na cópia) e o plano no MySQL 8.4.7 de produção, que pode não escolher o `index_merge` que o
  8.4.11 daqui escolhe; a migração não rodou em banco nenhum com os 4 milhões de linhas da agenda (só na
  agenda inventada, e o D220 já tinha medido o índice numa cópia só da agenda); nada foi aberto no navegador
  (o check-in e o Desmarcar das duas telas, com o `ORDER BY`, só pela suíte e pelos ensaios HTTP).
- **A verificação, no mesmo dia: três achados, consertados (o detalhe está no D220).** (1) O
  `ensaio-geral-do-corte.md` mandava aplicar na cópia o laço inteiro do passo 0, e o laço inteiro cria o
  índice na `franquiabv_dev`, que espera a sua resposta. Agora o ensaio tem o seu laço para a cópia do
  desenvolvimento, sem a sétima, e o `conferir-passo-zero-do-corte.php` confere que ele é o do passo 0 menos
  as migrações cujo cabeçalho diz "NAO rode na copia local": vermelho no ensaio de antes, com a sétima no
  laço da cópia, sem uma das seis, com a ordem trocada e com a frase tirada do cabeçalho. Se você autorizar
  o índice na cópia, a frase sai e a sétima entra, as duas juntas. (2) `verificar-schema.php`,
  `verificar-schema-postgres.php` e `conferir-schema-tool.sh` só olham colunas e saem iguais com o índice e
  sem ele: não eram prova dele (a linha de cima foi corrigida). E o `orm:schema-tool:update` propõe apagar
  o índice em todo banco que o tenha -- medido no da caracterização: um dos 7 `DROP INDEX` de 275 comandos,
  os outros seis de antes --, o que só pesa com `--force`, que nunca roda; nenhum portão mudou. Quem vê o
  índice: o portão do passo 0, o gerador com a carga do `schema.sql`, e o caso novo. (3) O caso novo
  procurava o índice pelo nome, e passava com um segundo índice igual de outro nome (a linha à mão no
  gerador que a P8 proibiu, só que sem colidir); agora exige um índice só com `id_pai` em `agendas`, nos
  dois bancos, e cai com essa linha no PostgreSQL e com um segundo `KEY` no MySQL, que a versão anterior
  deixava passar. Depois dos três: a caracterização inteira, 2.863 verdes em cada banco (16 pulados), e o
  `conferir-tudo.sh` com 0, 62 ok e o vermelho conhecido do D50. Nenhum código da aplicação mudou.

## 09/10/2026 -- BUILD-LINUX-DO-PUBLICAR: a build da tela nova passa no Linux do jeito que o `publicar` a faz, e o vhost gerado do modelo serve o `/novo/` num nginx 1.24 e num 1.30, com e sem a senha HTTP; o ensaio fica no repositório (a pergunta 59 do lote; nenhum débito novo, e nem a aplicação nem o deploy mudaram)

- **Para quem sobe:** a build que o `publicar`, o `atualizar` e o `voltar` fazem com o freio armado --
  se ela falha, o site sai do ar -- deixou de acontecer pela primeira vez no BVIP: rodou fora do Windows,
  no 2d3c6a0, e passou. A página "Subir" (`docs/ambiente-teste.md`) diz agora: antes de publicar um commit
  que mudou o `frontend/`, rode aqui o `scripts/ensaiar-build-do-publicar.sh` -- numa máquina com o
  repositório e o docker, e não no BVIP --, com o commit que vai subir; o "mudou" é contra a build
  instalada no BVIP (o `novo.commit`), e não contra o commit anterior, e o `voltar` reconstrói sempre, então
  vale antes de todo `voltar`. E registra o resultado de hoje.
- **O ensaio** sai com 0 (verde), 1 (defeito) ou 2 (não conseguiu medir, e nada falhou antes). Dos
  defeitos, a build ou o `nginx -t` reprovados, o `/novo/` sem a tela nova, o noindex ou o HSTS faltando,
  ou o 401 faltando com a senha tiram o site do ar no BVIP; o resto (o tipo, o cache, o 301, o 404 do
  asset que falta, a rota interna) o `conferir` do deploy não mede, e a tela nova fica no ar servida
  errada. Lê do `scripts/deploy-teste.sh` da origem o que copia -- a `IMAGEM_NODE`, o `sh -c` e os `-e` do
  `construir_front`, o padrão que ele confere no index, o bloco do `sed` do `instalar_vhost`, os dois
  `auth`, a `FRONT_RAIZ`, o `HTPASSWD` e o domínio padrão -- e sai com 2 se a forma mudou, em vez de rodar
  uma cópia velha. Exporta o `frontend/` por `git archive` (nunca a árvore de trabalho, que tem o
  `node_modules` do Windows) para um volume do docker, e não para uma pasta do Windows: ela não distingue
  maiúsculas, e o ext4 do servidor distingue -- o ensaio confere que o volume distingue. A build roda com a
  mesma imagem, a mesma expressão do usuário (`id -u`:`id -g` de quem roda; no BVIP, como root, `0:0`, que
  `USUARIO_DO_DEPLOY=0:0` ensaia), as mesmas variáveis, o mesmo `/app` e o mesmo comando.
  Depois confere o `dist` (o padrão do deploy; todo `src`/`href` do index em `/novo/` e no `dist`; todo
  `assets/...` citado presente; nenhum caminho do Windows); gera os dois vhosts do modelo pelo próprio
  `sed` do deploy, com o http2 decidido pela versão do nginx do contêiner como o deploy decide pela do
  host; passa o `nginx -t` de cada um num `nginx:1.24` com um certificado autoassinado no caminho do Let's
  Encrypt e o `dist` copiado como o `trocar_front` copia; e sonda o `/novo/` por `curl`. O que ele espera
  do cache e do fallback sai do modelo, e não de uma cópia. A senha do `htpasswd` do ensaio é aleatória e
  vai pela entrada, nunca impressa. Aceita um commit (`sh scripts/ensaiar-build-do-publicar.sh <commit>`)
  ou uma cópia da árvore -- é como se falsifica sem commit --, e apaga os contêineres, os volumes e o
  rascunho no fim. Fica fora do `conferir-tudo.sh` e do `ci.yml`, e o cabeçalho diz por quê: pede o docker
  e a rede do npm.
- **Medido hoje, no 2d3c6a0 -- verde, saída 0, em quatro rodadas:** a `node:22-bookworm-slim` do cache (id
  `43ac6c60b8f8`; node 22.23.3, npm 10.9.9); o `npm ci` instalou 94 pacotes em 20 s, com os binários
  nativos do Linux do Vite 8 vindos do `package-lock.json` gerado no Windows, e a build inteira (`npm ci`,
  `tsc -b` e `vite build --base=/novo/`) levou de 35 a 53 s; o `dist` saiu com o index, um JS (667 kB), um
  CSS (62 kB) e o favicon, todos citados em `/novo/` e presentes, sem caminho do Windows. O `nginx -t` dos
  dois vhosts passou. Sem a senha: `/novo/` dá o index (200, `text/html`, `no-cache`, noindex, HSTS, o
  mesmo arquivo do `dist`); o JS (`application/javascript`), o CSS (`text/css`) e o favicon
  (`image/svg+xml`) dão 200 com o conteúdo do `dist` e o cache do modelo (`public, max-age=31536000,
  immutable` nos assets); o asset que falta dá 404, e não o index; `/novo` dá 301 para
  `https://sistema.franquiabv.xyz/novo/`; e `/novo/agenda` dá 404 -- o modelo não tem fallback, e a tela
  não põe rota no caminho. Com a senha: 401 sem ela no index e no JS, 200 com ela, e o 404 do asset que
  falta. Com `IMAGEM_NGINX=nginx:1.30` (do cache), o outro ramo do http2 (`http2 on;`, do 1.25.1 em
  diante): o mesmo verde.
- **Os mutantes, cada um numa cópia do HEAD com uma mudança só** (o repositório não mudou): o import de CSS
  com a caixa trocada (`./Index.css` no `main.tsx`) -> 1, "Could not resolve './Index.css'", e o `tsc -b`
  passou antes -- o TypeScript não confere o import de CSS; o asset da tela nova caindo no index -> 1 (200
  com o index, com e sem a senha); o `/novo` com 302 -> 1; uma diretiva desconhecida na location do
  `/novo/` -> 1 (o `nginx -t` dos dois vhosts); o `--base=/` no `construir_front` -> 1 (o padrão do deploy
  some, e o index cita fora de `/novo/`); o `-w /app` mudado no `construir_front` -> 2 (a forma mudou); um
  `pushState` no `main.tsx` sem fallback no modelo -> 1; o noindex tirado da location do `/novo/` -> 1 (o
  `add_header` dela cancela o do `server`); o `auth_basic off;` na location do `/novo/` -> 1 (200 sem a
  senha); e a `IMAGEM_NODE` fora do cache -> 2 (o ensaio não baixa imagem). O contraprova: o fallback posto
  no modelo -> 0, e o ensaio passa a exigir o index em `/novo/agenda`. **A guarda do volume, arrancada:** o
  mesmo import com a caixa trocada, numa cópia do ensaio que monta a pasta do Windows no lugar do volume e
  não confere a caixa, sai **verde** (0, a build em 355 s) -- é a prova de que um ensaio na pasta do
  Windows daria verde falso. Dois achados da própria rodada, consertados no ensaio: o `index index.html`
  sem o ponto e vírgula, que eu tinha posto como mutante de sintaxe, é equivalente (o nginx lê a linha
  seguinte como mais nomes de índice, e tudo segue igual) -- trocado pela diretiva desconhecida --; e o
  `auth_basic off;` era lido como "pede senha" na conferência do vhost sem senha -- agora só o `auth_basic`
  com nome conta, e o caso cai só pela sonda.
- **A revisão (onze achados, todos verdadeiros), consertada no mesmo dia, só no ensaio e nas páginas:**
  (1) a guarda de forma conferia dois pedaços do `docker run`: um `--network none --memory=256m` nele, um
  comando a mais no `construir_front`, uma troca no `trocar_front` ou uma segunda `IMAGEM_NODE` passavam
  calados, e o ensaio dava 0 rodando outra coisa que não a do deploy. Agora o `construir_front`, o
  `trocar_front` e o `instalar_front` vão inteiros contra um molde -- sem os comentários, com as
  continuações juntas, e só o comando do `sh -c`, os `-e`, o padrão do index e o texto do `passo`/`falhar`
  variando -- e a `IMAGEM_NODE` tem de ter uma atribuição só; fora do molde, 2, com a diferença impressa.
  O que o `publicar` faz entre a build e a troca não entra no molde. (2) A página "Subir" mandava ensaiar
  antes do `voltar` só "para um commit que mudou o `frontend/`", e o `voltar` reconstrói sempre
  (`FORCAR_FRONT=sim`); e o "mudou" do deploy é contra o `novo.commit` da build instalada, e não contra o
  commit anterior. A página e o cabeçalho dizem agora quando rodar. (3) O modo diretório aceitava a árvore
  de trabalho e levava para a build o que o git não tem: rodado com `.`, empacotou 1.270 arquivos (o
  `.ds-sync`, o `ds-bundle`, o `dist-ds`, um `bash.exe.stackdump` dentro do `src/`), e não os 219
  versionados. Dentro de um repositório git, o `frontend/` do diretório não pode ter nada não versionado
  nem ignorado (o mudado e versionado vale: é como se falsifica); fora de repositório, vale a cópia. (4)
  "O mesmo usuário" era a mesma expressão: no BVIP, rodando como root, o `-u` dá `0:0`.
  `USUARIO_DO_DEPLOY=0:0` o ensaia, e passou (44 s); as permissões dos pais da raiz servida no BVIP, que
  o ensaio não mede, entraram no "o que ele não afirma". (5) A location do `/novo/` tirada do modelo
  dava 2 ("ajuste este ensaio"): agora é defeito, e as sondas seguem e mostram o efeito (o `/novo/` e o
  favicon com 502); só a location que mudou de forma -- outro modificador, uma regex com o mesmo
  caminho -- dá 2. (6) Um "não consegui medir" depois de um defeito já achado trocava o 1 pelo 2: agora
  o defeito vence. (7) A existência dos assets citados no `dist` era conferida na cópia do rascunho,
  uma pasta do Windows: com um `fundo.webp` na pasta pública da tela nova e o código citando
  `assets/Fundo.webp`, saía 0, e no servidor a imagem daria 404. Agora a existência é conferida na lista
  do tar tirado do volume, com a caixa exata. (8) A rota no caminho só era procurada no `src`: um roteador de biblioteca, cujo nome não
  aparece no `src`, passava com 0. Agora também o `pushState`/`replaceState` no JS do `dist` (o bundle de
  hoje não tem nenhum: o verde segue verde). (9) A procura de sobras de uma rodada morta via só
  contêineres, e só o nginx levava o rótulo: agora todo `docker run` do ensaio o leva, o aviso conta os
  volumes e imprime o comando que os apaga, e o cabeçalho deixou de prometer a limpeza no SIGKILL. (10) A
  estimativa de 08/10 (acima) dava o passo 4 por feito e ainda perguntava, em "O que não se sabe", se a
  build Linux passa: a pergunta é agora se ela passa no BVIP. O mesmo no rascunho da estimativa. (11) "1 =
  no BVIP, o site sairia do ar" valia só para parte dos defeitos: o `conferir` do deploy mede o `/novo/`
  com um JS de `/novo/assets/`, o noindex, o HSTS e o 401 com a senha, e não o tipo, o cache, o 301 do
  `/novo`, o 404 do asset que falta nem a rota interna -- esses deixam a tela nova no ar servida errada.
  A frase mudou na página, no cabeçalho e aqui. E os quatro logs que a primeira rodada deixou no `/tmp`
  do sistema foram para o rascunho da sessão (nenhum segredo neles).
- **Os mutantes da revisão**, cada um numa cópia do HEAD com uma mudança só ("antes" é o ensaio da
  primeira rodada, "depois" o de agora): `--network none --memory=256m` no `docker run` do
  `construir_front` -> antes 0, depois 2; um `rm -rf frontend/public` antes dele -> depois 2; o
  `chmod -R go-rwx` no `trocar_front` -> antes 0, depois 2; uma segunda `IMAGEM_NODE`
  (`"${IMAGEM_NODE_DO_ENV:-node:20-bookworm-slim}"`) -> antes 0, depois 2; o `-w /app/` -> depois 2; a
  location do `/novo/` apagada do modelo -> antes 2, depois 1; a mesma location sem o `^~` (outra forma)
  -> antes 2, depois 2 -- a primeira versão do conserto dava 1 aqui (a expressão de "outra forma" não via
  `location /novo/ {`), e foi corrigida antes de seguir; um `pushState` no `main.tsx` com o `listen 443`
  trocado por 8443 (o defeito, e depois a sonda sem resposta) -> antes 2, depois 1; o 8443 sozinho ->
  depois 2; o `fundo.webp` citado como `Fundo.webp` -> antes 0, depois 1; o `pushState` escrito
  `'pushState'` no `main.tsx` (no `src` o nome não aparece; no `dist`, sim) -> antes 0, depois 1; a
  árvore de trabalho como diretório -> antes 0 (1.270 arquivos), depois 2; um volume com o rótulo,
  deixado como o de uma rodada morta -> antes sem aviso, depois o aviso com "1 volume(s)"; e
  `USUARIO_DO_DEPLOY=root` -> 2. Os da primeira rodada, refeitos com o ensaio de agora: o
  `./Index.css` -> 1, o `--base=/` -> 1, o fallback posto no modelo -> 0. E o verde, de novo, no
  2d3c6a0: no nginx 1.24 (45 s), no 1.30 (46 s) e como root (44 s) -- oito rodadas verdes ao todo, de 35
  a 53 s. Nenhum contêiner nem volume do ensaio ficou para trás.
- **Na estimativa de 08/10** (acima): o passo 4 está feito, ali e no rascunho dela.
- **Débito:** nenhum fala da build nunca provada fora do Windows -- o D441 fala dos testes do front no CI,
  que este ensaio não roda --, e tudo passou: nenhum débito novo, nenhum alterado.
- **Portões:** `conferir-referencias-docs`, `conferir-links-mortos` (416 chamadas),
  `conferir-bytes-de-controle`, `conferir-scripts-servidos`, `conferir-segredos.sh` e
  `conferir-lacuna-do-ci.sh --so-declaracao`, todos com saída 0 -- e de novo depois da revisão.
- **Não provado:** nada rodou no BVIP. A imagem que ele baixar pode ser outra (a etiqueta anda), o
  registro do npm pode não responder de lá, e a duração da build lá não foi medida. O `nginx -t` do host
  lê os vhosts dos outros sites, e este só lê o deste; o nginx do BVIP pode não ser o 1.24 (os dois ramos
  do http2 passaram aqui, no 1.24 e no 1.30). O certificado, o certbot, o proxy para o ERP em `/` e as
  rotas negadas ficam com o `conferir` do deploy, no servidor, e também as permissões dos pais da raiz
  servida (aqui o docker os cria; lá, um pai que o `www-data` não atravessa dá 403 no `/novo/`, e o
  `conferir` o pega com o site já fora do ar). Os testes do front (o vitest) continuam sem rodar no Linux
  (D441).

## 09/10/2026 -- INDICE-NA-COPIA-LOCAL: o índice de `agendas.id_pai` criado na cópia local, nos dois bancos, e o Desmarcar dela deixa de ler a agenda inteira (a sua resposta 62, a opção (b); D220 segue preparado para a janela)

- **O que muda para quem usa: o Desmarcar da cópia local.** Achar a família do horário -- o que todo
  Desmarcar faz -- passou de ler os 4 milhões de linhas da agenda a ler 3. Medido na cópia, pelo método de
  verdade, 9 rodadas depois do aquecimento, um horário com dois filhos e um sem: no MySQL, de **3,9 a 5,5 s**
  (mediana, em duas rodadas de antes) para **0,7 a 0,8 ms**; no PostgreSQL, de **0,66 a 0,97 s** para
  **0,8 a 0,9 ms**. Os números de antes são maiores que os de 08/10 (2,8 s e 0,28 s), e as causas não foram
  separadas: no MySQL o plano de antes mudou (o `ORDER BY` do D455 levou a varredura do `type ALL` para a
  chave primária inteira) e a máquina estava carregada (as duas rodadas de antes discordam em até 40%); no
  PostgreSQL o plano é o mesmo de 08/10, e a carga é a explicação plausível. A produção não muda até a
  janela, e a base do teste do BVIP também não (abaixo).
- **O que foi feito, e só isso:** no MySQL da `franquiabv_dev`, a própria
  `scripts/migracao-agendas-indice-id-pai.sql`, como o laço a roda -- 12 s no servidor, pelo binlog, sem
  travar as escritas; na segunda vez, "indice ja existe" --; no PostgreSQL da cópia, como o dono do esquema,
  `CREATE INDEX CONCURRENTLY agendas_IDX_AGENDAS_id_pai ON bvip.agendas (inquilino_id, id_pai)`, o nome que
  o gerador emite -- uns 5 s, pelos horários do arquivo do índice, válido, um só. O esquema de `agendas` foi
  anotado antes e depois nos dois: no esquema, a única diferença é o índice novo. **Fora do pedido, e é
  meu:** um comando de conferência levou junto um `ANALYZE TABLE agendas` no MySQL da cópia, que recalculou
  as estatísticas do otimizador dos seis índices da tabela (nenhum esquema, nenhum dado); a medida de depois
  do MySQL rodou com elas, e por isso entre o antes e o depois mudaram o índice e as estatísticas. Para o
  Desmarcar não deve pesar (sem índice em `id_pai` não havia caminho nenhum), mas não foi medido sem ele;
  está escrito no D220.
- **No repositório, as duas mudanças juntas:** a frase "NAO rode na copia local" saiu do cabeçalho da
  migração (no lugar, que a cópia a recebeu em 09/10 pela sua resposta 62), e a sétima entrou no laço da
  cópia do desenvolvimento em `docs/ensaio-geral-do-corte.md` -- hoje os dois laços são o mesmo. O
  `procedimento-de-corte.md`, o comentário do portão do passo 0, o `ambiente-teste.md` e o
  `ambiente-desenvolvimento.md` (que ganhou a lista inteira do que o catálogo da cópia tem diferente do dump
  de produção: os ampliados do D189 e do D308, este, os outros do passo 0 em `vendas` e
  `pontuacoes_historicos`, e as tabelas novas) dizem o mesmo; o D220 registra a pergunta 62 e o medido.
  Nenhum código da aplicação mudou.
- **Provado:** o `EXPLAIN` na cópia mostra o índice nos dois bancos (no MySQL, o `index_merge` da `PRIMARY`
  com `IDX_AGENDAS_id_pai`; no PostgreSQL, o `BitmapOr` da `agendas_pkey` com `agendas_idx_agendas_id_pai`,
  7 páginas); os portões `verificar-schema.php`, `verificar-schema-postgres.php` (contra a cópia),
  `conferir-schema-tool.sh` e `conferir-passo-zero-do-corte.php` saem 0, e o último sai 1 com cada metade
  sozinha e com cada mutante (a frase de volta ao cabeçalho, a sétima fora do laço da cópia, a sétima fora
  de ordem); `testAFamiliaDoHorarioSeAchaPeloIndiceDeIdPai`, sem mudar, verde nos dois bancos de teste (4
  casos do Desmarcar em cada, 11 com as vizinhas da agenda); `contar-debitos.php` com os mesmos 453 e o D220
  em "preparado", e `conferir-debitos-repetidos.php` sem par novo. O `conferir-tudo.sh` deu 61 ok, o
  vermelho conhecido do D50 e **um vermelho de fora deste item**, no `ConveniosLigado.teste.tsx`: nas três
  rodadas inteiras do front desta noite, o caso do 422 do cadastro bateu os 5 s, e na primeira caiu também
  o do segundo clique no cadastrar (1.602, 1.603 e 1.603 de 1.604, em 279, 272 e 526 s, contra 116 s de
  madrugada); o arquivo sozinho passa (67 de 67), e nenhum arquivo do front mudou. Na verificação, com a
  máquina mais calma (de 23:59 a 00:07), o `conferir-tudo.sh` deu 62 ok, o D50 e nenhuma falha inesperada:
  os testes do front verdes, sem mudança no front.
- **Corrigido na verificação:** os tempos de criar passaram a citar o que ficou salvo (o binlog e o arquivo
  do índice), no lugar do relógio da sessão; a cronologia da resposta 62 (de manhã; o índice, à noite); o
  plano de antes do PostgreSQL com as duas rodadas; o que o `ANALYZE` mudou; a conta do front; e a tabela do
  `ambiente-desenvolvimento.md`, que deixava de fora os índices do passo 0 e as tabelas novas.
- **A base do teste do BVIP fica sem o índice** até a próxima cópia exportada daqui: a opção (c), que a
  incluía, não foi escolhida, e nada foi feito no BVIP. O próximo `atualizar` lá recusa pela migração (o
  cabeçalho mudou, só no comentário), e `ACEITAR_ESQUEMA=sim` é o certo, sem ALTER.
- **Não provado:** nenhum mutante na cópia -- apagar o índice para ver a medida voltar seria outro DDL, fora
  da autorização --, então a prova ali é o catálogo e o plano de antes e de depois. E o que já estava em
  aberto no D220: o tempo de criar e o plano no MySQL 8.4.7 de produção, a CPU e o pool da VPS.

## 10/10/2026 -- A comparação do push: ficar no OneSignal, trocar pelo Firebase, ou pôr o Novu na frente

A pedido do dono ("meça o esforço de trocar o onesignal pelo firebase. Compare com o Novu também ... e diga os pros
e contras de cada um deles"). Só leitura: o código, três contagens na cópia e a documentação oficial; nada mudou no
envio, e a ordem de deixar o OneSignal para depois continua valendo. O resultado está em
`docs/notificacoes-push-comparativo.md`: o Firebase direto custa de 71 a 139 h (servidor, aplicativo e
infraestrutura), mais o login do aplicativo se a rota do aparelho exigir sessão e as exigências atuais das lojas; o
Novu não substitui o Firebase -- ele fica por cima e entrega por ele; ficar no OneSignal e consertar o envio são de 5
a 11 h. A recomendação: o conserto do OneSignal primeiro, se o dono o liberar; o Firebase direto se a troca for por
custo ou controle; o Novu na nuvem só se vierem outros canais. Dois achados registrados: o D456 (a campanha de push
para "Usuários" usa a marcação dos clientes; latente, nunca usada na cópia) e o D457 (um backup do `local.php` com as
chaves antigas do OneSignal, fora do git).

## 10/10/2026 -- ADESAO-ETAPA-6

A etapa 6 da página da adesão ("Indique um amigo"), pelas respostas 51 e 53 do dono (D304 corrigido; D446 parcial; D458
registrado; na verificação, D459 e D460 registrados):

- **O amigo que já é cliente não ganha outro cadastro nem outra mensagem.** O telefone de cliente ativo ou inativo, de
  qualquer unidade, não cria nada nem manda WhatsApp, e a página responde como ao amigo aceito -- ela é pública, e não
  conta a quem tem um link quem é cliente da rede. A frase "Telefone já cadastrado no sistema" saiu (só saía no pedido
  montado à mão). O mesmo amigo mandado de novo também não cria outro. "+55" ou "055" no DDI não desviam a regra.
- **No máximo 5 indicações por cliente por dia;** a sexta é recusada com a frase do limite, qualquer que seja o telefone.
  O 5 era a sugestão da pergunta (a observação do dono veio vazia), numa constante; desde 11/10/2026 o teto é 10, o
  número do dono (a resposta 70, abaixo).
- **A primeira compra do amigo paga a comissão da unidade a quem indicou:** o amigo nasce com a mesma ligação do convite
  do aplicativo, e o crédito em dinheiro sai uma vez só, pelo caminho que o aplicativo já usava. O relatório Programa de
  Indicação passa a mostrar esses amigos. Os 10 pontos do envio ficaram como estavam.
- **A verificação fechou uma porta:** o amigo que não cabe nas colunas (o nome de mais de 100 letras, o emoji, o número
  com enchimento) dava erro 500 só quando o telefone NÃO era de cliente -- e contava quem é cliente, de graça e sem
  gastar o teto. Agora ouve uma frase só, antes da pergunta "já é de cliente?", e o telefone gravado sai dos mesmos
  dígitos da pergunta. O teto ganhou o caso que prova que é de cada indicador, e cada guarda da comissão "uma vez só",
  o seu caso.
- **Provado** nos dois bancos, em série, na versão final: `D304EtapaSeisDaAdesaoTest` (24 casos, 139 asserções)
  e os 493 casos dos vizinhos em cada banco, verdes (os "13 casos" de antes eram de uma versão anterior do
  arquivo); os mutantes, cada um com o seu caso vermelho (as tabelas no D304, no D446 e no D460). Nenhum WhatsApp sai
  nos testes.
- **No lote:** os 10 pontos do envio continuam?; o texto da página promete "pontos" e a comissão é crédito em dinheiro
  (e o agradecimento diz 50 pontos, que no Brasil são 60); a inferência pelo teto e o aplicativo que responde "já
  vinculado" (D458), com o custo da inferência corrigido (o "sim" sai de graça); e, da verificação, os 10 pontos que
  cada compra do amigo já dava a quem indicou, agora junto com a comissão; o amigo que nasce sem país e pula a etapa 3,
  que o ativa (D460); o indicador inativo, que o aplicativo recusa e a página liga; e a comissão que fica com quem
  indicou quando a primeira compra é cancelada (D459). **Não provado:** a corrida de dois envios ao mesmo tempo, a
  página no navegador e a produção.

## 10/10/2026 -- PUSH-FCM-ENVIO

A primeira parte da troca do OneSignal pelo Firebase, que o dono decidiu em 10/10/2026 (a opção A, pelo custo mensal
ao escalar, com o Novu auto-hospedado possível depois; D461 registrado; D456 corrigido; D3 preparado; D67 parcial;
D68 com a falha do push no registro):

- **Para quem usa, hoje, nada muda na produção:** o push continua saindo pelo OneSignal, com o mesmo pedido de antes
  (conferido byte a byte com a classe que saiu). Muda o que estava errado nele: o envio tem tempo limite (5 s para
  conectar, 30 s ao todo) e confere o certificado, a resposta é lida, e a falha (a chave recusada, o OneSignal fora do
  ar) vai ao registro das rotinas, uma vez por motivo e por rodada.
- **O Firebase está pronto e desligado:** o servidor fala com o FCM pela API v1, com o token da conta de serviço
  assinado à mão (sem biblioteca nova) e guardado até perto de expirar, a retentativa no 429 e no 5xx como a página de
  escala do Google manda (com um disjuntor para a queda) e o token morto devolvido. Liga-se pelo `local.php`
  (`push.provedor`: `onesignal`, o padrão; `ambos`; ou `fcm`), com o JSON da conta de serviço fora do repositório; sem
  ele, vai tudo pelo OneSignal. Hoje nenhum cliente tem aparelho registrado: o `ambos` manda tudo pelo OneSignal até a
  segunda parte, e o `fcm` cede ao OneSignal, com aviso, até o cadastro dos aparelhos existir.
- **A chave nova do OneSignal (D3) já tem caminho:** com a `app_api_key` no `local.php`, o envio passa à API nova
  (`api.onesignal.com`, cabeçalho `Key`); sem ela, a legada.
- **A campanha de push para "Usuários" é recusada** (D456): no formulário, na edição, no serviço e na rodada -- o id do
  colaborador chegava ao cliente de mesmo número. O WhatsApp para usuários continua.
- **O web push morto saiu**, com a classe `OneSignal`.
- **Nenhum ambiente fala com os provedores novos:** `api.onesignal.com`, `fcm.googleapis.com` e `oauth2.googleapis.com`
  no loopback dos três `docker-compose` e no `HOSTS_MORTOS` (só a lista; a carga do `deploy-teste.sh` não mudou). O
  contêiner desta máquina foi recriado; **no BVIP, a lista vale depois de `docker compose up -d app` e
  `docker compose restart nginx` em cada pilha.**
- **A medição** está em `docs/notificacoes-push-comparativo.md`: 9,8 mil push de campanha por mês (fev a jul), até 7
  mil por mês das rotinas automáticas pela regra do código (9,7 mil com o NPS dobrado; 3,1 mil entre quem abriu o
  aplicativo nos 30 dias), o OneSignal de US$ 92,87 (6.156 ativos, um piso) a US$ 1.219 por mês (100 mil) contra o
  FCM de graça, e o ritmo do envio em série contra um FCM de mentira (40 a 43 s sem atraso e 364 s com 50 ms, para
  6.573 destinatários; não são números de produção). A medição corrige a comparação de 09/10: o push automático não
  vai só às 8 unidades sem WhatsApp, vai também às que têm WhatsApp sem o texto da rotina.
- **A verificação, no mesmo dia,** achou oito pontos, e os oito foram consertados (o D461 os descreve): o 401 da chave
  da APNs no Firebase (o THIRD_PARTY_AUTH_ERROR) trocava o token de acesso por aparelho iOS e podia derrubar os
  Android da rodada -- agora é falha da mensagem, sem troca; as esperas do 429 e do 5xx (1, 2 e 4 s) contrariavam a
  página de escala do FCM e se repetiam a cada destinatário, 12,8 h numa queda para os 6.573 -- agora o `Retry-After`,
  60 s no 429, 10/20/40 s no 5xx, o mínimo de 10 s, o sorteio e o disjuntor; o `fcm` sem o cadastro dos aparelhos não
  mandava a ninguém, calado -- agora cede ao OneSignal e o verificador avisa; a configuração do OneSignal até o pedido,
  os tempos da produção, o nome do certificado e o padrão da fábrica ganharam caso; a medição chamava de teto a régua
  dos 30 dias, e foi refeita com as duas; e os mutantes do D456, que o registro dava como dos dois bancos, rodaram
  também no PostgreSQL.
- **Provado** nos dois bancos, em série: `EnvioDePushTest` (29 casos) e `PushNasRotinasTest` (8) -- 37 casos e 295
  asserções no MySQL e no PostgreSQL --, e os 415 casos dos vizinhos (os 23 arquivos de teste que citam o cron, o
  push, as campanhas ou o verificador) em cada banco, verdes, com 1 pulado declarado (`CronTravaSimultaneaTest`, sem a
  cópia da produção). Os mutantes, um por regra, todos com o caso ou o portão vermelho (a tabela abaixo; os da
  verificação rodaram sobre o código consertado, e os de antes também, de novo). Nenhum teste fala com o FCM, com o
  Google ou com o OneSignal de verdade.
- **Os portões:** `conferir-provedores-no-loopback.php` (os arquivos e o `--conteiner`), `conferir-tempo-limite-rede.php`
  (a lista de exceções ficou vazia), `conferir-rastro-das-rotinas.php`, `conferir-referencias-docs.php`,
  `conferir-debitos-repetidos.php`, todos com saída 0, e o `conferir-tudo.sh` inteiro, saída 0 (62 verdes e o vermelho conhecido do D50).
  Na verificação, de novo: os 62 verdes e o do D50, e os testes do front com 1 vermelho de 1.604 -- um caso de arrastar
  do `AgendaLigada.teste.tsx`, que passa sozinho; o arquivo sozinho derrubou outro caso de arrastar, e o front inteiro
  rodado de novo deu 1.604 de 1.604. Nenhum arquivo do front mudou neste item.
- **No lote:** o liga-desliga do push (Configurações > App > Push), que não desliga nada. **O que espera o dono** (o
  D461): o projeto Firebase e a conta de serviço, a chave `.p8` da Apple, a App API Key nova do OneSignal, quem mantém
  o aplicativo, e a escolha do login para a rota de registro do aparelho. **A segunda parte** (a tabela dos aparelhos,
  a rota, a limpeza dos tokens mortos, o guia do aplicativo e a porta do Novu) fica para depois da cópia do teste
  subir. **Não provado:** que o Google aceite o JWT, que o FCM entregue, que a API nova do OneSignal aceite o corpo, e
  a latência real até o Google.

| Mutante (sozinho) | Vermelho |
|---|---|
| OneSignal: `json_encode` com outra opção; `headings` antes de `contents` | `testOPedidoLegadoEOMesmoDaClasseVelha` (e o da API nova) |
| OneSignal: API nova sempre | o legado, a API nova e o "sem configuração" |
| OneSignal: o 200 sem `id` conta como entregue | `testARespostaDoOneSignalELida` |
| OneSignal: pede sem `app_id` ou sem chave | `testSemConfiguracaoNaoHaPedido` |
| JWT: SHA1 no lugar do SHA256; `exp` de duas horas | `testOJwtDaContaDeServico` |
| Conta de serviço: aceita dentro do repositório; o prefixo sem a barra | `testOArquivoDaContaSoServeForaDoRepositorio` (e a fábrica) |
| FCM: sem o link no `data` | `testOEnvelopeDoFcm` |
| FCM: o cache sem a margem de 5 min; o cache ignorado | `testOCacheDoToken` |
| FCM: sem retentativa; o `Retry-After` ignorado | `testO429EO5xxRepetemComEspera` (e, sem retentativa, os dois do disjuntor) |
| FCM: o UNREGISTERED não é morto; o INVALID_ARGUMENT de outro campo vira morto | `testOTokenMorto` |
| FCM: o 401 não troca o token | `testO401TrocaOTokenDeAcesso` |
| FCM: o 401 da APNs (THIRD_PARTY_AUTH_ERROR) troca o token | `testO401DaChaveDaApnsNaoTrocaOToken` |
| FCM: sem o piso de 10 s; o 429 sem `Retry-After` espera como o 5xx; as esperas velhas (1, 2, 4 s); sem o teto de 60 s; sem o sorteio | `testO429EO5xxRepetemComEspera` (as velhas, também os dois do disjuntor) |
| FCM: sem o disjuntor; sem o teto de 300 s da rodada; o disjuntor sem o mínimo de um minuto | `testUmaQuedaNaoPrendeARodada`, `testAsEsperasDaRodadaTemTeto` |
| FCM: a troca que falhou repete a cada destinatário | `testATrocaQueFalhaNaoSeRepeteACadaDestinatario` |
| Despachante: o `ambos` ignora o aparelho | `testOsTresModos` (e o dos mortos) |
| Despachante: sem a falha fechada | `testSemFcmOuComModoDesconhecidoVaiPeloOneSignal` (e a fábrica) |
| Despachante: o mesmo motivo de novo; sem o teto de 50 | `testAFalhaVaiAoRegistroUmaVezPorMotivo` |
| Despachante: não retira os mortos | `testOsTokensMortosVoltamAoCadastro` |
| Fábrica: nunca liga o FCM; o padrão vira `fcm`; o padrão vira `ambos` | `testAFabricaLeAConfiguracao` |
| Despachante: o `fcm` sem o cadastro dos aparelhos não cede ao OneSignal | `testOFcmSemOCadastroDosAparelhosVaiPeloOneSignal` (e a fábrica) |
| Fábrica: o OneSignal montado sem a configuração | `testAFabricaLevaAConfiguracaoDoOneSignalAoPedido`, e `testAConfiguracaoDoOneSignalChegaAoPedido` nos dois bancos |
| Rotina: a fábrica chamada sem o `Config` | `testAConfiguracaoDoOneSignalChegaAoPedido`, nos dois bancos |
| cURL: SSL desligado; o nome do certificado não conferido (`CURLOPT_SSL_VERIFYHOST` em 0) | `testOCertificadoEConferido` |
| cURL: sem `CURLOPT_TIMEOUT` | `testOTempoLimiteDesiste`, e o `conferir-tempo-limite-rede.php` (saída 1) |
| cURL: `TEMPO_LIMITE` em 0; a fábrica com uma hora de limite; o construtor aceitando o zero | `testOsTemposDaProducao` |
| `verificar-local-php.php`: não confere a conta de serviço; sem o aviso do `fcm` | `testOVerificadorConfereOPush` |
| `marketingPush`: não manda pelo despachante; o id vira inteiro | `testAFilaSaiPeloDespachante`, `testEmAmbosAFilaSeparaQuemTemAparelho` |
| Campanha: o push sem as tags | `testACampanhaDePushSaiPeloDespachante` |
| D456: sem a porta do formulário / da edição / do serviço / da rodada | o caso de cada uma, no MySQL e, na verificação, no PostgreSQL (a da edição cai no serviço, com a exceção) |
| A homologação sem o `api.onesignal.com`; o `HOSTS_MORTOS` sem o `fcm.googleapis.com`; o escopo do FCM fora das listas | `conferir-provedores-no-loopback.php` (saída 1, nos três) |

## 10/10/2026 -- PUSH-FCM-APARELHOS

A segunda parte da troca do push pelo Firebase (a sua decisão de 10/10: a opção A, com a C aberta). **Para quem
usa, nada muda hoje:** o `push.provedor` segue `onesignal` em todo lugar, e nesse modo o envio nem consulta a tabela
nova. O que entrou é o que o aplicativo novo vai usar.

- **A tabela dos aparelhos**, `clientes_aparelhos`: o cliente, o identificador do Firebase -- o token do FCM ou o FID,
  para onde o FCM está migrando, com o `tipo` que diz qual --, a plataforma, a versão do aplicativo e as datas. Um
  aparelho é de **um** cliente por vez: o identificador é único, e o mesmo aparelho que chega de outro cliente muda de
  dono numa instrução só. Entrou no `mysql.sql`, numa migração nova do passo 0 do corte (ensaiada num banco montado do
  dump de produção, duas vezes, igual à estrutura de hoje), no `schema.sql` do PostgreSQL (com o inquilino e o RLS) e
  na carga. **A cópia local não a recebeu** -- a pergunta está no lote --, e sem ela o ERP manda tudo pelo OneSignal,
  com uma linha no registro (medido nos dois bancos da cópia, só leitura), e a rota responde 503 (medido em dois
  bancos descartáveis sem a tabela, um de cada motor: a rota escreve, e na cópia o ensaio não a chamou).
- **O envio pelo FCM lê os aparelhos do banco**, em lote (a campanha e a fila: uma consulta a cada mil clientes, e não
  uma por destinatário), manda o FID no campo do FID, e tira do cadastro os mortos que o FCM devolve.
- **A rota do aparelho**, na API V4, com a sessão do cliente: `POST` e `DELETE /api/v4/clientes/:id/aparelhos`. O
  aparelho vai para o cliente escolhido no aplicativo; o `DELETE` só retira o do próprio cliente. Teto provisório de
  10 aparelhos por cliente.
- **O guia para quem mantém o aplicativo**, `docs/push-guia-do-aplicativo.md`, e **a porta do Novu**, sem código, na
  comparação. **O passo do servidor de teste** (a migração pela sua mão, depois do `atualizar`) está em
  `docs/ambiente-teste.md`.
- **Achado no caminho:** o D462 -- a carga do corte dá por carregada, com zero linhas, a tabela que a origem não tem.

- **Provado** nos dois bancos, em série: `AparelhosDoClienteTest` (16 casos, novo), `EnvioDePushTest` (35, 6 novos) e
  `PushNasRotinasTest` (9, 1 novo) -- 60 casos e 432 asserções no MySQL e no PostgreSQL --, e os 578 casos dos
  vizinhos (os 19 arquivos que citam o cron, o push, as campanhas, a sessão do cliente ou a API V4) em cada banco,
  verdes, com o 1 pulado declarado de sempre (`CronTravaSimultaneaTest`, sem a cópia da produção). O contrato: 71 de 71
  (os 67 de antes e 4 novos da rota, com 3 snapshots novos e nenhum dos antigos mudado). Sem a tabela, por ensaio: nos
  dois bancos da cópia (só leitura) e em dois descartáveis (com a rota, 503). A migração, num banco montado do dump de
  produção: o laço do passo 0 duas vezes, e a tabela igual à do `mysql.sql`. Os mutantes, um por regra, todos com o
  caso ou o portão vermelho (abaixo), e os arquivos de volta, conferidos pelo sha256.
- **Os portões**, com saída 0: `conferir-passo-zero-do-corte.php`, `conferir-unicidade-do-schema.php`,
  `conferir-limpeza-da-unicidade.php`, `verificar-schema.php`, `verificar-schema-postgres.php` (contra a cópia e contra
  um banco montado do `schema.sql`, como o CI), `conferir-tipos-do-mapeamento.php`, `conferir-rotas-sem-login.php`,
  `conferir-autorizacao-privilegiada.php`, `conferir-cobertura-contrato.php`, `conferir-cobertura-por-par.php` (322 ->
  326 pares), `conferir-segredos.sh`, `conferir-referencias-docs.php`, `conferir-debitos-repetidos.php` e
  `contar-debitos.php`, entre os 41 da lista do item (o `verificar-local-php.php` sai 1 no `local.php` do desenvolvimento, como antes); e o `conferir-tudo.sh` inteiro: saída 0 (62 verdes e o vermelho conhecido do D50, os testes do front inclusive).

**No lote (4 perguntas, PUSH-FCM-APARELHOS):** o login V4 no aplicativo novo (a recomendação: sim), a tabela na cópia
local e no BVIP (criar já), o teto de 10 aparelhos e o teste em aparelho (abrir no teste a saída para o Google, com um
projeto Firebase só de teste). **Não provado:** que o FCM aceite o `message.fid` e entregue -- nenhum caso fala com o
Google --, e a rota nunca foi chamada por um aplicativo.

| Mutante (sozinho) | Vermelho |
|---|---|
| Tabela: o identificador sem `UNIQUE` (no `mysql.sql`; no `schema.sql`) | a troca de dono, a segunda linha recusada, o DELETE e a troca pela rota (MySQL); no PostgreSQL, 15 dos 16 (o `ON CONFLICT` sem a chave) |
| Tabela: o identificador sem `ascii_bin` | `testOBancoRecusaASegundaLinhaEDiferenciaMaiuscula` |
| Tabela: a chave estrangeira sem a cascata (nos dois) | `testOClienteQueSaiLevaOsAparelhos` |
| Registro: o aparelho não muda de dono (o `ON DUPLICATE KEY` e o `ON CONFLICT`) | a troca de dono, pela rota e pelo repositório, e o DELETE, nos dois bancos |
| Registro: sem o teto por cliente | `testOTetoPorCliente`, nos dois |
| Retirada: apaga pelo identificador, de qualquer cliente | `testODeleteRetiraSoODoProprioCliente`, nos dois |
| Lote: sem o mais recente primeiro; uma consulta só, sem o teto de mil | `testOTokenEOFidConvivem`, `testOLoteEUmaConsultaACadaMilClientes` |
| Cadastro: cada cliente vai ao banco (N+1) | o lote, e a rotina no `ambos` e no `fcm`, nos dois |
| Mortos: ficam na memória da rodada; não saem do banco | `testOsMortosSaemDoBancoEDaMemoria` (o segundo, nos dois) |
| Despachante: o lote consulta no `onesignal`; o `onesignal` consulta o cadastro | `testOLoteSoNoModoQueConsulta` (o segundo, também os três modos e mais seis) |
| Despachante: o cadastro que falha para o envio; pergunta de novo depois da falha; a mensagem do Doctrine vai ao registro | `testOCadastroQueNaoRespondeVaiPeloOneSignal` |
| Despachante: o tipo não chega ao FCM; a retirada que falha para o envio; não retira os mortos | `testOTipoDoAparelhoChegaAoFcm`, `testARetiradaQueFalhaSoVaiAoRegistro`, `testOsTokensMortosVoltamAoCadastro` |
| FCM: o FID no campo do token; o FID morto não é morto; o envio ignora o tipo | `testOFidVaiNoCampoDoFid` |
| Fábrica: o cadastro do banco em todo modo; o cadastro sem o FCM ligado | `testAFabricaSoPedeOCadastroNoAmbosENoFcm` |
| Fila: sem o lote | `testEmAmbosAFilaSeparaQuemTemAparelho`, `testNoAmbosQuemTemAparelhoVaiPeloFcm`, `testNoFcmSoOFcm` |
| Campanha: sem o lote | `testACampanhaEmAmbosLeOsAparelhosNumLoteSo` |
| Rotina: sem o cadastro do banco | `testNoAmbosQuemTemAparelhoVaiPeloFcm`, `testNoFcmSoOFcm`, nos dois |
| Rota: o registro sem a barreira; a retirada sem a barreira | `testSemSessaoA401`, `testOIdDeOutroTelefoneA403` (o primeiro, nos dois); e o `conferir-autorizacao-privilegiada.php` (saída 1) |
| Rota: qualquer identificador; a quebra no fim; a versão sem o tamanho; qualquer tipo; qualquer plataforma | `testOCorpoQueNaoServeE422` |
| Rota: a retirada pelo identificador só, sem o par | `conferir-autorizacao-privilegiada.php` (saída 1) |
| Passo 0: a migração fora do laço; sem a frase "NAO rode na copia local"; a migração com outro DDL | `conferir-passo-zero-do-corte.php` (saída 1, nos três) |
| Unicidade: a chave dos aparelhos sem declaração | `conferir-unicidade-do-schema.php` (saída 1) |
| Limpeza: a tabela ausente sem a `UNIQUE KEY` do MySQL | `conferir-limpeza-da-unicidade.php` (saída 2, "não consegui medir") |
| Sem a frase no cabeçalho, contra a cópia sem a tabela | `conferir-tipos-do-mapeamento.php` e `verificar-schema-postgres.php` (saída 1) |
| O cadastro sem a retirada (um par a menos) | `conferir-cobertura-por-par.php` (saída 1) |
| As rotas sem login sem a rota nova | `conferir-rotas-sem-login.php` (saída 1) |

### Os achados da verificação, consertados no mesmo dia

A verificação (sete leituras, com um cético) achou 14 pontos, alguns repetidos; todos conferidos no código antes do
conserto. **Para quem usa, nada muda hoje**, como acima. O que mudou:

- **O aparelho vale enquanto o telefone do cliente for o da sessão que o registrou.** A tabela ganhou a coluna
  `telefone` (no `mysql.sql`, na migração, no `schema.sql` e na carga, regerados), a rota a grava com o telefone da
  sessão, e o envio só lê o aparelho cujo telefone ainda é o do cliente. Antes, o número digitado errado (ou
  reciclado pela operadora) que a unidade corrigia deixava o aparelho de quem tinha o número recebendo o push do
  cliente para sempre: o `DELETE` dele já era 403, e nada mais apagava a linha. É a regra que a sessão V4 já segue a
  cada pedido (D59), estendida ao aparelho. A migração foi ensaiada de novo no dump de produção, duas vezes, igual ao
  `mysql.sql`.
- **A memória do envio é curta.** Cada lista (a campanha, a fila) é uma foto nova, e o que foi lido há mais de 30
  segundos é relido, com os mil seguintes da lista numa consulta: o aparelho retirado no logout, ou passado ao irmão,
  no meio de uma campanha de minutos sai dela, e a campanha seguinte não usa a foto da anterior. Continua sem uma
  consulta por destinatário.
- **O banco que recusa o registro ou a retirada** (o deadlock, a conexão que caiu) responde 503 em JSON, e o registro
  do servidor leva só a classe e o SQLSTATE -- antes, a mensagem inteira do Doctrine, com o identificador do aparelho
  e o id do cliente, ia ao log pelo ouvinte de erro da API.
- **O guia** pede agora, no logout, apagar o identificador no próprio aparelho, além do `DELETE` (que não chega com a
  sessão de 30 dias vencida, ou sem rede); o reenvio do identificador a cada abertura do aplicativo (o teto de 10 escolhe
  pela data do último envio); e diz que no `ambos` a escolha é por cliente, e não por aparelho: o tablet no aplicativo
  velho de quem já registrou o celular no novo deixa de receber. **Uma pergunta nova no lote**: manter assim
  (a recomendação) ou mandar pelos dois a esse cliente.
- **O servidor de teste**: a migração roda de novo depois de todo `base`, que recria o banco sem a tabela
  (`docs/ambiente-teste.md`). **A próxima cópia**: o lugar de esvaziar a tabela é o endurecimento, com a contagem zero
  na conferência, como a `clientes_sessoes`, e o gatilho é a primeira cópia depois do passo 0 (D327, D461; o selo não
  mudou agora). E os textos que diziam 500 sem a tabela dizem 503, e o 503 da rota está dado como medido onde foi
  medido: em dois bancos descartáveis, e não na cópia.

**Provado**, em série: o item (`AparelhosDoClienteTest` com 21 casos, 5 novos; `EnvioDePushTest` 35;
`PushNasRotinasTest` 9) -- 65 casos e 467 asserções no MySQL e no PostgreSQL; os 17 arquivos vizinhos (337 casos, 1
pulado declarado) nos dois bancos; o contrato, 71 de 71, no MySQL e no PostgreSQL, sem snapshot mudado; e, só
leitura, nos dois bancos da cópia sem a tabela, o envio igual ao de antes (no `onesignal` nenhuma consulta; no `ambos`
e no `fcm`, uma que falha, uma linha e tudo pelo OneSignal).

| Mutante (sozinho) | Vermelho |
|---|---|
| O envio lê o aparelho sem conferir o telefone (sem o `EXISTS`; o `EXISTS` sem o telefone) | `testOAparelhoSaiDoClienteQuandoOTelefoneDeleMuda`, nos dois bancos |
| O registro não troca o telefone com o dono (o `ON DUPLICATE KEY`; o `ON CONFLICT`) | `testOMesmoAparelhoDeOutroClienteMudaDeDono`, em cada banco |
| A rota não grava o telefone da sessão | `testOAparelhoSaiDoClienteQuandoOTelefoneDeleMuda` |
| A rota sem pegar a falha do banco; o registro do servidor com a mensagem do Doctrine | `testOBancoQueRecusaNaoLevaOIdentificadorAoRegistro` |
| A rota pega a falha do banco antes da tabela ausente | `testATabelaQueNaoExisteContinuaA503DoCadastroIndisponivel`, nos dois bancos |
| A memória não vence; a releitura de um em um | `testAMemoriaVenceEAReleituraEEmLote` |
| A lista nova não descarta a anterior; só lê os que faltam | `testUmaListaNovaNaoUsaAMemoriaDaAnterior` (o segundo, também o do telefone) |

## 10/10/2026 -- PDV-TERCEIRA-RODADA

As suas respostas 54, 55 e 56 (a opção (a) nas três), no PDV.

- **A tela nova avisa quando os débitos estão somados só em parte (a 54).** O "Quitar" do ERP somou R$ 50,00 e nasceu
  um débito de R$ 10,00 depois: a comanda passa a dizer o somado e o devido (`debitosSomadosEmParte`), e a tela mostra
  "Débitos de R$ 50,00 somados, mas o cliente deve R$ 60,00: some o que falta pelo ERP antes de finalizar, ou os
  débitos ficam abertos." -- e finaliza sem a bandeira. Antes ela fechava calada. A recusa que se repetia (o débito
  novo com a comanda já aberta na tela nova e o aviso dos já somados) parou: a tela relê a comanda na recusa, pelo
  campo `pagarDebitos` do 422 e não pela frase, o aviso troca, e o clique seguinte sai sem a bandeira.
- **O finalizar do ERP recusa sem a quitação quando os débitos já estão somados (a 55).** A comanda aberta numa aba
  antes do "Quitar" de outra cobrava os débitos no total e não os baixava. Agora: "Os débitos do cliente já estão
  somados ao total (o "Quitar" foi feito em outra tela). Clique em "Quitar" antes de finalizar.", o bloco do "Quitar"
  volta com o devido, e esse "Quitar" não soma de novo -- só liga a quitação. Só no servidor: o JavaScript já mostrava
  o bloco nessa resposta, e não mudou. A venda sem débitos somados finaliza como antes.
- **Tirar e ajustar um item tiram o item arredondado (a 56, o D450).** Dois cortes de 45,90 com 5% (87,22), tirado
  um, deixam 43,61, e não 43,62: o total volta a ser a soma do que a tela mostra, nas duas portas (o PDV antigo e a
  API), e a venda não cai mais na recusa falsa dos débitos ao virar a comanda de outro cliente.

**Uma pergunta nova no lote**: a variante da 55 com um débito novo no meio -- o total cobre 50 de 60, e o finalizar do
ERP sem a quitação passa, com o cliente pagando os 50,00 e os 60,00 abertos. Recomendo recusar como a 55. O D380 fica
PARCIAL por ela; o D450 está corrigido.

**Provado**, em série: o item (`D380QuitarComMarcaTest` com 47 casos, 8 novos; `D450CentavoDoItemRetiradoTest`, 5,
novo; `D384ComandaDeOutroClienteTest`, 20) -- 72 casos e 292 asserções no MySQL e no PostgreSQL, com 10 vermelhos
contra o código de antes, os mesmos nos dois; os 51 arquivos vizinhos (710 casos, 2 pulados) nos dois bancos; o front,
1607 de 1607 (`ComandasLigada` com 3 casos novos, 2 vermelhos contra o código de antes), `build` e `lint` com saída 0;
`conferir-tudo.sh` com saída 0 (62 ok e o vermelho conhecido do D50). Nada disto foi aberto num navegador.

| Mutante (sozinho) | Vermelho |
|---|---|
| A comanda sem o campo novo; o campo também com o devido coberto; também sem nada somado; a venda feita com a chave | o caso do somado e do devido (e a recusa com a saída); o controle coberto; o controle sem "Quitar"; a venda feita -- nos dois bancos |
| O finalizar do ERP sem a recusa nova; sem o devido em `quitar_debitos`; com qualquer devido no lugar de `jaSomados` | a recusa e a saída dela; a recusa; os três controles que finalizam sem a bandeira -- nos dois bancos |
| Tirar o item cru; ajustar tirando o item cru; o ajudante sem arredondar | tirar pelo PDV e pela API, e a comanda do outro cliente; o ajuste; os quatro -- nos dois bancos |
| A tela sem o aviso; mandando a bandeira com o campo novo; sem reler no 422 do campo; sem a linha da releitura; relendo em todo 422 | os casos novos de `ComandasLigada`, cada um com o seu |

**A verificação do item (10/10/2026)**, conferida no código (cinco achados, e um sexto ao consertar um deles):

- **O "Quitar" do PDV antigo passou a trocar a tabela de pagamentos e o par "Finalizar"/"Finalizar fiado" pela conta
  nova.** Com o pagamento já cobrindo o total de antes, a tela ficava com o "Finalizar", e os débitos fechavam como fiado
  sem o gerente -- inclusive na saída da recusa da sua 55. O servidor devolve a tabela e o fiado, como no lançamento do
  pagamento, e o `mod-vendas.js` os aplica (`?cache=166`).
- **O troco da conta de antes (o D463, novo e corrigido):** o "Quitar" depois de um pagamento com troco deixava o troco
  antigo -- pagos 100,00 por 30,00 e somados 50,00 de débitos, o troco seguia 70,00, e não 20,00 --, e o finalizar o
  devolvia ao cliente com a dívida baixada. Na cópia, 1 venda concluída com esse rastro (2019, R$ 36,00). Corrigido nas
  duas portas.
- **A recusa da 55 ganhou o caso da venda em andamento** (o `/vendas/novo`); antes só a comanda estava provada.
- **Duas perguntas novas no lote**, com a recomendação: a tela nova aberta antes do "Quitar" do ERP, que fecha sem o aviso
  da sua 54 quando nasce um débito no meio (recomendo um 409 até a tela mostrar o aviso); e o "Quitar" em duas vendas
  abertas do mesmo cliente, que cobra os débitos duas vezes -- não vem deste item (recomendo o "Quitar" recusar quando
  outra venda aberta do cliente já os tem somados).

**Provado**, em série: o item (`D380QuitarComMarcaTest` com 54 casos, 7 novos; `D450CentavoDoItemRetiradoTest`, 5;
`D384ComandaDeOutroClienteTest`, 20) -- 79 casos e 329 asserções no MySQL e no PostgreSQL, com o `ComandaJson.php` na
versão final; 35 arquivos vizinhos (662 casos) nos dois bancos; o front, 1611 de 1611 (`quitarDaTelaAntiga` com 4 casos
novos), `build` e `lint` com saída 0. Os mutantes, um de cada vez:

| Mutante (sozinho) | Vermelho |
|---|---|
| A recusa da 55 só na comanda | a venda em andamento -- nos dois bancos |
| O "Quitar" sem o `fiado`; sem a `tabela`; o servidor de antes inteiro | 22 (todas as respostas conferidas); 3; 23 -- nos dois bancos |
| O serviço sem recalcular o troco; zerando sempre | 3; 2 -- nos dois bancos |
| O `pdvPagarDebitos` sem a tabela; sem o par de botões; o layout no 165; o JavaScript de antes | 3; 2; 1 (o piso); 4 |

Nada disto foi aberto num navegador.

## 10/10/2026 -- PACOTE-TERCEIRA-RODADA

As suas respostas 60 e 61, nas sessões de pacote.

- **O finalizar recusa a venda que deixaria um pacote negativo (a 60, a opção (b); o D452).** Duas vendas abertas do
  cliente com a mesma última sessão (a comanda num caixa e a venda do PDV em outro): a primeira fecha, e a segunda
  recebe "O pacote "Pacote Teste" (Corte Teste) não tem sessões suficientes para esta venda: falta 1 sessão. Tire da
  venda o item da sessão e lance-o de novo pelo preço." -- e nada é gravado. O mesmo para o item de 2 sessões que vai
  para o pacote que vence antes sem saldo para as duas, também dentro da mesma venda. Nas duas telas: no ERP, no aviso
  do finalizar; na tela nova, como toda recusa da finalização (o 409 `sessoes_do_pacote`, sem mudar a tela). A frase é
  provisória, minha. Os 17 pacotes negativos não mudam, e a venda que não gasta sessão deles não é recusada. Na cópia,
  nenhuma venda aberta tem item de pacote: ninguém é recusado hoje.
- **Tirar da venda o item da sessão passa a sessão ao que ficou pelo preço (a 61, a opção (a); o D453).** O mais
  antigo do mesmo serviço que o pacote cobre vira sessão (valor zero, a comissão do barbeiro sobre os 80,00 da sessão, e
  não sobre os 89,90), e o total cai. Nas duas portas que tiram item (o PDV antigo e a tela nova; o desmarcar da agenda
  não toca a comanda). Tirar item já apagava os pagamentos -- continua: o operador lança de novo pelo total novo. A marca
  do "Quitar" lê o mesmo débito somado de antes. Venda finalizada não muda.

**A corrida também fechou, na verificação do item (10 e 11/10/2026; o D464 corrigido, e com ele o D452).** Dois caixas
fechando no mesmo instante vendas com a mesma última sessão passavam os dois pela recusa, e a baixa regravava o
restante que cada pedido tinha lido. Agora a recusa É a baixa: um `UPDATE` só, `quantidade_restante =
quantidade_restante - ?` onde o restante ainda cobre as sessões da venda -- a conta no banco que você escolheu para o
estoque (a sua 20, o D388), com a condição da 60. Quem chega depois da última sessão não baixa nada e recebe a mesma
frase, nas duas telas. É a primeira gravação do finalizar, e por isso nada é gravado na recusa; se o finalizar morre
antes de a venda fechar, a sessão volta. O cancelamento devolve pela mesma conta. Sem trava, sem transação e sem esquema
novo. Medido no banco: com a linha presa pelo primeiro, o segundo esperou, não baixou nada, e o pacote terminou em 0 --
no MySQL e no PostgreSQL. Na tela nova, a recusa das sessões passou a vir antes da do fiado (a saída dela é tirar o item,
e tirar item revoga a autorização do gerente); no ERP, a recusa que só o finalizar vê diz a frase, e não um aviso vazio.

**O que fica, dito no D452:** o clique repetido no finalizar da mesma venda (a pergunta 2 do D321) baixa duas vezes se
houver saldo para as duas, como todo efeito do finalizar repetido; e, na tela nova, o gerente que autoriza o fiado antes
de finalizar ainda autoriza de novo se a sessão faltar. Dois pedidos HTTP ao mesmo tempo não foram feitos -- o que foi
medido foi o `UPDATE` nos dois bancos.

**Três perguntas no lote:** o item lançado com "Usar pacote: Não" também herda a sessão -- e, corrigido na verificação,
na tela nova esse é o caso de todo item, que herda como a sua 61 quer (recomendo deixar como está); a comanda pela agenda
paga a comissão sem a extra do barbeiro (o D465; recomendo a agenda somar a extra); e, nova, a frase da recusa manda
lançar pelo preço o item de 2 sessões que o outro pacote do cliente cobriria, e relançar com o "Sim" padrão repete a
recusa (recomendo a frase dizer "uma sessão por linha").

**Provado**, em série, nos dois bancos: o item -- `D452SessaoDoPacoteForaDaMesmaVendaTest` (20 casos, 9 da
verificação), `D453SessaoPassaAoQueFicouPeloPrecoTest` (15, 2 da verificação), `D435SessaoDoPacoteNaMesmaVendaTest` (8)
-- com 43 casos e 160 asserções; os vizinhos (os 43 arquivos de antes e mais 10 que tocam venda, pacote ou finalizar),
783 casos, verdes, com 1 pulado nos dois (o `VendasNoRelatorioTest`, que pede o histórico da produção). Na primeira
rodada do item, os testes de antes contra o código novo, antes de atualizá-los, deram 5 vermelhos no MySQL (os 3 do
D452 e os 2 do D435, que diziam o fato de antes); o PostgreSQL não rodou nessa medida, e os testes novos não rodaram
contra o código de antes -- o que cada caso pega dizem os mutantes. O front não mudou e não foi rodado.

**Os mutantes foram refeitos na verificação, sobre os arquivos finais:** a primeira rodada tinha usado versões anteriores
dos testes (31 casos). 34 mutantes, um de cada vez, os 42 casos em cada rodada (43 no último, o da devolução, rodado depois do caso dela), com o
mesmo vermelho nos dois bancos: 32 vermelhos e 2 verdes, ditos -- a devolução do erro no meio também com a venda já gravada fechada, e a baixa sem a
ordem do id; nenhum caso os pega, porque o primeiro não tem gatilho no arnês e o segundo só decide entre dois pedidos ao
mesmo tempo. As tabelas estão no D452 e no D453.

| Mutante (sozinho) | Vermelho |
|---|---|
| A baixa que não cobre não recusa; a baixa sem a condição; tudo ou nada sem devolver; `salvarPacotes` baixando de novo | 7 (os caminhos recusados e a corrida); 8; os dois pacotes; 14 |
| Sem reler o pacote; o erro no meio sem devolver; o cancelamento regravando o lido; a devolução subtraindo | o pedido que leu 3; o erro no meio; o cancelamento; 4 |
| A frase: o ERP sem ela na conferência, sem ela na recusa do serviço, sem escapar o pacote, sem escapar o serviço | o do ERP; a corrida pelo ERP; os dois nomes; o do serviço |
| A API: sem o 409 da conferência; sem a frase na recusa do serviço; o fiado antes das sessões | o da ordem; a corrida pela API; o da ordem |
| O `remover` sem passar a sessão; sem conferir se cobre; o mais novo; só um herda; sem o `break`; sem descontar | 10; 3 (com o vencido); 2; as duas sessões; uma por item; uma por item |
| O total não cai; cai pelo item cru; a comissão do preço; as guardas da venda editável e da sem cliente | 8; o do "Quitar"; o da agenda; a finalizada; a sem cliente |
| O saldo sem as sessões da venda; sem o pacote ativo; sem a validade | 2 (com o do repositório); o inativo; o vencido |

Os portões: `conferir-tudo.sh` com saída 0 (62 ok e o vermelho conhecido do D50), depois de a linha de base da cobertura por par subir de 328 para 330 com os dois pares novos (`baixarSessoesDaVenda` e `devolverSessoes`, os dois com caso que chama a classe); `contar-debitos.php`, `conferir-debitos-repetidos.php` e `conferir-referencias-docs.php` com 0. Nada disto foi aberto num navegador.

## 11/10/2026 -- ADESAO-QUARTA-RODADA

A etapa 6 da página da adesão ("Indique um amigo"), pelas respostas 63 a 70 do dono (D304 e D446 corrigidos; D460
corrigido no Brasil e parcial fora dele, na verificação; D458 e D459 decididos, sem mudança; D466 registrado; duas
perguntas novas ao lote):

- **O teto é 10 indicações por cliente por dia** (a resposta 70); era 5, provisório. A frase do limite diz "10".
- **A página promete "crédito", e não "pontos", na primeira compra do amigo** (a 64): "Indique um amigo e ganhe crédito
  na primeira compra dele". O agradecimento dos 50 pontos não mudou (não foi perguntado).
- **O amigo indicado daqui em diante dá a quem indicou só a comissão da primeira compra** (a 65): as compras dele não
  dão mais 10 pontos cada a quem indicou. Os amigos de antes, sem a ligação, seguem dando os 10 pontos.
- **O amigo nasce com o país da unidade de quem indicou** (a 66): a página o leva à etapa 3 (o endereço), e ele fica
  ativo ao concluí-la; o telefone dele aparece inteiro na etapa 1. Os 125 de antes, sem país, não mudam. Fora do
  Brasil, não: a etapa 3 é o CEP, e a página só a mostra a quem é do Brasil -- o amigo de uma unidade de fora vai da
  etapa 2 à 4 e adere inativo (hoje ninguém: a única unidade de fora, Portugal, está inativa). Foi ao lote (o D460).
- **Quem indica inativo (ou o cadastro do sistema, o "SEM CADASTRO") indica sem a ligação** (a 67, que não era a
  recomendada): o amigo nasce e recebe a mensagem, sem comissão para quem indicou; o teto do dia conta também essas
  indicações. O que a conta não vê: o amigo que conclui a etapa 3 no mesmo dia sai dela (a origem perde o número) --
  cada vaga a mais pede um amigo que concluiu o endereço; fechar pede tabela nova.
- **Ficam como estão, por decisão:** os 10 pontos do envio da etapa 6 (a 63), a comissão da primeira compra cancelada
  (a 68, o D459) e as duas portas do D304/D458 -- a inferência pelo teto e o "já vinculado" do aplicativo (a 69).
- **No lote, nova (o D466):** pela 65 escrita, o amigo de quem indica inativo, que nasce sem a ligação, dá ao inativo os
  10 pontos de cada compra -- o prêmio que a 67 tirou em comissão volta em pontos. Recomendo os 10 pontos só para quem
  está ativo na hora da venda (na cópia, 1 dos 183 amigos de antes tem o indicador inativo, e nenhum dos 79 lançamentos
  foi para inativo). O caso não alcança o amigo novo do Brasil, que a etapa 3 ativa antes da etapa 6 dele: alcança os
  amigos de quem chega inativo à etapa 6 -- o cliente sem país ou de fora do Brasil, ainda sem a adesão (118 na cópia,
  110 deles amigos de antes), e o cadastro do sistema. A primeira redação da pergunta dizia o contrário, e foi
  corrigida na verificação.

**Provado**, em série, nos dois bancos: `D304EtapaSeisDaAdesaoTest` com 33 casos e 207 asserções (eram 24 e 139), e os
vizinhos -- 38 arquivos achados pelo grep (finalizar, convite do aplicativo, etapa 6, origem de indicação, pontuação) --
com 758 casos e 3.369 asserções, verdes no MySQL e no PostgreSQL. A página da adesão foi renderizada pelo
`ViewRenderer` no teste (o texto, o campo oculto do país, o telefone), e não aberta num navegador; a etapa 2 do amigo
não foi despachada (a validação do e-mail consulta o DNS). 15 mutantes, um de cada vez, no PostgreSQL, sobre os
arquivos finais, todos vermelhos (as tabelas no D304, no D446 e no D460); os casos do teto leem a constante, e o 10
escrito está só no caso que o prova. O front não mudou e não foi rodado à parte (o `conferir-tudo.sh` roda os testes
dele).

**A verificação** (11/10/2026) achou cinco pontos, todos de prova ou de texto, e nenhum no código de produção: nenhum
caso prendia a soma das ligações com os amigos sem ela no teto (um caso novo prende, e a conta pela maior das duas
parcelas fica vermelha); a pergunta do D466 errava quem o caso alcança (corrigida, no lote e no D466); o amigo de fora
do Brasil ainda adere inativo (o D460 passou a parcial, com a pergunta nova); a regra 1 do D304 dizia que o teto conta
só as ligações (corrigida); e o caso da compra cancelada conferia os pontos só depois de cancelar, onde a resposta 65
não se via (agora confere antes, e a guarda arrancada o deixa vermelho). `D304EtapaSeisDaAdesaoTest`: 37 casos e 242
asserções, verde no MySQL e no PostgreSQL, em série. Seis mutantes novos, um de cada vez, todos vermelhos (todos no
PostgreSQL; o da soma, também no MySQL). Só o arquivo de teste mudou no código; os vizinhos não foram rodados de novo.

## 11/10/2026 -- PUSH-TERCEIRA-RODADA

A terceira rodada do push pelo Firebase, com as respostas 71 a 76 do dono (D461, D462, D59, D67, D3; D327 e D467).

**O que mudou para quem usa:**

- **O aplicativo novo entra pelo login V4, e a sessão renova** (a 71): `POST /api/v4/sessoes/renovacao`, com o Bearer de
  agora, troca o token que ainda vale por um novo, de mais 30 dias a partir do pedido; o de antes deixa de valer na
  hora. Sem tabela nova, no molde da confirmação (201, `token`, `tipo`, `expiraEmDias`), e o 401 mudo para o que não
  vale. Dois pedidos com o mesmo token renovam uma vez só (a troca é uma conta condicionada no banco). O aplicativo de
  hoje não muda: ele não fala V4. O guia do aplicativo diz quando renovar.
- **A tabela dos aparelhos está na cópia local, nos dois bancos** (a 72): criada na manhã de 11/10 (a migração no MySQL,
  e no PostgreSQL a tabela do gerador, com o inquilino e o RLS), vazia e igual à dos bancos da suíte; a frase "NAO rode
  na copia local" saiu da migração e ela entrou no laço do ensaio geral, juntas. Os dois portões que conferiam o
  mapeamento contra a cópia passaram a exigi-la. O passo do BVIP, que é do dono, foi conferido (e o `DB_NAME` da linha
  dele passou a tirar o comentário do fim da linha). O selo não mudou: esvaziar e contar a tabela na exportação é o
  passo antes da próxima cópia (D327).
- **O servidor de teste pode falar com o Google** (a 74), quando o dono ligar: o `docker-compose.teste-firebase.yml`
  troca a lista do loopback pela de `HOSTS_MORTOS` (`!override`: o compose junta as listas, e a homologação corta o
  Google) e monta a pasta da conta de serviço do projeto Firebase de teste, só para leitura; o `HOSTS_MORTOS` perdeu os
  dois nomes do Google; o OneSignal segue cortado. O passo (a conta no BVIP, a versão do compose, o `ambos`, as
  conferências e como desfazer) está em `docs/ambiente-teste.md`. Nada rodou no BVIP, e nenhum segredo entrou no
  repositório.
- **Registradas, sem código:** o teto de 10 aparelhos fica (a 73), a convivência por cliente fica (a 75) e o
  liga-desliga como está até o aplicativo novo (a 76) -- no D461, no D67 e no guia.
- **O php-fpm do corte** (o D67): os três requisitos decididos em 31/08 estão no `docs/procedimento-de-corte.md`
  ("Preparação", item 4); aplicar na produção é do dono.
- **No lote, três perguntas novas:** as 6 unidades ativas sem WhatsApp (18.735 clientes ativos, na cópia; o "8" de antes
  contava duas de fixture) -- ligar o WhatsApp nelas, recomendado, ou outro canal; um prazo máximo para a sessão
  renovada (recomendo 180 dias desde o código); e como o aparelho de teste entra no login V4 do servidor de teste, onde
  o código não chega a ninguém (o D467; recomendo o código lido na fila, pela mão do dono).

**Provado:**

- **A renovação**, `RenovacaoDaSessaoTest` (9 casos, 62 asserções), com os vizinhos `AutenticacaoDoClienteTest` e
  `AparelhosDoClienteTest`: 42 casos e 230 asserções, verdes no MySQL e no PostgreSQL, em série, sobre o código final.
  O contrato, com o banco montado de novo: 74 casos verdes no MySQL e 74 no PostgreSQL (3 novos:
  o 401 sem token, com o snapshot novo `v4-sessoes-renovar-sem-autenticacao`; as credenciais que não valem, entre elas o
  token da suíte sem o `Bearer`; e o GET que não casa rota).
- **Os mutantes da renovação**, um de cada vez, devolvidos pelo sha256, nos dois bancos, todos vermelhos:

  | mudança | caso vermelho |
  |---|---|
  | a troca sem a condição do hash de antes | `testDoisPedidosComOMesmoTokenRenovamUmaVezSo`, `testOPedidoQuePerdeACorridaNaoRecebeToken` |
  | a troca sem a validade (encerrada, vencida) | `testATrocaNaoAlcancaAVencidaNemAEncerrada` |
  | o serviço devolve o novo sem ter trocado | `testOPedidoQuePerdeACorridaNaoRecebeToken` |
  | o serviço sem o `refresh` da sessão | `testAValidadeNovaContaDoPedido` |
  | a validade nova contada da confirmação | `testAValidadeNovaContaDoPedido` |
  | a ação aceita o token sem o prefixo `Bearer` | `testPelaRotaSemCredencialE401` |
  | o serviço sem o `sessaoDoToken` (a sessão pelo hash, sem conferir) | `testOPedidoQuePerdeACorridaNaoRecebeToken` -- sozinho, só ele: a conta no banco repete a validade |
  | os dois de cima juntos | os dois casos acima e `testOQueNaoValeNaoRenova` |

  E pelos portões, cada um sozinho, saída 1: o `conferir-autorizacao-privilegiada.php` com o serviço sem o
  `sessaoDoToken`, com o serviço regravando a entidade (`setTokenHash`) e com a ação sem o `erroAutenticacao`; o
  `conferir-rotas-sem-login.php` com a rota fora da lista; e o `conferir-cobertura-contrato.php` com o contrato sem pedir
  a rota -- este só depois de tirar o caminho de um comentário do `ContratoTestCase`, que sozinho a dava por coberta (o
  portão lê o texto dos arquivos).
- **A tabela na cópia**: as duas criações (o comando, a hora, o binlog com um evento só), o `COUNT(*)` 0 e as colunas
  iguais às da suíte, no D461. O `conferir-passo-zero-do-corte.php` 0, e 1 com cada metade sozinha (a frase de volta no
  arquivo de verdade; o laço com sete por uma cópia). O de tipos e o `verificar-schema-postgres.php` 0 contra a cópia, e
  1 contra bancos descartáveis sem a tabela (apagados no fim); com a frase de volta, 0, "ausente de proposito".
- **A medida do D462**, só a que é de leitura: a contagem do `conferir-migracao.php` responde inteira nos dois bancos da
  cópia (86 tabelas). O ensaio geral não rodou: ele escreve no MySQL da cópia.
- **O teste com o Google**: o `docker compose config` (só renderiza) da pilha do teste com o arquivo novo, nesta máquina
  (v5.2.0): 0 `googleapis`, os 10 nomes nos dois endereços, a pasta montada só para leitura; sem ele, os 12 no loopback.
  O `conferir-provedores-no-loopback.php` 0 nos arquivos e no contêiner, e 1 com cada um dos nove mutantes (no D461).
- **Os portões**, em série, depois da última mudança de código (35, como o `conferir-tudo.sh` os chama), todos com 0:
  referências da documentação, débitos repetidos, rotas órfãs, segredos, ordenação sem desempate, cobertura do
  contrato, linha sorteada, tempo limite de rede, registro de acesso, autorização privilegiada, relógio nos testes,
  provedores no loopback (arquivos e contêiner), blocos do laboratório, guarda do ERP, rotas sem login, barreira de
  origem, relógio do banco, injeção de DQL, cobertura por par (331 pares, o novo declarado), bytes de controle, links
  mortos, fósseis, unicidade do schema, tipos do mapeamento, ACL da API nova, inventário atual (360 ações), passo 0 do
  corte, SQL do passo 3, crontab do passo 9, vigência do horário, limpeza da unicidade, `verificar-schema.php`,
  `verificar-schema-postgres.php` e o `schema-tool` ("Igual a linha de base"). Na primeira rodada, três saíram
  diferente de 0, e nenhum por defeito do código: o de segredos (1) acusou o exemplo de resposta da renovação no guia,
  com um valor no campo `token` (trocado por `...`, como o exemplo vizinho); o do inventário (2) e o da vigência (255)
  rodaram pelo PHP do Windows, que não abre o caminho `/d/...` nem acha o host `mysql` -- de dentro do contêiner, 0. O
  `contar-debitos.php` conta 465 débitos (o D462 passou a medido, o D467 nasce aberto).

**Não provado:** que o docker compose do BVIP aceite o `!override` (o passo manda conferir a versão); que o Google
aceite a conta de serviço de teste e o FCM entregue a um aparelho; a renovação chamada por um aplicativo de verdade; e
o ensaio geral do corte sobre a cópia, que escreve nela. O front não mudou e não foi rodado.

**A verificação do mesmo dia** achou oito pontos (seis distintos, todos de gravidade baixa), e os seis foram tratados
(o detalhe está no D461, "A verificação do mesmo dia"):

- **O `deploy-teste.sh conferir` volta a medir o Google no teste de sempre.** Com os dois nomes fora de `HOSTS_MORTOS`,
  a função deixava de perguntá-los ao contêiner também sem o arquivo do Firebase. Agora eles estão em
  `HOSTS_DO_GOOGLE`, medidos sempre que o `COMPOSE_FILE` não traz o `docker-compose.teste-firebase.yml`; a carga não
  mudou. O caso que pega é um ensaio novo, `scripts/ensaiar-hosts-mortos-do-deploy.sh`, no `conferir-tudo.sh` e no
  CI. Ele roda a função do roteiro com um dublê do contêiner, em 27 casos. Cinco mutantes, cada um numa cópia do
  roteiro, todos vermelhos:

  | mutante | casos vermelhos |
  |---|---|
  | a função como estava | 6 |
  | o Google medido também com o arquivo | 2 |
  | nada medido com o arquivo | 10 |
  | o `case` casando qualquer `teste` | 6 |
  | sem a resposta vazia reprovando | 2 |

  O `conferir-provedores-no-loopback.php` segura a lista igual ao `$foraDoTeste`. Ele sai 1 com a lista curta e com
  o arquivo de outro nome.
- **O portão de cobertura do contrato agora lê o verbo, ignora os comentários e acusa o snapshot sem caso.** A rota
  com `Method::class` (14 das 47) só conta como pedida por um pedido daquele verbo, com o caminho num literal. Quatro
  mutantes, no arquivo de verdade e devolvidos pelo sha256, dão 1, onde o portão de antes dava 0:
  - os dois POST da renovação apagados;
  - o caminho dos países só num docblock;
  - o `testRenovarSemToken` apagado;
  - os `DELETE` dos aparelhos trocados por `POST`.

  O comentário do `ContratoTestCase` que dizia o contrário foi corrigido.
- **O registro da criação no PostgreSQL ganhou o comando e o roteiro inteiro.** O roteiro saiu igual byte a byte do
  `schema.sql`. Ganhou também a lista inteira de privilégios: a cópia dá `SELECT` ao `bvip_bi`, que saiu com o Power BI
  em 09/09 e segue no servidor desta máquina com as 89 tabelas da cópia. O resíduo foi para o D9, e a pergunta, para o
  lote: apagar o papel, que é escrita na cópia; recomendo apagar.
- **Quatro documentos foram corrigidos:**
  - `docs/ambiente-desenvolvimento.md`: a `clientes_aparelhos` entrou na lista do que a cópia tem a mais que o dump
    (88 tabelas contra 83, medidas de novo);
  - `docs/ambiente-teste.md`: um passo 9 novo diz como um push sai do teste sem cron, na ordem que não apaga o código
    do D467, com o token das rotinas só para a medida, e nunca o `marketingEnvios`;
  - os comentários do `docker-compose.homologacao.yml` e do `docker-compose.desenvolvimento.yml`;
  - o "O que falta" de `docs/notificacoes-push-comparativo.md`.

**Na verificação, os testes do item rodaram de novo, em série:**

- a renovação e os vizinhos: 42 casos e 230 asserções, verdes no MySQL e no PostgreSQL;
- o contrato, com o banco montado de novo: 74 casos, verdes em cada banco.

O front não mudou.

## 11/10/2026 -- RESPOSTAS-SEM-RODADA

Os débitos que já tinham resposta sua e não estavam em rodada nenhuma (o levantamento de 11/10): D365 e D378
corrigidos no código; D428 fechado no texto; D334 anotado; o corpo de D31, D38, D207, D212 e D310 e duas frases deste
roteiro passaram a dizer o que as respostas decidiram. A verificação do mesmo dia (abaixo) registrou o D468 e o D469.

- **A retirada de produto do PDV antigo pede o privilégio do PDV, e não o do catálogo** (D365; a sua decisão 9 dos
  relatórios, "com o privilégio do PDV"). Vale para o modal "Retirada de produtos" das duas telas do PDV e para a irmã
  sem chamador. Quem tem o PDV e não tem o catálogo -- os 9 operadores que lançaram 101 das 126 retiradas de julho --
  segue retirando quando o código de hoje for ao ar. Quem tem só o catálogo deixa de retirar por esta rota, e pela tela
  não perde nada: sem o PDV, as duas telas do modal já não abriam para ele. **A recusa diz "Sem permissão para retirar
  produto do estoque."**, onde antes o aviso saía em branco.
- **O ensaio geral do corte roda a suíte em clones, com as fixtures só neles** (D378; a recomendação da sua resposta 7:
  "o clone de (b) no ensaio geral, sobre a cópia"). São duas fases novas:
  - `clone_pg`: o PostgreSQL carregado, copiado por TEMPLATE, com as configurações e os privilégios que o TEMPLATE não
    copia;
  - `clone_mysql`: o dump do MySQL da cópia, para a volta atrás.

  As origens ficam como a carga as deixou, e os clones saem no fim. Com isso a suíte não para mais numa cópia da
  produção por falta das fixtures. Junto, o veredito da suíte no registro, que saía em branco quando a suíte pulava
  caso, e o trap, que não desfazia o `local.php` do clone do MySQL.
- **Só texto:**
  - o D428 fecha: os três comentários que diziam que a loja escolhida esperava você dizem "a escolhida, pela resposta
    44";
  - o docblock do `UsuarioRepository::selecionarPorLogin` (D31) diz que o par e o repetido exato foram decididos pelas
    respostas 8 e 32;
  - o D207 registra a 18 (a) e a pergunta 140, do motivo do estorno;
  - o D38 registra a 74 (a): o projeto Firebase de teste é a credencial de sandbox do push, e o resto segue congelado;
  - o D334 fica anotado para a F0 de Sorteios, pela 36 (c);
  - o D310 diz a regra decidida, "de quem vendeu, por sessão executada", e não o percentual gravado na venda;
  - o D212 ganha a correção D-b: os 39.689 itens sem motivo são todos de venda com convênio, e 0 sem convênio,
    recontados na cópia;
  - duas frases deste roteiro deixam de dar a remoção dos assistentes como decidida (o D57 volta a você na P14).

**Provado** (o banco de cada prova está dito nela):

- `D365RetiradaPeloPrivilegioDoPdvTest`, em processo: **7 casos** depois da verificação (eram 6). O controller de antes do
  conserto, inteiro, dá **7 de 7 vermelhos no MySQL e no PostgreSQL**. Com os vizinhos que despacham o mesmo
  controller, 141 casos e 479 asserções, 3 pulados de propósito, verdes nos dois. **Oito mutantes**, um de cada vez,
  restaurados pelo sha1, todos vermelhos **nos dois bancos**, com os mesmos casos em cada um.
  - Até a verificação, o "vermelho antes" e os seis primeiros mutantes só tinham rodado no MySQL. Esta entrada dizia
    "nos dois bancos" para eles, e não era verdade.
- Os arquivos dos comentários do D31 e do D428, com os que despacham o mesmo código: 83 casos e 423 asserções, verdes
  nos dois.
- O D378, de dois jeitos.
  - Sobre bancos descartáveis meus, apagados no fim. As funções foram tiradas do próprio roteiro, com um `--filter` na
    suíte: 28 provas verdes, a suíte passando nos dois clones (67 casos cada). Sete mutantes, todos vermelhos. Um deles
    mostrou que o TEMPLATE não copia o fuso nem o `search_path`.
  - Pelo ensaio novo, `scripts/ensaiar-clones-do-ensaio-de-corte.sh`, que fica no `conferir-tudo.sh` e no CI: 11 casos
    verdes, sem tocar banco nenhum. Quinze mutantes do roteiro, cada um numa cópia, todos vermelhos:
    - sem a guarda do começo, e com a do `apontar` só pelo host: os dois `local.php` de execução morta, o trocado na
      carga e a recusa pelo `local.php`;
    - só sem a guarda do começo: os dois de execução morta e a recusa pelo `local.php`;
    - só a do `apontar` pelo host: o trocado na carga;
    - sem o REVOKE, a volta atrás na origem, a suíte do PostgreSQL na origem, as fixtures na origem (cada banco), sem o
      `criar-papeis.sh` no clone, sem as configurações do clone e o veredito de antes: o ensaio inteiro;
    - o trap de antes: as duas recusas do começo com o guardado velho e os dois `local.php` de execução morta;
    - o `CLONE_*` do ambiente: o caso dele;
    - o dump sem o `pipefail`: o dump que morre no meio;
    - o trap que nunca age: a suíte vermelha no clone do PostgreSQL e o dump que morre.

Os portões que o item toca saíram com 0, e o `conferir-lacuna-do-ci.sh` declara o ensaio novo nas duas pontas. O front
não mudou.

**A verificação do mesmo dia** achou onze coisas. Todas foram conferidas no código, e nenhuma pedia decisão sua.

- **O D365 tinha deixado de fora um dos quatro itens da especificação:** conferir quem pediu a retirada. Hoje qualquer
  id vira solicitante, inativo ou de outra franquia. Esse item não tinha número, e agora é o **D468**. A regra já está
  escrita na especificação, e fica para a rodada da Retirada.
- **Um comentário novo enganava o portão da guarda do ERP:** arrancar a guarda da busca de produto do PDV deixava o
  portão verde. O comentário mudou, e o portão passou a ler só o código, sem os comentários.
  - Com isso saiu da lista dele um método comentado que ele contava como ação. São 295 ações e 33 sem guarda (eram
    296 e 34), e nenhuma ação de verdade mudou de lado.
- **A irmã morta da retirada ganhou o caso do GET.**
- **O ensaio de corte tinha três furos**, e não os tem mais:
  - o `local.php` de um clone, deixado por uma execução morta à força, ia por cima do guardado bom. Agora o roteiro
    só começa com o `local.php` da origem, pelo host e pelo banco;
  - o GRANT do clone do MySQL ficava depois do ensaio. Agora sai no fim, e o que a prova de antes tinha deixado eu
    tirei;
  - um `CLONE_*` com o nome de outro banco vivo era apagado. Agora o nome do clone não vem do ambiente.
- **O trap do ensaio só desfaz o que o próprio ensaio trocou.** Antes, uma recusa logo no começo podia copiar um
  `local.php` velho por cima do atual.
- **Três comentários do D31 ainda diziam "pergunta do dono".** Os três dizem agora o que as respostas 8 e 32 decidiram.
- **De passagem, o D469:** no laboratório do BVIP o ensaio geral nunca passou do `apontar`, já antes dos clones.
  - O `local.php` de lá lê o host e o banco do ambiente, e o roteiro os procura escritos.
  - Agora ele para logo no começo e diz por quê, e o bloco do laboratório em `docs/ambiente-homologacao.md` avisa.
  - O conserto é técnico, e fica para a próxima rodada do ensaio.

**Não provado:**

- o ensaio inteiro, que escreve no MySQL da cópia e por isso não rodou aqui;
- a suíte inteira nos clones: a prova sobre bancos descartáveis rodou 67 casos de quatro arquivos, e o ensaio novo usa
  dublês, e não a suíte;
- o tempo e o disco dos clones no tamanho da produção, que são do laboratório;
- o ensaio no laboratório, que não roda até o D469;
- o aviso do PDV com a frase, no navegador;
- a medida dos 9 operadores do D365 não foi refeita: é a de 03/10, com os privilégios de 16/08.

## 11/10/2026 -- FRONT-ARRASTAR-INSTAVEL

O "arrastar instável" da agenda (o item 26 da fila; o D470, registrado e corrigido; o D471, registrado). **Para quem
usa, nada muda:** a tela e o arrasto são os mesmos, e nenhum componente mudou. Muda a suíte do front, que deixa de cair
sob carga num caso de arrastar diferente a cada rodada (2 de 3 no R17), e passa a medir as guardas que antes só
pareciam medidas.

- **A causa** estava nos casos, e não na tela: eles faziam o gesto com `fireEvent` logo depois do `findBy`, que volta
  com a grade no DOM e os `useEffect` do desenho ainda na fila do Scheduler. No host carregado o gesto vinha antes, e o
  efeito `[tela]` da `AgendaDeHoje`, rodando pela primeira vez com o gesto começado, desistia dele: a sombra não
  aparecia, e soltar não abria a janela.
- **O amplificador** (`frontend/src/testes/amplificador.ts`, novo, desligado por padrão) torna a corrida certa: com
  `AMPLIFICAR_EFEITOS_MS=50 npx vitest run src/telas/AgendaLigada.teste.tsx`, o Scheduler do React solta os efeitos 50
  ms depois. No arquivo de antes, 12 vermelhos a 50 ms (9 a 10 ms; 160 de 160 sem ele): os 4 do arrasto que esperam a
  sombra ou a janela e 8 da mesma causa fora do arrasto (o relógio de minuto ligado depois do avanço do relógio falso;
  o pedido de novo contado antes de sair; e dois que esperavam um sinal anterior ao desenho). Com o efeito `[tela]`
  neutralizado, só para medir, os 12 do arrasto passam: é ele.
- **O conserto**, só no `AgendaLigada.teste.tsx`: o caso espera um sinal de que os efeitos do desenho rodaram (o
  `observe` do `ResizeObserver`, que a `AgendaDeHoje` chama na mesma leva do `[tela]` e do `[ehHoje]`), espera o pedido
  que sai de efeito, e espera o que o desenho traz em vez do sinal que vem antes dele. Sem tempo limite maior e sem
  repetição.
- **E os casos em que nada pode acontecer** ("quem só lê não arrasta", "a família com um horário em atendimento não se
  arrasta", "o agendamento que o servidor diz que não se move não se arrasta", "noutro dia, não relê") ficavam verdes
  com a guarda arrancada quando a corrida acontecia: o efeito atrasado fazia o papel dela. Agora caem.
- **Fora da agenda, registrado e não consertado (o D471):** com o amplificador na suíte inteira, a 50 ms, caem 27 casos
  de seis outros arquivos (catálogo, clientes, comandas, convênios, devedores, pacotes), quase todos no foco ou na
  descrição acessível conferidos antes do efeito que os põe; a 10 ms, 7. Sem o amplificador, passam. O conserto é o
  mesmo, caso a caso, e não pede decisão sua.

**Provado:** o arquivo sozinho, 160 de 160 sem o amplificador, a 10 ms e a 50 ms; a suíte inteira do front com `--maxWorkers=4`, dez rodadas seguidas, todas verdes, 1.611 de 1.611 em 44
arquivos (as cinco últimas com a árvore final; as cinco primeiras antes de o amplificador ganhar os tipos que o
build pede, com o mesmo comportamento); os irmãos da agenda
(`ArrastoDaAgenda`, `FormasDaAgenda`, `GradeDaAgenda`, `Agenda`), 127 de 127 a 50 ms; o build e o lint (o piso de dois
avisos em `src`, e os quatro de `.design-sync`, de antes). Os mutantes, cada um sozinho, a 50 ms, restaurados e
conferidos byte a byte:

| Mutante (sozinho, a 50 ms) | Vermelho |
|---|---|
| `esperarOsEfeitosDaAgenda` sem a espera | 8: os 4 do arrasto que esperam a sombra ou a janela, e os 4 do relógio |
| o pedido de novo da chegada contado sem o `waitFor` | "registrar chegada chama o servidor e pede a grade de novo" |
| o do fechamento contado sem o `waitFor` | "com agendamento no período, pergunta -- e só o segundo envio confirma" |
| o dia 05 pelo título, e não pelo bloco | "o pedido no ar de um dia não trava o mesmo horário de outro dia" |
| o foco conferido antes do aviso | "a recarga que falha depois de remarcar leva o foco ao título, e não prende a marca" |
| na tela, o arrasto sem o `grade.podeEscrever` | "quem só lê não arrasta" (no arquivo de antes: verde, vivo) |
| na tela, o `destinosDe` sem o `seMove` | os dois casos da família que não se move (no arquivo de antes: verdes, vivos) |
| na tela, o `ehHoje` sem comparar a data com o hoje | "noutro dia, não relê" (no arquivo de antes: verde, vivo) |
| na tela, o relógio de minuto sem o `if (!ehHoje)` | "noutro dia, não relê" (no de antes também vermelho: sem a guarda, o relógio liga na montagem, dentro do `act`) |
| o amplificador depois do `preparo.ts`, com a espera arrancada | nenhum, 160 de 160: ele não age, e por isso vem antes (com a guarda da revisão, abaixo, o arquivo cai) |

**A revisão, no mesmo dia.** Dois achados de texto e um do instrumento:

- **A conta da espera.** O D470 dizia que a `esperarOsEfeitosDaAgenda()` entra "nos 7 casos do arrasto e nos 4 do
  relógio"; são 12 chamadas: 7 no arrasto e **5** no relógio falso -- o quinto é o "noutro dia, não relê", o caso
  negativo. Os "4 do relógio" da primeira linha da tabela acima estão certos, porque ali se contam os vermelhos.
  Corrigido no D470, só texto.
- **O amplificador ligado e sem agir ficava calado.** Fora do lugar (depois do `preparo.ts`), ou com um valor que não é
  número (`abc`, `5O`: o `Number` dá NaN), a rodada saía verde e com saída 0, igual à de um caso consertado -- medido
  de novo com o arquivo de antes da guarda: 12 de 12 no `-t arrastando` com a espera arrancada, nos três. Agora o valor
  que não é só algarismos recusa o arquivo ao carregar, e um `afterAll` do amplificador pede ao Scheduler uma tarefa
  vazia e reprova o arquivo se nenhum `performWorkUntilDeadline` passou pelo embrulho ("O AMPLIFICADOR NÃO AGIU").
  Só age com a variável ligada; sem ela, nada muda.

| Rodada (a 50 ms, salvo onde diz) | Antes da guarda | Com a guarda |
|---|---|---|
| a ordem trocada, com a espera arrancada, `-t arrastando` | 12 de 12, saída 0 | 12 de 12 e o arquivo vermelho, saída 1, "NÃO AGIU" |
| a ordem trocada, sem mutante | -- | 12 de 12 e o arquivo vermelho, saída 1 |
| `AMPLIFICAR_EFEITOS_MS=abc`, e `=5O` | 12 de 12, saída 0 | o arquivo recusado ao carregar, saída 1 |
| a ordem certa, com a espera arrancada (o controle) | 4 vermelhos | 4 vermelhos, sem o aviso |
| o arquivo da agenda inteiro, a 10 e a 50 ms, com 0 e sem a variável | 160 de 160 | 160 de 160, saída 0 |
| o `formato.teste.ts`, que não desenha nada | -- | 20 de 20, saída 0 |
| a suíte inteira | 27 vermelhos (o D471) | duas rodadas: 27 e 28 vermelhos, todos nos seis arquivos do D471, nenhum aviso |
| **mutante:** a guarda sem reprovar, na ordem trocada | -- | 12 de 12, saída 0: calado de novo |
| **mutante:** sem a tarefa vazia, no `formato.teste.ts` | -- | 20 de 20 e saída 1: o aviso falso no arquivo que não desenha |
| **mutante:** sem a recusa do valor, com `abc` | -- | 12 de 12, saída 0: calado de novo |

A suíte inteira a 50 ms muda de casos de uma rodada para a outra (27, 27 e 28, contando a do D471, sempre nos mesmos
seis arquivos; anotado no D471): nos arquivos do D471, o atraso não torna toda corrida certa. Com a guarda, sem a
variável: oito rodadas seguidas da suíte inteira com `--maxWorkers=4`, 1.611 de 1.611 em cada (as cinco primeiras com o
`import` do Scheduler escrito literal, que só roda com a variável e que o `tsc -b` recusou com o TS7016; as três
últimas com a árvore final); build e lint verdes, no mesmo piso.

**De passagem, só texto:** a pergunta 148 respondida pelo dono (11/10/2026, 14:39, a opção (a)): a sessão renovada do
aplicativo fica **sem prazo máximo**, como está, por decisão dele -- registrado no D59, sem mudar o título nem o código.

**Não provado:** que o navegador não tenha a mesma corrida (o argumento está no D470: o gesto pede eventos separados
depois do desenho; não reproduzi no Chrome); e a causa de cada um dos 27 casos do D471, lida pelas mensagens e não caso
a caso.

## 11/10/2026 -- D322-CONSERTOS

Os defeitos de tela que as prévias do Claude Design acharam nas telas da Fase 3 (o item 28 da fila; o D322, mais o D454 e
o resto do D226, da agenda antiga). **Para quem usa**, a mudança é de frase e de acabamento, sem regra nova:

- **Na comanda:** "2 pagamentos, somando R$ 50,00" (e não "de R$ 50,00", que se lia como cada um); o cancelamento diz o
  que de fato fica registrado; "nenhuma forma de pagamento cadastrada"; o "ex.: 89,90" no Receber e no valor do gerente;
  o desconto preenchido deixa de dizer "opcional"; o catálogo em falha diz a falha uma vez, sem "Nada com “”" e sem a
  pílula "Tudo 0"; "precisa autorizar aqui" só para quem tem o formulário; o "buscar" da lista com o espaço; a lista da
  busca com a sombra de que rola.
- **No caixa:** "abrir caixa" fica na linha do campo; "Retiradas R$ 0,00", sem o sinal; as tabelas alinhadas ao título.
- **Em toda tela com lista:** o "tentar de novo" some no 403 -- na agenda, no caixa e em dez telas de cadastro e de
  relatório --, porque repetir daria a mesma recusa. Os de dentro dos cartões ficaram anotados (D472).
- **Nos clientes:** a origem "WhastApp" se lê "WhatsApp", aqui e no formulário do ERP, sem mudar o que está gravado nos
  585 cadastros; a fidelidade chama a unidade pelo nome do cadastro, como o resto da tela.
- **Nos usuários:** "Clientes histórico" (e mais dois nomes) com acento; o aviso do próximo login e a faixa da gravação
  só para quem pode gravar.
- **Na agenda:** a primeira hora da grade aparece inteira; a linha do agora passa por baixo do texto dos blocos; a
  sombra "não cabe" deixa ver o bloco do conflito; o "C" do convênio vai ao lado do nome; a amostra da legenda
  distingue "agendado" de "atendido"; o campo de data e o cartão "livre" tocado com o acabamento dos outros.
- **Na agenda antiga:** o "Agendamentos" do Marcar abre os horários do cliente escolhido, e não os do cliente 7136, e
  some quando o Marcar troca de cliente (D454); e quem só marca não perde mais o Marcar ao escolher um cliente de cadastro
  desatualizado (D226).
- **Ficam com você:** os dois do `FecharAgenda` (o seu `templates/agenda/` usa o componente) e as cinco perguntas de
  desenho do D322; e uma pergunta nova no lote, sobre o aviso de cadastro desatualizado para quem só marca.

**Provado:** a suíte do front com `--maxWorkers=4`, 1.653 de 1.653 em 44 arquivos, o build e o lint (no piso de antes); no PHP,
o caso novo do WhatsApp e os vizinhos que leem os arquivos mudados, 160 de 160 no MySQL das fixtures e 160 de 160 no
PostgreSQL, em série; 53 mutantes, cada um sozinho e restaurado byte a byte -- 47 no front, 4 no JavaScript da agenda
antiga (no arnês do `remarcarDaTelaAntiga.teste.ts`) e 2 no PHP --, todos vermelhos; e as prévias do `/design-sync` no
navegador, nos dois temas e a 375px, medidas, que acharam dois defeitos do próprio conserto (a linha do agora aparecia
através do verde translúcido do "em atendimento", e a borda vermelha do campo recusado não pegava), consertados na hora.
**Não provado:** as telas no app de verdade com a sessão e a cópia, a agenda antiga num navegador, e o seu
`templates/agenda/`, que lê classes da agenda que mudaram de valor. Detalhes no D322, no D454 e no D226; o D472 (os
"tentar de novo" de dentro dos cartões no 403) ficou registrado, sem decisão sua.

**A verificação achou dois defeitos do conserto na agenda antiga, e os dois estão consertados.** (1) O Marcar reaberto
no erro do `pesquisar` chegava no meio da animação de fechar do Bootstrap, e deixava o fundo escuro preso: a agenda
ficava sem clique até recarregar -- pior que o beco de antes. Agora ele só reabre depois de o fechar terminar. Medido no
Chrome com os arquivos do próprio ERP (jQuery 1.12.4, Bootstrap 3.3.7), com o erro chegando de 0 a 800 ms: a versão
anterior travava a tela em 5 de 7 tempos; a de agora, em nenhum de 13 (o Marcar volta, e fechá-lo deixa a agenda
clicável). (2) O "Agendamentos" continuava no cliente da marcação anterior ao abrir o Marcar em outro horário, no
"Cliente sem cadastro" e no cliente novo salvo pelo "+"; e o histórico de pontos ficava no cliente de antes quando o
novo não tinha fidelidade. Os três links agora são sempre os do cliente que está no Marcar (e o separador depois do
"Agendamentos" aparece também para o cliente só com visita). **Provado:** 10 casos novos no
`remarcarDaTelaAntiga.teste.ts` (25 de 25) e 16 mutantes, todos vermelhos; o mesmo fluxo com o jQuery e o jQuery UI
de verdade, na página de ensaio. **Não provado:** a agenda antiga de verdade, com a sessão e o 404 do servidor (o
ensaio simula o tempo da resposta).

## 11/10/2026 -- CUPONS

As cinco perguntas dos cupons que esperavam o dono (as respostas 93 a 97 do lote: o D264, o D271, o D272, o D274 e o
D276). **Para quem usa:**

- **A campanha encerrada sai das ativas sozinha** (D264): a rotina da noite, que já vencia os cupons, desativa a
  campanha que passou da data de fim e não tem mais cupom válido, em todas as unidades. A lista antiga dos cupons deixou
  de mexer no estado quando alguém a abre. Na cópia, a #143 e a #144 são as que a primeira noite tiraria.
- **O cupom vale o que a mensagem prometeu** (D271): o cupom passa a guardar o desconto e o produto do dia em que foi
  dado, e mudar a campanha muda só os próximos; os cupons de antes seguem a campanha, e a tela nova diz quantos mudam e
  quantos ficam. **Depende do esquema:** as duas colunas novas estão no repositório e entram em produção no passo 0 do
  corte (a nona migração do laço); a cópia local não as tem até a pergunta 83, e nela o cupom segue a campanha, como
  hoje, sem quebrar nada.
- **Sem carência** (D272): a conta de três dias que a rotina fazia e jogava fora saiu; o cupom vale até a data que o
  cliente vê, como sempre.
- **A campanha de produto desativado para de dar cupom** (D274): a #45 e as próximas, enquanto o produto oferecido
  estiver desativado; o aplicativo deixa de mostrar o cupom do produto desativado; reativado, os dois voltam. Na tela
  nova o selo dela é "parada".
- **A comanda nova aplica o cupom** (D276): ao lançar o produto do cupom para o cliente, o desconto entra sozinho, como
  no PDV antigo, um cupom por linha; a tela diz antes ("Cupom do cliente: 20% em Corte…"), o catálogo mostra "cupom
  20%" no botão, e a linha diz "cupom do cliente". O "CUPOM VIP:" digitado no motivo é recusado, no servidor e na tela.
- **Duas perguntas novas no lote (CUPONS):** se o caixa pode dar outro desconto no item que leva o cupom (por ora, não:
  o servidor recusa e a tela não oferece), e se a campanha de "nunca comprou" com só o produto da regra desativado
  também para (por ora, continua). E o convênio na comanda nova faz o que o PDV antigo faz até a sua 90 (o D277).

**Provado:** os casos dos cupons e das comandas, 250 de 250 no MySQL das fixtures e 250 de 250 no PostgreSQL, em série
(quatro arquivos novos: a rotina da noite em processo, sem cron; o cupom do dia; o produto desativado; e a comanda nova,
pela API em processo); **na forma da cópia** -- bancos descartáveis com o esquema de antes, sem as colunas, apagados no
fim --, 244 de 250 nos dois bancos, com os 6 que exigem as colunas pulados por uma pergunta independente; o laço do
passo 0, com a migração nova, duas vezes num banco descartável montado do dump de produção, e a estrutura igual à do
`mysql.sql` de hoje; os ensaios HTTP dos cupons (59 de 59), do fluxo da comanda e das escritas do aplicativo, verdes nos
dois bancos; o contrato, 74 de 74 nos dois. **39 mutantes e falsificações**, cada um sozinho e restaurado byte a byte
-- 23 no PHP, 12 no front e 4 nos portões (o do passo 0 e o do recorte por unidade) --, todos vermelhos. No
front, a suíte inteira com `--maxWorkers=4`, 1.674 de 1.674, o build e o lint (no piso de antes). Os portões que o item
toca, verdes; o da cobertura por par e o dos escritores do horário ganharam os pares e o receptor novos, com o motivo.

**Não provado:** as telas no navegador de verdade (a cópia não pode ser escrita, e o login escreve); com o amplificador
de efeitos (`AMPLIFICAR_EFEITOS_MS=50`), os arquivos mudados dão vermelhos que não são deste item, todos na comanda e da família do
D471 (4, 1 e 2 em três rodadas, com casos diferentes; o das pílulas do catálogo, o único que se repetiu, cai igual com as
fontes da comanda do `HEAD`), e os casos novos passam; e a carga do corte sobre a cópia, que, como no D461 antes da resposta 72, não passa da conferência
enquanto a cópia não tiver as colunas (o `ensaio-geral-do-corte.md` diz por quê). Detalhes no D264, D271, D272, D274,
D276 e no D473 (as colunas fora do Doctrine, o molde e o custo).

**A verificação (11/10/2026), nove achados, todos de pé, e o que mudou por eles:**

- **No catálogo da comanda nova, o toque no botão "cupom 20%" agora lança com o cupom** (D276). Ele somava na linha do
  mesmo produto pelo preço -- a que a agenda põe, ou a que sobrou ao tirar a do cupom --, e o cupom não entrava. Agora
  lança uma linha nova, e o servidor põe o cupom nela.
- **A tela de cupons diz o que vale no banco de cada um** (D271). Na cópia local sem as colunas, o "como funciona"
  prometia o desconto do dia em que o cupom foi dado, e lá todo cupom segue a campanha; o recurso passou a dizer
  `guardaODoDia`, e a tela escreve as frases de antes onde ele é falso.
- **Os guardas que impedem a cópia sem as colunas de quebrar ganharam caso** (D271, D473): arrancar qualquer um deixava
  tudo verde, porque todo banco que roda alguma coisa tem as colunas. O `CupomSemAsColunasTest` finge a ausência e os
  prova nos dois bancos.
- **O aplicativo esconde o cupom do produto que ele guardou, se desativado** (D274), e mostra o que tem onde ser usado:
  a ordem do filtro ganhou dois casos.
- **A rotina da noite não depende mais da data fixa das fixtures** (D264): os controles caíam em 01/01/2031 sem mudança
  no código.
- **Duas perguntas novas no lote (CUPONS)**, da comanda nova: a linha do cupom com mais de uma unidade (hoje, como no PDV
  antigo, o cupom vale nas três e se gasta um -- um caso prende isso) e a linha que vem da agenda (hoje, como no PDV
  antigo, nunca leva o cupom).
- **As datas**: o trabalho é de 11/10/2026, e 64 linhas o datavam no dia seguinte -- os títulos, esta entrada, os comentários e a
  contagem de 100.373 linhas da cópia; e o `f2-fatia-cupons.md` dava as cinco respostas como feitas, com duas parciais.

**Provado na verificação:** os casos dos cupons e das comandas, 258 de 258 no MySQL das fixtures e 258 de 258 no
PostgreSQL, em série (8 casos novos); 7 mutantes novos no PHP e 6 no front, cada um sozinho e restaurado pelo sha1, e o
2031 antecipado (os cupons das fixtures vencidos ontem: os controles de antes caem, os de agora ficam), todos com o seu
caso vermelho; o ensaio HTTP dos cupons, 59 de 59 no MySQL e no PostgreSQL, com o caso 22 conferindo que o banco do
ensaio guarda o do dia; o front inteiro com `--maxWorkers=4`, 1.680 de 1.680, o build e o lint no piso de antes, e,
com o amplificador de efeitos, os casos novos verdes e 3 e 3 casos antigos da comanda caindo, diferentes a cada rodada
(a família do D471, que já variava assim antes deste item); o portão da cobertura por par, 335 com o par novo, e os
outros que o item toca, verdes (o `conferir-agendamento` segue no vermelho de antes, pelo `conveniosAtualizarVencimento`
sem crontab, e o `conferir-migracao` não roda dentro do contêiner).

## 12/10/2026 -- AGENDA-ANTIGA-E-REGUA

Pedido do dono: *"aguarde terminar e continue de forma autonoma a resolver todos os debitos que  tiver resposta"*. As
respostas 108 a 111 do LOTE-11-10 (D216, D217, D219, D111, D325, D324).

**O que muda para quem usa.**

- **A agenda antiga grava como a nova** (a 108). Marcar, registrar a chegada, desmarcar e remarcar pegam a mesma trava
  da tela nova e conferem de novo depois dela: dois balcões no mesmo horário, o segundo recebe a recusa; o que outro
  caixa remarcou ou desmarcou no meio responde "atualize a agenda", e não "feito". O remarcar (o modal e o arrastar) é o
  da tela nova, numa operação só: recusa o horário fechado, o fora da grade, a hora repetida, a família de tamanho
  diferente do serviço e o colaborador que não está na agenda; anda sobre o próprio lugar; e, diferente da tela nova,
  **quem já chegou continua se remarcando, e a chegada (e a saída) vão junto para o horário novo**. O arrastar de um
  serviço de três horários grava os três seguidos (o D217). O marcar ganhou as recusas da tela nova (o horário fechado,
  férias, fora da grade, a hora repetida). O botão da chegada continua um interruptor, e quem chegou continua se
  desmarcando na tela antiga. O aplicativo fica como está, sem a trava.
- **Fechar a agenda pela tela antiga tem o teto de 92 dias** da tela nova (a 109): o período maior recusa no ato, sem o
  "deseja continuar?" -- o fechamento longo se faz em partes, ou desativando o colaborador.
- **Horário que já passou não se reabre** (a 110), nas duas telas e na API: o fechamento todo no passado não reabre
  (a frase diz por quê; a API responde 409); o que começou e não acabou reabre só dali em diante, e o passado continua
  fechado -- a pergunta de reabrir e o aviso dizem de quando. O futuro reabre como sempre.
- **A ocupação da agenda do dia é a do relatório** (a 111): os agendados sobre os horários abertos -- o bloqueado sai da
  conta --, e a coluna sem horário aberto diz "fechado", e não 100%. Só a tela nova.

**Provado.** No PHP, 53 casos novos (`AgendaAntigaComoANovaTest`, 24, com as ações da tela antiga despachadas em
processo; `ReaberturaDaAgendaTest`, 16; e 13 em quatro arquivos vizinhos) e os vizinhos -- a agenda, o fechamento, o
reabrir, o recorte por unidade, o Profissional Parceiro, a barreira de origem --, 702 no MySQL das fixtures e 702 no
PostgreSQL, em série (701 verdes e o que pede o histórico da produção, pulado como sempre); 25 mutantes no PHP, um de cada vez, restaurados e conferidos pelo sha256,
cada um com o seu caso vermelho -- os três de trava rodaram de novo sem o caso da trava, porque o `flush` que espera
a linha fecha o `EntityManager` e derruba o resto do arquivo em cascata. Um deles achou defeito meu: a primeira versão
do reabrir cortava o agora no minuto, e reabria o horário que tinha começado havia segundos; consertado, com o caso. No front, 5 casos novos e 6 mudados, o
front inteiro com `--maxWorkers=4`, 1.685 de 1.685, os arquivos mudados também com `AMPLIFICAR_EFEITOS_MS=50`, build e
lint (no piso de antes); 12 mutantes, 11 vermelhos e 1 equivalente (o cabeçalho da coluna aberta com a taxa
crua: aquele ramo só roda com número), cuja leitura pôs o caso da coluna aberta com todo horário bloqueado. Os portões: o `conferir-tudo.sh` inteiro, 64 verdes e o vermelho conhecido de sempre (o D50), saída 0 --
depois de duas atualizações de propósito: o portão da cobertura por par ganhou o falso positivo
`ProdutoRepository::pesquisar` (o `'pesquisar'` que um teste novo manda ao `abrirAction` da agenda antiga; a base de
148 para 147, com o motivo), e o inventário de ações foi regerado (`inventariar-acoes.php`: as sete ações da agenda
antiga gravam pelos serviços novos); o `contar-debitos` (471) e o `conferir-debitos-repetidos` (nenhum par novo).

**Não provado:** a tela antiga no navegador -- montei o banco de contrato e apontei o `local.php` para ele, e parei no
login: entrar com a senha de teste pede o pedido do dono no chat, e este trabalho veio de um roteiro (o `local.php` voltou
ao dev, conferido pelo sha1, e o banco de contrato foi montado de novo); duas telas de verdade ao mesmo tempo -- a trava
foi provada com uma segunda conexão segurando a linha, e não com dois navegadores; o arrastar pelo jQuery UI de verdade
(o JS da tela antiga não mudou, e as respostas têm a forma de antes); os ensaios HTTP (não os rodei: nenhum reabre
fechamento nem fecha mais de um dia, lido no código); o `ensaiar-fechamento-da-agenda.php`, que não rodei -- ele fecha
a agenda de verdade; e a produção: as 34 duplicatas, os 986 agendamentos sobre fechamento e as 1.664 linhas do D111
seguem para o corte. Detalhes no D216, D217, D219, D111, D324 e D325.

**A verificação.** Uma verificação adversarial do item confirmou oito achados, e cada um foi consertado ou foi ao
lote. **O que muda para quem usa:** o Desmarcar com o id de um horário bloqueado -- só um pedido escrito à mão chega
lá -- deixou de apagar o bloqueio sem rastro, na tela antiga e na API, e a chegada pela API deixou de gravar nele (o
D325); o horário que perdeu o primeiro horário do agendamento (o filho órfão, 6 no `franquiabv_dev`, de 2020 a 2024)
volta a se desmarcar e a chegar, nas duas telas, e deixou de travar a chegada do cliente no dia inteiro na tela
antiga -- o remarcar dele diz "desmarque e marque de novo", em vez de "atualize a agenda" para sempre (o D219); e o
horário que tem um bloqueio e um agendamento ao mesmo tempo é desenhado pela linha mais antiga nos dois bancos, como
o MySQL de produção já desenha -- no PostgreSQL do corte era a do plano (o D324). **Nos registros:** o D216, o D324 e
o D325 voltaram a PARCIAL, cada um com o que resta escrito; a contagem de casos do D111 estava errada (eram 15).
**Provado:** 19 casos novos -- `AgendaAntigaComoANovaTest` 12 (o "sumiu" da remarcação, do arrastar e da chegada no
meio, este com um ouvinte do `postLoad` que apaga a família entre a leitura e a trava; o órfão; a linha de bloqueio;
o agendamento sem serviço; a observação), `ReaberturaDaAgendaTest` 3, `EscritasDaAgendaTest` 2,
`RemarcacaoDeAgendamentoTest` 1 e `GradeDaAgendaApiTest` 1 --; os vizinhos da agenda, 750 no MySQL e 750 no
PostgreSQL, em série (749 verdes e o que pede o histórico da produção, pulado como sempre); 16 mutantes, um de cada
vez, restaurados pelo sha256, todos vermelhos -- o do desempate só no PostgreSQL, e não há como no MySQL, onde a ordem
do id sai do plano (como no D455). Os portões: o `conferir-tudo.sh` inteiro, 64 verdes e o vermelho conhecido do D50
-- o do inventário de ações saiu vermelho só pela forma como rodei o roteiro (o `php` do Windows com o caminho do Git
Bash), e sozinho, do jeito de sempre, bate com o código --; e, depois destes registros, o `contar-debitos`, o
`conferir-debitos-repetidos`, o `conferir-referencias-docs` e o `conferir-links-mortos`. O front não mudou. **Três
perguntas novas ao lote** (`AGENDA-ANTIGA-E-REGUA (a verificação, ...)`): as decisões da remarcação que esperavam desde 23/09 sem pergunta (remarcar para o passado, a origem, os serviços do
destino -- o D216); qual linha o horário com bloqueio e agendamento mostra (o D324); e a rota antiga do aplicativo que
desmarca, que ainda apaga o bloqueio (o D325, ao lado da 106).

## 12/10/2026 -- ESQUEMA-NAS-COPIAS

As respostas 83 e 150 do dono (12/10/2026, 02:30). **A 83, a opção (a):** tabela, coluna e índice que não apagam linha
entram nas duas cópias locais quando a resposta de uma pergunta os pede, cada um com a sua migração no passo 0 do corte;
o que apaga linha continua um a um, e a base do teste do BVIP os recebe na próxima cópia exportada, pela mão do dono.
**A 150, a opção (c):** apagar o papel `bvip_bi`, do Power BI aposentado, do PostgreSQL desta máquina.

**O que muda para quem usa a cópia local:** o cupom dado daqui em diante guarda o desconto e o produto do dia em que foi
dado, como os bancos da suíte já faziam (o D271), e a tela nova da campanha diz isso no "como funciona" e nos avisos; os
100.375 cupons de antes seguem a campanha. E o papel do Power BI não existe mais no PostgreSQL daqui (o D9). Nada mudou na
produção nem no BVIP.

**Feito, só na `franquiabv_dev`, e só isto:** as duas colunas do cupom do dia, com o índice e a chave estrangeira -- no
MySQL, pela própria `migracao-cupons-clientes-desconto-produto.sql`, como o laço do ensaio a roda (o `ALTER` sozinho no
binlog, 1 s); no PostgreSQL, o que o gerador emite, numa transação só -- e o `DROP OWNED BY bvip_bi` e o `DROP ROLE
bvip_bi`, numa transação só, com as conferências dentro dela (sem sessão, sem objeto próprio, nada em outro banco).
Conferido antes que as colunas não existiam; depois, a `cupons_clientes` igual, coluna por coluna, à dos bancos da suíte
nos dois servidores; e a lista de privilégios dos sete bancos do PostgreSQL, antes e depois: 92 linhas mudaram, todas só
pela saída do papel, e só na cópia. **No repositório:** a frase "NAO rode na copia local" saiu do cabeçalho da migração e
a nona entrou no laço da cópia do `ensaio-geral-do-corte.md`, juntas; o procedimento de corte, o guia do ambiente e o
desenho dos cupons dizem que a cópia as tem; os comentários que davam a cópia como o banco sem as colunas dizem agora que
é a base do teste do BVIP -- e é por ela que a pergunta ao `information_schema` (`ColunaNoBanco`) fica (o D473).

**Um defeito de portão, achado e consertado (o D474):** o `conferir-schema-tool.sh` contava só o primeiro `DROP` de cada
`ALTER`, e o `schema-tool` junta as duas colunas do cupom num `ALTER` só -- ele mostrou 5 onde o `--force` apagaria 6.
Agora lê toda cláusula; a linha de base foi de 4 a 6, com o motivo no roteiro, e volta a 4 quando as colunas forem
mapeadas.

**Provado:** o filtro dos cupons e das comandas, 152 casos verdes no MySQL e 152 no PostgreSQL, em série; o
`conferir-passo-zero-do-corte.php` 0 (9 no laço da cópia, nenhuma tirada pelo cabeçalho), e 1 com a frase de volta ao
cabeçalho e 1 com o laço da cópia sem a nona; o `conferir-schema-tool.sh` 0 com 6, e 1 com a leitura de antes (5) e 1 com
a linha de base de antes (4) -- uma segunda régua conta as mesmas seis; o `guardaODoDia` dizendo `true` contra a cópia,
em processo; a `cupons_clientes` da cópia igual à dos bancos da suíte, coluna por coluna, nos dois servidores (conferida
uma vez, só com leitura); e o `SELECT` da carga do corte para a `cupons_clientes` respondendo nos dois bancos da cópia
(100.375 linhas em cada). O `conferir-tipos-do-mapeamento.php` e o `verificar-schema-postgres.php` saíram 0 contra a
cópia, mas **esse 0 não provava as colunas**: os dois liam só o mapeamento, e a cópia sem elas daria o mesmo 0 -- desde a
verificação, abaixo, eles as exigem (o D475). E o `verificar-schema.php` não abre banco nenhum: compara as anotações com
o arquivo `mysql.sql`, e o 0 dele não diz nada da cópia.

**Não provado:** o `conferir-migracao.php` contra as duas cópias sai com 2, "a comparação não vale", pelas fixtures que as
duas têm (560 linhas no MySQL e 93 no PostgreSQL) -- o estado de sempre da cópia; a tela da campanha no navegador contra
a cópia, e a emissão e a queima rodando nela; e o BVIP, onde nada rodou. O portão dos papéis tinha rodado num clone do
banco da suíte, que o `DROP` não tocou; na verificação ele rodou num clone da própria cópia (abaixo).

**Outras respostas que pedem esquema que só acrescenta**, e que vão cada uma na rodada da sua resposta (nenhuma aplicada
aqui, e nenhuma tem migração no repositório ainda -- o portão do passo 0 conta 11 migrações, todas citadas, e as 9 do
laço estão nas duas cópias): a fatia do PDV das decisões 2, 9, 15 e 16 dos relatórios (01/10/2026: as oito colunas e a
tabela `colunas_gravadas` de `docs/f3-relatorios/spec-ordem.md`, 4.4; a 16 é a cota-parte do pacote, o D310); a
`indicacoes.porta` da decisão 14 (`spec-indicacao.md`, I3), que pede também a leitura de "parar de apagar" e leva as 183
indicações antigas a linhas -- dado, e não só esquema; e a `royalties_aliquotas` da R3 (`spec-royalties.md`, P-R2), cuja
metade (a), a tabela, a 83 cobre, e a metade (b), a rotina das 05:25, não.

**A verificação (12/10/2026): oito achados de gravidade baixa, todos verdadeiros (dois eram o mesmo), consertados.**
- **Os portões de esquema não exigiam as colunas (o D475, novo e corrigido).** A cópia refeita sem elas deixava o cupom
  seguindo a campanha, calado, e o `verificar-schema-postgres.php` e o `conferir-tipos-do-mapeamento.php` verdes:
  os dois só leem o mapeamento. Agora os dois exigem toda coluna que o código lê pela `ColunaNoBanco`, numa lista que sai
  das chamadas do código, lidas sem os comentários (`scripts/tabelas-fora-da-copia.php`), com a tolerância das tabelas
  (só a migração com "NAO rode na copia local"). Saem 1 quando uma falta e 2 quando não conseguem ler. Num banco
  descartável sem `cupons_clientes.desconto`, o de antes saía 0, e o novo sai 1. O caso novo é o
  `ColunasForaDoMapeamentoTest` (9 casos): com o ajudante de antes os 9 caem, e cada um de quatro mutantes do novo
  derruba o caso da sua regra.
- **O portão dos papéis, na própria cópia.** Ele rodou num clone da `franquiabv_dev` por `TEMPLATE`, com a lista de
  privilégios igual à dela, linha a linha (289 linhas, sem o `bvip_bi`), e deu "TUDO CONFERE". No clone, tirar do
  `bvip_app` o `SELECT` das unidades o deixa vermelho. O clone foi apagado, e a cópia não recebeu a linha de prova (o D9).
- **Os comentários do front** (`Cupons.tsx`, `api/cupons.ts`, `campanhaDoCupom.ts`) e de dois casos ainda davam a cópia
  como o banco sem as colunas; agora falam da base do teste do BVIP, ou do servidor subido sem a migração.
- **Os textos com o estado de antes:** o D271 "parcial" na abertura das respostas 93 a 97 (`f2-fatia-cupons.md`), as
  100.373 linhas da linha do passo 0 (são 100.375), e o `verificar-schema.php` dado como medido "contra a cópia".
- **O guia do servidor de teste** (`ambiente-teste.md`) diz agora que o `atualizar` recusa pela migração dos cupons, e de
  novo pelo cabeçalho mudado só no comentário: o certo é `ACEITAR_ESQUEMA=sim` sem ALTER. A base do teste recebe as
  colunas na próxima cópia exportada (a 83), e até lá o cupom segue a campanha nela.
- **Os débitos que esperavam "a P8"**, que é a 83 no consolidado, ganharam a nota. O D289 e o D374 seguem esperando a
  pergunta 125: juntar as gêmeas apaga linha. O D368 espera a rodada da 134, respondida em 11/10/2026 com a (b).

Provado depois dos consertos: o filtro do item com o caso novo, 161 casos verdes no MySQL e 161 no PostgreSQL, em série;
os dois portões de esquema 0 contra a cópia, com as duas colunas lidas; e os cinco arquivos de teste dos cupons no front
(119 casos), o `lint` e o `build`.

## 12/10/2026 -- PDV-QUITAR-E-PACOTE

**As respostas 77 a 80 do dono, no PDV** (o D380 corrigido, e de novo parcial na verificação, com duas perguntas novas ao
lote; o D453 parcial, com uma pergunta nova ao lote). Para quem usa:

- **O "Quitar" recusa quando outra venda aberta do cliente já tem os débitos somados** (a 79): "Os débitos deste cliente
  já estão somados na comanda 123. Finalize-os lá." (ou "na venda em andamento 123"), no ERP e no `POST .../debitos` da
  API (409 `debitos_em_outra_venda`). A dívida não é mais cobrada duas vezes por duas vendas abertas do mesmo cliente.
- **A tela nova que leu a comanda antes do "Quitar" do ERP não fecha mais calada** (a 78): com os débitos somados só em
  parte, o finalizar recusa uma vez (409 `debitos_em_parte`) até o corpo trazer o somado que a tela mostrou
  (`debitosEmParteVistos`); a tela relê, mostra o aviso da 54, e o clique seguinte fecha como a 54 decidiu.
- **No finalizar do ERP, nada muda** (a 77, "Como está"): fechar sem o segundo "Quitar" cobra a parte somada e deixa os
  débitos abertos -- agora é a regra, e o caso que a fixa diz isso.
- **No PDV antigo, o "Usar pacote contratado: Não" fica no item** (a 80, a opção (b)): o item lançado assim não herda a
  sessão quando o item que a gastou sai da venda. Coluna nova `vendas_produtos.pacote_recusado`, fora do mapeamento (a
  `ColunaNoBanco`, o D473), no `mysql.sql`, no `schema.sql` e na carga do corte (pelos geradores), na décima migração do
  passo 0 e nas duas cópias locais (às 09:01 no MySQL e às 09:02 no PostgreSQL, com o comando, a hora e a conferência no
  D453; a base do teste do BVIP a recebe na próxima cópia exportada, e até lá o "Não" vale só para o lançamento, sem
  erro). A tela nova grava o valor que faz herdar, como hoje; o que ela deve gravar foi ao lote.

**Provado:** 141 casos do filtro do item verdes no MySQL e no PostgreSQL, em série (12 vermelhos contra o código de
antes, os mesmos nos dois); 807 casos dos 53 arquivos vizinhos verdes nos dois; 18 mutantes do servidor, cada um com o seu
vermelho, o mesmo nos dois bancos, e 4 do front; `vitest` 1687 de 1687, `build` e `lint` com saída 0; o passo 0 ensaiado
num banco descartável montado do dump (as dez, duas vezes, e a estrutura igual à do `mysql.sql`); a `vendas_produtos` das
duas cópias igual à dos bancos da suíte; e os portões do passo 0, do `schema-tool` (de 6 a 7), dos dois de esquema, do
recorte, da cobertura por par (de 335 a 338), do inventário (regerado: uma linha trocada, a do `pagarDebitos`) e dos
débitos com saída 0. Falsificados: o do passo 0 (1 sem a décima em cada laço e com outro tipo no `mysql.sql`), o
`verificar-schema-postgres.php` (1 num banco descartável sem a coluna), o do recorte (1 sem a unidade em cada entrada
nova), e o `schema-tool` e a cobertura, que saíram 1 antes da linha de base nova.

**Não provado:** nada foi aberto num navegador (a recusa da 79 no ERP, o 409 relido na tela nova, o "Não" no PDV); dois
"Quitar" no mesmo instante, em dois caixas, ainda passam juntos pela pergunta da 79 (lido no código); e a produção e o
BVIP, onde nada rodou.

**A verificação** (o mesmo dia): o D380 volta a PARCIAL. A dívida recebida pela tela de Devedores depois do "Quitar" ainda
sai duas vezes -- o mesmo dinheiro da 79, por outra porta, que estava no registro desde 06/10 e não tinha ido ao lote --:
foi ao lote, junto com a corrida de dois "Quitar" no mesmo instante, e dois casos fixam o que roda hoje. Na venda em
andamento, a frase da 79 passou a dizer quem a solta ("quem abriu essa venda a finaliza ou recarrega o PDV"), porque ela só
sai do PDV de quem a abriu, e um caso prova a saída; a de quem nunca volta ao PDV também foi ao lote. Os comentários do
`conferir-schema-tool.sh` e da `ColunaNoBanco`, e o título do D473, passaram a dizer a terceira coluna. Provado: 183 casos
verdes no MySQL e no PostgreSQL, em série (o filtro do item com os três arquivos de Devedores), e quatro mutantes novos,
cada um com o seu vermelho, o mesmo nos dois bancos.

## 12/10/2026 -- SESSAO-E-ROTAS

**As respostas 103 a 107, 122 e 158 do dono** (o D76, o D127, o D315, o D237, o D325 e o D399 corrigidos; o D77 parcial,
com o resto na janela e na produção; o D227 preparado para o corte; uma pergunta nova ao lote). Para quem usa:

- **O cookie de entrada sem senha tem o prazo do navegador** (a 103): o `token_web` só entra até 12 horas depois da última
  entrada da pessoa pela senha ou pelo cookie. Ninguém percebe -- o navegador já o apagava nesse prazo --, e o valor
  copiado de um navegador deixa de entrar para sempre. Sem mudar o banco.
- **O registro de acessos diz como a pessoa entrou** (a 104): "pela senha", "pelo cookie" ou "assumida por Beltrano", na
  lista de acessos da pessoa e na da unidade. A coluna `usuarios_acessos.local`, que ficou vazia desde 13/11/2025, voltou
  a ser gravada (a entidade passou a mapeá-la).
- **O administrador que assume outra pessoa só volta para si** (a 105): o "Autenticar como" de uma terceira pessoa, de
  dentro da personificação -- também o cadeado da lista quando a pessoa assumida é administradora --, é recusado com
  "Volte a ser você antes de assumir outra pessoa.", e a sessão fica como estava.
- **As três rotas antigas do aplicativo recortam pela unidade da sessão** (a 106): o desmarcar e os dois marcar antigos só
  alcançam a agenda da unidade de quem está logado, salvo "Agendas unidades" e o administrador. O aplicativo não muda.
  E o desmarcar recusa a linha do bloqueio (a 158) -- era a última porta que a apagava.
- **O link público avulso da pesquisa saiu** (a 107): `/pesquisadesatisfacao` deixou de existir, e o "copiar link" saiu
  da lista de pesquisas. A pesquisa segue respondida na etapa 5 da adesão.
- **"Vendas caixa" sai da lista** (a 122): a tela nova não a mostra nem a grava, a sessão não a lê (o menu Vendas, vazio,
  que só ela abria, deixou de abrir por ela), e o passo 3 do corte apaga o item e as linhas de quem o tem
  (`scripts/remover-funcionalidade-vendas-caixa.sql`, ensaiado num banco descartável, não aplicado em cópia nenhuma).

**Provado:** 990 casos de 53 arquivos (os novos `RegistroDeAcessosTest`, `RotasAntigasDoAplicativoTest` e
`VendasCaixaForaDaListaTest`, o `D315PersonificacaoTest` com os três casos do caminho do D127 reescritos, e os vizinhos)
verdes no MySQL e no PostgreSQL, em série, 3 pulados por pedirem o histórico da produção; 24 mutantes em processo, cada
um com o seu vermelho, e 3 por HTTP (a volta para qualquer um, o cookie sem prazo e a troca sem registro -- este só ficou
vermelho depois de o caso 22 passar a ler só a linha que o próprio pedido gravou); por HTTP, nos dois bancos de
contrato, o `ensaiar-sessao-do-erp.php` 23 de 23 (os casos novos 22, a personificação, e 23, o prazo do cookie), o
`ensaiar-escritas-do-aplicativo.php` (os cinco pedidos e o SAC, resíduo zero), o `ensaiar-rotas-publicas-do-erp.php`
23 de 23 (o 10 com a capacidade, 404) e o `ensaiar-usuarios-adm-api.php` 19 de 19; o `ensaiar-sql-do-passo-3.php`
com saída 0, falsificado em três cópias (saída 1), e um mutante equivalente escrito no D227; `vitest` 1687 de 1687,
`build` e `lint` com saída 0 (só comentários mudaram no front); e o corredor `conferir-tudo.sh` verde fora do vermelho
conhecido do D50 -- o `schema-tool` de 7 para 6 (a coluna mapeada), a cobertura por par (338, outro conjunto, com o
sha1 conferido), o inventário regerado (as duas ações que saíram e o `logAcesso` do `autenticarComo`), o recorte com
cinco entradas novas (cada uma falsificada arrancando o código) e a busca por tags, que acusou o `LIKE` do prazo do
cookie e passou com o `SUBSTRING`.

**Não provado:** nada foi aberto num navegador -- nem o cadeado nem a lista de acessos (não há administrador com senha no
banco de contrato; o caso 22 cria um só para o ensaio, por HTTP) --; o roteiro do passo 3 não rodou em banco com dados de
produção (só nos inventados do ensaio); e a produção e o BVIP, onde nada rodou.

**Ao lote:** se a entrada assumida conta no "Tot. Acessos" e no "Último acesso" da pessoa (`SESSAO-E-ROTAS`, com a
recomendação de contar só as dela).

**A verificação do mesmo dia** (sete achados, seis distintos -- dois eram o mesmo): o par misto dos marcar antigos -- o
colaborador da minha unidade com o serviço de outra franquia -- passou a ser recusado como na agenda do ERP ("- Informe o
serviço.", e o id que não existe deixou de dar 500; o D237); o nome da pessoa saía cru na lista de acessos da unidade, e
os dois nomes passaram a sair escapados, com o caso que faltava ao escape (o D76); o prazo do cookie ganhou a situação
que prova que a entrada é da própria pessoa (o D77), e o D77 diz o resíduo que é o desenho da resposta 103 -- o valor
copiado e usado a cada menos de 12 horas renova o próprio prazo, como o navegador; a frase do título da pesquisa deixou
de citar a página pública que saiu, e o desenho de Pesquisas ganhou a resposta da P5 (o D399); a pergunta do lote sobre o
"Tot. Acessos" passou a citar também a lista de unidades. E um achado de passagem, registrado sem conserto: o "Meus
dados" grava o nome do POST cru, sem o filtro (o D476). **Provado:** 849 casos de 39 arquivos (o item e os vizinhos de
cada classe mudada) verdes no MySQL e no PostgreSQL, em série, 3 pulados; nove mutantes novos, cada um vermelho nos dois
bancos; as três entradas novas do `conferir-recorte-por-unidade.php` falsificadas arrancando o código (saída 1); o
`ensaiar-escritas-do-aplicativo.php` verde nos dois bancos de contrato, resíduo zero; e os portões que as mudanças tocam
com saída 0. O front não mudou.

## 12/10/2026 -- PDV-E-CADASTRO

Onze débitos com resposta do dono, sem decisão nova: cinco com código e seis só no registro (D212, D416, D32, D41, D460;
D452, D167, D225, D467, D380, D453).

**O que muda para quem usa:**

- **O PDV antigo recusa no servidor como a tela nova** (a resposta 91; o D212): o desconto sem motivo ("Informe o motivo
  do desconto."), maior que o item ("O desconto é maior que o valor do item." -- antes, o item entrava cheio e a tela dizia
  "Adicionado") ou com o motivo maior que a coluna, e o preço fora da tabela por quem não é gerente nem administrador. Quem
  usa a tela não sente: o JavaScript já pedia o motivo, e o valor é travado e vem do catálogo. O motivo do cupom, que o
  próprio PDV escreve, e o desconto do convênio, que o servidor calcula, continuam entrando.
- **A tela antiga de usuários não oferece mais o "Externa (app)"** (a 112; o D416), e o servidor o recusa. Os 13 que o
  têm (todos inativos) ficam com ele, e a ficha deles edita e reativa sem trocar; reativado um deles, a tela nova mostra
  o aviso.
- **O cadastro novo recusa o telefone que já está em 10 cadastros ativos** (a 113; o D32), com a frase do dono, no modal
  do ERP (o da agenda e o do PDV), na página "Novo cliente" e na tela nova -- sem oferecer o dependente. A família de até
  10 continua entrando como dependente.
- **O parabéns não sai no dia de preenchimento** (a 114; o D41): no dia em que os aniversariantes passam do dobro de um
  dia comum (a mediana dos dias do ano; na réplica, 12/12 e 01/01), a rotina não enfileira nada -- nem a quem faz anos
  de verdade nesse dia.
- **A etapa 3 da adesão é de todo país** (a 146; o D460): fora do Brasil, com o endereço digitado, sem a busca pelo CEP e
  sem o estado; o amigo da etapa 6 de uma unidade de fora termina ativo, como no Brasil. Só quem não tem país gravado
  ainda pula a etapa 3, como a 66 deixou.
- **No registro, sem código:** a frase do item de 2 sessões fica (a 82; o D452); nenhuma limpeza da cópia agora (a 84; o
  D167 e o D225, ANOTADOS); o aparelho de teste entra pela mão do dono, no banco do teste, com o passo escrito no
  `docs/ambiente-teste.md` (a 149; o D467, DECIDIDO); os itens da tela nova herdam a sessão (a 159; o D453, PREPARADO: falta
  o passo 0 na produção); e Devedores com a soma pendente, a corrida de dois "Quitar" e a venda em andamento abandonada
  ficam como estão (a 160 e a 161; o D380, DECIDIDO).

**Provado:** 1.308 casos de 83 arquivos (o item -- os novos `D212DescontoEPrecoNoPdvAntigoTest`, 15 casos,
`D32TelefoneEmMuitosCadastrosTest`, 12, e `D41ParabensNoDiaDePreenchimentoTest`, 6; o `D304EtapaSeisDaAdesaoTest` com o caso de
Portugal reescrito e dois novos; o `UsuarioDadosTest` e o `D204EdicaoDeUsuarioNoErpTest` com três novos; o `D316` e o `D450`
lançando pelo preço de tabela, ou pelo gerente -- e os vizinhos de cada classe mudada) verdes no MySQL e no PostgreSQL, em
série, com 4 pulados, nenhum do item; 28 mutantes em processo, um por vez, restaurados e conferidos pelo sha256, cada um com o
mesmo vermelho nos dois bancos, menos um, dito no D460 (o controller sem a regra da etapa seguinte: só a etapa 2 o distingue, e
ela não se despacha); um mutante no front, vermelho em dois arquivos; `vitest` 1689 de 1689, `build` e `lint` com saída 0; o
JavaScript da adesão rodado em Node com um jQuery de mentira (o `.min.js` e o `.js` de agora levam Portugal à etapa 3; o do
HEAD, à 4); e os portões que o item toca com saída 0 -- o `conferir-cobertura-por-par.php` com os dois pares novos (de 338 a
340, o sha1 conferido tirando-os, e o "sem menção" de 147 a 146), o `auditar-group-by.php` com a marca conferida na contagem do
dia (a contagem do telefone saiu do `QueryBuilder` para o DQL em texto, que o auditor lê), o `conferir-debitos-repetidos.php`
sem o par D41|D300 (que caiu do limiar com o título novo), o recorte por unidade, a injeção de DQL, os booleanos, os caminhos
no PostgreSQL, os segredos, as referências da documentação, a lacuna do CI e o resto do corredor de código. O `contar-debitos.php`
conta 475 débitos (o D477 nasce aberto; 319 não estão mais de pé).

**Não provado:** nada foi aberto num navegador -- o aviso do PDV, o "Visível agenda", o cadastro com a frase do teto e a
página da adesão de fora do Brasil (o JavaScript dela rodou só em Node, com um jQuery de mentira); a etapa 2 da adesão não
foi despachada (o DNS); e a rotina do parabéns não rodou nem enfileirou nada -- a conta do dia foi provada em processo.
Nada rodou na produção nem no BVIP.

**Ao lote (quatro perguntas, PDV-E-CADASTRO):** o cliente sem país passa pela etapa 3? (recomendo como está); o que a
etapa 3 de fora do Brasil exige (recomendo como fiz); o desconto maior que o item depois do convênio ou na sessão -- o
**D477**, registrado (recomendo recusar pelo valor que sobrou); e o corte do "dobro de um dia comum" numa base pequena
(recomendo um piso, quando houver o segundo dono).

**A verificação (12/10/2026), nove achados, todos conferidos no código:**

- **O PDV antigo apara o motivo do desconto antes de tudo**, como a API (o D212): 100 letras entre dois espaços passavam
  na regra (que mede sem eles) e eram 102 caracteres na coluna de 100 -- o `flush` caía nos dois bancos. Na tela não
  acontecia (o campo vai até 50).
- **Casos que faltavam no D212:** a venda concluída e a cancelada recebem a recusa da situação, e não a do desconto; sem
  `venda_id`, o recusado não abre a venda em andamento; a venda sem cliente, recusada, não ganha o cliente; e o desconto
  é comparado com o valor vezes a quantidade (nenhum caso lançava mais de um).
- **Casos que faltavam no D32, pela API:** um dos 10 altera sem trocar o número (sem o `$atual === null`, a tela nova
  deixaria de editar quem já está num número de balcão), e o "SEM CADASTRO" não conta. E o dependente pode trocar o
  telefone para o número em 10, como a reativação do inativo que o tem: a 113 é do cadastro novo, e o comentário do teto
  dizia o contrário (e dava à cópia os números da produção) -- corrigido, caracterizado sem decidir, e ao lote.
- **O D460 volta a PARCIAL:** as duas perguntas dele estão no lote. E **a etapa 2 da adesão se despacha**: um domínio de
  e-mail só, que não existe, "existe" no processo do PHPUnit (`tests/Caracterizacao/DnsDoEmailDeTeste.php`), sem sair da
  máquina; a linha do controller que dá a etapa seguinte, verde trocada por `etapa + 1`, agora tem caso.

**Provado:** os 190 casos do filtro do item (os 181 de antes e nove novos: cinco no `D212DescontoEPrecoNoPdvAntigoTest`,
três no `D32TelefoneEmMuitosCadastrosTest`, um no `D304EtapaSeisDaAdesaoTest`) verdes no MySQL e no PostgreSQL, em série,
e com eles os vizinhos que passam pela validação do e-mail e pelo PDV (993 casos, um pulado, o declarado do
`VendasNoRelatorioTest`, que pede a cópia da produção); o `conferir-tudo.sh` sem falha inesperada (64 portões, e o
vermelho conhecido do agendamento); nove mutantes novos (`medir-mutantes-pdv-cadastro-verif.py`, no rascunho), um por vez, restaurados e conferidos pelo
sha256, cada um vermelho no caso novo, com o mesmo vermelho nos dois bancos. O front não mudou. **Ao lote, uma pergunta
nova** (PDV-E-CADASTRO, a verificação, o D32): o dependente que troca o telefone para o número em 10 (recomendo o teto
quando o telefone muda na alteração).

## 12/10/2026 -- WHATSAPP-F0-E-CENTRAL

A F0 do desenho do WhatsApp (`docs/f3-telas-restantes/tela-whatsapp.md`) e as respostas 115, 116, 117 e 147 do dono: sete
débitos (D305, D350, D360, D302, D300, D301 e D59), sem decisão nova; uma pergunta nova ao lote e um débito registrado
(o D478).

**O que muda para quem usa:**

- **Abrir a tela antiga do WhatsApp (Configurações › WhatsApp) não mexe mais na instância** (o D305): ela só consulta o
  estado. O código QR sai do botão **"Gerar código"** (e "Gerar outro código", quando ele vence), por POST, um pedido por
  vez por unidade, e com o WhatsApp já conectado o botão não pede nada ao provedor. A página do código consulta o estado
  em fila (uma consulta por vez, com tempo limite, parada em 3 minutos) e, conectado, volta à tela. O **desconectar diz o
  resultado** ("WhatsApp desconectado…" ou "não foi desconectado…"), e a consulta de 5 s não devolve mais o corpo do
  provedor. **A tela decide pelo banco**: desligar o WhatsApp da unidade em Unidades tira a tela de quem já entrou; e a
  unidade com o WhatsApp ligado e **sem chave** mostra "A chave do WhatsApp desta unidade não está cadastrada", sem falar
  com o provedor.
- **O código de resgate e o "resgate realizado" da fidelidade leem o WhatsApp e a chave da unidade no banco**, e não a cópia
  do login (o D350, parcial); sem chave, o WhatsApp não conta como canal, e a tela nova diz que nada foi enviado.
- **O WhatsApp que falha não se perde calado** (a 115 (b); o D302): a campanha não conta quem não recebeu, e a lista antiga
  das campanhas (Marketing › Notificações) ganhou a coluna **"Não receberam"**; a fila guarda a mensagem que falhou,
  marcada como `falhou:<a rotina>`, sem a chave, e não a tenta de novo.
- **O link da campanha de WhatsApp vai no fim da mensagem** (a 116 (a); o D300), nas que ainda não saíram.
- **A recusa da rotina das campanhas fala só do horário** (a 117 (a); o D301): o domingo continua saindo.
- **O código de entrada do aplicativo novo (o login V4) sai pelo WhatsApp central da franqueadora** (a 147; o D59), e não
  pelo da unidade: o cliente das 6 unidades sem WhatsApp passa a receber. **Qual número e qual credencial são do dono:** o
  `whatsapp.central` do `local.php` vem vazio no modelo, com o passo; vazio, ninguém recebe o código (o aplicativo
  instalado não usa o login V4). A chave não vai para a fila. No servidor de teste, o passo da 149 passou a pôr um central
  de mentira no `local.php` do teste (`docs/ambiente-teste.md`, 9.1).

**Feito:** `WhatsAppController` (a ação `codigo`, a trava, o banco a cada pedido, a credencial vazia) e a tela dele;
`ClienteService::unidadeQueMandaWhatsapp`; `CronJobService` (o resultado do envio na campanha e na fila, o link, a janela
em `foraDoHorarioDasCampanhas`, a linha do central e `usarFabricaDeWhatsApp` para o teste); `NotificacaoService` (quem não
recebeu); `PushApp::marcarFalha`; `ISBase\Util\WhatsAppCentral`, o `ClienteAutenticacaoService` e a fábrica dele; o
`local.php.dist` e o `verificar-local-php.php` com o central; a fixture 90001170 (o D360, a unidade ligada e sem chave) e a
marca das fixtures; o `conferir-remocao-por-post.php` com o `codigo`; o inventário das ações regerado (359). Os rótulos
W3, W4, W5, W6 e W8 do desenho ficam com o número do D305.

**Provado:** os 457 casos do filtro do item -- os dois arquivos novos, `WhatsAppTelaAntigaTest` (10) e
`WhatsAppQueFalhaTest` (6), e os vizinhos que tocam a fidelidade, o login V4, a fila, as campanhas, a sessão e os botões
por POST (`FidelidadeDaTelaNovaTest`, `AutenticacaoDoClienteTest`, `RenovacaoDaSessaoTest`, `AparelhosDoClienteTest`,
`PushNasRotinasTest`, `CampanhaCanceladaNaRodadaTest`, `CampanhaAudienciaTest`, `SegredosForaDoCodigoTest`,
`EnvioDePushTest`, `SessaoApiTest`, `UnidadeApiTest`, `EstadoPorPostNoMarketingTest`, `BotoesDoErpSoDaMesmaOrigemTest`,
`BarreiraDeOrigemNaEntradaTest`, `BilheteDoSorteioTest` e o `D14RaizDoModuloDeVendasTest`) -- verdes no MySQL e no
PostgreSQL, em série. Tudo em processo, com um WhatsApp de mentira: **nada falou com o provedor**, e nenhuma rotina de
envio rodou. Os 32 mutantes (`medir-mutantes-whatsapp.py`, no rascunho), um de cada vez, restaurados e conferidos pelo
sha256, todos vermelhos:

| mutante | caso vermelho |
|---|---|
| o `connect` de volta no GET da tela | `WhatsAppTelaAntigaTest::testAbrirATelaSoConsultaOEstado` (e mais dois) |
| sem a guarda de POST do código | `testOCodigoSoSaiPorPost` |
| o `whatsapp_ativo` lido da sessão | `testASessaoDizLigadoEOBancoDesligadoTudoRecusa`, `testOBancoLigadoComASessaoDesligadaAbreATela` |
| o `dados` do provedor de volta na consulta | `testAConsultaDeEstadoNuncaLevaOCorpoDoProvedor` |
| a consulta sem o `whatsapp_ativo` do banco | `testASessaoDizLigadoEOBancoDesligadoTudoRecusa` |
| sem o filtro da credencial vazia | `testSemCredencialNadaFalaComOProvedor` |
| o resultado do desconectar descartado | `testODesconectarDizOResultado` |
| o `connect` com a instância conectada | `testComAInstanciaConectadaOCodigoNaoPedeOConnect` |
| sem a trava por unidade | `testDoisPedidosDeCodigoAoMesmoTempoNaoReiniciamDuasVezes` |
| a consulta sem tempo limite; conectado recarregando o POST; o `setInterval` de volta | `testATelaDoCodigoConsultaEmFilaEParaSozinha` (os três) |
| o código de resgate decidindo pela sessão; com a chave da sessão | `FidelidadeDaTelaNovaTest` (três casos; um caso) |
| o "resgate realizado" decidindo pela sessão; com a chave da sessão | `testOResgateRealizadoLeOWhatsappEAChaveDoBanco` (os dois) |
| ligado sem chave contando como canal | `testOWhatsappLigadoSemChaveNaoContaComoCanal` |
| a fila apagando a que falhou; a linha marcada com a chave | `WhatsAppQueFalhaTest::testAMensagemQueFalhaFicaNaFilaMarcadaESemAChave` (e o do central) |
| a campanha contando a falha; o `$enviar` sempre "recebeu"; quem não recebeu ficando na lista; a coluna errada | `testACampanhaNaoContaQuemNaoRecebeu` (os quatro) |
| o link de volta ao `empty` | `testOLinkDaCampanhaDeWhatsAppVaiNoFim` |
| a frase de volta a "segunda a sábado"; o domingo fora da janela | `testODomingoContinuaEAFraseFalaSoDoHorario` (os dois) |
| a linha do central saindo com a credencial da linha | `testALinhaDoCentralSaiComACredencialDaConfiguracao`, `testSemOCentralALinhaDeleFalhaEFica` |
| o login sem central criando sessão; o central só de espaços valendo; o de provedor desconhecido valendo | `AutenticacaoDoClienteTest::testSemOWhatsAppCentralNaoHaSessaoNemMensagem` (os três) |
| o código de entrada levando a chave do central na fila | `testOCodigoVaiParaAFilaPeloWhatsAppCentralSemAChave` |
| a unidade sem WhatsApp barrando o código de novo | `testOClienteDeUnidadeSemWhatsAppTambemRecebeOCodigo` |

O `conferir-remocao-por-post.php` sai 0, e 1 com a guarda do código arrancada, com o formulário dele em GET e com um link
para ele (três mutantes de portão, `medir-mutantes-portao-post.py`).

**Portões:** o `conferir-tudo.sh`, com 63 portões ok e o vermelho conhecido do agendamento (o D50), e um vermelho que era
meu: o `conferir-rastro-das-rotinas.php` deu a fila (`marketingPush`) como rotina com registro, porque a rodada com falhas
escrevia uma linha no registro das rotinas -- um registro só da falha não é o rastro que o D68 pede, e a linha saiu (o
registro da falha é a própria linha da fila); o portão voltou a 0, e os 457 casos e os mutantes da fila rodaram de novo
depois disso, nos dois bancos. Rodados à parte, depois: as referências da documentação, os débitos repetidos (o
D13|D59 e o D300|D305 saíram, como o portão pediu; o D165|D318, que estava colado na linha do D300|D305, saiu junto e
voltou -- a verificação, abaixo, desfez o texto que o dava como par novo), o tempo limite de rede, a resposta das rotinas, os segredos, o inventário das
ações (regerado: o `codigo` entrou, 359) e a marca das fixtures. O `contar-debitos.php` conta 476 débitos (o D478 nasce
aberto; 322 não estão mais de pé). O front não mudou.

**Não provado:** nada foi aberto num navegador -- a tela antiga do WhatsApp foi renderizada pelo `ViewRenderer` em
processo, e a lista das campanhas também; o QR, a consulta da página e o conectado de verdade pedem o provedor, que está no
loopback (o D360), e o provedor de mentira é a F5. Nenhuma mensagem foi enviada: `marketingEnvios` e `marketingPush` não
rodaram fora do processo do teste. O WhatsApp central não existe ainda (é do dono). Nada rodou na produção nem no BVIP.

**Ao lote (uma pergunta, WHATSAPP-F0-E-CENTRAL):** quanto tempo a mensagem de WhatsApp que falhou fica na fila -- o
**D478**, registrado (recomendo apagar depois de 30 dias). **Fora deste item:** a tela nova do WhatsApp (a 136a) e as
fatias F1 a F5, numa série própria de rodadas.

**A verificação (12/10/2026): catorze achados, doze distintos (dois eram o mesmo), todos conferidos no código e de pé,
e todos tratados -- o das campanhas de antes do conserto até onde o código alcança, com o resto ao lote (o D479).**

- **A coluna "Não receberam" contava errado** (o D302). Na campanha `enviando` ela incluía quem ainda nem foi tentado
  (numa de 500 contatos, depois de 37 envios, "463 não receberam"), e nas de antes do conserto dizia "0" justamente
  onde o código antigo contava a falha como envio (as 526 de WhatsApp enviadas da cópia). A conta saiu da tela para a
  regra (`RegraDaCampanha::naoReceberam`): só a campanha de WhatsApp enviada a partir de 12/10/2026 mostra o número;
  a que está saindo, a de antes e a de push (que conta a falha como envio até hoje) mostram o travessão. O que a data
  não resolve -- as campanhas que a produção mandar pelo código antigo até o deploy -- é o **D479**, registrado, e foi
  ao lote.
- **O texto do provedor chegava à tela antiga do WhatsApp** (o D305): a linha "Mensagem API" imprimia o `mensagem` da
  consulta e do código, o `message` cru do provedor na falha. Agora são frases nossas.
- **O provedor do central em maiúsculas** (o D59): o "WAHA" do formulário de Unidades, escrito no `local.php`, desligava
  o login V4 em silêncio, com o verificador dizendo `ok`. `WhatsAppCentral` tira a caixa e os espaços, e o
  `verificar-local-php.php` ganhou a linha `whatsapp.central.provedor`, GRAVE para o provedor que nenhum cliente conhece
  (pela mesma classe).
- **A fiação do D301:** a rotina das campanhas (`marketingEnvios`) não tinha caso -- trocar a frase dela pela antiga, ou
  apagar a janela, ficava verde. A janela passou para antes da trava, o instante é do teste quando ele o dá
  (`usarAgoraDasCampanhas`), e o caso novo chama a rotina fora do horário **com a trava dela tomada**: sem a janela, ela
  para na trava, e nada chega às campanhas, nem sob o mutante.
- **O W8 era provado pelo texto:** o caso lia o que o script da tela declara, e não o que ele faz. O front ganhou
  `frontend/src/telas/whatsappDaTelaAntiga.teste.ts`, que roda o script do `instancia.phtml` com um jQuery, um relógio e
  um `setTimeout` de mentira (a primeira consulta aos 5 s, a próxima só depois da resposta -- pelo sucesso e pelo erro
  --, o conectado indo ao GET da tela, e a parada aos 3 minutos). Nenhuma tela do front mudou.
- **Checagens sem caso que as isolasse:** a chave em `WhatsAppController::temCredencial` (o caso da instância só de
  espaços despachava só o GET; agora cada metade sozinha, nas quatro ações), a instância em
  `ClienteService::unidadeQueMandaWhatsapp` (o D350), e cada metade de `WhatsAppCentral::daConfiguracao`.
- **Texto:** o motivo do par D165|D318 no `conferir-debitos-repetidos.php` voltou ao do HEAD -- ele não era par novo nem
  passou do limiar com o D478; estava colado na linha do D300|D305 e saiu junto com ela --, e o parágrafo acima foi
  corrigido; o D467 ganhou a nota datada (o login já não depende do WhatsApp da unidade, e a linha que falha fica na fila
  como `falhou:geral`); o D305 deixou de dizer mais do que os casos provavam.
- **Ao lote** foi a pendência do central, que estava só no D59 e no `local.php.dist`: qual número e qual credencial.

**Provado:** os 679 casos do filtro do item e dos vizinhos (os da fidelidade, do login V4, da fila, das campanhas, da
sessão, dos botões por POST e das rotinas que passam pelo `CronJobService`) verdes no MySQL e no PostgreSQL, em série,
com um pulado -- o declarado do `CronTravaSimultaneaTest`, que pede a cópia da produção. Os casos novos: no
`WhatsAppTelaAntigaTest`, `testATelaNuncaMostraOTextoDoProvedor` (e o `testSemCredencialNadaFalaComOProvedor` com cada
metade nas quatro ações); no `WhatsAppQueFalhaTest`, `testONaoReceberamSoContaAWhatsAppEnviadaDepoisDoConserto`,
`testARotinaDasCampanhasRecusaForaDoHorarioComAFraseNova` e `testOCentralComOProvedorEmMaiusculasSaiPeloWaha`; no
`AutenticacaoDoClienteTest`, `testOProvedorDoCentralEmMaiusculasValeComoOMinusculo` e os dois casos de metade; no
`SegredosForaDoCodigoTest`, `testOVerificadorRecusaOProvedorDoCentralQueNinguemConhece`; no `FidelidadeDaTelaNovaTest`,
a instância em branco e nula; e os quatro do vitest. O front inteiro (`npx vitest run --maxWorkers=4`: 46 arquivos, 1.693
casos), o build e o lint, verdes (o lint sem aviso no arquivo novo). Dezoito mutantes novos (`medir-mutantes-verificacao-whatsapp.py`, no rascunho), um de cada vez, restaurados e
conferidos pelo sha256, todos vermelhos:

| mutante | caso vermelho |
|---|---|
| o provedor do central com caixa | `testOProvedorDoCentralEmMaiusculasValeComoOMinusculo`, `testOCentralComOProvedorEmMaiusculasSaiPeloWaha`, `testOVerificadorRecusaOProvedorDoCentralQueNinguemConhece` |
| o verificador sem recusar o provedor | `testOVerificadorRecusaOProvedorDoCentralQueNinguemConhece` |
| o central sem conferir a instância; sem conferir a chave | `testSemOWhatsAppCentralNaoHaSessaoNemMensagem` (os dois) |
| a tela antiga sem conferir a chave; sem conferir a instância | `testSemCredencialNadaFalaComOProvedor` (os dois) |
| a fidelidade sem conferir a instância | `testOWhatsappLigadoSemChaveNaoContaComoCanal` |
| a tela com o texto do provedor na consulta; no código | `testATelaNuncaMostraOTextoDoProvedor` (os dois) |
| a rotina das campanhas com a frase de antes; sem a janela | `testARotinaDasCampanhasRecusaForaDoHorarioComAFraseNova` (os dois) |
| "não receberam" no push; na campanha saindo; nas de antes; a lista com a conta de antes | `testONaoReceberamSoContaAWhatsAppEnviadaDepoisDoConserto` (os quatro) |
| o script sem a parada dos 3 minutos; o sucesso sem marcar a próxima; o erro sem marcar a próxima | `whatsappDaTelaAntiga.teste.ts` (os três) |

**Portões**, rodados à parte, todos com saída 0: os débitos repetidos (nenhum par novo), o `contar-debitos.php` (477
débitos, com o D479 aberto; 322 não estão mais de pé), as referências da documentação, a remoção por POST, o tempo
limite de rede, a resposta e o rastro das rotinas, o relógio nos testes, a cobertura por par, os escritores do horário,
a guarda do ERP, a autorização privilegiada, os provedores no loopback, os bytes de controle, os segredos e o inventário
das ações (359, sem ação nova). O `conferir-tudo.sh` inteiro não foi rodado de novo.

**Não provado:** nada foi aberto num navegador; a tela antiga e a lista das campanhas foram renderizadas em processo, e
o script da tela rodou com um jQuery de mentira. A `marketingEnvios` foi chamada só fora do horário e com a trava
tomada pelo caso -- ela não chega às campanhas --; nenhuma mensagem saiu. Fica de fora, sem medir, o `message` em lista
das validações do WAHA (o `WahaApi::retornoErro` tipa a mensagem como texto, e lançaria antes da tela).

**Ao lote, duas perguntas novas** (WHATSAPP-F0-E-CENTRAL, a verificação): qual número e qual credencial do WhatsApp
central (recomendo um número novo da franqueadora, só para os códigos); e o **D479**, a coluna "Não receberam" nas
campanhas que a produção mandar até o deploy (recomendo uma coluna nova só para as falhas, esquema que só acrescenta).

## 13/10/2026 -- CONTAS-A-PAGAR

As respostas 139 a 143 do dono sobre Contas a Pagar, com a 18 (o autor): sete débitos (D135, D209 e D208 corrigidos; D205
e D207 preparados -- falta só a produção, no passo 0 do corte; D375 e D199 parciais), oito colunas que só acrescentam,
levadas às duas cópias locais pela resposta 83, um débito registrado (o D480) e três perguntas novas ao lote. A tela nova
de Contas a Pagar ainda não existe (a 7ª da ordem): mudou a tela antiga e a API.

**O que muda para quem usa:**

- **O total da despesa soma o que a tela mostra** (o D135, a 143): a parcela excluída sai do total. As 144 despesas da
  cópia passam a mostrar R$ 864.595,41 a menos no previsto -- dinheiro que nunca saiu.
- **O "Quitar" sem pagamento pede o motivo, e o guarda** (o D205, a 139 (b)): o modal de pagar ganhou o campo "Motivo,
  para quitar sem pagamento", que só o botão vermelho lê. A parcela já quitada não se quita de novo. **O estorno não
  desfaz a quitação com motivo**; a quitada pelo pagamento reabre, como sempre.
- **O pagamento de R$ 0 é recusado na tela antiga**, como a API já fazia (o D208, a 139): a frase manda usar o "Quitar",
  com o motivo.
- **O estorno guarda o motivo e quem estornou** (o D207, a 140 (a), com a 18): a tela antiga já pedia o motivo e o jogava
  fora; a API passou a pedir `{"motivo": "..."}` (422 sem ele). **Excluir a parcela, o lote e a despesa guarda quem
  excluiu** (o D199, a 18). O autor é quem entrou na sessão, e a pessoa assumida vai à parte, só quando difere.
- **A despesa da sangria e a retirada do caixa andam juntas** (o D375, a 141 (c)): estornar em Contas a Pagar a despesa
  que veio de uma retirada cancela a retirada, e o valor volta ao caixa; **com o caixa já fechado, o estorno é recusado**,
  com a frase que diz por quê. Cancelar a retirada no caixa **não apaga mais** a conta a pagar: estorna o pagamento dela,
  com o motivo "Retirada do caixa cancelada" e quem cancelou, e a exclui, deixando o rastro.
- **O lote mensal vence no mesmo dia da primeira, limitado ao último do mês** (o D209, a 142): 31/01, 28/02, 31/03,
  30/04. Os lotes já lançados não mudam.

**O esquema:** `scripts/migracao-saidas-motivos-e-autores.sql`, a décima primeira do laço do passo 0 do corte e do laço da
cópia do ensaio geral -- `saidas.excluida_por` e `excluida_em_nome_de`; `saidas_parcelas.quitacao_motivo`, `excluida_por` e
`excluida_em_nome_de`; `saidas_parcelas_pagamentos.estorno_motivo`, `estornado_por` e `estornado_em_nome_de` --, no
`mysql.sql`, no `schema.sql` e no `migrar-dados.sh` pelos geradores. Fora do mapeamento, no molde da `ColunaNoBanco` (o
D473): a base do teste do BVIP só as ganha na próxima cópia exportada, e sem elas o ERP segue sem guardar o motivo nem o
autor, sem erro. Nas duas cópias locais desde 01:05 (MySQL) e 01:06 (PostgreSQL) de 13/10/2026, com o comando, a hora,
o binlog e a conferência no D207; o passo 0 ensaiado num banco descartável do dump de produção, as onze duas vezes, e a
estrutura igual à do `mysql.sql` de hoje.

**Provado:** 358 casos do filtro do item e dos vizinhos (Contas a Pagar, o caixa, as colunas fora do mapeamento), verdes no
MySQL e no PostgreSQL, em série. Os casos novos: `ContasAPagarRespostasTest` (24), `D375SangriaAmarradaTest` (21),
`ContasAPagarSemAsColunasTest` (4, o banco sem as colunas fingido pela resposta guardada da `ColunaNoBanco`), o controle
de um centavo no `SaidaFinanceiroTest`, e `contasAPagarDaTelaAntiga.teste.ts` (5, no vitest, o `.min.js` servido e o
`.js`). O front inteiro (`npx vitest run --maxWorkers=4`: 47 arquivos, 1.698 casos), o build e o lint, verdes.
Quarenta e um mutantes, um de cada vez, restaurados e conferidos pelo sha256, todos vermelhos (37 no MySQL, dez deles
também no PostgreSQL -- dois destes, os guardas do autor da parcela e da despesa, refeitos limpos pela verificação, abaixo
--, e 4 no front); a primeira rodada deixou um vivo -- a amarra que devolvia ao caixa a retirada já cancelada --, e o caso
que o mata entrou:

| mutante | caso vermelho |
|---|---|
| quitar sem exigir o motivo; sem gravá-lo; a já quitada aceita | `testQuitarSemPagamentoSemMotivoQueSirvaERecusado`, `testQuitarSemPagamentoGuardaOMotivo`, `testQuitarSemPagamentoRecusaAParcelaJaQuitada` |
| o estorno desfazendo a quitação com motivo | `testOEstornoNaoDesfazAQuitacaoComMotivo` |
| o R$ 0 aceito | `testPagamentoDeZeroERecusado`, `testATelaAntigaRecusaOPagamentoDeZero` |
| a tela antiga sem passar o motivo; cada modal sem o campo | `testATelaAntigaPedeOMotivoParaQuitarSemPagamento`, `testOModalDePagarTemOCampoDoMotivo` |
| o estorno sem exigir o motivo; sem gravar o motivo e o autor; a API sem pedi-lo | `testOEstornoSemMotivoERecusado`, `testOEstornoGuardaOMotivoEOAutor`, `testAApiPedeEGuardaOMotivoDoEstorno` |
| o autor pelo `usuarioId`; sem conferir as chaves | `testNaPersonificacaoOAutorDoEstornoEQuemEntrou`, `testOAutorSemAsDuasChavesFalhaAlto` |
| a exclusão da parcela, das parcelas da despesa e da despesa sem o autor; a tela antiga e a API com o autor fixo | `testAsExclusoesPeloServicoGuardamOAutor`, `testAsExclusoesPelaTelaAntigaGuardamOAutor`, `testAsExclusoesPelaApiGuardamOAutor` |
| o mensal encadeado; o mês depois sem o limite | `testOLoteMensalDoDia31FicaNoUltimoDiaDeCadaMes`, `testARegraDoMesDepoisEPublicaEDiz` |
| o previsto e o pago contando as excluídas | `testOTotalDaDespesaNaoContaAExcluida`, `testATelaDasParcelasMostraOTotalDasLinhas`, `testOPagoDaExcluidaNaoEntraNoTotal` |
| os cinco guardas das colunas arrancados | os quatro casos do `ContasAPagarSemAsColunasTest` |
| o estorno sem a amarra; sem devolver ao caixa; devolvendo a já cancelada | `testOEstornoNoFinanceiroCancelaARetiradaDoCaixaAberto`, `testComOCaixaFechadoOEstornoERecusadoENadaMuda`, `testQuemEsperouOCaixaReleARetiradaEDevolveUmaVezSo` |
| o cancelamento no caixa apagando, sem excluir, sem estornar | `testCancelarARetiradaEstornaEExcluiADespesaSemApagar` e os vizinhos |
| o caixa travado antes da despesa; a retirada lida sem trava | `testAsDuasPortasTravamADespesaAntesDoCaixa`, `SaidaSobTravaTest::testAEscritaTravaADespesaAntesDeLer` |
| as duas portas sem a frase do caixa fechado | `testATelaAntigaDizOQueAconteceuComARetirada`, `testAApiRecusaComOCaixaFechadoECancelaComEleAberto` |
| o `.min.js` sem mandar o motivo, sem limpá-lo; o `.js` sem mandá-lo; o cache de volta a 165 | os casos de `contasAPagarDaTelaAntiga.teste.ts` |

**Achado no caminho, e consertado:** `SaidaSobTravaTest` exclui a despesa de verdade, fora da transação do caso, e o
`finally` dele não devolvia o autor novo -- ele ficava no banco da suíte para os casos seguintes (medido no PostgreSQL,
num mutante). Agora devolve.

**Portões**, rodados à parte, todos com saída 0: o passo 0 do corte (com três falsificações em cópias, cada uma saída 1),
o `schema-tool` (14, a linha de base nova), o `verificar-schema-postgres.php` e o `conferir-tipos-do-mapeamento.php`
contra as duas cópias (onze colunas lidas fora do mapeamento, nenhuma ausente), o recorte por unidade, a remoção por POST,
a API nova, a injeção de DQL, os booleanos em consulta e em coluna inteira, a cobertura por par, a guarda do caixa e a do
ERP, o relógio nos testes e no banco, as referências da documentação, os scripts servidos, a barreira de origem, as rotas
sem login, o tempo limite de rede, os débitos repetidos (um par novo, D375|D480, assuntos distintos, registrado) e o
`contar-debitos.php` (478 débitos; 327 não estão mais de pé). O inventário das ações foi regerado (duas linhas). O
`conferir-tudo.sh` inteiro não foi rodado.

**Não provado:** nada foi aberto num navegador; as telas antigas foram renderizadas e despachadas em processo, e o JS
rodou num jQuery de mentira. As duas portas ao mesmo tempo, na mesma sangria, não foram medidas -- o que se mediu é a
ordem das travas de cada uma.

**Ao lote, três perguntas novas** (CONTAS-A-PAGAR): o estorno da despesa da sangria cujo caixa já fechou (recomendo
continuar recusando); se o estorno no financeiro que cancelou a retirada exclui a despesa junto, como o cancelamento pelo
caixa (recomendo que sim; fica aberta até a resposta); e o **D480**, a retirada de R$ 0 com "financeiro" (recomendo
recusá-la).

## 13/10/2026 -- CONTAS-A-PAGAR, a verificação

A revisão do item achou nove pontos, todos conferidos no código e na cópia; sete consertados, e dois foram ao lote como
pergunta. O D375 continua PARCIAL; nenhum título mudou.

**O que muda para quem usa:**

- **A despesa da sangria com mais de um pagamento ativo recusa o estorno e o cancelamento no caixa** (o D375). A amarra
  da 141 é pela despesa, e a despesa aceita parcela lançada à mão: com o pagamento da sangria e um de fora, estornar o de
  fora devolvia o valor da retirada ao caixa com o da sangria ainda pago, e cancelar no caixa estornava os dois. Qual é o
  dinheiro da gaveta o dono não disse; até a resposta, as quatro portas (Contas a Pagar e o caixa, na tela antiga e na
  API) recusam, com a frase que diz por quê. Na cópia não há nenhuma.
- **O estorno repetido de um pagamento já estornado diz que já está estornado**, e não "o pagamento não foi estornado",
  na sangria de caixa fechado; e a frase do caixa fechado fala da despesa ("Esta despesa veio de uma retirada do caixa"),
  verdadeira também do pagamento lançado à mão depois de um estorno antigo (a retirada 11906 da cópia).

**Consertado no caminho:** a frase do outro pagamento lia a situação com `fetchFirstColumn`, e no PostgreSQL o `false` do
pagamento estornado é o mesmo `false` do fim das linhas -- a lista voltava vazia. Achado por um mutante que ficou vivo
só no PostgreSQL; lê a linha inteira agora.

**Documentação corrigida:** a migração conta as despesas, as parcelas e os pagamentos como o procedimento do corte
(13.238, 15.076 e 13.902, com as fixtures da cópia); "nenhuma das 666 parcelas excluídas recebeu um real" virou o que é
verdade -- 35 receberam 36 pagamentos (R$ 18.727,35), todos estornados, e nenhuma tem dinheiro ativo (o `Saida.php`, o
D135); o guia do servidor de teste não diz mais que as exclusões pedem motivo; e o D375 e o caso de teste não dizem mais
que as 17 retiradas se cancelam no caixa -- os caixas delas estão todos fechados.

**Provado:** 358 casos do filtro do item, verdes no MySQL e no PostgreSQL, em série; o `D375SangriaAmarradaTest` passou
de 13 a 21. Treze mutantes novos, todos vermelhos no PostgreSQL e doze no MySQL (o da leitura por `fetchFirstColumn` só
morde o PostgreSQL, e fica vivo no MySQL, onde a situação vem como número); dois deles são os vivos que a revisão achou (a
retirada já cancelada passando pelo caixa fechado; o caixa conferido depois de estornar e excluir). Os dois guardas do
autor (parcela e despesa) que a primeira rodada do PostgreSQL mediu com o banco sujo foram refeitos limpos: os cinco
guardas das colunas, cada um vermelho só no seu caso. Os 37 mutantes do item, de novo no MySQL contra o código de agora:
os 35 que ainda se aplicam, vermelhos; os dois das frases do caixa fechado nas portas não se aplicam mais (a linha
mudou), e os mutantes novos das quatro portas os cobrem.

**Ao lote, duas perguntas novas** (CONTAS-A-PAGAR, a verificação): qual pagamento da despesa da sangria é o da gaveta
(recomendo recusar parcela nova na despesa da sangria com a retirada ativa); e quem pode cancelar a retirada pelo estorno
em Contas a Pagar -- hoje qualquer pessoa com Contas a Pagar, e o administrador, cancela a retirada do caixa aberto de
outro operador, sem o privilégio nem a posse que o caixa exige (recomendo exigir o que o caixa exige).

## 13/10/2026 -- RESPOSTAS-162-A-167

As respostas 162 a 167 do dono, pelo pedido *"aguarde terminar e continue de forma autonoma a resolver todos os debitos
que  tiver resposta"*. **D76 e D460 corrigidos; D41 e D32 continuam PARCIAIS** (o resto deles não dependia
destas respostas); **o D477 feito no desconto e PARCIAL no motivo** (a verificação, abaixo); **o D481 registrado**; duas
perguntas novas ao lote.

**O que muda para quem usa:**

- **O "Tot. Acessos" e o "Últ. Acesso" dizem quando a pessoa usou o sistema** (o D76, a 162): a entrada assumida por um
  administrador não conta mais, na lista de usuários (o ERP e a tela nova) nem na de unidades (o ERP e a
  `/adm/api/unidades`). A troca continua nas listas de acessos, como "assumida por Beltrano".
- **O desconto maior do que o que sobrou do item zera o item** (o D477, a 165, a opção "b"): com o convênio de 20%, o
  corte de 89,90 fica em 71,92, e um desconto de 80,00 o deixa a zero -- até esta data ele era ignorado calado, e o item
  entrava a 71,92 com a tela dizendo "Adicionado". No PDV antigo e na tela nova, no lançamento e no ajuste. O maior que o
  preço de tabela continua recusado; o item da sessão do pacote continua a zero.
- **A troca do telefone para o número que já está em 10 cadastros é recusada também na alteração** (o D32, a 167), para
  o dependente também, com a frase do dono -- no modal e na página de edição do ERP e na tela nova. Quem já está no número
  e não o troca continua salvando; a reativação fica como está.
- **O parabéns numa base pequena sai todo dia** (o D41, a 166): a regra do dia de preenchimento só vale com o dia comum de
  30 aniversariantes ou mais. Na rede de hoje (perto de 660 por dia na réplica) nada muda.
- **A adesão de quem não tem país e a etapa 3 de fora do Brasil ficam como estão** (o D460, a 163 e a 164): as duas
  confirmam o que roda desde 12/10/2026.

**Provado:** 793 casos do filtro do item, verdes no MySQL e no PostgreSQL, em série (os testes novos:
`D76AcessosDaPropriaPessoaNasListasTest`, 3; `D477DescontoAteOValorDoItemTest`, 8; o `D41`, de 6 a 9; o `D32`, de 15 a
19, um deles caracterizando o D481; o `ConvenioDescontoTest`, o caso do ignorar virado no que zera e um novo); e o front
inteiro (`vitest --maxWorkers=4`, 1.699 casos), com um caso novo na edição de cliente, o lint e o build. Onze mutantes
do servidor, um por vez, todos vermelhos no MySQL e no PostgreSQL (as tabelas estão no corpo de cada débito), e o do
front (`errosDaRecusa` sem o motivo do teto) vermelho nos dois casos do teto, o do cadastro e o da edição. O primeiro
mutante da lista de usuários foi refeito: o vermelho dele era o do Doctrine recusando os parâmetros que sobravam, e não o
da contagem.

**Portões**, rodados à parte, todos com saída 0: o registro de acesso, a busca por tags, a injeção de DQL, o `GROUP BY`
no PostgreSQL, o relógio nos testes e no banco, a linha sorteada, a ordenação (empates e nulos), o recorte por unidade, a
guarda do ERP, a cobertura por par, as rotas sem login, a barreira de origem, a remoção por POST, o privilégio da API, os
segredos, os booleanos em consulta e em coluna inteira, os caminhos no PostgreSQL, os tipos do mapeamento, as referências
da documentação, os links mortos, os bytes de controle, os débitos repetidos (cinco pares novos -- quatro do D481 e o
D452|D477 --, todos de assuntos distintos, registrados) e o `contar-debitos.php` (479 débitos; 329 não estão mais de pé).
O `schema-tool` não foi rodado: nenhuma entidade mudou de mapeamento. O `conferir-tudo.sh` inteiro não foi rodado.

**Ao lote, uma pergunta nova** (RESPOSTAS-162-A-167, o **D481**): a etapa 1 da adesão, a página pública do programa de
fidelidade, troca o telefone do cliente sem conferência nenhuma -- nem o teto dos 10 cadastros; recomendo o teto também
ali, quando o telefone muda.

**A verificação** achou que o *"e o motivo fica"* da 165 não se cumpre com o motivo digitado: no item do convênio o nome
dele continua por cima (o D214; a resposta 90 do dono, "b", de 12/10/2026, manteve assim) e no da sessão o do pacote. O
item zerado por uma cortesia aparece como convênio de 100%, e na tela nova o "+" e o "−" dessa linha, que mandam só a
quantidade, a devolvem ao preço com o convênio sem aviso (143,84 com duas unidades; caracterizado no nono caso do
`D477DescontoAteOValorDoItemTest`, nos dois bancos). Nada decidido: **a segunda pergunta ao lote** (RESPOSTAS-162-A-167,
a verificação, o D477), com a recomendação de o motivo levar os dois só nesse caso e a tela nova recusar o "+" e o "−"
nessa linha. O D477 ficou PARCIAL. Corrigidos também quatro comentários que descreviam o comportamento de antes (o da API
da comanda, o do serviço, e os de dois testes).

**Não provado:** nada foi aberto num navegador.

## 13/10/2026 -- UNIDADES-GRUPOS-ROYALTIES

As respostas 98 e 131 a 135 do dono (de 11/10/2026), pelo pedido *"aguarde terminar e continue de forma autonoma a
resolver todos os debitos que  tiver resposta"*. **D367 corrigido; D124 e D368 preparados** (o que falta deles roda na
janela do corte); **D147 e D366 decididos**; **D44 e D351 continuam PARCIAIS** (o corpo diz o quê); **D482, D483 e D484
registrados**; três perguntas novas ao lote (a terceira e a correção da do D482 vieram da revisão, no fim desta entrada).

**O que muda para quem usa:**

- **Quem é lotado em unidade desativada deixa de entrar**, menos o administrador (o D351, a 132 "b"): pela senha e pelo
  cookie, e a sessão aberta sai no pedido seguinte. Com a senha certa, a entrada diz "A sua unidade está desativada no
  sistema: só o administrador da rede entra. Fale com ele."; com a errada, "Dados inválidos.". Na cópia, 97 pessoas ativas
  não administradoras.
- **O aplicativo não abre a unidade inativa pelo endereço**: o detalhe (`getUnidade`, v1 e v2) e os serviços dela
  respondem como para a que não existe. **As 4 fechadas com o WhatsApp ligado se calam**: parabéns, primeira visita,
  "sentimos sua falta", NPS da venda, campanha e indicação da adesão -- sem cair no push do aplicativo.
- **O relatório de royalties mostra a unidade inativa** que teve movimento no período, ou a pedida pelo id (a lista dela
  passou a oferecê-la, com "(inativa)"), e **a fatura do dia 16 cobra a desativada pelo ciclo que trabalhou** (o D367).
- **Mudar a taxa de royalty vale do ciclo seguinte** -- do próximo dia 16 --, e fica registrado quando e quem mudou (o
  D368, a 134 "b"): o retrato das 05:25 refaz ontem e hoje com a taxa do ciclo de cada dia, e o aumento dado no dia 15 não
  reescreve mais a quinzena já paga. A tela antiga diz isso ao lado das taxas -- onde a tabela da vigência está no banco;
  na base do teste do BVIP, que ainda não a tem, a frase não aparece. A cota-parte segue como estava.
- **O faturamento da unidade continua editável, e a tela avisa** "O boleto continua com os dados de quando a primeira
  fatura saiu." (o D366, a 133 "a"). Nada vai ao Asaas.
- **Quem cadastra colegas sem ser administrador pode pô-los em todo grupo sem administrador e sem funcionalidade de
  rede** (o D124, a 131 "b"): entram o Gerente, o Relatório vendas e o Vendas realizadas, e todo grupo novo entra sozinho.
  A recusa ao gravar diz o mesmo que a lista, inclusive no grupo de administrador.
- **A rotina das faturas de royalties do dia 16 tem a trava** contra duas execuções ao mesmo tempo, e continua emitindo em
  qualquer chamada as que esperam (o D44, a 98 "a"). A fatura cuja resposta do Asaas se perde fica com o aviso, e a chamada
  repetida ainda a emite de novo (o D484, ao lote).
- **Os arquivos da franqueadora continuam abertos a quem tem "Arquivos exibir"** (o D147, a 135 "a"): nada mudou.

**Esquema:** a tabela `royalties_aliquotas` (a 12ª migração do passo 0, `scripts/migracao-royalties-aliquotas.sql`), pela
resposta 83: no `mysql.sql`, no `postgres/schema.sql` do gerador e no roteiro da carga; criada às 08:44 nas duas cópias
locais (vazia, e igual à dos bancos da suíte), e o passo 0 ensaiado num banco descartável montado do dump (as doze, duas
vezes, 24 rodadas com saída 0, a estrutura igual à do `mysql.sql` de hoje). A base do teste do BVIP fica sem ela até a
próxima cópia exportada: ali a taxa nova continua valendo no dia em que muda, sem erro (`ColunaNoBanco`).

**Provado:** 1.118 casos do filtro do item (os novos e os vizinhos), verdes no MySQL e no PostgreSQL, em série -- os testes
novos: `FaturasDeRoyaltiesTravaTest` (3), `GruposQueONaoAdministradorPoeTest` (4), `D351UnidadeDesativadaTest` (13),
`D367RoyaltiesDaUnidadeInativaTest` (6), `D368VigenciaDaTaxaDeRoyaltyTest` (7) e um caso no `D304EtapaSeisDaAdesaoTest`; o
`ColunasForaDoMapeamentoTest` passou a exigir a coluna nova. Nenhum caso fala com o Asaas: a rotina das faturas roda em
processo com o cliente dublado, e o retrato das 05:25, que não fala com ele, roda em processo no banco da suíte (desde a
revisão, num caso do D367, como já rodava no `RoyaltiesTest`).
**29 mutantes, um por vez, todos vermelhos** no MySQL (a tabela está no corpo de cada débito); dois tiveram de ser
refeitos -- os de `GrupoRepository`, que é CRLF, não casavam a âncora --, e um sobreviveu na primeira rodada (a lista do
relatório sem as inativas: o caso olhava o repositório, e não a tela) e ganhou o caso que o mata. O front não mudou:
`vitest --maxWorkers=4`, o build e o lint, inteiros, como conferência.

**Portões**, rodados à parte, um por vez: 49 roteiros php com saída 0 -- os `conferir-*.php` do `conferir-tudo.sh`, o
`auditar-group-by.php` e o `verificar-schema-postgres.php`; o `conferir-cobertura-por-par.php` rebaseado no mesmo item (um par saiu de "sem menção", outro dos falsos positivos, e a
chamada não resolvida nova, lida); o `conferir-grupos-por-id.php` com a linha da lista de ids fora do inventário; o
`conferir-schema-tool.sh` (14, a base), o `conferir-segredos.sh`, o inventário e a lacuna do CI com 0; o `ensaiar-sql-do-
passo-3.php` com 0; os débitos repetidos (oito pares novos, de assuntos distintos, registrados, e um que deixou de
aparecer, tirado) e o `contar-debitos.php` (481 débitos; 333 não estão mais de pé). O `conferir-agendamento.php` segue no
vermelho de antes (o `conveniosAtualizarVencimento`), e o `conferir-papeis.sh` não rodou (pede a senha do papel).

**Ao lote, três perguntas novas** (UNIDADES-GRUPOS-ROYALTIES): se a unidade fechada para também o push do aplicativo (as
24 sem WhatsApp seguem mandando; recomendo que não mande por canal nenhum, o D483); se o aplicativo recusa a agenda e a
marcação pelo colaborador da unidade inativa (recomendo recusar, o D482); e, da revisão, se a fatura cuja resposta do Asaas
se perdeu é emitida de novo (recomendo deixá-la de fora da repetição até a conferência, o D484).

**Não provado:** nada foi aberto num navegador, e nenhum ensaio HTTP rodou; a entrada recusada, a sessão encerrada e o
`PUT` da unidade foram despachados em processo.

**A revisão (no mesmo dia):** a verificação do item achou doze pontos, dois deles o mesmo, e os onze foram tratados:

- **D124:** a recusa ao gravar (`UsuarioService::recusaDoGrupo`) deixava passar o grupo de administrador, que a lista
  tira -- o molde dele não tem linha, e ela só olhava a rede. Agora recusa, com o motivo e a frase próprios, e um caso
  confere, grupo a grupo, que a lista e a recusa dizem o mesmo.
- **D44:** a trava passou a ser medida também na criação das faturas do dia 16 (com retrato no ciclo), os casos passam o dia
  à rotina em vez de usar o de hoje, e a resposta do controller do cron foi medida. A fatura cuja resposta do Asaas se
  perde fica anotada; a reemissão dela é o **D484**, novo, que foi ao lote.
- **D367:** o retrato das 05:25 roda em processo num caso novo, no banco da suíte e sem Asaas (como já rodava no
  `RoyaltiesTest`), com três inativas no dia: a linha do retrato deixou de estar só lida.
- **D368:** um caso com as quatro taxas diferentes (as fixtures têm 5/5/5, e uma troca de colunas passava em tudo), o
  formulário antigo despachado com o POST inteiro (o autor da vigência, pela porta que se usa hoje), o banco fingido sem a
  tabela (cada guarda medida sozinha), e a frase da taxa na tela antiga só onde a tabela está no banco.
- **D482:** o registro e a pergunta apontavam a marcação nas rotas antigas (`agendaSalvarV2`/`V3`), que já exigem a sessão
  do ERP; a porta aberta é o `POST /api/v4/agendamentos`, do contrato novo, e a agenda pelo `getAgenda`. Corrigidos os dois
  textos, com uma opção (c) nova: fechar só a marcação v4.

**Provado:** 1.346 casos do filtro do item e dos vizinhos, verdes no MySQL e no PostgreSQL, em série (11 pulados, todos de
relatórios que pedem a cópia da produção, nenhum do item); **18 mutantes novos, um por vez, todos vermelhos** no MySQL. Os
portões php do `conferir-tudo.sh` com 0, menos o `conferir-agendamento.php` (o vermelho de antes); o
`conferir-cobertura-por-par.php` com o `EstadoRepository::popularCombobox` fora dos falsos positivos (de 25 para 24, o
D177); o `ensaiar-sql-do-passo-3.php` (no host), os segredos, o inventário e a lacuna do CI com 0; os débitos repetidos com
um par novo registrado (D366|D484), e o `contar-debitos.php` com 482 débitos, 333 que não estão mais de pé. O front não
mudou.

## 13/10/2026 -- PARCEIRO-RADAR-E-FLUXO

As respostas 118 e 137 do dono, pelo pedido *"Continue de forma autonoma a resolver os debitos com resposta"*. **D309
e D311 corrigidos; D306 e D307 parciais** (o corpo diz o quê; o D311 ficou parcial enquanto o servidor não rodava, e
voltou a corrigido com a prova do servidor, depois do reinício da máquina -- o fim desta entrada);
**D486 corrigido** (achado no caminho); **D485, D487 e D488 registrados**; três perguntas novas ao lote
(PARCEIRO-RADAR-E-FLUXO). *Na primeira versão desta entrada, o D311 saía corrigido, o D488 não existia e eram duas
perguntas; a revisão do mesmo dia mudou as três coisas, e a prova do servidor, no fim da entrada, devolveu o D311 a
corrigido.*

**O que muda para quem usa:**

- **O painel compara com o ano anterior** (o Radar de Temperatura, a 137 "a"): em "Os números", a escolha "o mês
  anterior | o ano anterior". Com o ano anterior, os números e os clientes de junho de 2025 comparam com junho de 2024 --
  no mês em andamento, os mesmos dias --, e o retorno compara com o mesmo período de lá. É a conta do painel, das vendas,
  com todos que venderam, e não mais a soma do grupo 1 da tabela do Dash.
- **O painel mostra o movimento por dia da semana** (o Fluxo de Movimento): um cartão que abre fechado, com cada dia da
  semana -- quantos tiveram venda, o faturamento por dia com venda e a média de uma unidade da rede no mesmo dia, e a
  diferença. Aberto, acompanha o mês.
- **Saíram do ERP antigo** o atalho "Radar de Temperatura" do topo, os itens Dash. Colaboradores, Radar de Temperatura e
  Fluxo de Movimento do menu e as três telas -- com o extrato e as estrelas do plano de carreira, que só os
  administradores viam --, e as duas rotinas que gravavam as tabelas delas (23:30 e 07:00 do dia 1). As tabelas
  (`dashboard_colaboradores` e `dashboard_movimentos`) ficaram guardadas, sem nenhuma linha apagada. O Profissional
  Parceiro é tela nova só (`/profissional-parceiro`); no menu novo, o item virou botão.
- **No Profissional Parceiro, um pedido da rede de cada vez** (a 118 "a"): quem troca de mês com os clientes da rede ou o
  melhor da rede ainda no servidor espera o primeiro terminar, e só o último pedido sai; um segundo pedido da mesma pessoa
  (outra aba, a API direto) volta na hora com "Já há uma consulta da rede em andamento. Espere ela terminar e tente de
  novo." (o 429, código novo da convenção). Os clientes de uma unidade não esperam. A fila é da aba: sair da tela e
  voltar com um deles no ar também espera. O movimento por dia da semana do painel anda numa fila igual.
- **No corte**, o passo 9 tira as duas rotinas do crontab guardado (23 linhas, das 25), e o ensaio do passo 9 prova.

**Provado:**

- **O front:** 1.711 testes em 47 arquivos, com quatro processos; build com 0; lint com 0 e os 6 avisos de sempre. Doze
  casos novos -- nove no painel (a comparação e a semana) e três no Profissional Parceiro (a fila da rede e o 429) -- e
  três do Profissional Parceiro reescritos para a fila, um deles com nome novo: o melhor da rede de maio agora espera o de
  junho. *(A primeira versão desta frase dizia treze; o diff tem doze.)* **14 mutantes da tela, um por vez, todos
  vermelhos** -- quatro da fila e dez do painel (a fila de cada
  consulta, só a rede na fila, o "ainda quer", a comparação no pedido e no recorte, as frases do retorno, a semana de
  outro mês, o atualizar e a comparação que não a pedem de novo, a escolha parada com pedido no ar, o dia sem venda),
  com o arquivo devolvido byte a byte.
- **Os portões estáticos, no host:** os php do `conferir-tudo.sh` que não pedem banco com 0, menos o
  `conferir-agendamento.php` (o vermelho de antes, o D50) -- o de cobertura por par com a base nova (de 340 para 323
  pares, nenhum novo), o de concatenação em consulta (de 28 para 25), o de referências com as duas ausências explicadas,
  o de recorte com o `semanaAction`; o inventário regerado (de 359 para 351 ações) e conferido; os segredos e a lacuna do
  CI com 0; o `ensaiar-crontab-do-passo-9.sh` com 0, e vermelho com o filtro das rotinas arrancado; os débitos repetidos
  sem par novo, e o `contar-debitos.php` com 485 débitos, 336 que não estão mais de pé.
- **O portão de agendamento acompanha** (o D486): a pergunta "o crontab chama algo que sumiu?" nunca achava nada; agora,
  com o código de hoje e a tabela de antes, acusa as duas rotinas.

**Não provado naquela hora -- o servidor** (provado depois do reinício da máquina, no fim desta entrada): o Docker desta
máquina travou às 14:08 (um `docker compose up -d` de outra sessão ficou
pendurado, e cada `docker exec` e cada checagem de saúde passou a esperar para sempre: 1.400 processos `runc` presos na
VM); o reinício do Docker Desktop não voltou -- a VM do WSL não desliga, e o Docker diz "unable to start" --, e a máquina
precisa ser reiniciada. **Nenhum teste PHP rodou**, em banco nenhum: os casos novos do `ProfissionalParceiroTest` (a
trava), do `PainelDaUnidadeTest` (o ano anterior e a semana, com o oráculo das linhas cruas) e do
`RegrasDoPainelDaUnidadeTest` e `RegrasDoProfissionalParceiroTest`, os casos que saíram ou mudaram, e os mutantes do
servidor ficam para a rodada seguinte, com o Docker de pé. Os arquivos PHP passaram no `php -l` (o 8.2 do host). Nenhum
ensaio HTTP rodou, e nada foi aberto num navegador.

**A revisão (13/10/2026), depois da verificação: doze achados, todos conferidos no código.** O Docker continuava sem
subir (`docker ps`: "Docker Desktop is unable to start"; a VM do WSL de 09/10 segue de pé), e a máquina não foi
reiniciada: o servidor seguiu sem rodar em banco nenhum.

- **O administrador não chega ao painel** (o D487, reescrito): o painel é só do gerente de unidade que não é
  administrador, e o D487 e a pergunta do lote diziam que o administrador via nele a unidade da sessão. Ele perdeu as duas
  opções inteiras, como quem tinha o privilégio sem ser gerente. A pergunta foi reescrita com a premissa certa, e a
  recomendação mudou: o painel abrir ao administrador, como item do menu, com a escolha da unidade e sem a rede.
- **Quatro números do Radar sem par no painel** (o D488, novo): o valor dos serviços e o dos produtos, a ocupação da agenda
  e os agendamentos pelo app contra o ano anterior. Pergunta nova ao lote, com a recomendação de levar os dois valores.
- **A fila da rede vivia em cada instância da tela**, e o Console remonta a tela a cada escolha no menu: sair e voltar com o
  melhor da rede no ar mandava o segundo, e o servidor devolvia o 429. A fila foi para o módulo
  (`frontend/src/telas/filaDaRede.ts`), e o preparo dos testes a esvazia depois de cada caso.
- **A semana do painel lia a rede do mês sem fila**: trocar de mês com o cartão aberto punha várias leituras da rede no
  servidor. Ela anda na mesma fila da aba. O servidor não a trava (a 118 fala do Profissional Parceiro): está escrito no D306.
- **O D311 saía corrigido sem o servidor ter rodado**, e três textos davam o servidor por provado: o D311 virou PARCIAL;
  o comentário do `medir-adm-api-no-postgres.php` e as duas linhas do `docs/f3-relatorios.md` dizem o que rodou. **O D309
  ficou CORRIGIDO**, e o corpo diz por quê: a prova da saída da tela é estática, e a conta do caso do layout rodou no PHP
  do host (verde hoje, vermelha com o layout do `HEAD`).
- **O ensaio do passo 9 não tinha a irmã que fica** (`relatoriosDashboardFaturamentoAtualizar`): entrou no crontab
  inventado, convertida, e o filtro largo (`relatoriosDashboard` inteiro) e o filtro arrancado ficam vermelhos.
- **Abrir a semana com os números de outro mês no ar** ganhou caso (tirar o `|| relendo` fica vermelho).
- **A rota da semana** ganhou o `PainelDaUnidadeTest::testOsCaminhosDoPainelLevamCadaUmASuaAcao` (os três caminhos pelo
  roteador, e o POST que não casa), e o `medir-adm-api-no-postgres.php` pede da semana o faturamento da unidade
  (`PRECISA_SOMAR`), e não só os sete dias, que vêm sempre.
- **O cenário do ano anterior ganhou a compra de crédito** (`CREDITO_EM_2024`, em 08/06/2024): os clientes novos de junho
  de 2024 são 3, e ler as compras só da faixa do mês daria 2.
- **As contagens** de casos e de mutantes, no D306, no D311 e nesta entrada, foram acertadas pelo diff e pelo relato.
- **O menu Relatórios do ERP antigo** ganhou o `MenuDosRelatoriosSemAsTelasQueSairamTest` (o Dash. Colaboradores fora da
  conta do `menuAcesso`, e as três telas fora do `menuAtivo`).
- **Os comentários falsos**: o do `menu-do-erp.ts` (o Radar e o Fluxo "continuam no ERP antigo"), os do
  `CronTravaSimultaneaTest` (o docblock órfão do D40 e o "mesmo padrão do de colaboradores") e o cabeçalho do
  `ColaboradoresNoRelatorioTest` (os chamadores que não existem mais).

**Provado na revisão:**

- **O front:** 1.714 de 1.714 em 47 arquivos (`--maxWorkers=4`), três casos novos (sair e voltar com o melhor da rede no
  ar; a semana aberta com os números de outro mês no ar; trocar de mês duas vezes com a semana no ar); build com 0; lint com
  0 e os 6 avisos de sempre. **Três mutantes novos, um por vez, cada um vermelho só no caso dele**, com o arquivo devolvido
  byte a byte: a fila de volta a cada instância, a semana sem a fila, e o `|| relendo` arrancado. Com
  `AMPLIFICAR_EFEITOS_MS=50`, os cinco arquivos de caso mudados pelo item: os quatro da tela e do menu verdes, e o
  `Console.teste.tsx` com um vermelho por rodada na comanda, um caso diferente a cada vez (3 rodadas: 1, 1 e 0) -- a família
  do D471, que não é deste item.
- **No PHP do host, sem banco** (o 8.2; o conteiner é 8.1): o `RegrasDoPainelDaUnidadeTest` e o
  `RegrasDoProfissionalParceiroTest` inteiros, por um arremedo do PHPUnit (49 e 42 casos, verdes), e **sete mutantes das
  regras do servidor vermelhos ali**, um por vez (a comparação sempre com o mês anterior, as faixas sem juntar, o mês antes
  do comparado errado, o 29 de fevereiro sem o teto, a semana contando o dia sem a unidade, a semana com a venda sem
  unidade, a semana pelo calendário); a conta do caso das rotas, com o roteador montado do `module.config.php`, verde, e
  vermelha com a semana levando à ação dos clientes; a do caso do menu, verde, e vermelha com o Dash. Colaboradores de
  volta no `menuAcesso` e com o Radar de volta no `menuAtivo`; a do caso do layout, verde, e vermelha com o layout do
  `HEAD`. O `php -l` nos 50 arquivos PHP mudados, sem erro.
- **O ensaio do passo 9** com 0, e com 1 nos dois mutantes do filtro.
- **Os portões do `conferir-tudo.sh` que não passam pelo docker, no host:**
51 com 0 -- entre eles o de referências, o de links mortos, o de rotas sem login, o de recorte, o de cobertura por
  par, o de concatenação em consulta, o de relógio nos testes, o do inventário, os segredos e a lacuna do CI --; o de
  agendamento com 1 (o `conveniosAtualizarVencimento`, o D50 de sempre, "chamadas órfãs no crontab . 0"); o
  `ensaiar-sql-do-passo-3.php` com 2 ("o MySQL do conteiner não respondeu"); e os quatro que falam com o banco do host
  (`conferir-ordenacao-nulos`, `-relacoes-doctrine`, `-unicidade`, `-limpeza-da-unicidade`) sem medir. Os sete que
  passam pelo docker e o `conferir-schema-tool.sh` não rodaram.
- **Os débitos:** o `contar-debitos.php` com 486 débitos, 335 que não estão mais de pé (o D311 voltou a parcial, e o
  D488 entrou); o de repetidos sem par novo.

**Na revisão, não provado -- o servidor** (provado depois, logo abaixo): nenhum caso PHP rodou no PHPUnit, em banco
nenhum: os do item e os da revisão (as
rotas do painel, a compra de crédito de 2024, o menu do ERP antigo). Os mutantes do controller -- a trava do D311 (o 429
arrancado, sem o id da pessoa, também na unidade, o melhor da rede sem trava, a trava antes das guardas), a semana lendo a
unidade em vez da rede e as compras só da última faixa -- esperavam o Docker. Nenhum ensaio HTTP rodou (o
`medir-adm-api-no-postgres.php` com o `PRECISA_SOMAR` também não), e nada foi aberto num navegador. A máquina precisava
ser reiniciada antes do ciclo.

**A prova do servidor (13/10/2026, depois do reinício da máquina):**

- **O PHPUnit de verdade** (o 10.5, no PHP 8.1 do contêiner), nos bancos só de fixtures que o CI monta
  (`rodar-caracterizacao-como-ci.sh` e `rodar-caracterizacao-postgres.sh`), filtrado nos arquivos do item e nos vizinhos
  -- 26 classes, entre elas o `ProfissionalParceiroTest` (71), o `PainelDaUnidadeTest` (42), os dois `Regras*` (49 e
  42), o `MenuDosRelatoriosSemAsTelasQueSairamTest`, o `CronTravaSimultaneaTest`, o `D203TextoDigitadoNasTelasDoErpTest`,
  o `ColaboradoresNoRelatorioTest`, o `VendasNoRelatorioTest` e os que leem as rotas, os controllers e as rotinas:
  **MySQL, 651 de 651 com 10.453 asserções; PostgreSQL, 651 de 651 com 10.452**; nos dois, 5 pulados com o motivo
  escrito, nenhum deste item.
- **O vermelho da primeira rodada, e o conserto:** 6 casos do `SegredosForaDoCodigoTest`, nenhum do item. O piso das
  rotinas do cron ("verde sobre nada não é verde": 26 rotinas com token, e 22 sem as quatro que cobram ou enviam) não
  acompanhou as duas rotinas que saíram -- o controller tem 24 e 20. O piso desceu para 24 e 20, com a data no
  comentário; o segundo piso só apareceu na segunda rodada, porque o primeiro parava o caso antes dele.
- **Os nove mutantes do controller, um por vez, no MySQL, com o arquivo devolvido e conferido por sha1 depois de cada
  um, todos vermelhos:** sete da trava do D311 no `testOsPedidosDaRedeSaoUmDeCadaVezPorPessoa` (o 429 arrancado nos
  clientes da rede e, à parte, no melhor da rede; a trava sem o id da pessoa; a trava também nos clientes de uma
  unidade; o melhor da rede sem trava; a trava antes das guardas, no melhor da rede e, à parte, nos clientes da rede) e
  dois do painel (a semana lendo a unidade em vez da rede, no oráculo da semana; as compras de crédito só da última
  faixa, no ano anterior: 2 clientes novos no lugar de 3). **O D311 foi a corrigido**: o `contar-debitos.php` com 486
  débitos, 336 que não estão mais de pé; o de repetidos sem par novo.
- **Não rodou:** os ensaios HTTP (o 429 pelo nginx, o `medir-adm-api-no-postgres.php` com o `PRECISA_SOMAR`) e a tela
  no navegador; e nem o ciclo completo nem a suíte inteira, que ficam para o ciclo. O `local.php` continua em
  `franquiabv_dev`.

## 14/10/2026 -- CAIXA-E-SANGRIA

As respostas 171 a 175 do dono sobre a despesa da sangria e a retirada do caixa: o **D375** e o **D480** corrigidos, sem
esquema (a exclusão com o autor já tinha as colunas de 13/10/2026), sem pergunta nova. Mudaram a tela antiga e a API de
Contas a Pagar e a tela antiga e a API do caixa; a tela nova do caixa já recusava o zero, e não mudou; a de Contas a Pagar
ainda não existe.

**O que muda para quem usa:**

- **O caixa já fechado continua recusando o estorno da despesa da sangria** (a 171 (a)), com a frase de sempre: quem
  errou corrige por um lançamento novo.
- **O estorno em Contas a Pagar que cancela a retirada tira a despesa de Contas a Pagar** (a 172 (b)), como o
  cancelamento pelo caixa: a parcela e a despesa saem com quem estornou, sem apagar. A tela antiga diz "..., o valor
  voltou ao caixa, e a despesa saiu de Contas a Pagar.". Antes a parcela ficava aberta, como uma dívida na lista do mês.
- **A despesa da sangria não recebe parcela nova enquanto a retirada está ativa** (a 174 (b)): "Esta despesa veio de uma
  retirada do caixa e não recebe parcela nova: o que for de fora vira outra despesa.", na tela antiga e com 409 na API.
- **Só quem poderia cancelar a retirada no caixa a cancela pelo estorno** (a 175 (a)): o privilégio do caixa e o caixa
  ser seu. Para os outros -- a pessoa de Contas a Pagar que não opera aquele caixa, o administrador, o operador sem o
  privilégio do caixa --, "Esta despesa veio de uma retirada do caixa aberto, e só o operador daquele caixa a cancela:
  cancele a retirada no caixa. O pagamento não foi estornado.", e nada muda.
- **A retirada de R$ 0 é recusada nas duas portas do caixa** (a 173 (a), o D480), com ou sem "lançar no financeiro": a
  tela antiga diz "- Valor"; a API, 422 com o campo `valor`.

**O provado:**

- **O PHPUnit** no PHP 8.1 do contêiner, nos bancos só de fixtures que o CI monta, em série, filtrado no item e nos
  vizinhos (`D375|D208|ContasAPagar|Saida|Caixa|ColunasForaDoMapeamento|Retirada|D206D207|D365|D380|RegraDaSaida`):
  **MySQL, 453 de 453 com 1.853 asserções; PostgreSQL, 453 de 453 com 1.853**. Os casos novos: 9 no
  `D375SangriaAmarradaTest` (30 no total), 8 no `D208RetiradaNoFinanceiroTest` (com os dados), e a leitura da retirada
  com trava no lançamento da parcela, no `SaidaSobTravaTest`. O vermelho da primeira rodada: o caso do zero comparava o
  caixa depois da entrada da API, que recalcula o saldo antes de conferir (o cache de toda escrita dela) -- o caso passou
  a comparar o caixa só na entrada da tela velha.
- **Vinte e dois mutantes**, um por vez, com o arquivo restaurado e conferido pelo sha256, rodando o filtro do item
  (71 casos) nos dois bancos -- **todos vermelhos no MySQL e no PostgreSQL**:

  | Mutante | Caso vermelho |
  | --- | --- |
  | o serviço aceita a retirada de R$ 0 | `testOServicoRecusaARetiradaDeZeroENaoGrava` (3 dados) |
  | a tela velha do caixa com a conferência de antes | `testATelaVelhaRecusaARetiradaDeZeroComOCampo` (3) |
  | a API do caixa sem o campo do zero | `testAApiRecusaARetiradaDeZeroComOCampo` |
  | a parcela nova entra na despesa da sangria | `testADespesaDaSangriaNaoRecebeParcelaNovaComARetiradaAtiva`, `testAsDuasPortasDizemQueADespesaDaSangriaNaoRecebeParcela` |
  | a retirada lida sem trava no lançamento | `SaidaSobTravaTest::testAEscritaTravaADespesaAntesDeLer` |
  | a retirada cancelada segurando a parcela nova | `testADespesaDaSangriaNaoRecebeParcelaNovaComARetiradaAtiva` |
  | a tela antiga sem a frase da parcela nova | `testAsDuasPortasDizemQueADespesaDaSangriaNaoRecebeParcela` |
  | a API sem o 409 da parcela nova | o mesmo |
  | o estorno cancela a retirada de qualquer um | `testQuemNaoOperaOCaixaNaoCancelaARetiradaPeloEstorno`, `testAsDuasPortasDizemCanceleARetiradaNoCaixa` |
  | `operadorDoCaixa` sem o privilégio do caixa | `testAsDuasPortasDizemCanceleARetiradaNoCaixa` |
  | `operadorDoCaixa` lendo quem entrou, e não a pessoa da sessão | `testNaPersonificacaoOperaOCaixaQuemASessaoMostra` |
  | a tela antiga não passa o operador | `testATelaAntigaDizOQueAconteceuComARetirada`, `testAParcelaQuitadaComMotivoSeguraADespesa` |
  | a API não passa o operador | `testAApiRecusaComOCaixaFechadoECancelaComEleAberto`, `testNaPersonificacaoOperaOCaixaQuemASessaoMostra` |
  | o estorno que cancelou sem excluir a despesa (o de antes) | 7 casos, entre eles `testOEstornoNoFinanceiroCancelaARetiradaDoCaixaAberto` |
  | o estorno excluindo sem ter cancelado | `testARetiradaJaCanceladaNaoDevolveDeNovoAoCaixa` |
  | o caixa dizendo que cancelou a retirada já cancelada | `testQuemEsperouOCaixaReleARetiradaEDevolveUmaVezSo` |
  | a tela antiga sempre com a frase de antes | `testATelaAntigaDizOQueAconteceuComARetirada` |
  | a tela antiga sempre dizendo que a despesa saiu | `testAParcelaQuitadaComMotivoSeguraADespesa` |
  | a despesa saindo com a quitada dentro (sem a regra do D205) | `testAParcelaQuitadaComMotivoSeguraADespesa` |
  | quem opera perguntado antes de reler a retirada | `testQuemEsperouOCaixaReleARetiradaEDevolveUmaVezSo` |
  | a frase de quem não opera sumida | `testQuemNaoOperaOCaixaNaoCancelaARetiradaPeloEstorno`, `testAsDuasPortasDizemCanceleARetiradaNoCaixa` |
  | a frase de quem não opera sem olhar quem opera | `testQuemNaoOperaOCaixaNaoCancelaARetiradaPeloEstorno` |

- **O portão da guarda do caixa** (`conferir-guarda-do-caixa.php`) lê a quarta porta -- `CrudController::operadorDoCaixa`
  com o privilégio do caixa do ERP, e as duas ações que estornam em Contas a Pagar, derivadas do código, chamando-o --:
  0 no código, e 1 em cada um dos cinco mutantes (o ajudante com outro bit, sem bit, com o bit de variável; cada ação sem
  perguntar quem opera).
- **Os outros portões que o item toca, com 0:** `conferir-acl-da-api`, `-guarda-do-erp`, `-recorte-por-unidade`,
  `-acl-como-booleano`, `-remocao-por-post`, `-autorizacao-privilegiada`, `-cobertura-por-par`, `-referencias-docs`,
  `-links-mortos`, `-relogio-nos-testes`, `-booleanos-em-consulta`, `-booleano-em-coluna-inteira`, `-bytes-de-controle`,
  `-barreira-de-origem`, `-injecao-dql`, `-rotas-sem-login`, `-escritores-do-horario`, `-fosseis`, `-scripts-servidos` e
  `conferir-segredos.sh`. O `conferir-inventario-atual.sh` deu 1 (a cadeia do `retiradaCancelar` mudou com a extração de
  `excluirASangriaSemDinheiro`), o inventário foi regerado, e deu 0.
- **Os débitos:** o `contar-debitos.php` com 486 débitos, 338 que não estão mais de pé; o de repetidos sem par novo.
- **Medido na `franquiabv_dev`** (só contagens): nenhuma retirada de R$ 0; nenhuma retirada cancelada com despesa;
  nenhuma despesa de sangria com mais de uma parcela; 3 caixas abertos com sangria ativa.
- **Não rodou:** os ensaios HTTP, a tela no navegador, o front (não mudou) e o ciclo completo. O `local.php` continua em
  `franquiabv_dev`.

**A verificação do item** (no mesmo dia) achou quatro coisas, todas conferidas no código e corrigidas, sem esquema:

- **A API do estorno devolvia a parcela excluída com cara de conta em aberto** (`situacao` "aberta"), sem dizer que a
  retirada foi cancelada nem que a despesa saiu. O recurso da parcela ganhou o `estado` (`ativa` ou `excluida`), e a
  resposta do estorno traz ao lado `saida: {id, estado}` e `retirada` (`null` na despesa da tela; `{id, estado}` na da
  sangria) -- `SaidaJson::estorno`, com a retirada lida depois do estorno (`SaidaService::retiradaDaDespesa`). A
  exclusão da parcela pela API, que também devolvia a excluída como aberta, sai com o `estado` pelo mesmo conserto.
- **O comentário do estorno dizia que 16 das 17 sangrias foram excluídas à mão**: foram as parcelas (contado de novo na
  `franquiabv_dev`: das 17, todas com o pagamento estornado, 16 com a parcela excluída, 0 com a despesa). O comentário e
  o "O que não mudou" do D375 dizem isso.
- **O D480 dizia que o "abc" ia ao serviço**: recebia "Valor indiponível." (no PHP 8, "5000" < "abc" é verdadeiro). O
  texto diz isso, e o "- Valor" de agora tem caso.
- **O caso do estorno da sangria já cancelada com o caixa fechado não conferia a despesa nem a parcela.** Passou a
  conferir.

O provado da verificação: o filtro do item, **455 de 455 com 1.865 asserções no MySQL e no PostgreSQL**, em série (dois
casos novos: `testAApiDizQueADespesaFicaQuandoAQuitadaASegura` e `testATelaVelhaDizOCampoAoTextoQueNaoENumero`; e
conferências novas em quatro casos que já existiam). **Nove mutantes**, cada um sozinho, restaurados pelo sha256, todos
vermelhos nos dois bancos (filtro de 118 casos):

| Mutante | Caso vermelho |
| --- | --- |
| a parcela sem o `estado` | `testAApiRecusaComOCaixaFechadoECancelaComEleAberto`, `testAApiDizQueADespesaFica...`, `testAApiPedeEGuardaOMotivoDoEstorno`, `testExcluirAParcelaAbertaContinua200` |
| a parcela sempre `ativa` | os mesmos, menos o da despesa da tela |
| o `estado` da despesa lido da parcela | `testAApiDizQueADespesaFicaQuandoAQuitadaASegura` |
| a retirada sempre `ativa` | `testAApiRecusaComOCaixaFechado...`, `testAApiDizQueADespesaFica...` |
| a ação do estorno devolvendo só a parcela (a de antes) | os dois, e `testAApiPedeEGuardaOMotivoDoEstorno` |
| a ação sem passar a retirada | `testAApiRecusaComOCaixaFechado...`, `testAApiDizQueADespesaFica...` |
| a retirada da resposta lida só se ativa | os mesmos |
| o estorno da sangria já cancelada excluindo a despesa com o caixa fechado | `testARetiradaJaCanceladaComOCaixaFechadoEstornaSoNoFinanceiro` -- e **verde** contra o caso de antes (o controle) |
| a tela velha do caixa com a conferência de antes | `testATelaVelhaDizOCampoAoTextoQueNaoENumero` e os três dados do zero |

A `retirada` de um estorno aceito é sempre `cancelada`, e o mutante que a fixa assim é equivalente. Os portões com 0:
`conferir-acl-da-api`, `-guarda-do-caixa`, `-guarda-do-erp`, `-recorte-por-unidade`, `-injecao-dql`,
`-booleanos-em-consulta`, `-booleano-em-coluna-inteira`, `-relogio-nos-testes`, `-referencias-docs`, `-links-mortos`,
`-bytes-de-controle`, `-cobertura-por-par`, `-fosseis`, `-autorizacao-privilegiada`, `-acl-como-booleano`,
`-remocao-por-post`, `conferir-inventario-atual.sh` (351 ações, bate sem regerar) e `conferir-segredos.sh`;
`contar-debitos.php` (486, 338 que não estão mais de pé) e o de repetidos (nenhum par novo). Nada no front, nenhum
ensaio HTTP, nenhum navegador.

## 14/10/2026 -- WHATSAPP-E-APLICATIVO

As respostas 168, 169, 170, 178 e 179 do dono sobre o WhatsApp e o aplicativo: o **D482** corrigido, o **D483** decidido
(as 4 fechadas passam ao aplicativo, como as 24), o **D478** parcial (o fim da falhada; a causa e a hora ficam para a F3,
e o conflito com a limpeza de 24 h foi ao lote), o **D479** decidido como está, e o **D59** com o número central decidido
e o passo de preencher conferido; o **D489** registrado na verificação. Sem esquema; três perguntas novas ao lote (a
verificação, no fim desta entrada); nada no front.

**O que muda para quem usa:**

- **A mensagem de WhatsApp que falhou sai da fila depois de 7 dias** (a 168 (c), o D478), pela própria rotina da fila
  (`marketingPush`), que já roda a cada minuto -- nenhuma rotina nova no crontab. Só a falhada: a pendente e a enviada não
  mudam. A coluna "Não receberam" das campanhas não muda: a conta é da campanha, e não da fila. **Na produção, a limpeza
  de 24 h da fila inteira (o evento do D26, e a rotina da resposta 127 depois do corte) a apaga antes:** foi ao lote (a
  verificação, abaixo).
- **As 4 unidades fechadas com o WhatsApp ligado passam ao push do aplicativo** (a 178 (c), o D483), como as outras 24: o
  parabéns, a primeira visita, o "sentimos sua falta" e o NPS da venda saem pelo aplicativo para quem o usa. O WhatsApp
  delas continua calado; a campanha e a indicação fazem o que já faziam pela unidade sem WhatsApp.
- **O aplicativo trata o colaborador da unidade inativa como o que não existe** (a 179 (b), o D482): a agenda dele
  (`/api/agendas/getAgenda/:id` e a da v2) responde `sucesso: false`, e a marcação nova (`POST /api/v4/agendamentos`), o
  422 "Informe o colaborador.". No contrato congelado, só esse caso muda, e com a forma de sempre.
- **"Não receberam" fica como está** (a 170 (c), o D479): as campanhas que a produção mandar pelo código antigo até o
  deploy mostram "0", que nelas não quer dizer que todos receberam.
- **O WhatsApp central é um número novo da franqueadora** (a 169 (a), o D59). O passo de preencher estava no
  `local.php.dist`, mas o corte o perdia: o passo 7 de `docs/procedimento-de-corte.md` troca o `local.php` pelo
  `local.postgres.php.dist`, que não tinha o bloco, e não mandava levar o central. Agora os dois têm o bloco vazio e o
  passo, e o passo 7 tem a entrada `whatsapp.central`. Nenhum valor real; falta, do dono, a instância nova e o aparelho.

**O provado:**

- **O PHPUnit** no PHP 8.1 do contêiner, nos bancos só de fixtures que o CI monta, em série. O filtro do item
  (`WhatsAppQueFalhaTest|D351UnidadeDesativadaTest`): **MySQL, 27 de 27 com 134 asserções; PostgreSQL, 27 de 27 com
  134**. Os vizinhos (o item mais `AparelhosDoCliente`, `AutenticacaoDoCliente`, `D41ParabensNoDiaDePreenchimento`,
  `EnvioDePush`, `GradeDaAgendaApi`, `PushNasRotinas`, `RegrasDaAgendaDoParceiro`, `SegredosForaDoCodigo`, `UnidadeApi`,
  `D304EtapaSeisDaAdesao`, `ValidacaoDeAgendamento`, `D367RoyaltiesDaUnidadeInativa`, `WhatsAppTelaAntiga` e
  `FidelidadeDaTelaNova`): **294 de 294 com 7.504 asserções nos dois bancos** (antes dos dois últimos ajustes de teste,
  que só tocaram os dois arquivos do item, rodados de novo acima). Casos novos: dois no `WhatsAppQueFalhaTest` e três no
  `D351UnidadeDesativadaTest`; os quatro casos da R30 que diziam "fechada, não sai -- nem pelo aplicativo" dizem agora
  "sai pelo aplicativo", e o `testARegraDaUnidade` confere a regra pela linha. Nenhum WhatsApp nem push de verdade: o
  WhatsApp é o de mentira, e as rotinas só enfileiram.
- **A suíte de contrato**, com o banco de contrato montado de novo: **74 de 74 no MySQL e no PostgreSQL**, sem regravar
  snapshot. Nenhum cobria o colaborador da unidade inativa (as fixtures têm 5 unidades, todas ativas); o caso é provado em
  processo, e a forma dele é a do `agenda-selecionar-v2-sem-dados`.
- **Quinze mutantes**, um por vez, com o arquivo restaurado e conferido pelo sha256, rodando o filtro do item (ou o caso
  do mutante) -- **todos vermelhos no MySQL e no PostgreSQL**:

| Mutante | Caso vermelho |
| --- | --- |
| a rotina da fila sem a chamada da limpeza | `testAFalhadaSaiDaFilaDepoisDeSeteDias`, `testONaoReceberamDaCampanhaNaoDependeDaFila` |
| o prazo a 8 dias | `testAFalhadaSaiDaFilaDepoisDeSeteDias` ("a falhada de mais de 7 dias ficou na fila") |
| o prazo a 6 dias | o mesmo ("a falhada de menos de 7 dias saiu da fila") |
| a limpeza pela rotina, sem o `falhou:` | os dois do D478 |
| a limpeza pelo que NÃO é a falhada | os dois do D478 |
| a limpeza de toda linha velha, de qualquer fila | `testAFalhadaSaiDaFilaDepoisDeSeteDias` ("a pendente velha foi apagada em vez de mandada") |
| a limpeza pelo relógio de agora, e não o de quem chama | `testAFalhadaSaiDaFilaDepoisDeSeteDias` |
| a regra da unidade sem a situação dela | os quatro casos das rotinas e `testARegraDaUnidade` |
| o parabéns de volta ao calado | `testOParabensDaUnidadeFechadaSaiPeloAplicativo` |
| a primeira visita de volta ao calado | `testAPrimeiraVisitaDaUnidadeFechadaSaiPeloAplicativo` |
| o "sentimos sua falta" de volta ao calado | `testOSentimosSuaFaltaDaUnidadeFechadaSaiPeloAplicativo` |
| o NPS da venda de volta ao `whatsappCalado` | `testONpsDaVendaDaUnidadeFechadaSaiPeloAplicativo` |
| a agenda do aplicativo sem a conferência da unidade | `testAAgendaDoColaboradorDaUnidadeInativaRespondeComoADoQueNaoExiste` |
| a marcação v4 sem a conferência | `testAMarcacaoDoAplicativoNovoRecusaOColaboradorDaUnidadeInativa` |
| a regra do colaborador contando quem não tem unidade | `testARegraDoColaboradorDaUnidadeInativa` |

- **Os portões, com 0:** `conferir-acl-da-api`, `-autorizacao-privilegiada`, `-barreira-de-origem`,
  `-booleanos-em-consulta`, `-booleano-em-coluna-inteira`, `-bytes-de-controle`, `-cobertura-contrato`,
  `-cobertura-por-par` (rebaseado: o par novo `PushAppRepository::apagarFalhasAntigas`, de 323 para 324, o ISApi de 109
  para 110, mencionado em teste; tirando-o, o sha1 volta ao de antes), `-dia-do-cenario-da-agenda`,
  `-entidade-duplicada`, `-fosseis`, `-guarda-da-agenda`, `-guarda-do-erp`, `-guarda-do-caixa`, `-injecao-dql`,
  `-linha-sorteada`, `-links-mortos`, `-provedores-no-loopback`, `-rastro-das-rotinas`, `-recorte-por-unidade`,
  `-referencias-docs`, `-relacoes-doctrine`, `-relogio-nos-testes`, `-remocao-por-post`, `-resposta-cron`,
  `-rotas-orfas`, `-rotas-sem-login`, `-tempo-limite-rede`, `-tipos-do-mapeamento`, `-caminhos-postgres`,
  `-acl-como-booleano`, `-ordenacao-empates`, `-ordenacao-nulos`, `-scripts-servidos`, `-protecao-do-webroot`,
  `-registro-de-acesso`, `-grupos-por-id`, `-escritores-do-horario`, `-buffer-de-saida`, `-busca-por-tags`,
  `-ancora-de-medicao`, `conferir-segredos.sh` e `conferir-inventario-atual.sh` (351 ações, bate sem regerar);
  `contar-debitos.php` (486, 341 que não estão mais de pé) e o de repetidos (nenhum par novo). Nada no front, nenhum
  ensaio HTTP, nenhum navegador; nada rodou na `franquiabv_dev` além de SELECT de contagem.

### A verificação (14/10/2026)

Seis achados de uma revisão do item, todos conferidos no código e verdadeiros; dois eram o mesmo, lido duas vezes, e dois
outros o mesmo mecanismo. **Nenhum pedia código novo: três são escolhas do dono, e foram ao lote; os outros, documentação
que mentia.**

- **Os 7 dias da 168 nunca chegam enquanto houver a limpeza de 24 h** (o achado médio). A pergunta 168 dizia "hoje nada
  apaga essas linhas", e na produção o evento `limpar_push_app_24h` (o **D26**) apaga de hora em hora toda linha de mais
  de 24 h, a falhada também; e a resposta 127 (12/10/2026, a (a)) leva essa limpeza para o crontab no corte, com o mesmo
  prazo. Se a de 24 h poupa a falhada é do dono, entre duas respostas dele: **foi ao lote**, com a recomendação de poupar.
  Os comentários de `PushApp::DIAS_DA_FALHA_NA_FILA`, de `apagarFalhasAntigas` e do `marketingPush`, que diziam a
  pendente fora da limpeza "nunca, por velha que seja", dizem agora que é só desta; o D26 ganhou a resposta 127, que não
  estava nele, e o cruzamento com a 168. Nas cópias locais o evento existe e não apaga (o definer `andre@%` não existe).
- **Os 7 dias contam do pedido, e não da falha** (dois achados, baixos). `marcarFalha` não regrava a `data`: numa fila
  atrasada, ou na sobra das filas do `marketingPushOutros` (uma chamada às 10:00, 100 linhas por fila), a linha de pedido
  velho que falha sai já na rodada seguinte. Contar da falha é perder a hora do pedido, ou uma coluna que nenhuma resposta
  pediu: **foi ao lote**, com a recomendação de ficar como está. **Caso novo**,
  `testAPendenteVelhaQueFalhaSaiNaRodadaSeguinte`, que descreve o de hoje (o caso de antes datava as linhas depois de
  marcá-las, e não via a diferença); o comentário da constante, que dizia que a fila manda "logo depois", diz em qual fila.
- **A sobra das 100 é um débito novo, o D489:** as filas do parabéns e do "sentimos sua falta" saem 100 linhas por dia, e
  na produção a sobra é apagada pela limpeza de 24 h antes da chamada seguinte, sem ser mandada -- na cópia de 16/08,
  316 parabéns das 06:00 e 385 "sentimos sua falta" das 07:00 ainda na fila à noite. Como mandar o resto muda o ritmo do
  WhatsApp e o crontab do corte: **foi ao lote**, com a recomendação de as duas filas saírem a cada minuto das 10h às 22h.
- **O D351 ficou velho** (dois achados, baixos, o mesmo): o "O que resta" dava o D482 e o D483 como pendentes e mandava
  fechar o balcão pelo `whatsappCalado`, que saiu. Agora diz que os dois voltaram respondidos e feitos, e que o balcão se
  fecha pela `mandaWhatsApp` com a regra da 178; o "calada, e não desviada" ganhou a nota de que acabou.
- **O D483 dizia CORRIGIDO de um comportamento que a 178 manteve:** passou a **DECIDIDO** ("as 4 passam ao aplicativo,
  como as 24"), como o D458, o D459 e o D479. O título novo tirou do limiar três pares conhecidos do
  `conferir-debitos-repetidos.php` (D478|D483, D298|D483, D351|D483), que saíram da lista com o motivo.

**O provado:** o filtro do item, em série, **28 de 28 com 138 asserções no MySQL e no PostgreSQL** (era 27 e 134; o caso
novo). **Um mutante**, sozinho, com o arquivo restaurado e conferido pelo sha256: a marcação regravando a `data` com a
hora da falha -- **vermelho só no caso novo**, nos dois bancos ("a marcacao mudou a data da linha"); o
`testAFalhadaSaiDaFilaDepoisDeSeteDias` ficou verde com ele, que era o achado. **Os portões, com 0:** os 41
`conferir-*.php` da lista acima e o `conferir-debitos-repetidos.php` (nenhum par novo, depois de tirar os três),
`conferir-segredos.sh` e `conferir-inventario-atual.sh` (351 ações); o `conferir-cobertura-por-par.php` sem par novo (os
137 sem menção, na base). `contar-debitos.php`: **487 débitos, 341 que não estão mais de pé** (o D489 aberto; o D483
segue no balde dos fechados). Três perguntas novas no lote. Nada no front, nenhum ensaio HTTP, nenhuma rotina de envio;
na `franquiabv_dev`, só SELECT de contagem (a fila por rotina e por minuto, os eventos e o definer).

## 14/10/2026 -- CADASTRO-E-CONVENIO

As respostas 176 e 177 do dono (o **D481** e o **D477**), pelo pedido *"continue de forma autonoma a resolver todos os
debitos que  tiver resposta"*.

**O que muda para quem usa:**

- **A página da adesão (a etapa 1) tem o teto dos 10 cadastros, quando o telefone muda** (a 176, "b"). O cliente que, pelo
  link da adesão, troca o telefone para um número que já está em 10 cadastros ativos ouve a frase do ERP e da tela nova --
  "Este telefone já está em 10 cadastros. Informe o telefone do próprio cliente." --, e a etapa não grava nem pontua; quem
  confirma o telefone que já tem continua, o dependente inclusive; abaixo do teto, a troca passa, mesmo para o número de
  outro cliente (a "c", a recusa do repetido, não foi a escolhida).
- **No item do cliente de convênio em que o balcão dá desconto, o motivo leva os dois** (a 177, "c"): "Convênio X; <o
  motivo digitado>", cortado em 100, no PDV antigo e na comanda nova, no lançamento e no ajuste -- no item que a cortesia
  zera e no desconto que cabe. Sem desconto do balcão, e no convênio que já zera o item sozinho, o motivo segue o do
  convênio. **Na comanda nova, o "+" e o "−" dessa linha recusam**, sem pedido ao servidor: "Corte tem o desconto do
  convênio e um desconto do balcão. Para mudar a quantidade, tire o item e lance de novo." Até aqui o "+" devolvia o item
  zerado ao preço com o convênio, e a cortesia sumia sem aviso. A comanda passa a dizer qual linha é essa
  (`descontoDoBalcaoNoConvenio`, como o `cupom`).
- **Ficou como estava, e foi ao lote:** o motivo com a marca do cupom no item do convênio (o **D277**: o convênio apaga a
  marca, e o cupom não queima). Levar os dois motivos ali faria o fechamento passar a gastar o cupom -- é escolha do
  dono, que a 90 deixou e a 177 não citou. Uma pergunta nova no lote.

**O provado:** nos dois bancos, em série, o filtro do item e dos vizinhos (o D481, o D32, o D477, a regra do item, o
convênio, o ajuste, a comanda, os cupons na comanda nova, o D212, o D184, o D304, a adesão com pesquisa, o tudo-ou-nada,
o D435, o D450, o D452, o D453, o D316, o D417, as APIs de venda e de cliente): **350 de 350 com 1.264 asserções no MySQL e
no PostgreSQL**. Casos novos: `D481TelefoneNaEtapa1DaAdesaoTest` (5), dois no `D477DescontoAteOValorDoItemTest` (de 9
para 11, e os de antes passaram a provar os dois motivos e a marca da linha) e dois no `RegraDoItemDaComandaTest`; o caso
que caracterizava a troca sem conferência, no `D32TelefoneEmMuitosCadastrosTest`, saiu (é a recusa do D481). **Nove
mutantes do servidor**, um por vez, restaurados e conferidos pelo sha256 (`medir-mutantes-r47.py`), **todos vermelhos nos
dois bancos**: na etapa 1, sem a conferência (3 casos), sem o "muda o telefone" (1) e com os cadastros do sistema na conta
(1); no convênio, sem os dois motivos (5), sem o "desconto de fato" (1), com a marca do cupom (1), a comanda sem a marca
da linha (2), a marca sem o prefixo do convênio (1) e o separador sem motivo digitado (2). **Na tela nova:** o vitest
inteiro, `--maxWorkers=4`, **1.715 de 1.715 em 47 arquivos**; `npm run build` e `npm run lint` com 0 (o lint com seis
avisos, nenhum nos arquivos do item); dois mutantes da tela (sem a recusa, e a recusa depois do caso do convênio), os dois vermelhos no caso
novo. Com `AMPLIFICAR_EFEITOS_MS=50`, a `ComandasLigada.teste.tsx` cai em 3 casos que variam a cada rodada (sempre "as
pílulas do catálogo andam pelas setas") -- **e cai igual com os três arquivos da tela no HEAD**, medido duas vezes: não é
deste item; os dois casos do convênio passam com ele. **Os portões:** 49 dos 51 `conferir-*.php` com 0, entre eles o
`conferir-cobertura-por-par.php`, rebaseado (o ISCadastro de 123 para 124 chamadas: a etapa 1 chama o
`ClienteRepository::contarClientesPorTelefone`, par já vigiado; o conjunto e o sha1 ficam) e o
`conferir-debitos-repetidos.php` com os três pares do D481 que saíram do limiar tirados e o D460|D481 acrescentado, com
o motivo; o `conferir-agendamento.php` (o `conveniosAtualizarVencimento` sem crontab) e o `conferir-migracao.php` (pede o
docker de dentro do contêiner) saem 1 sem ler nada do que mudou. `contar-debitos.php`: **487 débitos, 343 que não
estão mais de pé** (eram 341: o D477 e o D481). **Não provado:** nada foi aberto num navegador -- nem a página da adesão
nem a comanda; o `PUT` montado à mão, só com a quantidade, na linha do convênio com o balcão, continua recalculando só o
convênio (nenhuma tela o manda). Na `franquiabv_dev`, só SELECT de contagem (os convênios com ";" no nome, o tamanho dos
nomes e o de 100%).

**A verificação (o mesmo dia):** oito achados confirmados, conferidos no código, e os oito tratados.

- **A marca da linha passa a ler também o valor.** A linha cujo motivo não traz o "; " -- **o cupom no cliente de
  convênio** (a comanda nova o põe sozinho, e o serviço troca o motivo dele pelo do convênio, o D277), o **convênio de nome
  com 90 caracteres ou mais** (o corte come o separador) e **as linhas gravadas antes de hoje** -- saía sem a marca, e o
  "+" da tela nova ainda tirava o desconto do balcão calado (os 7,19 do cupom de 10% no corte de 89,90). Agora, com o
  prefixo do convênio, o desconto da linha maior que a parte do convênio (a porcentagem atual do cliente sobre o preço da
  linha, a conta do serviço e da medição do D214) marca a linha; o de 100% não. O que se grava não mudou, nem a tela.
- **O cupom continua sem queimar** (o D277): a pergunta do lote passou a contar ao dono que o "+" tirava o cupom calado, e
  que agora recusa.
- **O D481 vai a PARCIAL:** o teto conta os ativos, e o amigo da indicação nasce inativo -- dois amigos que trocam para o
  número em 9 na etapa 1 passam os dois, e a etapa 3 os ativa sem conferir, levando o número a 11. Regra nova: pergunta
  nova ao lote (a recomendação é ficar como está, como a reativação da 167), e um caso que o caracteriza. E o caso do
  cadastro do sistema põe o número no "Cliente Avulso" também (a lista sem ele passava verde).
- **O D214 vai a PARCIAL** (era ANOTADO, e o corpo dizia o contrário do código): o motivo digitado fica junto do convênio;
  resta o do cupom (o D277, no lote). O `ConvenioDescontoTest` ganhou o motivo digitado quando o convênio não vale na venda
  (inativo, outra unidade, dia desligado, produto), que nenhum caso prendia. Os comentários da tela que ainda davam a razão
  antiga do "desconto" escondido foram corrigidos (só texto).
- **Fica, e está escrito no D477:** o corte em 100 é o da coluna inteira, como a opção da 177; o convênio de 77 caracteres
  da cópia (16 clientes ativos) deixa 12 caracteres para o motivo digitado.

**O provado na verificação:** nos dois bancos, em série, o filtro do item e dos vizinhos (o de cima e mais o D317, os
cupons inteiros e os convênios inteiros): **603 de 603 com 2.209 asserções no MySQL e no PostgreSQL**. Casos novos: quatro
no `D477DescontoAteOValorDoItemTest` (15), um no `RegraDoItemDaComandaTest`, um com quatro conjuntos no
`ConvenioDescontoTest` e um no `D481TelefoneNaEtapa1DaAdesaoTest` (6). **Oito mutantes novos**, um por vez, restaurados e
conferidos pelo sha256 (`medir-mutantes-r47v.py`), **todos vermelhos nos dois bancos**: o nome do convênio no motivo com
qualquer convênio do cliente (4) e antes do `convenioAtivo` (2); a lista dos cadastros do sistema sem o avulso (1) e sem o
"SEM CADASTRO" (1); a marca só pelo motivo (4), a comanda sem a porcentagem (3), o valor sem descontar a parte do
convênio (4) e a parte sobre o preço unitário (1). **Na tela nova** (só comentários): o vitest inteiro, `--maxWorkers=4`,
1.715 de 1.715 em 47 arquivos; o build e o lint com 0 (seis avisos, nenhum nos arquivos do item); os dois casos do
convênio passam com `AMPLIFICAR_EFEITOS_MS=50`. **Os portões:** 49 dos 51 `conferir-*.php` com 0 (o
`conferir-debitos-repetidos.php` depois de a marca do D214 ficar curta, "(o D477)": com "(a resposta 177)" ela fazia o
par D214|D477 só pelas palavras); o `conferir-agendamento.php` e o `conferir-migracao.php` saem 1 como antes, sem ler
nada do que mudou; o `conferir-segredos.sh` e o `conferir-inventario-atual.sh` (351 ações) com 0. `contar-debitos.php`:
**487 débitos, 341 que não estão mais
de pé** (eram 343: o D481 e o D214 a PARCIAL). Na `franquiabv_dev`, só SELECT de contagem (o tamanho dos nomes dos
convênios e os clientes ativos neles).

## 14/10/2026 -- RECEBER-PREPREENCHIDO

O pedido do dono, textual: *"dentro da comanda, em Recebimento, já pre-preencher com o valor total da compra, mais de 95%
de todas as compras são realizadas com apenas uma forma de pagamento. ao digitar, o sistema já coloca a virgola sozinho,
exemplo; digita-se 8990 e automaticamente já preenche como 89,90."* Só front: nada de PHP, banco ou rota.

**O que muda para quem usa** (o Receber do detalhe da comanda, na tela nova):

- **O campo Valor chega com o que falta receber.** Na comanda sem pagamento é o total da compra; com um pagamento já
  lançado, o resto (total menos recebido -- o `fiado.valor` que o servidor já manda, e não uma conta da tela); paga por
  inteiro, ou vazia, o campo fica vazio, com o "ex.: 89,90". O caso de 95% das vendas passa a ser escolher a forma e
  apertar "receber", sem digitar.
- **Ele acompanha a comanda enquanto ninguém digitou:** item lançado ou tirado, desconto, pagamento recebido -- o campo
  passa ao novo resto. O que a pessoa digitou fica (o campo apagado também). **Depois de receber, volta ao que falta na
  comanda nova**, e não ao vazio -- selecionado, se o foco ficou no campo (o recebimento pelo Enter) --, **e a forma volta
  ao "escolha…"**: a segunda forma de um pagamento dividido se escolhe e recebe o resto sem digitar, e o segundo Enter
  sem escolher a forma é recusado ("Escolha a forma de pagamento."), como o campo vazio o recusava antes.
- **A máscara de dinheiro:** só dígitos, e os dígitos são centavos -- 8990 mostra 89,90, 5 mostra 0,05, 123456 mostra
  1.234,56 (o milhar com ponto, como o resto da casa); o apagar tira o último dígito; colar "R$ 89,90" ou "89,90" dá
  89,90; zero à esquerda não fica (o 0,05 apagado vira campo vazio, e não 0,00); a tecla que não é dígito -- a vírgula,
  o ponto, o espaço, uma letra -- não muda nada, nem com o pré-preenchido selecionado. Teclado numérico no celular
  (`inputMode="numeric"`). **Ao focar, o valor fica selecionado inteiro** (pelo clique, pelo Tab ou pela palavra
  "Valor"): o primeiro dígito substitui o pré-preenchido, e quem recebe só uma parte digita direto.
- **O que vai ao servidor não mudou:** o decimal do protocolo ("1234.56"), e o campo vazio continua recusado com "Valor
  como 89,90.".
- **Não mudaram, de propósito:** os outros campos de dinheiro da tela nova (retirar, abrir e fechar o caixa, o desconto
  em R$, o valor editável do lançar, o receber do fiado dos Devedores) e o PDV antigo. A máscara mora no `formato.ts`
  (`centavosDigitados` e `textoDaMascara`) e serve a eles quando for pedido.
- **O que a máscara lê diferente:** colar um valor de uma casa só ("89,9") dá 8,99 -- os dígitos são centavos. O que a
  tela mostra tem sempre duas casas, e colar dela dá certo.

**O provado:** no `ComandasLigada.teste.tsx`, o grupo do receber foi de 2 para 17 casos (125 no arquivo; eram 110): o
total sem pagamento enviado sem digitar, o resto com um pagamento, vazio na paga e na vazia, o que acompanha a comanda
nova, o digitado (e o apagado) que fica, a máscara (8990, 5, 123456, o apagar, as letras), o zero à esquerda, o colar com
"R$" e sem, o foco pela palavra "Valor" e pelo clique que seleciona (e o segundo clique que não), o envio de 1.234,56 como
"1234.56", o campo depois de receber uma parte (25,00 de 45,00) e o vazio recusado. No `formato.teste.ts`, quatro casos
da máscara (24 no arquivo; eram 20). **Dez mutantes**, um por vez, restaurados e conferidos pelo sha256
(`mutante-receber.py`), **todos vermelhos**: o pré-preenchido com o total, e não o resto (5 casos); o que se mostra sem
dividir por 100 (17) e o que se lê sem dividir por 100 (13); o acompanhar que passa por cima do digitado (1); o
pré-preenchido guardado na montagem (3); o foco sem selecionar (1); o soltar do clique sem refazer a seleção (1); o campo
vazio depois de receber (1); o zero à esquerda que fica (2); o teclado decimal (4). **O front:** o vitest inteiro, com
`--maxWorkers=4`, 1.734 de 1.734 em 47 arquivos; o build e o lint com 0 (seis avisos, nenhum nos arquivos do item). Com
`AMPLIFICAR_EFEITOS_MS=50`, os 17 casos do receber passaram em 3 de 3 rodadas sozinhos e em 3 de 3 do arquivo inteiro
(os três que abrem a comanda esperam o formulário, e não o título); o arquivo inteiro deu 2 a 4 vermelhos por rodada,
outros a cada vez, todos fora do item -- o **D471**, que fica: o do HEAD deu 3 e 6, e "as pílulas do catálogo andam pelas
setas" cai sozinho em 3 de 3 também no HEAD. O `formato.teste.ts`, verde com o amplificador.

**No Chrome**, com o componente de verdade montado pelo servidor do Vite e uma comanda escrita à mão (nenhum pagamento
recebido, nada gravado): o campo chegou com 45,00; o clique que foca -- no texto e no branco do campo --, o Tab e a
palavra "Valor" deixaram o valor inteiro selecionado; 8990 deu 89,90, o apagar 8,99, e 0456 em seguida 89.904,56; letras
e pontuação não entraram; o digitado ficou com a comanda trocada; sem digitar, 45,00 → 20,00 → vazio (a paga). Medido
também: no Chrome, o clique que foca não desfaz a seleção do foco, e o segundo clique põe o cursor. **Não medido:** o
Safari (WebKit), que tem fama de desfazer a seleção ao soltar o botão -- o `onMouseUp` do campo a refaz no clique que
focou, e o caso que o prende escreve essa ordem à mão.

**A revisão** (sete achados confirmados, todos de gravidade baixa: três defeitos de tela, um deles achado duas vezes; um
caso fraco; e o aviso das prévias, achado duas vezes):

- **O recebimento pelo Enter** deixava o foco no campo e o resto novo (25,00) sem seleção: o 1000 de quem divide o resto
  entrava atrás e dava 250.010,00. Agora, focado e sem digitar, o que falta fica selecionado também quando muda -- e só
  com o foco no campo: **no Chrome o `select()` puxa o foco** (medido: o foco foi de outro campo para o selecionado), e
  sem a guarda o item lançado levaria o foco ao Valor.
- **A tecla que não é dígito, com o pré-preenchido selecionado** (o estado logo depois do foco), trocava a seleção: a
  vírgula de quem digitava "45,00" esvaziava o campo, contava como digitado, e o que falta não voltava. Agora o
  `beforeinput` cancela a inserção sem dígito (a tecla e o colar), e a que não se cancela cai no `onChange`, que a ignora.
  O apagar continua apagando: o campo apagado é o que a pessoa quis.
- **Um segundo envio depois de receber uma parte** mandava o resto na forma da primeira, sem ninguém escolher (antes, o
  campo vazio o recusava): a forma volta ao "escolha…".
- **O caso dos 15 dígitos** só tinha noves, e não provava qual dígito sai: ganhou "1234567890123456" → 123456789012345.

Quatro casos novos no receber (21; 129 no arquivo): o Enter com o resto selecionado e o segundo Enter recusado; a tecla e
o colar sem dígito com o valor selecionado, que fica selecionado e segue a comanda; a inserção sem dígito pelo
`onChange`; e o campo sem foco, que não é selecionado. O caso da parte recebida confere também a forma de volta ao
"escolha…" e o segundo toque recusado. **Oito mutantes**, um por vez (`mutante-receber-revisao.py`, restaurados e
conferidos pelo sha256), **todos vermelhos**: sem selecionar o resto (2 casos); selecionar também o digitado (6); sem a
guarda do foco (1); sem o `beforeinput` (1); o `beforeinput` só da tecla, e não do colar (1); o `onChange` sem a recusa
(1); a forma que fica (2); o `slice(-15)` (1). **O front:** o vitest inteiro, 1.738 de 1.738 em 47 arquivos; build e lint
com 0 (os seis avisos de antes, nenhum nos arquivos do item). Com `AMPLIFICAR_EFEITOS_MS=50`, os 21 casos do receber em 3
de 3 rodadas, e o `formato.teste.ts` verde; o arquivo inteiro deu 3, 2 e 6 vermelhos, todos fora do receber (o D471).
**No Chrome**, o mesmo componente pelo servidor do Vite, com o `fetch` dublado na página (um pedido só, ao dublê; nada
foi ao servidor): a vírgula, o hífen, o ponto, o espaço e a letra com 45,00 selecionado deixaram 45,00 selecionado (o
`beforeinput` confiável, cancelado); 2000 e Enter mandaram 20.00, e o campo ficou focado com 25,00 selecionado e a forma
em "escolha…"; 1000 deu 10,00; o segundo Enter, "Escolha a forma de pagamento.", sem pedido. Com o foco em outro campo,
a comanda nova trocou o valor sem tirar o foco de lá. **Não medido:** o colar no Chrome (o painel sem foco de janela não
escreve na área de transferência); o caso do colar sem dígito passa pelo dublê do navegador.

**As prévias do Claude Design** (não editadas; o dono re-sincroniza). O campo pré-preenchido aparece em toda célula que
desenha o `Receber` com uma comanda aberta, com o privilégio de escrever e com fiado -- quatro, e não só a do
`Receber.tsx`:

- `Receber.tsx`, "Recebimento": 92,80 (o que falta na 48213). O comentário do topo fica velho duas vezes: "o valor aceita
  vírgula" e "o campo está vazio". A "JaQuitada" segue vazia, com o exemplo.
- `Comanda.tsx`, "NaComanda": 92,80 no Receber da direita, e o comentário dela ("o campo está vazio") fica velho. A
  "JaExistia" (paga por inteiro) segue vazia.
- `DetalheDaComanda.tsx`, "NoBalcao": 109,70; "NoToque": 80,10. A "RecemAberta" (sem item) segue vazia; a "SoParaVer" e a
  "Finalizada" não têm o Receber.
- Não mudam: a "FalhaDeRede" do `FalhaDeLista.tsx` (um formulário escrito à mão, sem o Valor) e as do
  `TelaDeDevedores.tsx` (o receber do fiado é outro componente).

## 14/10/2026 -- CORTE-EM-DUAS-ETAPAS

A decisão do dono, textual: *"escreva a variante do procedimento de corte. guarde todo o trabalho feito do lado do
postgres, mas vamos colocar a migração do mysql para o postgres em um segundo momento."* Só documentação e leitura: nada
rodou contra a produção, o BVIP ou banco nenhum.

**O que mudou:**

- **`docs/procedimento-de-corte-etapa-1.md`, novo: a etapa 1**, o código novo (o ERP com as correções, a tela nova em
  `/novo/` e a API em `/adm/api`) **substituindo** o antigo sobre o MySQL de produção, com o passo 0. Tem: o que se lê na
  produção antes, só leitura (vinte leituras, todas a medir); a release montada fora da produção (o `git archive`, o
  `composer`, a build com `--base=/novo/`); a janela em treze passos, com comando, conferência e volta; o teste à mão e o
  acompanhamento; a volta ao código antigo sobre o mesmo banco; o que a etapa não faz; o ensaio que falta; e o que a etapa
  2 herda. Ele **aponta** para o laço do passo 0, o passo 3 e os blocos do passo 9 do procedimento, e não os copia: são o
  que os portões conferem.
- **`docs/procedimento-de-corte.md` é a etapa 2, guardado inteiro**: uma nota no topo (18 linhas acrescentadas, nenhuma
  tirada) diz o que a etapa 1 já terá feito e que ali é conferido, e não refeito. Uma nota curta no topo do
  `docs/ensaio-geral-do-corte.md` (6 linhas): ele é o ensaio da etapa 2, e a etapa 1 não tem ensaio escrito.
- **Quinze perguntas ao lote** (CORTE-EM-DUAS-ETAPAS), nenhuma decidida: o aplicativo instalado, a janela, o passo 0, o
  passo 3, a rotação das credenciais, o crontab, as limpezas de dentro do MySQL, o desempenho, o backup, até quando se
  volta, o ensaio, o que a produção recebe entre as etapas, quando a release e o `local.php` chegam ao servidor, a volta e
  o `migrar-senhas`, e a venda e o push do teste à mão. **Estão no arquivo do lote desta rodada, e a página do lote, por
  onde o dono responde, ainda não as tem**: até irem a ela, o dono não as vê, e a etapa 1 não começa sem a primeira.

**O que a leitura achou:**

- **Nenhum documento punha o código no servidor.** Nenhum passo do procedimento põe o código lá, nem a tela nova em
  `/novo/`: o deploy só aparecia por implicação ("o codigo novo esta no ar", no passo 9; "o mesmo deploy", no passo 3), e
  a tela nova, só como referência de comportamento. A etapa 1 é justamente essa parte.
- **O código novo tranca dez rotas do aplicativo instalado** (o D59 e o D91: 401 sem sessão do ERP). No dia da troca,
  o aplicativo dos clientes para de marcar e de desmarcar, e o aplicativo novo não existe. É a primeira pergunta do lote,
  e a etapa 1 não começa sem a resposta.
- **A volta ao código antigo não estava escrita nem ensaiada.** A volta do procedimento termina no código novo sobre o
  MySQL, e a do `scripts/ensaiar-corte.sh` é a do PostgreSQL para o MySQL. Três roteiros do passo 3 a quebram, se rodarem
  na etapa 1. Com o "Vendas caixa" apagado, ninguém entra no código antigo. O Líder com a visão de gerente ganha nele
  poder em qualquer unidade. O dicionário de CEP apagado tira dele a busca por CEP de vez.
- **A produção não tem `local.php`**: é a etapa 1 que o cria (o dia 0), e só depois dela o `cp` do passo 7 do
  procedimento encontra o que guardar.
- **Quatro portões leem o procedimento pela estrutura dele** (o do passo 0, o do passo 3, o do crontab do passo 9 e o da
  conta das `unidadeVendas*`). Escrever a etapa 1 dentro dele deixaria o do passo 0 com saída 2, por causa de um segundo
  laço contra a produção. Por isso o documento é outro.
- **Inconsistências que a etapa 2 herda**, escritas no documento novo e não mexidas no procedimento (os portões leem o
  texto):
  - o passo 3 roda `mysql franquiabv`, e o banco de produção é o `franquia_producao`;
  - "o 8.0.46 de produção", contra o 8.4.7 da coleta de 19/08 — e os mesmos documentos guardam o 8.0.46 do dump de 16/08,
    tirado em `localhost`: quem decide é a leitura da produção;
  - "as dez" migrações do ensaio, que são 12. O "São 25 entradas" do passo 1 está certo hoje (a produção, congelada,
    ainda chama as duas rotinas que saíram do código, e o primeiro bloco do passo 9 as tira); depois da etapa 1, o crontab
    que a etapa 2 guardar terá 24 linhas de `curl`;
  - o `pgrep` do passo 1 não vê rotina nenhuma (elas rodam no php-fpm), o passo 2 não diz como a equipe e o servidor
    passam pela manutenção, e o teste à mão lança uma venda de verdade sem dizer o que fazer com ela;
  - a tabela do que sobrevive à volta, que não traz `dashboard_colaboradores`, `royalties`, a `v46` nem o "Vendas caixa".

**A verificação** do documento novo, conferida achado por achado no repositório, mudou nele:

- **a volta reabria a `/autenticacao/migrar-senhas` com um dano novo.** A cópia do vhost do passo 3 é de antes do bloqueio
  (o passo 6 do dia 0 está pendente), e a volta não o repunha; o roteiro bloqueia só `location =`, e o
  `/index.php/autenticacao/migrar-senhas` chega ao mesmo roteamento por fora. Depois da etapa 1, a rota devolveria a senha
  de antes a quem trocou a sua no código novo: a `migrarSenhasAction` do `eaf1fca` faz `setSenha(getSenhaPhp83())`, e o
  código novo não grava mais a `senha_php83` (o D79). Agora o vhost novo vem antes do roteiro, as duas formas são negadas
  no passo 8 e de novo na volta, e a conferência é ler o vhost, nunca um `curl`; uma defesa que não depende do nginx (a
  `senha_php83` apagada na janela) foi ao lote;
- **o `local.php` "pelo passo 3 do dia 0"** cairia na árvore viva do código antigo, que carrega todo
  `config/autoload/{,*.}local.php`: passou a ser escrito fora da árvore servida e conferido com o caminho, e o `chown`
  que o dia 0 não faz está escrito;
- **a manutenção não deixava passar o próprio servidor**, de onde saem as sondas e o cron: ela tem de deixá-lo passar,
  responder 503 (um 200 dava verde falso) e sobreviver à troca do vhost; o `/novo/` passa a ser conferido pelo conteúdo;
  "parar o PHP" saiu desta etapa;
- **sem o `MAILTO`, o passo 12 não tinha conferência**: o recuo agora está escrito, tira as duas linhas das rotinas que
  saíram, grava a marca da conta dos 10 minutos e só instala com o `cron.token` do `local.php` batendo com o do crontab
  (a conferência rodou num crontab inventado, no gawk e no mawk);
- **o `mv` das pastas de upload aninhava** a `layout/img/avatar` (a release traz as três imagens padrão): agora é `mv -T`,
  com a pasta da release de lado, a `logs` na lista e o `data/` por `cp -aT`;
- **o passo 1 não via rotina nenhuma** (elas rodam no php-fpm): o sinal passou a ser o `SHOW PROCESSLIST` sem conexão da
  aplicação, em `Query` nem em `Sleep`, e a janela recomendada, a começar depois das 00:05;
- **a volta trocava a árvore com o site no ar**: agora fecha como a janela (o cron, a manutenção, a espera), repõe o
  bloqueio, troca, confere e só então libera;
- **as leituras gravavam no servidor e chamavam roteiros que lá não existem**: tudo pela entrada do `ssh`, e o crontab
  lido com o token tirado;
- **a venda do teste à mão ia ao NPS e aos royalties**: a recomendação (no cadastro de quem testa, cancelada antes do cron)
  foi ao lote;
- **o ensaio no BVIP não cronometraria os dois `ALTER` maiores** (a base de lá já tem as duas colunas) e **aplicaria mais
  que "as quatro"** (o índice de `id_pai` que a pergunta 62 deixou fora de lá, e talvez os aparelhos);
- e frases: o 10:00 não é o primeiro WhatsApp (o `marketing/envios` manda já no passo 12), a versão do MySQL e o sistema
  do servidor ficaram em aberto, o D189 foi medido num laptop, as rotinas que a janela pula não se refazem todas, e o
  cupom do dia entrou no que fica no banco. O achado do `_old` no login não procedia: o código antigo também acrescenta
  `_old` ao desativar (`UsuarioService::ativarRemover`, `eaf1fca`), e reativar no antigo deixa o sufixo como já deixa hoje.

**Os portões** (no contêiner, antes e de novo depois da verificação, com os mesmos números): `conferir-referencias-docs.php` 0 (51 documentos, 1.148 caminhos, 0 ausentes sem motivo);
`conferir-passo-zero-do-corte.php` 0 (14 migrações no repositório, 14 citadas, 12 no laço);
`conferir-links-mortos.php` 0 (413 chamadas, nenhum morto novo). E os dois que leem o procedimento sem banco, para provar
que a nota do topo não os tira do lugar: `ensaiar-crontab-do-passo-9.sh` 0 e
`ensaiar-medir-chamadas-unidade-vendas.sh` 0. **Não rodou:** o `ensaiar-sql-do-passo-3.php`, porque ele monta um banco
descartável (nada contra banco nesta rodada); o texto do passo 3 que ele lê não mudou.

**Não provado:** tudo o que a etapa 1 manda fazer na produção. Ela nunca foi executada nem ensaiada, o roteiro de publicar
na produção não existe (o molde é o `scripts/deploy-teste.sh`), e o que se sabe da produção (a versão do PHP e do MySQL, o
`sql_mode`, o buffer pool, o nginx, o backup, o crontab) está marcado como a medir.

### As escolhas do dono, aplicadas (CORTE-ETAPA-1-ESCOLHAS, no mesmo dia)

O dono respondeu as quinze na página (189 a 203, entre 19:24 e 19:33), e o `docs/procedimento-de-corte-etapa-1.md`
passou a segui-las, com as respostas anteriores que valem para o corte (a 5, a 7, a 10, a 11, a 31 e a 128, a 50, a 62, a
83, a 106, a 122, a 126, a 127, a 158, a 182 e a 184 a 186). Só documentação e leitura, de novo.

**O que se decidiu:** as dez rotas do aplicativo abertas como hoje, até o aplicativo novo (189 b) — e o recorte da 106
sai junto enquanto elas estiverem abertas, porque ele lê a unidade da sessão e, sem sessão, recusaria toda marcação; a
janela numa noite útil, das 00:05 a antes das 05:25, com o limite das 5h, testada pelo dono com um usuário de cada papel
por uma manutenção que só deixa a equipe e o servidor passarem (190 a); o passo 0 na janela, depois do backup (191 a); do
passo 3, só o Líder (192 a); o `cron.token` na janela e as outras oito credenciais depois do fim da volta (193 b); o
crontab novo nesta etapa (194 a), **sem e-mail** — a 128 (b), de 12/10, que a pergunta 194 tinha esquecido, manda o aviso
ao log do sistema; as duas limpezas de dentro do MySQL no crontab, com o evento de hora em hora desligado na janela (195
a); o índice da grade no passo 0 (196 b, e a 126 a); a volta até o dono declarar o fim (198 a); a troca e a volta no BVIP,
duas vezes, e o passo 0 num banco descartável (199 a); as entregas desde o dia seguinte (200 c), com a regra que as faz caber
na 198 (até a declaração, só migração que só acrescenta e deixa o código antigo funcionando, e cada entrega com a sua
volta); o pacote e o `local.php` no começo da janela (201 a); o `migrar-senhas` negado nas duas formas **e** a cópia
antiga das senhas anulada (202 b); e a venda de teste cancelada com o motivo "teste do corte" (203 a).

**O que o documento ganhou:** a tabela das decisões, cada uma com onde entrou; a seção "O que falta fazer antes da
janela", com treze itens conferidos no repositório (o décimo terceiro veio da verificação, abaixo); a linha do tempo da janela; e as duas linhas de dado do passo 6,
escritas e não ensaiadas — o inverso do Líder (`UPDATE grupos SET gerente = 0 WHERE id = 8 AND nome = 'Lider'`, só se o
`SELECT` de antes deu 0) e a cópia das senhas (`UPDATE usuarios SET senha_php83 = NULL WHERE senha_php83 IS NOT NULL`):
a `migrarSenhasAction` do `eaf1fca` só escolhe quem tem a coluna não nula, e a coluna é anulável no dump de produção;
**nula, e nunca vazia** — com o vazio, o `getSenhaPhp83` do código antigo devolve uma senha sorteada, e a rota daria a
cada ativo uma senha que ninguém sabe.

**O que falta antes da janela** (os treze): reabrir as dez rotas (código, **D91**); a migração do índice da grade, com o
passo 0, o `mysql.sql`, as cópias e o gerador (código, **D189**); as duas rotinas das limpezas (código, **D26**); o
crontab sem o `MAILTO`, com o aviso no log, e a conferência do passo 9 (código, **D331**); o `local.php` com os valores de
hoje e o token novo (escrito; **D3**); as duas linhas do passo 6, ensaiadas (**D2**, **D79**, **D124**); o backup da
janela (do dono); o roteiro de publicar na produção, para a janela e para as entregas (código, a registrar); a página de
manutenção (a registrar); o roteiro do ensaio no BVIP (a registrar); a venda de teste, com a medida do que a venda
cancelada deixa no retrato, na comissão e nos pontos (a registrar); e uma conferência do `local.php` fora do servidor que
não conecte no banco (código, a registrar).

**Ao lote, seis perguntas** (CORTE-ETAPA-1-ESCOLHAS): o backup da janela (a 197 veio sem opção: a rotação de 24 horas
apaga o dump antes do fim da volta, as três réplicas recebem tudo na hora, o diário da Hostinger não se sabe, e a rotina
de hora em hora não pode disparar no meio das migrações); os ajustes no servidor, a memória do MySQL (se a leitura der a de
fábrica) e os três do php-fpm (a 196 b pede a leitura e não diz o que fazer; a 201 a diz "Nada muda no servidor antes da
janela"); quando e como se publicam as entregas depois da janela (a 200 c); o que vem depois do recuo do crontab, se o
crontab novo não entrar na janela (a 193 b); as duas guardas das dez rotas que não leem a sessão (a 189 b contra a
verificação da 106 e a 158); e o retrato dos royalties da véspera, se a janela ou a volta passar das 05:25 (a rotina só
recalcula ontem e hoje).

**A verificação, no mesmo dia** — as correções que ela pediu, sem nada rodado: o passo 3 escreve o backup na forma da
resposta, sem fixar o ensaio da restauração (só a 191 a e a 202 b, que já põem o backup na janela, antes do passo 0), com
a rotina de hora em hora (o `dumpv3.sh` do **D33**) fora do passo 4, a restauração num primário com três réplicas a
escrever e a linha da `senha_php83` de novo depois dela; a janela espera o crontab novo (a 194 a), e o recuo da 193 (b)
fica como reserva da janela, com o recarregar do PHP-FPM que faltava; a 128 vale porque a pergunta 194 a esqueceu, e não
por ser "a mais nova"; a limpeza de 24 horas roda antes do envio das 10:00, para a 186 (c) seguir "como está"; a volta
depois do passo 12 espera as rotinas em curso; o passo 7 na ordem certa (o `cp -aT` e as pastas antes da troca de nome,
os proxies apagados depois da cópia do `data/`); a leitura 21, das réplicas, e a conferência delas nos passos 4 e 13; a
leitura 13 com o espaço dos três `ALTER` que copiam; os outros agendadores do passo 1 de volta no passo 12 e na volta; o
`verificar-local-php.php`, que conecta no banco (o documento dizia que não); a `salt` do **D78**, que guarda a senha de
todos e fica depois do passo 6; o que o cancelar da venda de teste não desfaz (a comissão da indicação, o cupom do sorteio
e o do checkout) e o caixa de quem testa; o primeiro WhatsApp do código novo, às 08:00, e não 10 minutos depois do passo
12; o ensaio com duas vezes só a troca e a volta (a 199 a); e o índice da grade fora das entregas.

**O procedimento da etapa 2** ganhou só um parágrafo na nota do topo: as escolhas respondidas, e o que delas muda texto
de lá numa rodada própria (o índice no passo 0, o aviso no log no passo 9 e no 8). Nada do trabalho do PostgreSQL mudou.

**Os portões** (no contêiner): `conferir-referencias-docs.php` 0 (51 documentos, 1.169 caminhos, 0 ausentes sem motivo);
`conferir-passo-zero-do-corte.php` 0 (14 migrações, 14 citadas, 12 no laço); `conferir-links-mortos.php` 0 (413 chamadas,
nenhum morto novo). No Git Bash: `conferir-segredos.sh` 0; `ensaiar-crontab-do-passo-9.sh` 0 e
`ensaiar-medir-chamadas-unidade-vendas.sh` 0, os dois que leem o procedimento, para provar que a nota não os tira do lugar.
Depois da verificação, de novo: `conferir-referencias-docs.php` 0 (51 documentos, 1.177 caminhos, 0 ausentes sem motivo),
`conferir-passo-zero-do-corte.php` 0 (14, 14, 12), `conferir-links-mortos.php` 0 (413) e `conferir-segredos.sh` 0; o
`procedimento-de-corte.md` não mudou na verificação.

**Não provado:** nada do que a etapa 1 manda fazer foi ensaiado; as duas linhas do passo 6 e o `ALTER EVENT` estão sem
portão até o ensaio.

## 14/10/2026 -- AS-DEZ-ROTAS-ABERTAS

> *"pode começar pelas dez rotas do aplicativo"* -- e a resposta 189 (b): *"A etapa 1 sobe com as dez rotas abertas ao
> aplicativo de hoje: como estão hoje na produção, e elas fecham quando o aplicativo novo estiver em campo. A exposição
> fica a de hoje, nem maior nem menor. É trabalho de código, antes da janela."*

**Para quem usa:** o aplicativo instalado nos celulares continua marcando, desmarcando e mostrando a agenda, os cupons,
os sorteios, o consumo e as indicações depois da troca de código da etapa 1. As dez rotas que o código novo trancava
desde 03/09/2026 (as seis leituras do **D59** e as quatro escritas do **D91**) respondem de novo sem sessão, como a
produção de hoje: o mesmo corpo, o mesmo status e o `Access-Control-Allow-Origin: *` que nove delas mandavam no código de
julho (a V3 nunca o mandou; o aplicativo é chamado de outra origem, o `capacitor://`). A exposição volta a ser a de hoje,
por decisão do dono, até o aplicativo novo (o `/api/v4`) estar em campo. Nada muda no ERP, na tela nova, na `/adm/api` nem
no `/api/v4`, que continuam com o login.

**O interruptor** é a constante `AplicativoController::ROTAS_DO_APLICATIVO_INSTALADO`, em `abertas` -- no código, nunca no
`local.php`. A guarda de login das dez e o recorte da resposta 106 (o **D237**) ficam escritos atrás dela. **Fechar é
trocar a constante para `fechadas`**: o 401 e o recorte voltam, o cabeçalho sai, e a suíte de contrato e o ensaio das
escritas seguem a constante sozinhos -- medido com ela trocada: o contrato 74 de 74 com os snapshots de fechadas, e o
ensaio verde entrando como o gerente. As duas guardas que não leem a sessão (o serviço da unidade do colaborador e a linha
do bloqueio da 158) **ficam** até a resposta da pergunta 208; medido para ela na cópia, **0 dos 71.402** atendimentos de
2026 pelo aplicativo têm o serviço de outra unidade (341 de 969.426 em todos os anos, o último em 2025). Os três débitos
ganharam o ANOTADO da 189 (b), com o que fecha de novo e quando.

**O que mudou:** `module/ISApi/src/Controller/AplicativoController.php` (a constante, o ajudante do cabeçalho, as dez
guardas e o recorte atrás do interruptor); `tests/Caracterizacao/DezRotasDoAplicativoInstaladoTest.php` (novo) e
`RotasAntigasDoAplicativoTest.php` (os dois estados); `tests/Contrato/AplicativoApiTest.php` (os doze pedidos sem sessão
escolhem o snapshot pela constante, e conferem o cabeçalho) e `ContratoTestCase.php` (guarda os cabeçalhos da resposta);
os snapshots -- os de antes do D59 (`7d10399^`) repostos do git byte a byte para o estado aberto, e os três 401 de nome
antigo renomeados para `cliente-selecionar-sem-autenticacao`, `cliente-selecionar-post-sem-autenticacao` e
`cliente-consumo-post-sem-autenticacao`; `scripts/ensaiar-escritas-do-aplicativo.php` (sem sessão, de outra origem, com o
cabeçalho conferido no sucesso; e a agenda do caso 1 só volta se saiu, porque repô-la sem conferir derrubava o `finally`
com chave duplicada); os portões `conferir-autorizacao-privilegiada.php` (as dez exigem também o interruptor),
`conferir-recorte-por-unidade.php` (o desmarcar em duas entradas) e `conferir-debitos-repetidos.php` (dois pares que o
título novo do D91 tirou do limiar); `docs/api-aplicativo.md` (a frase "não podem mudar" volta a valer, até o aplicativo
novo); o `procedimento-de-corte-etapa-1.md` (a seção das dez, o item 1 de "pronto" e de "O que falta", o item 15 de "O que
falta medir", a tabela das decisões); e comentários na `BarreiraDeOrigem` e no teste dela.

**Provado:**

- caracterização, o item e os vizinhos (`DezRotasDoAplicativoInstalado`, `RotasAntigasDoAplicativo`, `PelaEntradaDoErp`,
  `BarreiraDeOrigemNaEntrada`, `ValidacaoDeAgendamento`, `D351UnidadeDesativada`, `D420DataValida`, `D432DataNaForma`,
  `D14RaizDoModuloDeVendas`, `EmailsDoSistema`): **402 de 402** no MySQL como o CI e **402 de 402** no PostgreSQL; só os
  dois do item, 61 (33 + 28);
- a suíte de contrato: **74 de 74** no MySQL e no PostgreSQL, sem regravar snapshot nenhum; com a constante em `fechadas`,
  74 de 74 no MySQL;
- o `ensaiar-escritas-do-aplicativo.php`: saída 0 nos dois bancos de contrato, os seis casos 200 sem sessão e resíduo
  zero; com a constante em `fechadas`, saída 0 entrando como o gerente;
- a contagem na cópia (`franquiabv_dev`, só `SELECT` de contagem): 0 de 71.402.

**Os mutantes**, um de cada vez, restaurados da cópia limpa e conferidos pelo sha1 e pelo `git diff` (em processo, no MySQL
como o CI, salvo onde dito):

| mutante | vermelho |
|---|---|
| o interruptor sempre fechado | 20: os 10 de "abertas" do `DezRotas` e os 10 abertos do `RotasAntigas` |
| o interruptor sempre aberto | 23: os 10 de "fechadas sem sessão", 9 de "fechadas com a sessão" (o cabeçalho), o valor desconhecido, e os 3 do recorte fechado |
| o erro de digitação abre (só `fechadas` fecha) | 1: `testUmValorDesconhecidoFecha` |
| a constante em `fechadas` | 1: `testAEtapa1SobeComAsDezAbertas` -- e o contrato e o ensaio VERDES, de propósito: é o fechar |
| o cabeçalho arrancado | 9 (as nove de julho); no contrato, 11 de 74; no ensaio, os casos 1, 2 e 4 |
| o cabeçalho também na V3, no começo dela | 1 (a V3 de "abertas") |
| o cabeçalho também na V3, só no caminho do sucesso | **nenhum em processo** (o pedido vazio para antes); no ensaio, o caso 5 |
| a guarda do cupom sem o interruptor | 2 (a lista derivada do código e o cupom aberto); o portão de autorização, saída 1 |
| a guarda do desmarcar arrancada | 3 (a lista, o desmarcar fechado e o valor desconhecido); o portão de autorização, saída 1 |
| a guarda do desmarcar sem o interruptor | no ensaio, o caso 1 (401), com resíduo zero depois do conserto do `finally` (antes, saída 255) |
| o recorte do desmarcar fora do interruptor | 1 (o desmarcar aberto na outra franquia); o portão do recorte, saída 1 |
| o recorte do v2 fora do interruptor | 4 (o v2 aberto); no ensaio, o caso 4 ("Informe o colaborador.") |
| o recorte do v3 fora do interruptor | 4 (o v3 aberto) |
| o recorte do v2 arrancado também fechado | 1 (o v2 fechado na outra franquia); o portão do recorte, saída 1 |
| a linha do bloqueio arrancada | 3 (os dois fechados e o aberto); o portão do recorte, saída 1 |
| o serviço do v2 como no código de julho | 6 (4 falhas e 2 erros, abertos e fechados) |

**Os portões:** `conferir-autorizacao-privilegiada.php` 0 (52 guardas), `conferir-recorte-por-unidade.php` 0 (203
métodos), `conferir-cobertura-contrato.php` 0 (70 snapshots, 0 sem caso), `conferir-guarda-do-erp.php` 0,
`conferir-barreira-de-origem.php` 0, `conferir-rotas-sem-login.php` 0, `conferir-relogio-nos-testes.php` 0,
`conferir-referencias-docs.php` 0 (51 documentos, 1.183 caminhos), `conferir-links-mortos.php` 0 (413),
`conferir-passo-zero-do-corte.php` 0, `conferir-debitos-repetidos.php` 0 (depois de tirar os dois pares), e, no Git Bash,
`conferir-segredos.sh` 0. `contar-debitos.php`: 491 débitos, 11 adiados (o D59, o D91 e o D237 entre eles).

**Fica de fora:** a pergunta 208 (as duas guardas que não leem a sessão ficam ou saem), com o dono; o fechar, que é outra
entrega, com o aplicativo novo em campo; e as diferenças de julho que não são de quem chama, que o D91 lista (corrigidas
na verificação, abaixo: a primeira versão desta frase citava as datas do D420 e do D432 e o colaborador da unidade
inativa do D482, que as dez abertas NÃO recusam, e esquecia os cupons).

**Não provado:** que o aplicativo instalado lê a resposta pelo navegador do aparelho e precisa do cabeçalho (ele não está
neste repositório; o cabeçalho volta por ser o da produção); nada foi visto num aparelho, nem no ambiente de teste, nem na
produção; a forma do sucesso das seis leituras sem sessão é a do contrato (os snapshots de carga), e não a de um ensaio.

### A verificação (15/10/2026)

Nove achados confirmados (três deles o mesmo, por lentes diferentes), todos verdadeiros no código; um é de comportamento
— um ensaio do CI vermelho —, os outros de documento. Nenhum muda o que as dez respondem.

- **O caso 20 do `scripts/ensaiar-sessao-do-erp.php`, que roda no CI, exigia o 401 do aplicativo sem o cookie** e, com as
  dez abertas, saía 1 (medido antes do conserto: 22 de 23, o 20 DEFEITO, com 200 "ID inválido."). A entrega não o tinha
  rodado. Passou a seguir a constante, como o contrato e o ensaio das escritas: abertas, o pedido sem o cookie chega à ação
  e ela responde o "ID inválido." do id 0; fechadas, o 401. 23 de 23 nos dois bancos de contrato, e com a constante em
  `fechadas`. Os outros chamadores das dez rotas, derivados por `grep` dos dez caminhos em `scripts/`, `tests/` e no
  `ci.yml` (e não de memória): só este e o ensaio das escritas exigiam algo do aplicativo sem sessão.
- **O D91, o procedimento da etapa 1 e esta entrada diziam que as dez abertas recusavam o colaborador da unidade inativa
  (o D482) e a data que não existe (o D420 e o D432).** Não recusam: as regras do marcar são as de julho (o
  `validarAgendamento` é o `agendaSalvarV2ValidarFormulario` do `eaf1fca`, só com o `$hoje` de fora), o
  `daUnidadeInativa` só é perguntado na agenda e na v4, e a data passa pelo `dataHoraBr_to_dateTime` de julho (31/02 grava
  em 03/03). Os três documentos dizem agora o que o código faz, e três casos novos o prendem.
- **A 189 (b) contra a 179 (b):** o D482 deixou o V2 e o V3 de fora porque eles exigiam a sessão e recortavam por ela, e
  as duas coisas saíram com as dez abertas. Um pedido montado à mão marca na agenda da unidade fechada, como a produção.
  **Não decidi**: foi ao lote (AS-DEZ-ROTAS-ABERTAS, 15/10/2026, com a recomendação de recusar, como as duas guardas da
  208), e até a resposta fica como a produção. O D482 ganhou o "PARCIAL em 15/10/2026" e nota nos dois parágrafos com a
  premissa velha. Na cópia, 99 colaboradores ativos em 24 unidades inativas; nenhum atendimento pelo aplicativo com eles
  nos 90 dias antes da cópia.
- **A lista do D91 com as diferenças de julho, refeita ação a ação** contra o `eaf1fca` (as dez, o serviço e as consultas
  que elas chamam): entraram os cupons (o do produto desativado escondido, o D274 e a resposta 96 (a); o desconto e o
  produto guardados, o D271 e a 94 (a)), o serviço que não existe (200 onde julho dava 500) e a ordem do que empata.
- **O V3 grava o corpo do pedido anônimo em disco** (`logarJsonRecebido`) antes de qualquer conferência, como julho, e
  nenhum documento o citava: entrou no D91, no D59 (remissão), no procedimento e no docblock do interruptor.
- **`module/ISConfiguracao/config/rotas-publicas.php`** — a tabela que é a guarda do login — dizia "sessão" nas dez. O
  texto delas diz agora o que as defende nos dois estados (abertas, nada; a busca pelo telefone, o teto de taxa do nginx),
  e as chaves não mudaram (`conferir-rotas-sem-login.php` 0, as mesmas 211 rotas anônimas).
- **Restos:** o procedimento abria a seção das dez no presente ("tranca"), e falava em 71.404 atendimentos; o comentário
  de `colaboradorDaUnidadeDaSessao` e o docblock do `RotasAntigasDoAplicativoTest`, também (recontado: 71.402, a conta
  escrita); o docblock do `BarreiraDeOrigemNaEntradaTest` perto da linha 327; e o da classe `AplicativoApiTest`, que dava os
  snapshots abertos como "o que a produção entrega" — no sorteio o snapshot tem `status: string`, e julho manda nulo (o
  D56).

**Os casos novos**, verdes nos dois bancos, cada um vermelho sem o conserto ou com o mutante:

| caso | o que prende |
|---|---|
| `DezRotasDoAplicativoInstaladoTest::testATabelaDaGuardaDescreveAsDezPeloInterruptor` | o texto das dez rotas na tabela da guarda (as rotas saem do roteador); vermelho antes do conserto |
| `RotasAntigasDoAplicativoTest::testAbertasOMarcarSemSessaoMarcaComOColaboradorDaUnidadeInativa` (v2, v3) | abertas, o colaborador ativo da unidade posta inativa marca, como julho |
| `RotasAntigasDoAplicativoTest::testAbertasAMarcacaoComADataQueNaoExisteCaiNoDiaQueOPhpCalcula` (v2, v3) | abertas, 31/02/2031 grava em 03/03/2031 |
| `RotasAntigasDoAplicativoTest::testAbertasOV3SemSessaoGravaOCorpoDoPedidoEmDisco` | abertas, o corpo do pedido anônimo no `data/logs/agenda_json/` (o caso apaga o arquivo que acha) |
| o caso 20 do `ensaiar-sessao-do-erp.php` | o aplicativo sem o cookie chega à ação, pela constante; vermelho antes do conserto |

**Os mutantes**, um de cada vez, restaurados dos bytes lidos antes e conferidos pelo sha1 e pelo `git diff`:

| mutante | vermelho |
|---|---|
| o V2 recusa o colaborador da unidade inativa | 1: o caso da unidade inativa, v2 |
| o V3 recusa o colaborador da unidade inativa | 1: o caso da unidade inativa, v3 |
| o `validarAgendamento` recusa a data que não existe (`checkdate`) | 2: o caso da data, v2 e v3 |
| o V3 sem o `logarJsonRecebido` | 1: o caso do corpo em disco |
| a agenda do cliente com o texto antigo na tabela da guarda | 1: o caso da tabela |
| a guarda do desmarcar sem o interruptor | no ensaio da sessão, o caso 20 (401 onde as dez abertas pedem a ação), saída 1 |
| a constante em `fechadas` | no ensaio da sessão, nenhum: o caso 20 passa pelo 401, de propósito (é o fechar) |

**Provado:** a caracterização do item e dos vizinhos (as dez classes da entrega e o `CupomDeProdutoDesativadoTest`),
**413 de 413** no MySQL como o CI e **413 de 413** no PostgreSQL; só os dois do item, 67 (34 + 33) nos dois bancos; a suíte
de contrato, **74 de 74** no MySQL e no PostgreSQL, sem regravar snapshot (os doze abertos conferidos byte a byte com o
`7d10399^`); o `ensaiar-escritas-do-aplicativo.php` (resíduo zero) e o `ensaiar-sessao-do-erp.php` (23 de 23), saída 0 nos
dois bancos de contrato. Os portões: `conferir-autorizacao-privilegiada.php`, `conferir-recorte-por-unidade.php`,
`conferir-rotas-sem-login.php`, `conferir-guarda-do-erp.php`, `conferir-barreira-de-origem.php`,
`conferir-cobertura-contrato.php`, `conferir-relogio-nos-testes.php`, `conferir-referencias-docs.php`,
`conferir-links-mortos.php`, `conferir-passo-zero-do-corte.php`, `conferir-debitos-repetidos.php` (tirados o D416|D482 e
o D436|D482, que o título novo do D482 deixou abaixo do limiar) e `conferir-segredos.sh`, todos 0; `contar-debitos.php`:
491 débitos, 11 adiados, e o D482 entre os parciais.

## 15/10/2026 -- PONTE-PARA-A-ANTIGA

O pedido do dono, textual: *"continue, desta vez montando as telas que ainda não foram migradas. continue de forma
autonoma até a exaustão"*. É a primeira fatia da fila das telas que faltam (97 fatias, montada em 14/10/2026), e é só
de front: nada de PHP, banco ou rota.

**O que muda para quem usa** (o menu e a tela não migrada, na tela nova):

- **A tela que ainda não foi migrada leva à versão antiga.** Abaixo do aviso "Esta tela ainda roda na versão antiga do
  ERP", o "abrir na versão antiga" abre a tela antiga em outra aba, no mesmo endereço do item do menu; a moldura nova
  fica aberta. São 29 destinos do menu (eram 33). O caminho fora do menu não ganha o link: o menu não diz que ele existe
  no ERP. A busca, os filtros e o botão de ação do cabeçalho continuam desligados, com o motivo, como o desenho do canvas
  os pôs.
- **Quatro itens abrem direto na versão antiga, em aba nova**, e a tela nova deixa de prometer que "o conteúdo entra
  quando for migrado": Visagismo › Consultor Visagista e Teste de Estilo (o visagismo está fora da migração, na ordem
  de 27/09/2026), Configurações › Aplicativo › Push (a resposta 76 c) e Configurações › Assistentes VIP, a administração
  (a resposta 144 a). O "Assistentes VIP" de uso continua no Console: ele entra na tela nova.
- **"Agendas" volta ao menu de quem só tem "Agendas visualizar".** A guarda do `layout.phtml:164` é "gerenciar ou
  visualizar", e a cópia do menu tinha só o primeiro. Na cópia de desenvolvimento, contadas pelas linhas de privilégio
  de cada pessoa (é o que a sessão lê, e não o molde do grupo), 176 das 356 pessoas ativas que não administram não viam
  o item (171 das 180 do grupo Colaborador); 174 delas têm o "Agendas visualizar" e voltam a vê-lo, e 2 não têm nenhum
  dos dois e seguem sem ele, como no ERP antigo. A agenda nova lê com o mesmo `agendas/visualizar/ler`.
  O `ItemDeMenu` ganhou a guarda em lista, que quer dizer "qualquer um de" (`temOPrivilegio`, uma conta só para o menu
  e para o Console). O conjunto do Colaborador nos testes e nas prévias tinha `gerenciar` no lugar de `visualizar`, e
  por isso nenhum caso via o defeito; os dois foram acertados pela medição.

**Débitos:** o D57 DECIDIDO (a 144 a: o uso entra, a administração fica na tela antiga, já consertada em 04/10/2026), o
D347 DECIDIDO (a 101 c: a senha dada por outra pessoa fica como hoje) e o D105 DECIDIDO e PARCIAL (a 120: os seis mortos
ganham destino nas fatias 21, 26, 41, 90 e 91 da fila, e cada uma tira a sua entrada do `conferir-links-mortos.php`).
Nenhum débito novo: os dois que a fila previa (a tela sem caminho para a antiga e a guarda de Agendas) fecharam aqui.

**Os casos novos**, verdes, e cada um vermelho com o mutante:

| caso | o que prende |
|---|---|
| `menu-do-erp.teste.ts`: a guarda de Agendas é a do layout | lê o `if` em volta do item no `layout.phtml` e compara com a lista do menu |
| `menu-do-erp.teste.ts`: a guarda em lista passa com qualquer um | o `temOPrivilegio`, "qualquer um de" e nunca "todos" |
| `menu-do-erp.teste.ts`: o que abre em aba nova, cada um pelo seu motivo | reescrito: os seis `novaAba`, com o motivo de cada um (o `_blank` do layout ou a decisão do dono) |
| `Console.teste.tsx`: 29 destinos caem na tela não migrada | abre o Console em cada destino que o menu entrega a ele (44) e conta; todos com o link no endereço do item |
| `Console.teste.tsx`: o endereço e o link da ponte | o `href`, o `target="_blank"` e o `noopener`; fora do menu, sem link |
| `Console.teste.tsx`: o que fica na versão antiga abre lá | o Consultor Visagista é link em aba nova, e o Console fica na agenda |
| `Console.teste.tsx`: Agendas com gerenciar ou com visualizar | e some sem os dois |

**Os mutantes**, um de cada vez, restaurados dos bytes lidos antes (sha1 conferido):

| mutante | vermelho |
|---|---|
| a guarda de Agendas só com `gerenciar` | a guarda do layout; Agendas com gerenciar ou visualizar |
| `temOPrivilegio` com `every` | a guarda em lista; Agendas com gerenciar ou visualizar |
| o `podeVer` da `Moldura` com `includes` | Agendas com gerenciar ou visualizar |
| o Push sem `novaAba` | o caso dos motivos; os 29 (dá 30) |
| o Consultor Visagista sem `novaAba` | o caso dos motivos; os 29; o que fica na versão antiga abre lá |
| o Teste de Estilo sem `novaAba` | o caso dos motivos; os 29 |
| a administração dos Assistentes sem `novaAba` | o caso dos motivos; os 29 |
| a ponte arrancada | o link da ponte; os 29 |
| a ponte sem `target="_blank"` | o link da ponte |
| o endereço também fora do menu | o endereço da ponte |
| o `podeAbrir` do Console e o `naoInformados` da `Moldura` com `includes` | nenhum caso (nenhum item lido ali tem lista); o `tsc` os recusa, TS2345 |

**Provado:** o vitest inteiro, 1.745 de 1.745 em 47 arquivos (`--maxWorkers=4`); o menu, o Console e o `ConsoleLigado`
com `AMPLIFICAR_EFEITOS_MS=50`, 140 de 140; `tsc -p tsconfig.app.json --noEmit`, `npm run build` e `npm run lint` saem 0
(o aviso do `Console.tsx`, `only-export-components` no `descreverTela`, é o de antes). Visto no navegador, num arnês
temporário apagado no fim: o Console do Colaborador mostra "Agendas", os dois itens do Visagismo com `target="_blank"`, e
a ponte da Cota-parte nos dois temas. Os portões `conferir-guarda-do-caixa.php` (lê o menu), `conferir-referencias-docs.php`,
`conferir-links-mortos.php`, `conferir-debitos-repetidos.php` e `conferir-segredos.sh` saem 0; `contar-debitos.php`: 491
débitos, 304 fechados, 61 parciais (o D105 entre eles) e 79 abertos. A prévia da `TelaNaoMigrada` ganhou o endereço, a do
Console troca o colaborador no Visagismo pelo colaborador nos Assistentes (`ColaboradorNosAssistentes`), e o
`_moldura.ts` passou ao `visualizar`; o envio ao Claude Design fica para depois. **Não rodado:** a caracterização (nada
mudou no servidor; os dois casos PHP que leem o `menu-do-erp.ts` leem as constantes do painel e do parceiro, que não
mudaram).

### A verificação (15/10/2026)

Sete achados confirmados, todos verdadeiros no código e todos tratados; um é de teste (o filtro sem caso), os outros de
documento e de comentário. Nenhum muda o que a tela faz.

- **O filtro do endereço de módulo da ponte não tinha caso.** Em `descreverTela`, o `/^\/mod-[a-z]+\//` impede o link
  para o que está no menu e não tem versão antiga num `/mod-…` (o painel, o Profissional Parceiro, a agenda em "/", o
  Docs APIs e o Yungas); arrancado, nada ficava vermelho, porque hoje nenhum deles chega à tela não migrada. Caso novo no
  `Console.teste.tsx` ("o destino do menu que não é endereço de módulo não ganha a ponte"), com a lista derivada do menu
  e conferida como controle. Mutantes: o filtro arrancado e o filtro trocado por "começa com barra", vermelhos só nele.
- **A conta de quem não via "Agendas" era a do molde do grupo, e a sessão lê as linhas de cada pessoa** (o `SessaoAcl`
  monta a ACL com `privilegios_usuarios`). Recontada na cópia, só SELECT: das 356 ativas que não administram, 176 não
  viam o item, 174 voltam a vê-lo e 2 seguem sem ele (sem nenhum dos dois privilégios, também no ERP antigo); nenhuma
  tem só o `gerenciar`. O conserto continua o mesmo; mudaram o docblock do `temOPrivilegio`, os comentários dos dois
  testes, a nota do consolidado e esta entrada.
- **O D57 punha na conta do dono a condição do Zapier**, que era da recomendação da pergunta 144: o dono marcou (a) com a
  observação vazia, e a desativação não ficou em aberto. E o uso não tem a F6 (as páginas públicas, a P15, a fatia 89):
  é a F1b, a F2, a F3 e a F7, as fatias 86 a 88. O texto do D57 diz isso agora.
- **O desenho dos Assistentes não tinha a resposta:** o topo, a 1.2 ("hoje as duas abrem a `TelaNaoMigrada`"), a 1.5, a
  tabela das fatias, a F4, a F5 e a P1 dizem agora que a P1 foi respondida B (a 144 a), que a administração abre a tela
  antiga em aba nova e que a F4 e a F5 não se fazem. No consolidado, a P7 e a P14 ganharam o "Respondida em", e as linhas
  das telas 13 e 14 e da seção 3 dizem o que a P14 destravou.
- **A prévia do Console dizia que o menu confere 39 privilégios;** são 40 desde a guarda de Agendas em lista, e a lista do
  administrador ganhou o `agendas/visualizar/ler`.

Junto: a nota da fatia no consolidado tinha entrado como linha nova e empurrado em uma linha as referências a ele (o
`telas-consolidado.md:373` do `README.md` desta pasta apontava para uma linha em branco); voltou para dentro da linha de
cima, e as respostas novas entraram nas linhas que já existiam, nos dois desenhos (414 e 724 linhas, como no HEAD). A
citação da 120 no D105 voltou à letra do dono.

**Provado:** o vitest inteiro, 1.746 de 1.746 em 47 arquivos (`--maxWorkers=4`); o menu, o Console e o `ConsoleLigado`
com `AMPLIFICAR_EFEITOS_MS=50`, 141 de 141; `tsc -p tsconfig.app.json --noEmit`, `npm run build` e `npm run lint` saem 0;
`conferir-guarda-do-caixa.php`, `conferir-referencias-docs.php`, `conferir-links-mortos.php`,
`conferir-debitos-repetidos.php`, `contar-debitos.php` (491 débitos, 304 fechados, 61 parciais, 79 abertos) e
`conferir-segredos.sh` saem 0, e o `git diff --check` também.

## 15/10/2026 -- PERGUNTA-NO-BOTAO

O pedido do dono, textual: *"continue, desta vez montando as telas que ainda não foram migradas. continue de forma
autonoma até a exaustão"*. É a fatia 2 da fila das telas que faltam, e é só de front: nada de PHP, banco ou rota. Ela
prepara as telas seguintes (avisos, VIP Class, assistentes, sorteios, banners...), que perguntam no botão e escolhem
"todas ou as marcadas".

**O que muda para quem usa:** pouco, e de propósito, só nas linhas -- os pacotes do cliente (fidelidade), as
movimentações do estoque e os serviços do pacote. São três coisas, todas no D494:

- O **duplo clique no "não" voltava a perguntar**: o "não" sai da célula, o botão da linha fica debaixo do cursor e
  recebia o segundo clique. Visto no navegador, na fidelidade e no estoque; o clique seguinte caía de novo no "não", e
  não cancelava nada -- era a pergunta que não fechava. Agora o segundo clique do duplo não faz nada, como já era no
  "desativar" dos convênios, cupons e usuários.
- O **duplo clique no "confirmar" com a recusa rápida**: o segundo clique apagava a recusa e voltava a perguntar; agora
  a recusa fica, e o botão em "cancelar" (deduzido do código, e fixado num caso da fidelidade).
- A **linha que perguntou e saiu da tela** -- a página seguinte do estoque, o lançamento, o "só os com sessões" da
  fidelidade -- voltava perguntando, a um clique de cancelar; agora volta em "cancelar". Já era assim antes da fatia, e
  a verificação o achou.

**As peças novas** (`frontend/src/telas/`):

- `perguntaNoBotao.ts`: o `usePerguntaNoBotao` (qual linha pergunta, o clique que pergunta ou age, o "não" que devolve o
  foco, a pergunta que se desfaz quando o estado relido muda -- o do registro no botão único, o da lista nas linhas), o
  `semRepeticao` (o Enter seguro) e o `cliqueRepetido`. As seis cópias -- `Convenios.tsx`, `Cupons.tsx`,
  `Usuarios.tsx`, `Pacotes.tsx`, `Estoque.tsx` e `FidelidadeDoCliente.tsx` -- passaram a ela, e o botão de estado do
  pacote, que não pergunta, ao `cliqueRepetido`. O estado da pergunta é um valor simples, no tipo. A pergunta não se
  desfaz pelo tempo nem pelo Escape: nenhuma das seis o fazia, e um caso diz isso.
- `escolhaDoVinculo.ts` e `TodasOuAsMarcadas.tsx`: o "todas ou as marcadas" dos convênios (o D256), a parte pura e o
  formulário, com as frases e os ids de cada tela; duas escolhas cabem no mesmo formulário (as unidades e os grupos dos
  avisos). As marcadas partem do `camposDoVinculo` -- as ligadas, as inativas também --, e não de cada tela. O
  `formularioDoConvenio.ts` e o formulário dos convênios as usam. No navegador, o HTML do "onde vale" e o da pergunta
  do "desativar" saíram iguais aos de antes, menos o `id` do filtro das unidades.

**Os testes:** os casos que já existiam nas seis telas passam sem mudança. Novos: 13 da peça da pergunta, 7 da escolha
do vínculo e 7 do formulário dela, e oito nas telas -- um em cada tela das linhas (a recusa desfaz a pergunta, e
perguntar de novo tira a recusa da vez anterior), dois nos cupons (o "atualizar" e a recusa que não é conflito desfazem
a pergunta), um no estoque (a pergunta sai ao virar a página e ao lançar) e dois na fidelidade (a recusa rápida e a
troca da lista).

**Os mutantes da fatia**, um de cada vez, restaurados e conferidos pelo hash:

| mutante | vermelho |
|---|---|
| o `detail > 1` arrancado da peça | 10 casos, nas seis telas e na peça |
| a regra antiga das linhas na peça (`detail > 1` só na linha que pergunta) | o duplo clique no "não", a ação que não pergunta, e o "não reativa" dos convênios |
| sem desfazer a pergunta quando o estado relido muda | o "atualizar desfaz a pergunta" dos convênios e dos usuários, o "relida inativa" dos usuários e o da peça |
| o "não" sem devolver o foco | 6 casos, em cinco telas e na peça |
| o `semRepeticao` vazio | 8 casos, nas seis telas e na peça |
| perguntar noutra linha sem desfazer a primeira | "uma de cada vez", da peça |
| ativar perguntando, nos convênios | "ativar o inativo não pergunta" |
| a pergunta que fica depois de agir, nos usuários | o 403 dos usuários |
| editar sem desfazer a pergunta, nos cupons | "editar desfaz a pergunta" |
| a pergunta que fica depois da resposta, no estoque e nos pacotes | a recusa do estoque ao virar a página; o caso novo dos pacotes |
| a recusa que fica ao perguntar de novo, no estoque, nos pacotes e na fidelidade | o caso novo de cada uma (antes, nenhum) |
| as opções sem as ligadas inativas | 10 casos: convênios, a escolha e o formulário dela |
| a escolha que falta não recusada | 5 casos: convênios, o formulário do convênio e a escolha |
| a lista sem ordem, no corpo e na alteração | 11 e 6 casos |
| o Enter do filtro que envia; o "tentar de novo" sem o foco | o caso dos convênios e o do formulário, em cada um |
| o `id` do filtro fixo; o selo "inativa" fixo | as duas escolhas no mesmo formulário |

**A verificação em três lentes** confirmou 9 achados (dois deles o mesmo), todos tratados: o D494 contava um caso só da mudança que se vê, e
dizia que o clique seguinte cancelava -- era dedução, e o código a desmente --; o estado da pergunta aceitava qualquer
coisa (o objeto literal travaria a tela em laço, e a função viraria inicializador); a peça não fixava o `'perguntou'`
nem o teclado; os cupons não tinham caso para as duas ligações da pergunta; as marcadas iniciais da escolha do vínculo
ficavam a cargo de cada tela; o botão de estado do pacote tinha uma sétima cópia da guarda; e, nas linhas, a pergunta
sobrevivia à lista. Os mutantes dela, um de cada vez, conferidos pelo hash (os 14 vermelhos):

| mutante | vermelho |
|---|---|
| o `'perguntou'` devolvido como `null` | 5: os dois casos novos da peça (o passo e o teclado) e o da recusa em cada tela das linhas |
| o clique do teclado (`detail` 0) tomado por repetido | 7: o do teclado na peça, cinco da fidelidade e o Enter seguro dos usuários |
| o estado da pergunta de volta a `unknown` | o `tsc` sai 2: as duas diretivas do caso da peça sobram |
| os cupons sem o estado relido | "atualizar desfaz a pergunta", o novo dos cupons |
| os cupons sem desfazer a pergunta ao agir | "a recusa que não é conflito desfaz a pergunta", o novo dos cupons |
| o `camposDoVinculo` só com as ligadas ativas; sem nenhuma | 19 e 16 casos: os convênios, o formulário do convênio, a escolha e o formulário dela |
| o botão de estado do pacote sem o `cliqueRepetido`; sem o `semRepeticao` | "desativar: o duplo clique e o Enter seguro não desfazem", nos dois |
| o `cliqueRepetido` sempre falso | 14 casos em 7 arquivos: as seis telas, o botão do pacote e a peça |
| o estoque sem o `relendo`; a fidelidade sem o "todos" | o caso novo de cada um |
| a peça sem desfazer pelo estado | 8: o "atualizar" dos convênios e dos usuários, o "relida inativa" dos usuários, os dois da peça (o botão e as linhas) e os novos dos cupons, do estoque e da fidelidade |
| a regra antiga das linhas na peça | 4: os três de antes e o da recusa rápida da fidelidade |

Voltar à regra antiga só nas linhas não é uma linha na peça: os botões de estado precisam da regra inteira (o "não
reativa" dos convênios cai), e seria uma opção por chamada.

**Provado:** o vitest inteiro, 1.781 de 1.781 em 50 arquivos (`--maxWorkers=4`); `tsc -p tsconfig.app.json --noEmit`,
`npm run build` e `npm run lint` saem 0 (os 6 avisos do lint são os de antes, nenhum nos arquivos tocados). Com
`AMPLIFICAR_EFEITOS_MS=50` nos 14 arquivos de caso tocados: 514 de 526, e os 12 vermelhos são do D471, nos mesmos três
arquivos, sem nenhum caso novo entre eles -- na HEAD, os mesmos arquivos dão 13 (o D471 foi relido com os números).
**Não rodado:** a caracterização e os ensaios (nada mudou no servidor). As prévias do Claude Design não mudam: as telas
são chamadas do mesmo jeito.

## 15/10/2026 -- REGRA-DO-TEXTO

O pedido do dono, textual: *"continue, desta vez montando as telas que ainda não foram migradas. continue de forma
autonoma até a exaustão"*. É a fatia 3 da fila das telas que faltam, e é só de servidor: a regra do texto de uma vez
para todas, que as 14 regras novas das telas que faltam vão usar (o item 5 do que se faz uma vez para todas, em
`docs/f3-telas-restantes/telas-consolidado.md`), e o primeiro uso dela, a S1a das etiquetas (o D344, agora parcial).

**O que muda para quem usa:** o nome de um item do catálogo ou de um pacote, pela tela nova, deixa de aceitar `<`, `>`,
a quebra de linha e a tabulação; a recusa aparece no campo Nome, com a frase "Sem < nem >: o nome aparece cru em telas
do ERP." ou "Sem quebra de linha nem tabulação.", como já era nos convênios e nos cupons. Ninguém fica preso: na cópia,
nenhum dos 9.509 nomes tem `<` ou `>`, e dos dez com tabulação (de 2016 a 2020) os quatro ativos a têm só nas pontas,
que a tela apara antes de mandar; os quatro com tabulação no meio estão inativos, e se editam sem reativar: quem mudar o
nome de um deles recebe a recusa até tirar a tabulação. *(A primeira versão deste parágrafo dizia "a tela antiga de
serviço e de produto não muda"; desde a verificação, abaixo, ela tem a mesma regra.)*

**O que mudou por dentro:**

- A peça é a `ISBase\Regra\RegraDoTexto` (`module/ISBase/src/Regra/RegraDoTexto.php`): `conferir` (de 1 a N caracteres
  depois do `trim`, sem caractere de quatro bytes, sem `<` nem `>`, sem controle (do ASCII; do Unicode desde a
  verificação), procurado antes do `trim`; a
  frase do primeiro motivo que falha, e a tela pode dizer a sua) e `chavesQueNaoSeAlteram` (o 422 da chave com o nome
  dela). O catálogo e os pacotes passam a usá-la; o convênio, o cupom e a pesquisa trocaram a cópia pela peça sem
  mudar o que aceitam, e os casos deles passam como estavam. Os `CABE_NA_COLUNA` dos filtros do ERP (convênio, cupom,
  pesquisa, mensagem, assistente) e o `SEM_SINAL_DE_TAG` da pesquisa são os da peça.
- O escape do Gerenciar e da lista das etiquetas já estava no ar desde 04/10/2026 (a varredura do D203), sem caso; o
  `TelasDasEtiquetasDoErpTest` o prende, com uma marca por campo.
- A `docs/convencao-api.md` diz a regra do texto e a da chave, na seção do corpo do erro.

**Os mutantes**, um de cada vez, no MySQL, restaurados da cópia e conferidos byte a byte (os 23 vermelhos):

| mutante | vermelho |
|---|---|
| a peça sem o `<` e o `>` | 13: o `RegraDoTextoTest`, os novos do catálogo, do pacote e da API, e os do convênio, do cupom e da pesquisa |
| a peça sem o controle | 18, nos mesmos |
| a peça sem o emoji | 10: o `RegraDoTextoTest` e os do catálogo, do pacote, do convênio, do cupom e da pesquisa |
| o emoji comparado com `=== 1` (o UTF-8 inválido passaria) | o caso do UTF-8 inválido |
| o teto com `>=`; sem o `trim`; em bytes | 7, 5 e 5 |
| o controle procurado no texto aparado | 2: o controle na ponta, na peça (a tabulação) e no pacote (a quebra) |
| o vazio sem o `trim` | 8 |
| a frase de quem chama ignorada (só a do `<`: a do controle sobrevivia, ver a verificação) | 10 |
| a chave sem a frase dita; nenhuma chave recusada | 6 e 6: a peça e as cinco regras |
| o convênio sem as frases do nome; o cupom sem a do teto; a pesquisa sem a do `<`; o catálogo sem a do estado | 1, 1, 2 e 1 |
| o catálogo e o pacote de antes da fatia (o vão do D344) | 3 e 2: os novos da regra e os da API |
| cada um dos cinco escapes das duas views | o caso da view, cada um |

**Provado:** a caracterização dos arquivos tocados e dos vizinhos (as regras, as telas antigas do convênio, da pesquisa
e dos pacotes, as APIs de convênio e de produto, o `D201`), 352 de 352 no MySQL e 352 de 352 no PostgreSQL, com os 34
casos novos (`RegraDoTextoTest`, `D344NomeComSinalDeTagNaApiTest`, `TelasDasEtiquetasDoErpTest` e um em cada regra);
o vitest inteiro, 1.783 de 1.783 em 50 arquivos (`--maxWorkers=4`), com um caso novo no catálogo e um nos pacotes;
`tsc -p tsconfig.app.json --noEmit`, `npm run build` e `npm run lint` saem 0 (os 6 avisos de antes). Com
`AMPLIFICAR_EFEITOS_MS=50` nos dois arquivos de caso tocados, 167 de 177: os 10 vermelhos são do D471, nenhum dos dois
novos (que esperam a frase, e não o foco). Os portões que a fatia toca saem 0: referências dos docs, débitos repetidos,
contar débitos, links mortos, cobertura por par, bytes de controle, relógio nos testes, fósseis, privilégio da API
nova, guarda do ERP, recorte por unidade, escritores do horário, remoção por POST, scripts servidos e segredos.
**Não rodado:** o ciclo inteiro, os ensaios HTTP e o contrato (o ensaio de recusas não tem caso do nome com `<`; a prova
pela API é a em processo).

**Na verificação do mesmo dia** (três leitores sobre a fatia; onze achados, todos confirmados no código e corrigidos):

- **A tela antiga gravava o `<` também.** O D344 dizia que o produto e o serviço do ERP passavam o nome pelo `StripTags`;
  o filtro valia só na conferência, e o `novo` e o `editar` do produto, do serviço e do pacote entregavam o POST cru ao
  serviço -- `Pomada <img src=x onerror=alert(1)>` passava como "Pomada" e ia ao banco inteiro. Os três formulários
  passaram a ter a regra da API (`RegraDoTexto::validadoresDoFormulario`, sem o `StripTags`, que cortava calado) e a
  gravar o nome do `getData()`, aparado. **O que muda para quem usa a tela antiga:** o nome com `<`, `>`, tabulação no
  meio, emoji ou mais de 100 caracteres é recusado no campo, com a frase da tela nova (antes, o `<` e o `>` sumiam da
  conferência e iam ao banco, e o nome de 101 caracteres dava 500 no MySQL -- o mutante do teto o mostrou); o nome é gravado sem os espaços das pontas.
- **O formulário e a API discordavam no controle**: a tela antiga gravava a tabulação no meio que a tela nova recusava
  ao alterar. Os validadores do formulário são os cinco motivos da API; o convênio e o título da pesquisa ganharam o do
  controle.
- **O controle era só o do ASCII**: o NEL e o resto do C1, o U+2028 e o U+2029 e os que invertem a direção do texto
  passavam. A peça os recusa; na cópia, nenhum nome de item, convênio ou cupom, nem título ou pergunta de pesquisa, os
  tem. E o nome feito só de espaço do Unicode ou de invisível (U+00A0, U+3000, U+FEFF) passou a ser vazio.
- **A chave em UTF-8 inválido** num corpo de formulário dava o 422 sem corpo; a peça a devolve com `?` no byte inválido.
- **O texto de várias linhas**: os avisos e os assistentes a mostram (6 dos 74 avisos e 1 das 4 descrições da cópia),
  e a nota do consolidado dizia que nenhum desenho a pedia. A peça ganhou o `conferirComQuebras`.
- **A frase de quem chama no controle** não tinha caso; cada motivo tem o seu.
- **Os documentos**: o D344 dizia "só quem reativar" (o inativo se edita sem reativar) e a convenção dizia que todo
  texto da API passava pela peça. Seis não passam; registrados no D495.

**Os mutantes da verificação**, um de cada vez, no MySQL, restaurados da cópia e conferidos com `cmp` (33
vermelhos de 33):

| mutante | vermelho |
|---|---|
| a peça só com o controle do ASCII; sem o C1; sem o U+2028 e o U+2029; sem o `Bidi_Control` | 13, 4, 3 e 8 |
| o `$` no lugar do `\z` (a quebra no fim passaria) | 2 |
| o vazio só pelo `trim`; o "algo que se vê" falhando aberto no UTF-8 inválido | 8 e 1 |
| a frase de quem chama ignorada no obrigatório, no teto, no emoji e no controle | 2, 4, 6 e 1 |
| a chave sem o `mb_scrub` | 2: a peça e a API |
| o de várias linhas conferido como o de uma; aceitando a tabulação | 3 e 3 |
| o formulário sem o controle; sem o "algo que se vê"; sem parar no primeiro; sem a frase de quem chama | 29, 2, 2 e 1 |
| o formulário com o teto um acima | 3 falhas e 33 erros: o `INSERT` de 101 caracteres dá 500 no MySQL (`Data too long`) |
| o filtro do produto, do serviço ou do pacote sem a regra; com o `StripTags` de volta | 2 cada |
| o `novo` e o `editar` de cada uma das três telas gravando o POST cru | 1 cada, os seis |
| o convênio e o título da pesquisa sem o controle no filtro | 1 e 1 |

**Provado:** a caracterização das regras, das telas antigas e das APIs vizinhas (o filtro das regras do texto, do catálogo, do pacote,
do convênio, do cupom, da pesquisa, da mensagem e do assistente, as telas do ERP, o `D202`, o `D201`, o `D314` e as APIs
de produto e de convênio), 965 no MySQL e 965 no PostgreSQL, com 1 pulado nos dois (o de sempre: o ativar das mensagens,
no `D199`); `RegraDoTextoTest` com 86 casos, `D344NomeNaTelaAntigaDoCatalogoTest` (novo) com 18 e
`D344NomeComSinalDeTagNaApiTest` com 6. Os portões que a verificação toca saem 0: referências dos docs, débitos
repetidos, contar débitos (o D495 entra aberto), links mortos, cobertura por par, bytes de controle, relógio nos testes,
fósseis, guarda do ERP, recorte por unidade, rotas sem login, remoção por POST, privilégio da API nova, escritores do
horário, scripts servidos, barreira de origem e segredos.
**Não rodado:** o ciclo inteiro, os ensaios HTTP e o contrato; o front não mudou (as frases são as mesmas), e o vitest
não foi rodado de novo. Nada foi visto no navegador.

## 15/10/2026 -- RESPOSTAS-DO-BALCAO

O pedido do dono, textual: *"Faça o commit e push. Continue para a fatia 4"*. É a fatia 4 da fila das telas que faltam,
sem tela nova: três respostas do dono no servidor, com o que a tela nova e a antiga mostram delas.

**O que muda para quem usa:**

- **A comissão do corte aberto pela agenda soma a extra do barbeiro** (a resposta 81, opção "a"; o D465): como no PDV
  antigo e na comanda nova. O mesmo corte, pelo mesmo barbeiro, rende a mesma comissão pelas três portas. Na cópia, 11
  extras em serviço, de 5 barbeiros.
- **A agenda grava "Convênio X" no item do convênio** (a resposta 90, opção "b"; o D363), como o PDV. Na comanda nova, a
  linha que veio da agenda deixa de mostrar o "desconto" do balcão já preenchido com o do convênio (o "aplicar" o somava
  de novo: o desconto em dobro), e o "+" e o "−" passam a recalcular o convênio, em vez de recusar por "desconto sem
  motivo". A 177 (c) vale nela: o desconto do balcão leva os dois motivos, e o "+" e o "−" recusam.
- **A quitação de fiado e a compra de crédito pela tela antiga recusam o caixa aberto em outro dia** (a resposta 88,
  opção "a"; o D232 e o D280), com a mesma frase do PDV: "O caixa foi aberto no dia dd/mm/aaaa, para realizar uma nova
  venda você precisa fechar o caixa e abrir novamente." O "Quitar" mostra a frase no modal; a página "Comprar Crédito"
  nem abre, e leva ao caixa, como a venda. Na cópia, 154 das 971 quitações e 269 das 5.054 compras de crédito caíram
  num caixa de dia anterior. As 16 quitações em dobro de 2018 a 2023 ficam.

**O que mudou por dentro:** a agenda (`VendaService::comandaAbrirAgenda`) deixou a sua cópia da comissão e chama a do
lançamento (`VendaProdutoService::calcularComissao`, pública); o motivo do convênio é uma conta só,
`RegraDoItemDaComanda::motivoDoConvenio`, para as duas portas; a frase do caixa de outro dia é
`RegraDoCaixaDoDia::fraseDaTelaAntiga`, para o PDV, a quitação e a compra. O `conferir-recorte-por-unidade.php` ganhou
duas vigias (o bloco da recusa nas duas ações antigas).

**Fica de fora, com pergunta:** a agenda dá o convênio em qualquer item marcado -- a lavagem, o produto da barbearia e
o do empório --, e o caixa só no serviço (o D496, no lote; na cópia, desde 2025, 5 horários de produto, 1 de cliente com
convênio, e nenhum de lavagem desde 2020). Até a resposta, a comanda nova recusa o "+" e o "−" nessa linha, em vez de
tirar o desconto calado. A compra de crédito do ERP segue sem a trava da API (o D381).

**A verificação da fatia** (três lentes, 6 achados confirmados, todos baixos; 4 distintos, dois deles achados duas vezes) mudou duas coisas
para quem usa, na comanda nova:

- **A linha da lavagem ou do produto que veio da agenda com o convênio recusa o "+" e o "−"** ("veio da agenda com o
  desconto do convênio, que o caixa não dá neste item"): o servidor recalcularia pela conta do caixa e o desconto
  sumiria. A comanda a marca (`convenioQueOCaixaNaoDa`); a regra do tipo mora em `RegraDoItemDaComanda::oCaixaDaOConvenio`,
  que a conta do lançamento passou a ler. O D496 e a pergunta foram reescritos para o alcance inteiro (não só a lavagem).
- **A linha que a agenda gravou sem motivo antes da troca, numa comanda ainda aberta, é a do convênio**: sem o
  "desconto" que somava o dele de novo (o dobro do D363 seguia aberto nela, e a frase do "+" levava o operador para lá),
  e com o "+" que recalcula e grava o motivo. A comanda a marca (`convenioSemMotivo`); com o desconto acima da parte do
  convênio de hoje, ela vem marcada como a do balcão, e o "+" recusa. O D363 segue CORRIGIDO, com o corpo dizendo o que
  fica (a comanda que atravessa para um dia sem convênio, como toda linha do convênio); o D465 diz que essas linhas
  ficam com a comissão do lançamento até um "+" ou "−".

**Os mutantes**, um de cada vez, no MySQL, restaurados e conferidos pelo sha1 (todos vermelhos):

| mutante | vermelho |
|---|---|
| a agenda de volta à comissão sem a extra | 3 |
| a agenda sem o motivo do convênio | 4 |
| o motivo do convênio sem o corte em 100 | o caso do nome longo (o MySQL estrito derruba a comanda), na agenda e no `ConvenioDescontoTest` |
| a compra de crédito sem a recusa; só no POST | 2; 1 |
| a quitação sem a recusa | 1 |
| a frase trocada pela da API; o PDV com outra frase | 4; 1 |
| no portão: a consulta arrancada, o `if` falso, a frase trocada | sai 1 em cada |

**Provado:** `D363D465ComandaPelaAgendaComoOPdvTest` (novo, 14 casos; 24 depois da verificação) e `CaixaNaoAtravessaODiaTest` (6 casos novos,
despachados em processo), com os vizinhos (a comanda, o convênio, o fiado, a fidelidade, as sessões do pacote, o caixa,
as telas antigas do PDV): 664 no MySQL e 664 no PostgreSQL, em série. O front mudou só num comentário
(`ajusteDaLinha.ts`): tsc e lint saem 0. O `conferir-tudo.sh` (65 portões) saiu com dois vermelhos, consertados e
rodados de novo sozinhos (saem 0): o relógio nos testes (a data da frase esperada lia `date` e `strtotime`) e o
inventário das ações (as duas ações antigas passaram a chamar o `VendaPagamentoService`; o `inventario-acoes.csv` foi
regerado). Os outros saem 0, menos o vermelho conhecido do agendamento (o D50); entre eles o recorte por unidade (205
métodos), os débitos repetidos (o par D363|D496 registrado, com o motivo; o D477|D496 saiu quando a verificação reescreveu o título do D496) e o contar débitos.
**Não rodado:** o ciclo inteiro, os ensaios HTTP e o contrato. Nada foi visto no navegador.

**Depois da verificação:** a API nova da comanda ganhou as duas marcas da linha (`convenioSemMotivo`,
`convenioQueOCaixaNaoDa`), e o front, a recusa e a linha do convênio sem motivo (`ajusteDaLinha.ts`, com 2 casos no
`ComandasLigada.teste.tsx`). Os mutantes da verificação, um de cada vez e restaurados pelo sha1, todos vermelhos: as
duas marcas sempre falsas (2 e 3); o valor ignorado na linha sem motivo (1); sem o tipo (1); com o convênio desligado
(1); o motivo da agenda só no serviço (4); o caixa dando em todo tipo (4); a conta do lançamento em todo tipo (1); no
front, sem a recusa (1) e sem a linha sem motivo (1).

## 15/10/2026 -- RESPOSTAS-DE-15-10

O pedido do dono, textual: *"respondido, continue"*. É a fatia 4b da fila das telas que faltam, sem tela nova e sem
bloco na fila: as respostas do dono de 15/10/2026 que pediam código (a 227 e a 226) e as seis do corte (204 a 209), que
pediam o documento.

**O que muda para quem usa:**

- **A comanda aberta pela agenda dá o convênio só no serviço, como o caixa** (a resposta 227, opção "a"; o D496). A
  lavagem e o produto marcados no horário do cliente de convênio entram pelo preço, como no PDV antigo e na comanda nova:
  o mesmo produto sai igual pelas três portas. Na cópia, desde 2025, a agenda teve 5 horários de produto e nenhum de
  lavagem. Na comanda nova, a recusa do "+" e do "−" fica para a linha já gravada com o convênio fora do serviço, e a
  frase deixou de dizer "veio da agenda": "tem o desconto do convênio, que o caixa não dá neste item".
- **As marcações antigas do aplicativo (o V2 e o V3) recusam o colaborador de unidade inativa** (a resposta 226, opção
  "b"; o D482), com a mesma resposta do número que não acha ninguém, "Informe o colaborador.", como a marcação nova.
  Nada muda para o aplicativo instalado (ele não chega lá); fecha o pedido montado à mão. Vale com as dez rotas abertas e
  fechadas.
- **O procedimento do corte da etapa 1 aplica as seis respostas do corte** (todas (a)): o backup da janela é o dump da
  rotina, disparado à mão, guardado fora do servidor até o fim da volta, com a restauração ensaiada antes (a 204); a
  memória do MySQL e os três do php-fpm entram na janela (a 205); as entregas depois saem à noite, como a janela (a 206);
  depois de um recuo do crontab, o novo entra na noite seguinte (a 207); as duas guardas das dez rotas ficam, e a conta
  zero não volta ao dono (a 208); e o retrato dos royalties pulado se refaz à mão no mesmo dia (a 209). Uma pergunta nova
  do corte foi ao lote, da verificação: o teto curto do pool do site contra a volta que não desfaz o php-fpm (o D497).

**O que mudou por dentro:** `VendaService::comandaAbrirAgenda` lê `RegraDoItemDaComanda::oCaixaDaOConvenio`, a regra do
lançamento; `AplicativoController::semOColaboradorDaUnidadeInativa` (a regra do D482, `Usuario::daUnidadeInativa`) no V2 e
no V3, nos dois estados do interruptor `ROTAS_DO_APLICATIVO_INSTALADO`, com três vigias novas no
`conferir-recorte-por-unidade.php`. No `docs/procedimento-de-corte-etapa-1.md`: o passo 3 reescrito (o dump da rotina, a
cópia de fora e a restauração), o começo do passo 4 (a memória), o pool das rotinas no passo 8, o bloco da noite seguinte
ao recuo no passo 12, o bloco do retrato à mão na linha do tempo, as entregas à noite em "Depois da janela", o item 14 de
"O que falta" (os dois ajustes escritos para o servidor de lá), a volta, e a tabela das sete decisões.

**O que a marca da comanda ainda alcança** (a nota da rodada): além da linha que a agenda gravou com o código da fatia 4
(que não chegou à produção), **uma linha nova**: o motivo digitado no balcão que começa por "Convênio " num item que não é
serviço, com desconto. Sem a marca, o "+" tiraria o desconto digitado calado. Na cópia, **nenhuma**: o prefixo se
compara pelos bytes, e as 20.997 linhas que começam por "Convênio " são todas de serviço. A primeira escrita desta
entrada dava 7 (`probar`, a última de 03/10/2022), contadas pela collation da coluna, que ignora caixa e acento: elas
começam por "convenio", "CONVENIO" ou "Convenio", não vêm marcadas, e o "+" devolve o desconto delas (a verificação).

**Fica de fora:** os quatro fatos do backup que a pergunta 204 pedia (se uma restauração já foi
testada; o minuto, o usuário e o comando do `dumpv3.sh`; se ele leva os eventos e a posição do log binário; onde ele
grava) não vieram com a resposta, e são do dono: a leitura 14 do procedimento os pede, e o passo 3 só se escreve com o
comando de lá com eles. O destino de fora do servidor, também do dono. Nada disso é escolha, e não foi ao lote. **E o
roteiro da restauração num primário com três réplicas**, que é escrita do repositório (o GTID, a replicação ou as
réplicas refeitas) e pede as leituras 1 e 21: não foi escrito, e a primeira escrita desta entrada não o listava aqui. Os
três ganharam débito na verificação, o **D498**.

**Os mutantes**, um de cada vez, no MySQL das fixtures, restaurados e conferidos pelo sha1 (todos vermelhos):

| mutante | vermelho |
|---|---|
| a agenda sem a regra do caixa (o convênio de volta a todo tipo) | 5 |
| a marca da comanda sempre falsa | 3 |
| o ajudante do D482 que nunca recusa | 6 |
| o V2 sem a chamada | 3, e o `conferir-recorte-por-unidade.php` sai 1 |
| o V3 sem a chamada | 3, e o portão sai 1 |
| o ajudante só com as dez abertas | 2 (os do estado fechado) |

**Provado:** a caracterização dos cinco arquivos tocados e vizinhos (`D363D465ComandaPelaAgendaComoOPdvTest`,
`RegraDoItemDaComandaTest`, `RotasAntigasDoAplicativoTest`, `DezRotasDoAplicativoInstaladoTest`,
`D351UnidadeDesativadaTest`): 129 no MySQL e 129 no PostgreSQL, em série. O front: vitest 1.785 de 1.785
(`--maxWorkers=4`), build e lint saem 0; com `AMPLIFICAR_EFEITOS_MS=50`, o `ComandasLigada.teste.tsx` dá 8 vermelhos que
não são desta rodada (o D471: a versão da HEAD, rodada à parte, dá 7, que variam; o caso da recusa passa na versão nova).
A suíte de contrato, 74 de 74 no MySQL e 74 de 74 no PostgreSQL (o banco de contrato montado antes), e o
`ensaiar-escritas-do-aplicativo.php`, sem sessão, sai 0 nos dois bancos (resíduo zero). Os portões:
`conferir-referencias-docs`, `conferir-passo-zero-do-corte`, `conferir-links-mortos`, `conferir-recorte-por-unidade` (208 métodos), `contar-debitos` e `conferir-debitos-repetidos` saem 0 (o par D482|D483
saiu da lista, porque o título do D482 mudou), e o `conferir-segredos.sh`, 0. D496 e D482 com a marca de CORRIGIDO.
**Não rodado:** o ciclo inteiro, e nada foi visto no navegador.

**A verificação em três lentes confirmou 11 achados** (três deles o mesmo: a contagem do D496), todos tratados:

- **a contagem das "7 linhas" que a marca ainda alcançaria estava errada: são 0.** Ela foi feita pela collation da
  coluna, que ignora caixa e acento; pelos bytes do prefixo, como o `str_starts_with` e o `startsWith` comparam, as
  20.997 linhas com "Convênio " são todas de serviço. Corrigida no comentário do `temOConvenioQueOCaixaNaoDa`, no D496,
  aqui e no caso do PDV; o `RegraDoItemDaComandaTest::testAMarcaForaDoServicoLeOPrefixoPelosBytes` prende a comparação
  (as quatro formas das 7 não são marcadas). E o D496 citava no presente um caso que a fatia apagou;
- **205: o pool do site "com o teto curto" contradizia a volta "sem nada a desfazer"** — com o vhost de antes, as
  rotinas voltariam a um pool que as cortaria toda noite, caladas. É escolha do dono, e foi ao lote (o **D497**); até a
  resposta, o pool do site fica com o teto das rotinas, e a volta segue como a 205 (a);
- **206: as entregas ganharam a hora-limite** — liberadas até 01:10 menos o tempo da volta, ou volta-se à liberação
  anterior, como o limite das 5h da janela; e o que se pula se o cron voltar depois das 01:10;
- **204: o dump de hora em hora que já disparou** não era esperado, e correria junto com o dump à mão e com o `ALTER` do
  passo 4: o passo 2 (e o das entregas) espera agora nenhum dump em curso (o `pgrep` e o usuário do dump), e o passo 3
  compara o tamanho com a última rodada inteira; e o roteiro da restauração com as três réplicas ganhou débito (o
  **D498**);
- **209: o retrato à mão** — pulado passou a ser o crontab que só voltou depois das 05:25, conferido pelo log do nginx (e
  não a liberação depois das 05:25: com o crontab de antes reposto às 05:20, a das 05:25 roda, e uma segunda rodada no
  código antigo, sem trava, gravaria a linha de royalties em dobro); a conferência pelo `data/logs/log_scripts.txt`, que a
  rotina de royalties nunca escreve, deu lugar à tela; e, numa volta na noite de 15 para 16, antes das 09:00 da fatura;
- **208: o item 15 de "O que falta medir"** ainda a dava "no lote".

Provado de novo, em série: os cinco arquivos da fatia, 130 no MySQL e 130 no PostgreSQL (um caso novo); o mutante da
comparação sem caixa e sem acento (a conta da collation) dá 1 vermelho, o caso novo; e os portões
`conferir-referencias-docs`, `conferir-passo-zero-do-corte`, `conferir-links-mortos`, `conferir-recorte-por-unidade`,
`contar-debitos` (142 de pé, os dois novos), `conferir-debitos-repetidos` e `conferir-segredos.sh` saem 0. O front, o
contrato e o ensaio não mudaram de código, e não foram repetidos.

## 15/10/2026 -- DESTAQUES-INSTAVEL

O pedido do dono, textual: *"continue de forma autonoma até a exaustão"*. É a fatia T1 da fila das telas que faltam, um
item técnico sem bloco na fila: o ensaio de permissão que saiu 1 três vezes no MySQL, no melhor da rede do Profissional
Parceiro, e passou sozinho logo depois (o **D499**).

**O que muda para quem usa:** nada que se veja numa tela que funcionava. O melhor da rede, os clientes da rede e o código
do WhatsApp deixam de responder com a página de erro quando o arquivo da trava deles, em `/tmp`, é de outro usuário do
servidor — o que acontecia aqui depois de a suíte rodar como root. Não era a ordem das linhas: o melhor da rede já
desempatava pelo id, e quem não administra nunca viu quem é.

**O que mudou por dentro:** `TravaDeExecucao::obter` abre para ler o arquivo que não abre para escrever, e o `flock`
trava igual — a mesma trava para os dois donos; as aberturas vão sem aviso, e o motivo vai na exceção.
`CrudController::acquireLock`, a cópia dela, passou a delegar. O ensaio de permissão imprime o começo do corpo que não é
JSON. O D98 (a mesma trava envenenada, no cron) registra o que muda para a produção: uma rotina parada no servidor por
uma trava de outro dono volta a rodar quando o código subir — a pergunta foi ao lote (TELAS-RESTANTES [corte]). As
consultas do Profissional Parceiro e do Painel foram lidas pela régua do D72 e do D490: nenhuma escolhe linha sem
desempate.

Provado, em série: a falha reproduzida com o código de antes (a suíte deixou a trava de root, e o ensaio saiu 1 com o
aviso do `fopen` no corpo) e desfeita só trocando o dono do arquivo; o `D499TravaDeOutroDonoTest` (3 casos, sem banco)
verde no MySQL, no PostgreSQL e como www-data, com os vizinhos (132 no MySQL e 132 no PostgreSQL); cinco mutantes
vermelhos, um por vez, e o arquivo do HEAD inteiro vermelho nos três casos; o ensaio de permissão 0 três vezes seguidas
no MySQL, cada uma com o banco de contrato montado de novo e a trava plantada como root, e 0 no PostgreSQL com a mesma
planta, feita como root logo depois do `rm -f /tmp/*.lock` do roteiro (o `medir-ensaios-pg-plantado-t1.sh` é o de sempre
com o `touch` e o `chmod 0644` a mais).

**A verificação em três lentes confirmou 5 achados, todos tratados.** O D499 nomeia quem apagava o arquivo antes de cada
falha: uma rodada parcial do roteiro do PostgreSQL, só com o ensaio das escritas do aplicativo, que apaga as travas e não
recria a do melhor da rede — a suíte do ciclo seguinte a criava como root. A conferência do ensaio era vazia: em
fevereiro de 2026 ninguém concorria ao melhor da rede, e os dez destaques saíam nulos; agora o roteiro dá ao colega da
unidade as vendas do mínimo, apaga-as no fim, e exige concorrente e destaque preenchido, sem o nome. A exceção da trava
leva os dois motivos, o da escrita primeiro — o da leitura sozinho escondia o da criação. Os vizinhos que chamam rotina
proibida (`CronTravaSimultaneaTest`, `FaturasDeRoyaltiesTravaTest`, `WhatsAppQueFalhaTest`) ficam para o ciclo. E a
redação da planta no PostgreSQL ficou como o roteiro é. Provado: um caso novo na trava (a pasta sem escrita) e três
mutantes vermelhos nele, como root e como www-data; no ensaio, a API mutada para publicar o nome deu 1, o ensaio sem o
concorrente deu 1, e a mesma API mutada com a conferência de antes deu 0; o filtro da fatia, 133 testes em cada banco;
o ensaio 0 três vezes no MySQL, com o banco montado de novo e a trava de root, e uma no PostgreSQL, sem resíduo.

## 15/10/2026 -- RESPOSTAS-DO-PROGRAMA

A fatia 5 da fila das telas que faltam (o pedido do dono: "continue"): três respostas dele no programa de pontos e na VIP
Class, sem tela nova, e duas do corte no procedimento da etapa 1.

**O que mudou para quem usa:**

- **Os 10 pontos de quem indicou ficam na unidade da compra do amigo** (a resposta 92 (b), o D312): o saldo e o registro
  vão juntos para a unidade da venda, e o recálculo seguinte não os apaga mais — antes, quando quem indicou era de outra
  unidade, os 10 pontos sumiam. Vale no PDV antigo e na comanda nova, que finalizam pelo mesmo serviço. Os lançamentos
  de antes em outra unidade não se refazem sozinhos (na cópia, 1 ainda válido, de 10 pontos, que o próximo recálculo da
  pessoa tira do saldo dela): a pergunta foi ao lote, e o D312 fica parcial.
- **Só ganha os 10 pontos quem está ativo na hora da compra do amigo** (a resposta 145 (c), o D466), também pelos amigos
  de antes: a mesma condição do convite do aplicativo e da comissão (a resposta 67), numa regra só
  (`RegraDaFidelidade::premiaQuemIndica`), que o próprio convite do aplicativo também pergunta. Quem é reativado volta a
  ganhar na compra seguinte.
- **O curso da VIP Class sem pergunta fica bloqueado até ter checkpoint** (a resposta 119 (a), o D373), na tela antiga: o
  cartão e a página do curso dizem "sem checkpoint", a do checkpoint mostra o aviso no lugar do formulário, e o envio é
  recusado sem gravar. As 23 conclusões que já existem ficam, e o certificado delas segue.
- **No procedimento de corte da etapa 1:** o teto curto do pool do site só depois de o dono declarar o fim da volta,
  numa entrega à noite, e entre o que espera a declaração em "Até quando se volta" (a resposta 228 (a); o D497,
  decidido); e a listagem das travas do servidor antes do corte, a leitura 22, com a decisão do dono sobre uma trava de
  marketing de outro usuário antes de o código subir (a resposta 229 (a); a nota no D499) — lida pelo modo de cada trava:
  a que o pool só lê volta a rodar com o código novo, e a que ele nem lê segue parada e pede apagar a trava, se o dono a
  quiser de volta.

**Não regrediu:** a 65 (b) — o amigo com a ligação do convite segue dando só a comissão — e a 68 (c) — a primeira compra
cancelada guarda a comissão.

Provado, em série, nos dois bancos: `D312PontosDaIndicacaoTest` (9 casos: a unidade da venda pelas três portas — o
serviço, o PDV antigo e a comanda nova pela API —, o cancelamento, quem indicou inativo, o reativado e o cadastro do
sistema), `D373CursoSemPerguntaTest` (11 casos, pelo despacho em processo e pelas telas desenhadas), o caso do D304 que
prendia o inativo virado e o do convite do aplicativo, e a regra em `RegraDaFidelidadeTest`; com os vizinhos (os 55
arquivos de teste que tocam as classes mudadas), 1.070 testes e 4.518 asserções em cada banco (4 pulados por desenho, os
mesmos de antes). Dezessete mutantes, um por
vez, cada um com caso vermelho. Os portões que a fatia toca saíram 0, e o da cobertura por par ganhou o par novo da conta
das perguntas (`contarAtivasPorCurso`). **Não provado:** as telas no navegador e o desenho do PDF do certificado; o ciclo
inteiro fica para a rodada seguinte.

**A verificação em três lentes confirmou 9 achados, todos tratados:** o convite do aplicativo ficou com a cópia da
condição, e passou a perguntar a regra (com o caso que o prende); os lançamentos de antes em outra unidade, com os 10
pontos que o próximo recálculo tira da coluna da unidade do cadastro, foram ao lote (o D312 de corrigido a parcial; os
dois achados eram o mesmo); o desenho do relatório da indicação dizia o D312 aberto e o saldo na unidade de quem indicou;
"Até quando se volta" não punha o teto curto entre o que espera a declaração; a leitura 22 lia só o dono da trava, e a de
modo `-rw-------` segue parada com o código novo; o D373 contava 22 cursos ativos com o das fixtures (são 21); e a recusa
que apagasse a conclusão de antes e o certificado em si não tinham caso — o PDF, que o `FPDFTable` manda com `exit`, roda
num processo filho do phpunit: chamado na suíte, ele a encerrava com saída 0 e sem o resumo. O roadmap publicado
acompanha o ciclo.

## 15/10/2026 -- RESPOSTA-121-SCRIPTS-LEGIVEIS

A fatia 5b da fila das telas que faltam (o pedido do dono: "continue"), só no ERP antigo: a resposta 121 (a) — servir o
script legível e apagar os oito minificados (o **D90**).

**O que muda para quem usa:** nada que se veja. O layout do ERP, as três telas da senha, a adesão à fidelidade e as duas
páginas da pesquisa NPS passam a carregar o script legível, que é o que se edita; os oito `.min.js` da casa, cópias feitas
à mão que nenhum passo gerava, saíram. O conserto que entrasse só no legível não chegava mais à tela, e agora chega. O
`?cache=` de cada um ficou: o endereço do legível nunca tinha sido servido.

**A comparação, antes de apagar:** os dois lados de cada par passados pelo mesmo minificador, e o diff por token, trecho
a trecho. Nenhum minificado tinha código que o legível não tivesse, e todo commit que mexeu num lado mexeu no outro. A
única diferença era o modo estrito: o `layout.min.js` e o `fidelidade_cadastro.min.js` começavam por `'use strict'`, e os
legíveis o ganharam, com a medição de que nada neles muda com ele; o `mod-autenticacao.js` já era estrito, e ficou. A
tabela está no D90.

**O que mudou por dentro:** `conferir-scripts-servidos.php` recusa o `.min.js` na pasta da casa, a tela que o pede e o
script pedido que não existe, e segue conferindo os 125 manipuladores; `conferir-segredos.sh` deixou de pular
`public/layout/scripts/` e os `*.min.js` (uma chave plantada no `geral.js` passava pelo de antes e para no de agora); o
`contasAPagarDaTelaAntiga.teste.ts` roda só o legível; e os comentários que mandavam mudar também o `.min.js` dizem o de
hoje. Os seis pares do tema (`app` e `quick-sidebar` em `global/scripts`; `components-color-pickers`,
`components-select2`, `login-4` e `login-5` em `pages/scripts`) ficam como vêm: são de terceiros e não mudaram desde a
importação. O `.min.js` de quatro deles segue pedido pelo layout (o do `app`, também por doze telas fora dele), o do
`login-4` pelas três telas da senha, e o script do `login-5` ninguém pede.

Provado, em série: o `D90ScriptsLegiveisTest` (4 casos, as sete telas desenhadas pelo `ViewRenderer`, o layout com a
sessão de quem entrou) vermelho com o código de antes e verde depois; com os vizinhos (a adesão, a sessão, Contas a
Pagar), 189 testes em cada banco; o front, 1.783 (eram 1.785: o teste de Contas a Pagar rodava os dois arquivos), o
build e o lint; onze mutantes, um por vez, cada um com o seu vermelho; e no navegador, no ambiente local, a entrada e a
redefinição da senha e as duas páginas da pesquisa NPS recebem o legível com 200, ligam os seus botões e não deixam erro
no console, e os 41 scripts do layout e os 17 da adesão, carregados numa página em branco do mesmo ambiente, rodam sem
erro com os legíveis estritos. **Não provado no navegador:** o layout logado, a confirmação da senha e a adesão de
verdade — nessas, a prova é o teste em processo e a página em branco. O ciclo inteiro fica para a rodada seguinte.

**A revisão do mesmo dia** confirmou oito achados, todos tratados (a tabela está no D90). O que muda por dentro:
`conferir-scripts-servidos.php` passou a ler os scripts sem os comentários — os legíveis têm 104, e os minificados não
tinham nenhum, então uma função apagada que sobrasse num comentário passava — e a exigir o nome inteiro; recusa a
cópia órfã com outro nome na pasta da casa; e conta os seis pares do tema, com quem pede cada um (o D90 contava
quatro). O comentário do `SaidasController` volta a dizer que só o script de antes de 04/10 não recarrega; as notas
dos desenhos apontam para as linhas certas do layout; e dois textos dizem o que se mediu, e não mais. O caso novo,
`D90PortoesDosScriptsTest` (10 casos, sem banco), põe os dois portões à prova em árvores de rascunho — o de scripts
com um defeito plantado por vez, o de segredos com uma credencial inventada num repositório git de rascunho — e prende
o `'use strict'` dos dois legíveis. Provado, em série: o caso novo e os vizinhos, 227 testes em cada banco; nove
mutantes, um por vez, cada um vermelho no caso certo; e os portões que a fatia toca, todos com saída 0.

## 15/10/2026 -- RESPOSTAS-DO-FINALIZAR-E-DA-DEVOLUCAO

A fatia 5c da fila das telas que faltam (o pedido do dono: "espere acabar e continue de forma autonoma"), sem tela nova:
duas respostas do dono no dinheiro -- a 85 (a), *"O servidor fecha a corrida, e a tela antiga sai do modal"*, e a 89 (b),
*"A (a), e a devolução sai do caixa aberto como uma retirada"*.

**O que muda para quem usa.** No PDV antigo, a recusa "Esta venda já foi finalizada ou cancelada" deixa de prender o
operador no modal: leva à venda nova (ou à lista das comandas), como o sucesso, com a frase como aviso na página seguinte;
e o "Confimar" do fiado só volta quando o finalizar responde. Dois caixas -- ou o ERP e a tela nova -- que finalizam a
mesma venda no mesmo instante não a finalizam mais duas vezes: um vence, e o outro recebe a recusa sem gravar nada. A
retirada ou a devolução de crédito feita no meio de uma venda não é mais desfeita por ela. Devolver crédito ao cliente
passou a tirar o dinheiro do caixa aberto de quem opera, como uma retirada, e a deixar registro de quem autorizou, quem
operou, quanto e quando; o extrato do crédito (o histórico do cliente no ERP) mostra a devolução. Sem caixa aberto, a
devolução é recusada (o ERP leva ao caixa, a tela nova oferece "ir ao caixa"). O valor que passa do saldo do caixa também
é recusado, mas essa recusa não é da resposta 89 -- veio com a regra da retirada, e o saldo não é o dinheiro da gaveta --:
fica até a resposta da pergunta nova (o D501).

**O que mudou por dentro.** A primeira escrita do finalizar é um `UPDATE` condicional da venda (`status = 1` onde ainda
é o que o pedido conferiu), que só um de dois pedidos vence; a recusa das sessões e o erro no meio a reabrem. O crédito
usado, o do programa, o troco, a comissão de indicação e o caixa somam por conta no banco. A compra de crédito pelo ERP
passou pela trava do cliente, como a da API (o D381). A devolução trava o cliente, o crédito e o caixa, cria a retirada
pela regra da API e grava a linha em `clientes_creditos_devolucoes` -- tabela nova, nas duas cópias locais, no
`mysql.sql`, no `schema.sql`, na carga e no laço do passo 0 (a décima terceira migração, ensaiada no banco descartável do
dump: 26 rodadas com saída 0, e a estrutura igual à da suíte menos a `iframe_bi`). O valor que não é positivo é recusado
(o D382). D179, D381 e D382 corrigidos; D321, D228, D383 e D370 parciais (o dado de antes do D321 é a pergunta 86; o
cancelamento de venda continua regravando, porque dois cancelamentos ao mesmo tempo ainda passam os dois -- o resto do
D383; a conferência e o índice do D370 são da fatia 26). Duas perguntas novas ao lote: o que cancelar a retirada de uma
devolução faz com o crédito (o D500), e se a devolução maior que o saldo do caixa é recusada, conferida pelo dinheiro da
gaveta, ou sai (o D501).

**A verificação da fatia (três lentes) trouxe nove achados; oito foram tratados assim, e um não se confirmou na
medida.** A soma do caixa vinha num
comando à parte, depois do `flush` que põe a venda no caixa já confirmado: o recálculo do caixa de outra sessão (a
devolução, a retirada e o fechamento da API, a tela de fechamento do ERP) que caísse no meio contava a venda e recebia a
soma por cima -- a venda duas vezes na coluna. Agora a venda entra no caixa e a soma vem junto, na mesma transação, com o
cliente e o caixa travados nessa ordem (a da devolução e da compra), e a falha da soma desfaz as duas e reabre a venda. O
ramo da venda em andamento (o nulo do PDV antigo) ganhou a prova que lhe faltava, e a do erro depois da venda gravada
fechada também. O cenário passou a ter um sorteio ativo e o cupom do checkout aberto: "quem chega depois não grava sorteio
nem cupom" comparava zero com zero. A recusa da devolução maior que o saldo do caixa foi à página (o D501), e fica como está
até a resposta. O D383 passou a dizer o que o cancelamento e o resgate de pontos ainda apagam por cima. Um achado não se
confirmou: a limpeza dos casos fora da transação cair em cascata vinha de uma versão anterior do arquivo (com o mutante do
fechamento aplicado, a limpeza de hoje apaga a linha da pontuação e nada cai em cascata, medido nos dois bancos).

**Provado**, em série: a caracterização inteira, 3.705 no MySQL e 3.705 no PostgreSQL (com os 11 pulados de sempre), antes
da verificação; os casos novos -- `D321FinalizarUmaVezSoTest` (26 depois da verificação, seis deles fora da transação do
teste, com OUTRO PROCESSO: o finalizar daqui espera mais de 0,7 s e perde quando o outro confirma, na comanda e na venda em
andamento, e vence quando o outro desiste; a soma do caixa e a do crédito esperam a retirada e a devolução do outro e somam
por cima; e o recálculo do caixa feito pelo outro no meio do finalizar conta a venda uma vez) e `D179DevolucaoRegistradaTest`
(15) --, e os
vizinhos refeitos (o D316, o D380, a devolução sob trava, o tudo ou nada da fidelidade, as colunas fora do mapeamento);
o front, 1.794 (eram 1.783: `finalizarDaTelaAntiga.teste.ts`, as funções do arquivo servido, e a devolução da
`FidelidadeLigada`), com os efeitos amplificados, o build e o lint; o ensaio do passo 0; e os portões (`conferir-tudo.sh`)
com saída 0, depois de rebasear a cobertura por par (seis pares novos, todos com teste) e de regerar o inventário das
ações. Os mutantes, um por vez, restaurados e conferidos pelo sha1:

| Mutante | Vermelho |
|---|---|
| o `UPDATE` da venda trocado por `return true` | as duas portas na corrida, quem chega depois, os dois casos com outro processo, a ordem das escritas (nos dois bancos) |
| o `UPDATE` sem a condição do estado | no PostgreSQL, os mesmos; no MySQL, só o caso que lê o SQL -- ver abaixo |
| a recusa das sessões sem reabrir a venda | a venda em andamento volta ao nulo, e quatro do D452 |
| o erro no meio sem reabrir a venda | o caso do erro, e o do D452 |
| o ERP sem perguntar se a venda fechou | o ERP na corrida (a frase das sessões no lugar da do D316) |
| a API sem perguntar se a venda fechou | a API na corrida |
| a recusa do ERP sem `ja_finalizada` | quatro do D316, o do D321 e o do D380 |
| o caixa, o crédito usado, o troco e a comissão de volta à regravação (quatro mutantes) | cada um o seu caso de "outra sessão no meio" |
| a fidelização contando a venda que fecha | a fidelização |
| o caixa sem reler a entidade | o caixa somado no banco e o do outro processo |
| a compra pelo ERP regravando o crédito; e chamando `creditoComprar` direto | a compra pelo ERP (as duas) |
| a devolução sem conferir o caixa; sem o recálculo do saldo do caixa; sem o registro; com o valor sem a máscara | cada um os seus (sem caixa; além do caixa, nas três portas; o registro e o extrato; dez casos com a retirada de cem vezes) |
| a devolução aceitando o valor negativo | o serviço e o ERP |
| a API sem o caixa antes da credencial; o ERP sem ir ao caixa; o extrato sem as devoluções | cada um o seu |
| no script antigo: sem a saída do "já finalizada" no finalizar e no fiado; o "Confimar" voltando antes; o finalizar que não devolve o "Confimar"; o `?cache=` em 166 | cada um o seu (`finalizarDaTelaAntiga.teste.ts`) |
| na tela nova: a recusa sem "ir ao caixa"; o aviso de antes | os dois da devolução |
| (verificação) a soma do caixa fora da transação do `flush`, depois dele confirmado | o recálculo do outro processo no meio (a venda duas vezes) e a falha da soma (a venda fica fechada no caixa sem ela), nos dois bancos |
| (verificação) a bandeira da venda gravada fechada nunca ligada | o erro depois da venda gravada fechada (ela era reaberta), nos dois bancos |
| (verificação) o ramo do nulo sem a condição do nulo | no MySQL, o caso que lê o SQL; no PostgreSQL, ele, quem chega depois na venda em andamento e o caso com outro processo |
| (verificação) o finalizar recusado gravando o cupom do sorteio; ou o cupom | quem chega depois (comanda e venda em andamento) e as duas portas na corrida, nos dois bancos (e o controle, no do sorteio) |
| (verificação) a mesma transação sem a trava do caixa | nenhum, nos dois bancos: a chave de `vendas.caixa` já segura a linha do caixa até o fim da transação, e o recálculo de fora espera por ela; a trava fica para não depender da chave |

**O MySQL e o PostgreSQL contam linha diferente, e um mutante mostrou:** o `UPDATE` sem a condição do estado, numa venda
que outro já fechou, dá 0 linhas no MySQL (que conta as que MUDARAM: 1 sobre 1 não muda) e 1 no PostgreSQL (que conta as
que casaram) -- no MySQL o finalizar repetido seria recusado por acaso, e no PostgreSQL, que é o banco do corte, passaria.
Por isso a condição é a do estado conferido, e o caso que a vigia no MySQL lê o SQL. **Não provado:** a tela antiga num
navegador; dois pedidos HTTP de verdade ao mesmo tempo (a corrida foi provada no banco, com outro processo segurando a
linha); e a produção.

## 16/10/2026 -- RESIDUO-DO-D321

Um item técnico sem bloco na fila (o pedido do dono: "espere acabar e continue de forma autonoma"): o ciclo da fatia 5c
saiu vermelho só na caracterização, nos dois bancos, em dois casos do `RelatorioClientesInjecaoTest` -- 5 clientes na
unidade A, onde há 4.

**O que muda para quem usa:** nada. O defeito era do teste novo da 5c, e não do finalizar. Dos seis casos do
`D321FinalizarUmaVezSoTest` que rodam fora da transação do teste, o do recálculo do caixa no meio do finalizar é o único
em que o finalizar vence de verdade; a limpeza devolvia o caixa e o crédito, mas não o cliente, que ficava com a última
visita na unidade A -- e o relatório de clientes, que filtra por ela, o contava. O serviço foi inocentado na medida: o
banco de teste depois do arquivo inteiro, comparado linha a linha com o recém-montado, só tinha essa linha diferente, e
nenhuma porta chama o finalizar com transação aberta.

**O que mudou por dentro:** a limpeza dos casos de fora devolve inteiras as quatro linhas que o finalizar que vence
grava fora da venda (o cliente, o crédito, o caixa e o pacote), também quando o caso falha, e confere no fim que nada
sobrou -- o vazamento passa a ficar vermelho no caso que o deixa. A mesma régua (o banco do MySQL depois da suíte inteira,
comparado linha a linha com o recém-montado) achou dois irmãos: os casos de fora do `DevolucaoDeCreditoSobTravaTest`
(também da 5c) e do `CaixaSobTravaTest` (de antes) deixavam a data de alteração do caixa das fixtures, que nenhum teste
lê; as duas limpezas a devolvem, e conferem. O registro está no D321.

Provado, em série: o par `RelatorioClientesInjecaoTest|D321FinalizarUmaVezSoTest` vermelho com a limpeza de antes e verde
com a de agora, nos dois bancos; a caracterização inteira, 3.711 casos no MySQL e 3.711 no PostgreSQL (os 11 pulados de
sempre), saída 0, e depois dela os dois bancos iguais, linha a linha, aos recém-montados (no PostgreSQL, menos a hora de
criação do inquilino, que é a da montagem). Os mutantes, um por vez, restaurados e
conferidos pelo md5:

| Mutante | Vermelho |
|---|---|
| a limpeza sem devolver o cliente | o caso do recálculo (a conferência: a última visita, a unidade, o barbeiro e o `hash`) e os dois do relatório, nos dois bancos |
| a limpeza sem devolver linha nenhuma | cinco dos seis casos de fora, no MySQL (o sexto começa do que o anterior deixou) |
| a limpeza da devolução só com o `valor_final` | o caso de fora da devolução (a conferência do caixa: a `data_alteracao`), no MySQL |
| a limpeza da retirada sem a `data_alteracao` | o caso de fora do `CaixaSobTravaTest` (a conferência do caixa), no MySQL |
| (verificação) a limpeza sem esperar o outro processo que o caso não esperou | o caso novo do caso de fora que cai cedo (o crédito em 70, e não 100), nos dois bancos |

**Não provado:** a devolução de uma coluna booleana (nenhum caso de hoje muda uma).

**A verificação da rodada achou dois pontos, os dois tratados.** O caso de fora que caísse antes de esperar o outro
processo deixava a escrita dele: a limpeza lia a linha antes do COMMIT do outro, a via igual à de antes e não gravava
nada. A limpeza agora espera os outros processos que sobraram, depois de desfazer a transação daqui e antes de ler, e um
caso novo cai de propósito com o outro segurando o crédito. E a rodada "dos três arquivos mudados mais o relatório" (40
casos) era anterior ao conserto do `CaixaSobTravaTest` e não o tinha: os quatro arquivos juntos dão 65 casos, verdes nos
dois bancos. De novo, em série: o par com o relatório, 31 casos nos dois bancos; a caracterização inteira, 3.712 casos no
MySQL e 3.712 no PostgreSQL (os 11 pulados de sempre), saída 0, e os dois bancos, depois dela, iguais aos recém-montados. O registro está no D321.

## 16/10/2026 -- RESPOSTAS-DO-CUPOM-E-DO-CONVENIO

A fatia 5d da fila das telas que faltam (o pedido do dono: "espere acabar e continue de forma autonoma"): quatro respostas
do dono sobre o cupom do cliente -- a 187 (c), *"A (b), e o cupom não soma ao convênio: o item do convênio leva só o maior
dos dois descontos"*; a 152 (b), *"Fica o maior"*; a 154 (c), *"A linha do cupom tem uma unidade"*; e a 155 (b), *"A comanda
nova oferece o cupom na linha da agenda"*.

**O que muda para quem usa.** No serviço do cliente de convênio, nas duas portas (o PDV antigo e a comanda nova), o cupom
deixa de somar ao convênio: o corte de 89,90 com o convênio de 20% e o cupom de 10% sai a 71,92 (era 64,73, e o cupom
nunca se gastava); com o cupom de 30%, sai a 62,93, só com o cupom, que o fechamento gasta. O cupom que perde fica com o
cliente (a pergunta nova do lote: ou ele se gasta mesmo assim?). Na comanda nova: o produto do cupom aceita outro desconto,
e fica o maior -- a tela diz qual, e o cupom só se gasta se for ele; lançado com 3 unidades, o produto entra em duas linhas,
uma de 1 com o cupom e outra de 2 pelo preço (o desconto em valor com mais de uma unidade é recusado, com a frase do que
fazer); e a linha que veio da agenda mostra "usar o cupom do cliente (20%)", que a troca pela do cupom num toque. No
cliente de convênio, só aparece o cupom que vence o convênio. O PDV antigo, fora do convênio, fica como estava.

**O que mudou por dentro.** O cupom vai ao serviço à parte do desconto do balcão (a chave `cupom`), e
`VendaProdutoService::calcularLinha` compara o desconto dele, sobre o preço da linha, com tudo o que a linha leva sem ele
(`RegraDoItemDaComanda::oCupomVence`; no item do convênio, o convênio e o do balcão somados, a 177 (c)). O motivo do cupom
que vence no convênio leva os dois, "Convênio X; CUPOM VIP: ..." (`motivoDoConvenioComOCupom`), e a linha é a do cupom, e
não a do balcão. A ação nova `POST /adm/api/comandas/:id/itens/:item/cupom` (`usarCupomAction`), com as guardas do ajuste;
a comanda marca a linha que pode trocar (`podeUsarOCupom`). A comanda só oferece o cupom que o lançamento de fato daria
(`convenioDoLancamento`, que lê também as sessões livres do pacote). D477 corrigido; D276 e D277 parciais (o cupom que
perde para o convênio, o desconto em reais no produto do cupom lançado com mais de uma unidade e o nome longo do convênio
foram ao lote; a primeira escrita dava o D276 por corrigido). Para os relatórios de descontos (as fatias 50 e 51): "Convênio X; CUPOM VIP: ..." é
desconto do cupom, e não do convênio -- a nota está no `spec-descontos.md`, antes da D1.

Provado, em série: a caracterização da fatia e dos vizinhos (o item, o convênio, os cupons, a comanda, o pacote), 501
casos no MySQL e 501 no PostgreSQL, saída 0 -- `D277CupomNaoSomaAoConvenioTest` (novo, 13), `CupomNaComandaNovaTest` (23;
eram 13), `RegraDoItemDaComandaTest` (+5), e dois casos do `D477DescontoAteOValorDoItemTest` com o sentido novo; no front,
1.808 casos (`cupomDaComanda.teste.ts`, 10; o bloco do cupom da `ComandasLigada.teste.tsx`, 14), o build e o lint. 27
mutantes (19 no servidor, 8 na tela), um por vez, restaurados e conferidos pelo sha256: todos deixaram o seu caso vermelho (um sobreviveu na primeira
rodada -- a comanda fechada também dava 409 pela lista vazia dos cupons -- e o caso passou a ler a frase). Os portões que a
fatia toca, saída 0 (as rotas sem login e a guarda da API falsificadas contra a ação nova). **Não provado:** nada aberto
num navegador; o ciclo completo e os ensaios HTTP, que são do orquestrador.

**A verificação da fatia (16/10/2026)** confirmou achados em três lentes, e os consertos são estes. O cliente de convênio
com sessão livre do pacote pagava mais com o cupom: a linha de uma unidade do cupom caía na regra antiga das sessões livres
e saía sem o convênio, e a outra unidade também (o corte lançado com 2 e o cupom de 10%: 170,81, contra 143,84 só com o
convênio) -- agora as linhas que saem de um lançamento, e da troca, perguntam as sessões livres pela quantidade lançada. A
troca pelo cupom comparava com o convênio de agora, e não com o desconto que a linha tem: a linha da agenda com o convênio,
num dia em que a unidade já não o dá, trocava pelo cupom menor -- agora a troca só passa quando melhora a linha
(`RegraDoItemDaComanda::oCupomMelhoraALinha`, `asOutrasUnidadesGuardamODesconto`), e a marca da linha e a ação leem a mesma
conta. O PDV antigo lia a chave `cupom` do POST cru, e um pedido montado à mão zerava o item sem motivo -- agora o serviço
recebe o cupom ao lado do pedido, e só a API o monta. O nome do convênio de 80 caracteres ou mais deixava o cupom maior de
fora -- agora o nome leva o corte, e a marca fica (foi ao lote, com a recomendação de ficar assim). Na tela nova, a prévia e
a nota diziam "Fica o cupom" no cliente de convênio quando o servidor ficava com o convênio e o balcão -- agora a comanda
manda, com cada cupom, o convênio que ele enfrenta e a partir de quantas unidades, e a prévia faz a conta do servidor. A
linha aberta pela agenda tem caso próprio (`D276LinhaDaAgendaTrocaPeloCupomTest`, 5). Provado, em série:
`D277CupomNaoSomaAoConvenioTest` de 13 para 23 casos, `RegraDoItemDaComandaTest` +1; com os vizinhos (o item, o convênio,
os cupons, a comanda, o pacote, o D212, o D237, o D314, o D321, o D363/D465, o D384, o D426, o D435, o D450, o D452, o D453 e
o D477), 475 casos no MySQL e 475 no PostgreSQL, saída 0; no front, 1.816 casos (`cupomDaComanda.teste.ts` +5,
`ComandasLigada.teste.tsx` +3, também com o amplificador de efeitos, três rodadas), o build e o lint; 19 mutantes novos (11
no servidor, 8 na tela), todos vermelhos; os portões que a fatia toca, saída 0. O registro está no D276 e no D277.

## 16/10/2026 -- ORDEM-DOS-ITENS-DA-VENDA

Um item técnico sem bloco na fila (o pedido do dono: "espere acabar e continue de forma autonoma"), o D490: no ciclo
das fatias de 14 e 16/10, o `D450CentavoDoItemRetiradoTest` saiu vermelho no PostgreSQL com os dois itens da comanda
trocados de lugar, e verde na repetição.

**O que muda para quem usa:** no MySQL de hoje, nada que se veja -- ele já devolvia as linhas pelo id, por acaso. No
PostgreSQL, o banco de depois do corte, as listas param de trocar de lugar: os itens e os pagamentos da comanda nova,
do detalhe da venda, do histórico de consumo (e do PDF dele), do aplicativo e da tabela do PDV antigo saem na ordem em
que foram lançados, também depois de um ajuste ou de um desconto; o colaborador da última visita do cliente volta a
ser sempre o do último serviço lançado; a última visita do cliente no Marcar da agenda escreve os serviços na ordem em
que foram lançados; a nota do NPS vai sempre para a venda do primeiro item da pesquisa; e, fora da venda, as retiradas
no detalhe do relatório de caixas (a cancelada não muda mais de lugar), as áreas do usuário na tela nova, as opções do
checkpoint da VIP Class, as duas parcelas de uma saída que vencem no mesmo dia e os itens do pacote no fechamento.

**O que mudou por dentro:** a ordem pelo id no mapeamento de sete coleções (`Venda::$produtos`, `$pagamentos`,
`Caixa::$retiradas`, `Usuario::$areasAtuacao`, `VipClassCheckpointPergunta::$respostas`, `Produto::$pacoteItens` e,
na verificação, `Nps::$itens`), o id como desempate das parcelas (`Saida`, no mapeamento e no `getParcelas`), `ORDER BY`
pelo id nas cinco consultas que leem os itens ou os pagamentos de uma venda fora da coleção (a tabela do PDV antigo e a
API das vendas da unidade), e o id do item como desempate da última visita na agenda
(`VendaProdutoRepository::pesquisarUltimosServicos`, na verificação). As outras 30 coleções do mapeamento ficam sem
ordem, cada uma com o motivo escrito no caso novo, que lê o mapeamento inteiro e fica vermelho com uma coleção nova sem
ordem e sem motivo. Sem esquema. O registro está no D490.

**A verificação** (três lentes, 5 achados confirmados, todos tratados): o motivo escrito para deixar `Nps::$itens` sem
ordem estava errado (a rotina grava a nota na venda do primeiro item); a última visita da agenda lia os itens da venda
sem desempate; o mecanismo escrito nos comentários ("a linha do UPDATE pula para o fim") contradizia o próprio sintoma
do D450, em que a linha ajustada voltou primeiro, e o texto antigo do D490 ("o ajuste regrava a outra") era falso; as
oito rodadas do D450 no PostgreSQL não provavam o conserto; e seis das treze mudanças não tinham prova no MySQL.

Provado, em série: `D490OrdemDasColecoesTest` (novo, 16 casos) nos dois bancos, com as linhas gravadas de id
embaralhado, o SQL que chega ao banco (a prova do MySQL para as consultas e para o `Criteria` das parcelas), o ajuste
pela API da linha de menor id com o PostgreSQL lendo pela ordem do disco, a rotina do NPS e a frase da agenda; os
vizinhos (131 arquivos que leem o que mudou -- a venda, a comanda, o PDV, o caixa, as saídas, os usuários, a VIP
Class, os pacotes, os cupons, o crédito, o NPS, o cadastro do cliente), 2.017 casos no MySQL e 2.017 no PostgreSQL (5
pulados em cada, como na rodada da fatia), saída 0; o contrato, 74 e 74 (na fatia; a verificação não toca o que ele lê -- a API V4, a do aplicativo e a do
visagismo não leem os itens do NPS nem a última visita da agenda). Dezesseis mutantes, um por vez, restaurados pelo
sha256: os treze da fatia (sete deles rodados de novo na verificação, com os casos novos) e três novos (N1, N2, N3);
todos deixaram o seu caso vermelho no PostgreSQL, e desde a verificação todos também no MySQL (os do mapeamento pelo
caso do mapeamento, os das consultas pelo SQL).
**Não provado:** o resultado das consultas no MySQL (o InnoDB devolve pelo id de qualquer jeito; lá só o SQL fica
vermelho); as oito rodadas do D450 da fatia não provam o conserto -- cada uma remonta o banco, e o D450 passa também sem
a ordem; o caso do ajuste só prova com o PostgreSQL lendo pelo disco (sem isso, verde também sem a ordem: o mutante
N3); nada aberto num navegador; o front não muda (a tela mostra na ordem do JSON), e o vitest não rodou; o ciclo
completo e os ensaios HTTP são do orquestrador.

## 16/10/2026 -- RESPOSTAS-231-E-232

A fatia 5e da fila das telas que faltam (o pedido do dono: "continue, desta vez montando as telas que ainda não foram
migradas. continue de forma autonoma até a exaustão"): as duas respostas sobre a devolução de crédito que sai do caixa (a
resposta 89, da fatia 5c), as duas diferentes da recomendação -- a 231 (a), *"cancelar a retirada da devolução desfaz a
devolução -- o crédito volta ao cliente, e o registro fica marcado como desfeito, com quem desfez e quando"*, e a 232 (c),
*"a devolução que o gerente autoriza sempre sai, e o saldo do caixa pode ficar negativo, que a tela do caixa mostra"* --, e
as três iguais à recomendação (233 a, 234 a, 235 a), que só mudam textos.

**O que muda para quem usa.** Cancelar no caixa (na tela antiga, ou pela API) a retirada que veio de uma devolução de crédito
devolve o crédito ao cliente, na mesma linha que a devolução baixou; o ERP diz *"A devolução de crédito foi desfeita: o valor
voltou ao crédito do cliente."*, a tela nova do caixa marca a linha, e o extrato do crédito mostra a devolução desfeita, com
quem e quando. Cancelar de novo não devolve de novo, e o caixa fechado continua não cancelando. A devolução maior que o saldo
do caixa sai -- R$ 80,00 num caixa de R$ 50,00 deixam -30,00 --, e as duas telas do caixa mostram o saldo negativo com o sinal;
a sangria seguinte é recusada, como toda sangria que passa do saldo. Os avisos da devolução dizem isso. Do cupom, nada muda: o
que perde para o convênio fica com o cliente, o desconto em reais em várias unidades segue recusado, e o nome longo do
convênio segue cortado.

**O que mudou por dentro.** `CaixaService::retiradaCancelar` pergunta ao registro se a retirada é de uma devolução e, se é,
`ClienteService::desfazerADevolucao` trava o cliente e o crédito antes do caixa (a ordem da devolução), cancela a retirada
sob a trava do caixa e devolve o crédito por conta no banco, marcando o registro. A retirada da devolução não confere mais o
saldo (`registrarRetirada`, só para ela), e a recusa `alem_do_caixa` saiu das duas portas. O recurso do caixa da API ganhou
`retiradas[].devolucaoDeCredito` (`{cliente, desfeitaEm}`, nulo na sangria). **Esquema que só acrescenta** (a 83 a e a própria
231 (a)): `clientes_creditos_devolucoes.desfeita_por` e `desfeita_em`, no `CREATE TABLE` e num `ALTER` idempotente da
`migracao-clientes-creditos-devolucoes.sql` (já no laço do passo 0), no `mysql.sql`, no `schema.sql` e na carga, e nas duas
cópias locais às 10:29 de 16/10/2026 (0 linhas na tabela); sem as colunas no banco, o cancelamento é o de antes. D500 e D501
corrigidos; D276 e D277 corrigidos (as respostas 234, 233 e 235 fecharam o que restava); o D179 continua corrigido.

**Provado**, em série: os casos novos -- `D500DevolucaoDesfeitaTest` (11, com dois cancelamentos ao mesmo tempo: outro
processo segura o cliente e o crédito, e o crédito volta uma vez só) e `D501DevolucaoAlemDoSaldoTest` (6) --, os três do
`D179DevolucaoRegistradaTest` que mudaram de sentido e o `CaixaSobTravaTest` refeito; com os vizinhos (o caixa, o crédito, a
fidelidade, o finalizar, a devolução, a sangria, a despesa, os cupons do convênio), 834 casos no MySQL e 834 no PostgreSQL
(3 pulados em cada), saída 0; o front, 1.818 (eram 1.816), com os efeitos amplificados nos dois arquivos, o build e o lint;
o portão do passo 0 verde, e vermelho com o `mysql.sql` de cópia sem uma coluna; 12 mutantes do servidor (cinco também no
PostgreSQL) e 3 do front, todos vermelhos (a tabela está no D500 e no D501) -- e um deles mostrou o porquê da trava pela referência: com o cliente buscado
antes da trava, no MySQL a foto da transação nasce antes da espera, e o segundo cancelamento devolvia o crédito de novo.
**Não provado:** a tela antiga num navegador; dois pedidos HTTP de verdade ao mesmo tempo (a corrida foi provada no banco,
com outro processo); a produção; e o ciclo inteiro (a caracterização toda, o contrato, os ensaios HTTP), que é do
orquestrador.

**A verificação** confirmou seis achados, todos tratados (o D500, "A verificação da fatia"): o crédito volta à linha da
unidade da devolução e à mais antiga do par, agora com caso (na cópia, 32.358 clientes têm crédito em mais de uma unidade);
sem uma das duas colunas, nenhuma consulta as nomeia (antes só o caminho era provado); cancelar a sangria deixa em paz a
devolução do mesmo caixa; na personificação, o extrato diz *"desfeita por <quem entrou>, na sessão de <quem opera o
caixa>"*, sem coluna nova (a pessoa assumida é quem opera o caixa); o modal do cancelamento na tela antiga avisa que a
retirada da devolução, cancelada, desfaz a devolução; e o `docs/ambiente-teste.md` diz o que fazer no primeiro `atualizar`.
O `D500DevolucaoDesfeitaTest` foi a 17 casos; 10 mutantes novos, 9 vermelhos no MySQL e o da ordem pelo id vermelho no
PostgreSQL (no MySQL a InnoDB devolve o par pela chave primária).

## 16/10/2026 -- UNIDADES-SERVIDOR

A fatia 6 da fila das telas que faltam: a F0, a F1 e a F2 do desenho de Unidades (`docs/f3-telas-restantes/tela-unidades.md`),
o servidor que a tela nova (as fatias 7 e 8) vai chamar.

**O que muda para quem usa.** Na tela antiga de unidades, só o administrador vê o Nova, o editar e o ativar/desativar -- quem
só lê vê a lista, e o Nova não leva mais a um 404; quem tivesse "escrever" sem ser administrador volta à lista com *"Só o
administrador altera unidade: ela é da rede."* (hoje ninguém é assim: nenhuma linha do privilégio na cópia). Salvar a edição
não reativa mais a unidade desativada em outra aba, grava o texto já aparado, e recusa com a frase o texto acima do tamanho
do campo, o emoji, o `<` e o `>` (antes, erro na tela, ou o `<...>` cortado calado) e o e-mail sem forma. Nos sócios, o de
outra unidade e o removido são recusados, e o que não existe não dá mais erro. Pela API, a taxa de royalty de 3,5% fica 3,5%
(gravava 35%), o ticket não cresce cem vezes, o ID da Yungas não some num `PUT` que não o manda, o `PUT` vazio é recusado, e
o cadastro de unidade deixa de dar erro sempre -- e pede o faturamento, como o ERP.

**O que mudou por dentro.** A regra pura `RegraDaUnidade` (conferir, traduzir, e o formulário do ERP campo a campo);
`UnidadeService::update` com a linha travada primeiro e o `alteradoEm` que a tela leu (409 `mudou`), o ID da Yungas só
quando a chave vem, e `definirEstado` no lugar do interruptor; a `/adm/api/unidades` só do administrador nas quatro escritas,
com o 400 do corpo vazio, a chave que não se grava recusada com o nome, `idYungas` e o `alcance` no recurso (as pessoas que
deixam de entrar, as que entraram em 30 dias, o royalty do ciclo), a lista leve (sem o total e o último acesso: 1.384.862
linhas lidas por página contra 58; com o tipo e o WhatsApp ligado, as colunas da tela nova) e o endereço novo
`GET /adm/api/unidades/filtros?estado=` (as opções das inativas também). O ERP grava o `getData()` do formulário com o id da
rota, o tipo da lista (ou o que a unidade já tem), os tetos das colunas e a recusa da `RegraDoTexto` (os quinze textos que
tinham o `StripTags` o perderam). Sem esquema novo. D333, D502, D503, D504, D505, D507, D508 e D510 corrigidos; D506 e D509
parciais (a lista antiga segue com os combos dela; o token do formulário de sócio, com o porquê); D377 parcial (o Nova). Os
portões `conferir-rotas-sem-login.php` (a rota nova), `conferir-cobertura-por-par.php` (dois pares, 331 para 333) e
`conferir-debitos-repetidos.php` (cinco pares de títulos parecidos, com o motivo) mudaram junto.

**A verificação** confirmou doze achados, e os doze foram tratados no código, num caso ou no documento: a lista
sem o tipo e o WhatsApp que a seção 1 do desenho põe como colunas; o cadastro pela API sem o faturamento (a decisão 5 do
desenho); o sócio que não existe responder 200 com `sucesso: false`, e não o 404 do desenho (fica, e o desenho diz por quê:
é o que a tela antiga sabe dizer); o navegador que a F1 pedia (no lugar dele, a página de editar renderizada de verdade e o
POST montado dos campos que ela desenha); o D503 citar como prova do nulo um caso que media o contrário; o cadastro do ERP
sem caso; o "PUT que devolve o que leu" que conferia só cinco colunas (agora a leitura seguinte inteira); o alcance no `GET`
e no `PUT`, o `/filtros` das ativas e o limite de cima do retrato sem caso (o caso do alcance usa o dia 20 do mês corrente
como hoje); o `alteradoEm` e a ordem "o corpo vence o padrão" no cadastro sem caso; o `conferir-tudo.sh` rodado antes da
última edição; o título do D510 ("sete colunas", são oito); e o formulário antigo que cortava o `<...>` calado e gravava o
que a API recusa. A página serializada mostrou, também, o que já era assim e ficou como está (no D507): o campo vazio do ticket
grava zero, e a UF que a tabela `estados` não tem seria apagada ao salvar -- a produção tem as 27.

**Provado**, em série, depois da última edição de código e de teste: os testes da fatia -- `RegraDaUnidadeTest` (10),
`UnidadeEscritaApiTest` (11), `TelasDasUnidadesDoErpTest` (9), `UnidadeApiTest` (13) -- com o D76, o D351, o D368 e o D199
que mudaram ou que eles alcançam, 129 casos no MySQL e 129 no PostgreSQL (3 pulados em cada, do D199, de outras telas), saída
0; os vizinhos (a ACL da API, a barreira de origem, a guarda de login, a remoção por POST, os convênios, os royalties, os
acessos), 665 e 665, saída 0; os ensaios de permissão e de recusas nos dois bancos, saída 0 (os casos 155 a 162 com o status esperado); 57 mutantes do servidor e
três controles, os 57 vermelhos (as tabelas estão no D333: 38 da implementação e da retomada, 19 da verificação); e o
`conferir-tudo.sh`, rodado depois da última edição, inclusive desta entrada, com 64 portões ok e o vermelho conhecido do D50, "Nenhuma falha inesperada". **Não provado:** a tela antiga
num navegador (o clique do desativar e o JavaScript das máscaras); a caracterização inteira, o contrato, o build e o lint
do front (o front não mudou), que são do ciclo.

## 16/10/2026 -- UNIDADES-LEITURA

A fatia 7 da fila das telas que faltam: a F3 do desenho de Unidades (`docs/f3-telas-restantes/tela-unidades.md`), a tela
nova em leitura, sobre o servidor da fatia 6.

**O que muda para quem usa.** "Unidades", em Configurações, abre a tela nova: a lista das ativas, 20 por página, com o nome
e o número ao lado (há nome repetido), o tipo, o telefone, se aparece no aplicativo, se o WhatsApp está ligado e o NPS --
"não apurado" deixa de parecer zero. Os filtros de país, UF, cidade e bairro são os da situação escolhida, e as inativas
passam a ter os delas (a cidade só de inativas e Portugal aparecem). Abrir uma unidade mostra o cadastro em dez seções, e
cada número diz o que faz: o nome que se monta do endereço, o horário que a agenda não usa ("fechado", e não meia-noite), os
dias sem convênio, as taxas de royalty do ciclo em andamento -- a que alguém mudou neste ciclo aparece com "passa a 3,5% em
16/11/2026", porque a fatura deste ciclo ainda cobra a de antes --, o royalty do ciclo até hoje, a fidelidade e os pontos
com a frase de cada campo, o faturamento com "o boleto continua com os dados de quando a primeira fatura saiu", o ticket
calculado às 04:10, o ID da Yungas e os sócios. O CPF do sócio administrador não aparece. **A tela só lê**: o administrador
continua alterando na tela antiga, e ganha os caminhos dela -- a unidade nova, "alterar na tela antiga", a mensageria --, e o
"atualizar" traz o que lá se gravou; quem escreve sem ser administrador lê a frase "Só o administrador altera unidade: ela é
da rede."; o total e o último acesso ficam na lista antiga, com o link. A escrita por seção é a fatia 8.
*Desde a verificação, abaixo: a inativa diz "oculta (inativa)" e "calado (inativa)", e o royalty do ciclo em reais só
aparece ao administrador.*

**O que mudou por dentro.** No front, `api/unidades.ts`, a tela pura `Unidades.tsx`, o invólucro `UnidadesLigado.tsx` e a
regra da leitura em `leituraDaUnidade.ts` (a taxa por texto, sem `Number`; cada nulo com o nome dele); o Console com
`CAMINHO_DAS_UNIDADES` e `PRIVILEGIO_DE_EDITAR_UNIDADES` (o `podeEditar` = escrever E administrador), e a conta da tela não
migrada de 29 para 28 destinos; a prévia `Unidades` no `.design-sync` (seis células, dados em `_unidades.ts`), e a dos
Usuários com os valores da agenda de depois do D345 (o D511). No servidor, um achado da seção 5: desde o D368 o cadastro é a
taxa NOVA, e a ficha mostraria "serviço 3,5%" de um ciclo cobrado a 3% -- o recurso ganhou `royaltiesDoCiclo`, as taxas que
o ciclo em andamento usa (`VigenciaDaAliquota::linhasDaUnidade` e a `aliquotaDoCiclo`; o cadastro sem vigência ou sem a
tabela), chave só de leitura, que o `PUT` recusa com o nome. Sem esquema novo. O D506 segue PARCIAL (a tela nova filtra
pela situação, e a lista antiga, que o menu do ERP ainda abre, segue com os combos só das ativas); D511 registrado e
corrigido; D512 registrado e parcial (abaixo, na verificação); o D368 (que segue PREPARADO, a edição das
taxas é a 8) e o D377 com a nota do que a tela nova já diz.

**Provado**, em série, depois da última edição de código: os testes da fatia e os vizinhos (as unidades, o D351, o D367, o
D368, os royalties, os acessos do D76, os privilégios pelos controllers, a barreira de origem, as colunas fora do mapeamento,
a sessão, os convênios), 758 no MySQL e 758 no PostgreSQL, 3 pulados em cada (os que pedem a cópia da produção), saída 0 --
entre eles `D368VigenciaDaTaxaDeRoyaltyTest::testORecursoDizATaxaDoCicloEmAndamento` e `::testAsTaxasDoCicloNosDoisLadosDoDia16`,
e `UnidadeEscritaApiTest::testOsTiposDaTelaNovaSaoOsDoRecurso`, que prende os tipos de `api/unidades.ts` às chaves que o
controller devolve (o `GET`, a lista, os filtros e os sócios); o front inteiro, 1.878 casos (eram 1.818; `leituraDaUnidade`
16, `UnidadesLigado` 29, o contrato do invólucro 9 e os do Console 6), também os da fatia com `AMPLIFICAR_EFEITOS_MS=50`, o
build e o lint (os seis avisos de antes); o tipo das prévias (só os dois erros conhecidos do `_relatorios.ts`); os telefones
e os documentos da prévia, nenhum na cópia; 38 mutantes do front e 11 do servidor, um por vez e restaurados, todos
vermelhos; e os portões, na lista do relatório da fatia. **Não provado:** a tela num navegador, a 375 px e nos dois temas,
contra o banco de contrato; a caracterização inteira, o contrato e os ensaios, que são do ciclo; a prévia no Claude Design.

**Na verificação** (16/10/2026; onze achados confirmados, todos tratados -- três eram o mesmo, o D506):

- **A inativa dizia "visível" e "ligado"**, o contrário do que as respostas 132 (b) e 178 (c) fizeram o sistema fazer: o
  aplicativo não mostra a unidade inativa nem pelo endereço direto, e o WhatsApp dela se cala (`Unidade::mandaWhatsApp` é
  "ligado E ativa"). Na cópia, 7 inativas estão marcadas visíveis e 4 têm o WhatsApp ligado. A lista diz agora "oculta
  (inativa)" e "calado (inativa)", e a ficha diz a marca e o que volta a valer se ela for reativada (a regra em
  `leituraDaUnidade.ts`: `noAplicativo`, `whatsappNaLista`, `aplicativoDaFicha` e `whatsappDaFicha`).
- **O royalty do ciclo em reais aparecia a quem só lê**, de qualquer unidade da rede; no ERP, ele só aparece no relatório de
  royalties, que pela palavra do dono é só do administrador. O `alcance` (que existe para a frase do desativar) só vai ao
  administrador no `GET` da unidade (`Adm/UnidadesController::recurso`; o D512). **O faturamento e os sócios** na ficha de
  quem lê seguem o desenho -- no ERP, só quem altera os via --, e a pergunta foi ao lote (TELAS-RESTANTES [seguranca]).
  Hoje ninguém além dos administradores tem o privilégio (0 linhas na cópia).
- **O D506 fechado como CORRIGIDO** com a lista antiga no menu do ERP e os combos só das ativas: volta a PARCIAL, com o resto
  no corpo; a nota do U18 (no D377) e este registro diziam o mesmo, e foram corrigidos.
- **O ativar e o desativar**: o cartão "Na tela antiga" diz ao administrador que estão no botão da linha, na lista antiga (a
  inativa, com as inativas escolhidas na busca), porque a edição antiga não grava a situação (o D508).
- **O botão "copiar horários da unidade"** está na agenda de cada usuário, na tela antiga, e não na tela de usuários: a tela
  nova de Usuários deixa a agenda lá.
- **Os casos que faltavam**: o foco ao voltar da segunda linha e depois do "atualizar"; a carga na tela; a releitura que não
  abre nem pagina; o "anterior"; a página que já não existe; o "atualizar" durante a carga; o 403 dos sócios. E os
  cabeçalhos de dois testes do front citavam o `UnidadeApiTest` no lugar do `UnidadeEscritaApiTest`.

**Provado na verificação**, em série, depois da última edição de código: os testes da fatia e os vizinhos (o filtro
`Unidade|D351|D367|D368|Royalt|D76|PrivilegiosPelosControllers|Barreira|Origem|ForaDoMapeamento|Sessao|Convenio`), 1.079 no
MySQL e 1.079 no PostgreSQL, 3 pulados em cada, saída 0 -- entre eles o
`UnidadeEscritaApiTest::testOAlcanceSoVaiAoAdministrador`; o front inteiro, 1.889 casos (`leituraDaUnidade` 18,
`UnidadesLigado` 38 e o contrato do invólucro 9), os da fatia e do Console com `AMPLIFICAR_EFEITOS_MS=50` (169), o build e
o lint (os seis avisos de antes); o tipo das prévias (só os dois erros conhecidos do `_relatorios.ts`); 25 mutantes novos do
front e 3 do servidor (o alcance a todos, a ninguém, e ao contrário), um por vez e restaurados byte a byte, todos
vermelhos; e o `conferir-tudo.sh`, 64 portões ok e o vermelho conhecido do D50, "Nenhuma falha inesperada". **Não
provado:** o navegador, a caracterização inteira, o contrato e os ensaios, que são do ciclo.

## 16/10/2026 -- UNIDADES-ESCRITA

A fatia 8 da fila das telas que faltam: a F4 e a parte de tela da F5 do desenho de Unidades
(`docs/f3-telas-restantes/tela-unidades.md`), sobre a tela em leitura da fatia 7 e o servidor da fatia 6 -- as taxas pela
resposta 134 (b) do dono, o faturamento pela 133 (a), o desativar pela 132 (b), e a unidade nova. A P3 (a validade dos
pontos) segue "como hoje", com a pergunta no botão.

**O que muda para quem usa.** O administrador altera a unidade na tela nova, seção por seção: cada cartão da ficha
(endereço, contato, horário, dias de convênio, royalties, fidelidade, aplicativo e agenda, faturamento, números e
integrações) tem o seu "alterar", que abre só os campos dele, com a frase do que gravar faz, e grava só o que mudou. Se outra
aba ou a tela antiga gravou no meio, a tela não grava por cima: relê a unidade e diz "A unidade mudou desde que a tela a
leu: confira e tente de novo." As taxas de royalty se alteram com "A taxa alterada vale a partir de 16/11/2026: o ciclo em
andamento e a fatura dele continuam com a taxa de antes." (só onde a vigência está no banco); o faturamento, com "O boleto
continua com os dados de quando a primeira fatura saiu."; e reduzir a validade dos pontos pergunta no próprio "salvar"
quantos pontos e de quantos clientes vencem na rotina das 23:00 -- nenhum, grava sem perguntar. O "desativar" do cabeçalho
pergunta no próprio botão e diz o que acontece: sai das listas e do aplicativo, as N pessoas lotadas nela deixam de entrar
(quantas entraram em 30 dias; o administrador continua), o WhatsApp se cala e as mensagens automáticas saem pelo
aplicativo, e a fatura e o relatório seguem com ela pelo ciclo que trabalhou; o "ativar" não pergunta, e diz o que volta.
A unidade nova se cadastra aqui ("cadastrar unidade", na lista): o obrigatório é o do ERP -- o tipo, o endereço, a
inauguração, o DDI, o horário de segunda a sábado, as taxas, a fidelização e o faturamento; o CPF do sócio administrador, só
na tela antiga, que o pede ao salvar --, e o resto parte do padrão, à vista (oculta no aplicativo, ao contrário do cadastro
antigo, que já vinha "visível"; a nota diz). O país, a UF, a comissão de indicação e o aviso de cadastro são as listas da
tela antiga. Ficam na tela antiga, com os links: a chave do WhatsApp,
os sócios, os textos das mensagens e o histórico de acessos. O ticket médio e o NPS continuam só de leitura.

**O que mudou no código.** No front, a regra pura `frontend/src/telas/formularioDaUnidade.ts` (os campos pelo caminho do
recurso, a conferência da `RegraDaUnidade` e da `RegraDoTexto`, o corpo só com o que mudou, pelo valor, e as frases), os
formulários em `Unidades.tsx` e a escrita em `UnidadesLigado.tsx`, no molde dos convênios (a pergunta no botão pela peça da
fatia 2, o 409 que relê por baixo, o Enter seguro, o botão ocupado); `api/unidades.ts` ganhou o corpo, o `PUT`, o `POST`, o
estado e a contagem. No servidor, o endereço novo `GET /adm/api/unidades/:id/pontos-a-vencer?dias=` (do administrador que
escreve; só conta, com o corte do `expirarPontuacao`: `PontuacaoHistoricoRepository::aVencerComOPrazo` e `::diaDaProximaRodada`)
e a chave `royaltiesDoCiclo.mudancaValeDe` no recurso (o dia 16 seguinte, ou nulo sem a vigência -- a pergunta da tela
antiga). Sem esquema novo. Os portões aprenderam a rota nova (`conferir-rotas-sem-login.php`, `conferir-cobertura-por-par.php`,
334 pares) e o ensaio de permissão a pede. A prévia `Unidades` ganhou três células (alterando os royalties, perguntando ao
desativar, a unidade nova) e os tratadores da escrita. O D351, o D366 e o D368 ganharam a nota da tela nova; nenhuma marca
mudou, nenhum débito novo.

**Achado na fatia:** a contagem com a validade de seis dígitos (999.999 dias, que a API aceita) caía nos dois bancos -- o
corte virava um ano negativo, que o MySQL e o PostgreSQL recusam como data; o corte fica em 1900, e conta o mesmo.

**A verificação (17/10/2026)** confirmou 13 achados (dois repetidos), todos tratados:
- **as listas da tela antiga:** o país, a UF, a comissão de indicação e o aviso de cadastro eram texto livre na tela nova, e
  são `<select>` na edição antiga, que não marca o valor de fora e grava nulo ao salvar -- a comissão de 33% virava zero na
  venda. Agora são as mesmas listas (o valor que a unidade já tem fora delas continua opção, e só vai se mudar), e a
  `RegraDaUnidade` aceita o mesmo pela API;
- **o obrigatório do ERP:** o cadastro pede também o DDI, a inauguração e o horário de segunda a sábado, e a alteração não
  os apaga (o domingo continua podendo ficar fechado) -- sem eles, a edição antiga não salvava a unidade; a API pede o mesmo.
  O CPF do sócio administrador segue só na tela antiga, e o cadastro diz que ela o pede (o D369);
- **a unidade nova oculta no aplicativo**, ao contrário do cadastro antigo: a nota do formulário passou a dizer a diferença;
- **a validade dos pontos ganhou teto** (99.999 dias, os cinco dígitos do ERP), na API, na tela e na contagem, e o corte de
  data da rotina das 23:00 para em 1900 -- com 999.999 dias a rotina caía nos dois bancos e nenhum ponto da rede vencia;
- **a contagem e a rotina têm o mesmo corte**, pela mesma função, e um caso roda a rotina e confere a contagem linha a
  linha da borda; o dia da rodada pelo endereço é provado dos dois lados das 23:00, com o relógio do controller fixado;
- **"a rotina das 23:00"** também na ficha, e o aviso de salvo da fidelidade diz que a validade vale para os pontos já dados;
- **o "desistir" e o "voltar" com a contagem no ar desistem** (antes, a contagem que chegava com zero gravava a validade);
- **os casos que faltavam:** o desativar no ar, o 409 do estado que espera a releitura, a recusa sem 409, e o voltar e o
  desistir do cadastro no ar.

**Provado**, em série: os testes da fatia e os vizinhos (o filtro
`Unidade|D351|D367|D368|Royalt|D76|PrivilegiosPelosControllers|Barreira|Origem|ForaDoMapeamento|Sessao|Convenio|Fidelidade|D312|Pontua`),
1.162 no MySQL e 1.162 no PostgreSQL, 3 pulados em cada, saída 0 -- entre eles os quatro casos novos do
`UnidadeEscritaApiTest` (os pontos que a validade vence, o dia da rodada, as guardas da contagem e a data da taxa mudada) e o
`D368VigenciaDaTaxaDeRoyaltyTest` com e sem a tabela; o front inteiro, 1.953 casos (eram 1.889: `formularioDaUnidade` 19,
`UnidadesLigado.escrita` 37 e o contrato do invólucro 17), os da tela com `AMPLIFICAR_EFEITOS_MS=50` (129, e os 6 das
unidades no Console), o build e o lint (os seis avisos de antes); o tipo das prévias; o ensaio de permissão no banco de
contrato, "recusou 65 de 65"; 54 mutantes, um por vez e restaurados byte a byte -- 20 do servidor e 34 do front --, todos
vermelhos menos um equivalente (tirar o atalho do prazo que não diminui: a consulta já não acha nada); e o
`conferir-tudo.sh`, 64 portões ok e o vermelho conhecido do D50. **Depois da verificação**, de novo em série: o mesmo filtro,
1.168 no MySQL e 1.168 no PostgreSQL (os seis casos novos: as listas e o obrigatório do ERP e o teto na `RegraDaUnidadeTest`;
o dia da rodada pelo relógio, a contagem contra a rotina e a rotina com a validade de séculos no `UnidadeEscritaApiTest`), 3
pulados em cada, saída 0; o front inteiro, 1.967 casos (`formularioDaUnidade` 24 e `UnidadesLigado.escrita` 46), os 125 dos
arquivos da tela e os 6 das unidades no Console com `AMPLIFICAR_EFEITOS_MS=50`, o build e o lint (os mesmos seis avisos); 32
mutantes novos, um por vez e restaurados byte a byte -- 16 do servidor e 16 do front --, todos vermelhos no caso dele; e os
portões que a fatia toca, com saída 0. **Não provado:** a tela num navegador, a 375 px e nos dois temas, contra o banco de
contrato; a caracterização inteira, o contrato e os ensaios nos dois bancos, que são do ciclo; a prévia no Claude Design.

## 16/10/2026 -- Quanto falta: cerca de 50% do codigo e dos debitos ate o fim do Corte

O dono perguntou: "estime a % de conclusao ate o momento. conclusao eu digo ate completar o que falta de codigo e debitos ate a fase 5". O roadmap tem cinco fases, de 0 a 4, e a ultima e o Corte; a conta vai ate o fim dela, sem as janelas de operacao na producao.

**Cerca de 50% feito, entre 45% e 55%**, em volume de codigo, com os testes:

| Parte | Linhas, aproximadas |
|---|---:|
| Feito desde a importacao (15/08): 112 mil de testes, 90 mil da tela nova, 66 mil de portoes e ensaios, 79 mil no servidor | ~352 mil |
| Falta: as 86 fatias da fila das telas que faltam | ~250 mil |
| Falta: os debitos de pe fora da fila (113 dos 134; uns 30 de operacao; o D49 grande sozinho) | ~80 a 110 mil |
| Falta: o codigo de preparo do corte (os 13 itens antes da janela da etapa 1) | ~10 a 20 mil |
| Falta, no total | ~340 a 380 mil |

**Como foi medido.** O feito e a soma das insercoes desde o commit da importacao, sem os documentos, o `mysql.sql`, o `schema.sql`, a carga, as fixtures e os lockfiles. A fila, pelo tamanho estimado de cada fatia, corrigido pelo que as 11 ja feitas custaram de verdade (21,5 mil linhas reais contra 14,7 mil previstas, 1,46 vez). Os debitos, cruzando os de pe com os citados nas fatias que faltam: so 21 dos 134 estao dentro delas. A regua dos commits (intervalos de ate 60 minutos) nao serve mais desde 15/10: o trabalho corre nos agentes entre um commit e outro (15 commits em 43 horas de relogio).

**Outras reguas.** No menu, 20 dos 48 itens a migrar ja abrem a tela nova (42%; 6 ficam na antiga por decisao do dono). Nos debitos, 376 dos 510 nao estao mais de pe (74%), mas o registro cresce a cada fatia. A fila criada em 14/10 esta em 8% do volume dela.

**O que pode mexer na conta.** Os relatorios sao 37 das 86 fatias que faltam (cerca de 44% do volume) e nenhum foi feito para calibrar; cada fatia abre debitos novos; linha de codigo mede volume, nao dificuldade.

**Quanto tempo.** O dono perguntou: "quanto 'tempo' estima ate finalizar?". De 4 a 10 semanas, conforme o ritmo:

| Ritmo | Tempo | Termina perto de |
|---|---:|---:|
| Sem parar, como a semana de 09 a 16/10 | 4 a 5 semanas | 15 a 25/11 |
| Com as pausas (perguntas, pausas do dono, limite de uso da conta) | 6 a 10 semanas | fim de nov. a fim de dez. |

A semana medida (09/10 16h a 16/10 14h30): 49 commits, quase 24 horas por dia, um a cada 3,4 h; cada fatia de agora leva umas 3 h de ponta a ponta; o limite semanal da conta parou o trabalho depois de umas 45 rodadas, que e o teto pratico (cada fatia gasta cerca de 1,6 milhao de tokens nos agentes). O que falta, em rodadas: cerca de 180 (as 86 fatias, umas 110; os debitos fora da fila, umas 45, com o D49 sozinho umas 10; as respostas do dono que mudam codigo, umas 20; o preparo do corte, umas 6). A conta por linhas da o mesmo: cerca de 360 mil linhas a 500 por hora, umas 700 horas. Fora da conta: as janelas do corte na producao, que dependem do dono, e os relatorios, sem nenhum feito para calibrar.

## 17/10/2026 -- ACL-API

A fatia 9 da fila das telas que faltam: a F1 do desenho de Grupos e privilégios (`docs/f3-telas-restantes/tela-acl.md`), o
servidor que a tela nova (a fatia 12) vai chamar -- e a resposta 212 (b) do dono, a reativação.

**O que muda para quem usa.** Quem é reativado -- na tela nova de usuários, no botão da lista antiga ou no campo "Status" da
edição -- volta com o molde de hoje do grupo, como num cadastro novo: os ajustes de quando a pessoa saiu se perdem, e quem
saiu sem linha nenhuma (1.245 inativos na cópia) volta podendo trabalhar. Quem não é administrador deixa de reativar quem é
de grupo com funcionalidade de rede no molde ("os 4 do grupo 5"), com a frase *"O grupo desta pessoa tem uma funcionalidade
que vale para a rede inteira, e ela volta com o molde do grupo: só um administrador a reativa."* A tela de usuários ainda
não avisa isso antes do "ativar" (o D513, para a fatia que mexer nela). A tela antiga de moldes grava como antes, agora com
a linha do grupo travada: ela e a API nova gravam uma depois da outra.

**O que mudou por dentro.** A `/adm/api/grupos` ganhou cinco endereços, no `Adm\GruposController`: a lista dos grupos (os
papéis, as pessoas ativas e o total, as unidades, os moldes, o selo "de rede", a marca "quem não é administrador põe
pessoas aqui" -- lida da mesma consulta da lista suspensa, a 131 (b) -- e os ajustes), o grupo aberto (o catálogo inteiro,
sem "Vendas caixa", cada funcionalidade com os selos "de rede" e "não guarda nada", o molde e as quatro contagens), o
alcance (`GET .../privilegios/:funcionalidade/alcance?ler=&escrever=`, o que a gravação faria, sem gravar), o `PUT` do molde
com `{ler, escrever, antes}` e a exclusão por `POST .../exclusao` com `{antes}`. Ler é de `configuracoes/acl/ler`; gravar e
excluir, de `acl/escrever` e de ser administrador (403 `motivo: administrador`); 404 para o grupo, a funcionalidade e "Vendas
caixa" (a 122 (a)); 422 para o nível que não é booleano, o "escrever sem ler", o "nada" (que é a exclusão), o grupo de
administrador, o `antes` que falta ou não serve e a chave que o pedido não leva; 409 `mudou` e `sem_molde`. A conta é uma
só: o alcance e a gravação passam por `PrivilegioService::destinos` (a `RegraDoMolde`), e a gravação devolve o que fez na
forma do alcance; o mesmo `PUT` duas vezes responde 200 sem gravar. O molde lido continua no endereço de antes, com
`usuarios/ler` (a decisão 4 do desenho). Na reativação, `UsuarioService::mudarEstado` regrava as linhas pelo molde, e
`recusaDaReativacao` pergunta também pelo molde do grupo (`grupoTemFuncionalidadeDeRede`, a mesma pergunta da troca de
grupo; `RegraDoPrivilegio::concedeDeRede`, a da linha). Sem esquema novo. D336 corrigido de novo (o resto, com a 212 (b));
D513 registrado; notas no D374 (a trava) e no D177 (a cobertura). Os portões `conferir-rotas-sem-login.php` (as cinco
rotas), `conferir-recorte-por-unidade.php` (o grupo é da rede, e só o administrador grava), `conferir-autorizacao-privilegiada.php`
(a reaplicação e a pergunta do grupo), `conferir-cobertura-por-par.php` (um par novo, 334 para 335; um falso positivo a
menos), `conferir-debitos-repetidos.php` (o par D336 e D513, com o motivo) e `conferir-rotas-orfas.php` (passa a ler
também os controllers da `/adm/api`: a rota do molde lido, com o controller e a ação lado a lado, saía "controller
ausente"; falsificado com a ação trocada na rota) mudaram junto, e o `docs/inventario-acoes.csv` foi refeito (a cadeia
das três ações antigas do molde passa por `escrever`).

**Provado**, em série: os testes da fatia e os vizinhos -- `MoldePelosControllersTest` (21, novo), `RegraDoMoldeTest`,
`RegraDoPrivilegioTest`, `UsuarioAtivacaoTest`, a propagação, o ERP dos moldes e dos privilégios, a personificação, a
barreira de origem e as telas de usuários --, 548 casos no MySQL e 548 no PostgreSQL (3 pulados em cada, do D199, de outras
telas), saída 0; 40 mutantes, um de cada vez e restaurados byte a byte, todos vermelhos no caso dele (36 do servidor
novo, 4 da reativação); os ensaios de permissão (71 de 71, com as seis rotas novas) e de recusas (219 de 219, com os casos
163 a 171) nos dois bancos, saída 0; e o `conferir-tudo.sh`, rodado depois da última edição de código, com 64 portões ok e
o vermelho conhecido do D50, "Nenhuma falha inesperada". **Não provado:** dois pedidos ao mesmo tempo no
mesmo grupo (a trava é provada pela ordem das consultas, não por concorrência); a caracterização inteira, o contrato e os
ensaios restantes, que são do ciclo.

**A verificação** confirmou 10 achados, todos consertados. Na tela nova de usuários, depois do "ativar", a aba "Privilégios"
seguia mostrando as linhas de quando a pessoa saiu, que a 212 (b) tinha acabado de trocar pelo molde: o `UsuariosLigado`
relê os privilégios depois de ativar, sem os velhos à vista, e uma gravação de caixa que estava no ar deixa de valer. O
nível que já vale não grava nada pelas três portas -- o alcance dizia "N pessoas mudam" e contava as gêmeas juntadas, o
`PUT` respondia `gravado: false`, e o "editar" do ERP as juntava; agora os três dizem e fazem o mesmo nada. A recusa da
reativação pelo grupo de rede responde `motivo: de_rede`, como o desenho pede (a frase continua a do grupo). O ensaio de
recusas ganhou a barreira de origem nas duas escritas do molde (172 e 173, com um corpo que não gravaria nem sem ela;
falsificado com a barreira arrancada: 422 e 409, nada gravado). A frase do alcance sobre os inativos ("não mudam agora;
quem for reativado volta com o molde novo") foi corrigida no desenho, e entrou no D513 para a fatia 12. E cinco casos
novos no `MoldePelosControllersTest` (26): a releitura do molde depois da trava no "editar" e no "remover" do ERP (arrancada,
tudo ficava verde), o "atribuível" de depois com o molde de rede noutra funcionalidade, no grupo de administrador e no
grupo inativo, a guarda de leitura do alcance e a ordem privilégio, papel, existência e forma, e o que não grava sem
marcar a transação de fora (sem pontos de salvamento); e "o alcance diz o que a gravação faz" passou a andar pelos seis
destinos, em quatro gravações. 17 mutantes novos, um de cada vez e restaurados byte a byte, todos vermelhos no caso dele
(13 do servidor, 4 do front), mais o da barreira no ensaio. Depois dos consertos, em série: os testes da fatia e os
vizinhos, 604 casos no MySQL e 604 no PostgreSQL (1 pulado em cada), saída 0; o front inteiro (1.970), o build e o lint (no
piso); os ensaios de permissão (71 de 71) e de recusas (221 de 221, com o 172 e o 173) nos dois bancos, saída 0.


## 17/10/2026 -- PRIVILEGIO-DERRUBA-A-SESSAO

A fatia 10 da fila das telas que faltam: a resposta 102 (b) do dono, *"A pessoa sai do sistema quando a permissão muda.
Sem coluna; ela entra de novo digitando a senha (o 'lembrar' do aparelho deixa de valer), já com as novas."* O D291.

**O que muda para quem usa.** Quem tem um privilégio concedido ou tirado -- na aba Privilégios da tela nova de usuários,
na janela de permissões do ERP, no molde do grupo (pela API da fatia 9 ou pela tela antiga) ou na troca de grupo da
edição -- sai do sistema no pedido seguinte, nas duas telas (a página antiga em até 48 s, sem clicar), com a frase
"Atualizamos o sistema, pra isso precisamos que você faça login novamente."; o "lembrar" do aparelho deixa de entrar, e
ela entra com a senha, já com o que mudou. Até aqui o privilégio tirado valia até o próximo login. Não sai quem regrava o
mesmo nível, quem só ganha ou perde uma linha "nada", nem quem é do grupo de administrador (que passa por tudo). A aba
Privilégios diz, antes de gravar, "Ao gravar, Ana sai do sistema, se estiver nele, e entra de novo digitando a senha, já
com o que mudou", e cada gravação termina em "A pessoa sai do sistema, se estiver nele, e entra de novo com a senha." --
diziam "vale no próximo login". A tela antiga ganhou a frase na janela de permissões (no salvar e no remover, com o
"se" -- só quem tem o acesso mudado sai --, e, para quem é do grupo de administrador, que ela não sai), no formulário do
molde, no "Remover" da lista dos moldes e no campo "Grupo" da edição.

**O que mudou por dentro.** Sem coluna e sem esquema novo: a gravação que muda o efetivo da pessoa apaga o `token_web`
dela na mesma transação e antes das linhas, relida com a trava da linha (`UsuarioService::tirarDoSistema`, pela pergunta
pura `RegraDoPrivilegio::saiDoSistema`), e a
regra do um dispositivo por vez (`UmDispositivoPorVez`), que a guarda de despacho já chama em todo pedido, encerra a sessão
que guarda o token velho -- o mecanismo da senha nova pelo link (o D347). As portas são as de sempre: `gravarPrivilegio` e
`removerPrivilegio` (a API, a janela do ERP e a propagação do molde) e `salvarPrivilegios` (a troca de grupo, a
reativação e o cadastro). O alcance e a gravação do molde (`/adm/api/grupos`) devolvem `saem` -- quantas pessoas saem do
sistema, e em quantas unidades --, pela mesma conta da gravação; a tela de grupos (a fatia 12) tem a frase no desenho.
D291 corrigido; D514 registrado (a personificação aberta segue com a foto de quando a pessoa foi assumida). Débitos de pé:
135, como antes (um fechado, um aberto).

**Provado**, em série: os testes da fatia e os vizinhos -- `PrivilegioDerrubaASessaoTest` (8, novo: o `PUT` e a exclusão da API,
a janela do ERP, a troca de grupo, a reativação, o grupo de administrador e a linha "nada", com o token no banco, a guarda de
despacho chamada como o evento a chama e o cookie), três casos novos no `MoldePelosControllersTest` (29) e "o alcance diz o
que a gravação faz" conferindo `saem` contra os tokens apagados --, 317 casos no MySQL e 317 no PostgreSQL, saída 0; 17
mutantes, um de cada vez e restaurados byte a byte, todos vermelhos no caso dele (14 do servidor, 3 do front); o front
inteiro (1.971), o build e o lint (no piso); o contrato (74 e 74) e os doze ensaios nos dois bancos, mais o
`medir-adm-api-no-postgres`, saída 0 -- nenhum roteiro tinha sessão que caísse pela mudança; o caso 24 novo do
`ensaiar-sessao-do-erp.php`, por HTTP (o gerente entra, o administrador lhe dá "ler" pela janela do ERP, a sessão do gerente
responde 401 e o cookie cai no formulário; quem mudou segue; o mesmo nível salvo de novo não tira ninguém), 24 de 24 nos dois
bancos, e falsificado com a chamada arrancada (23 de 24); e o `conferir-tudo.sh`, 64 portões ok e o vermelho conhecido do
D50, "Nenhuma falha inesperada".

**A verificação** confirmou dez apontamentos, sete distintos, e todos foram tratados. O que mudou: (1) o token saía pelo
objeto, e o `flush` só grava o que mudou desde a leitura -- com o token lido nulo (a pessoa tinha clicado em "Sair") e
um login dela depois da leitura, nulo sobre nulo não gravava nada, e a sessão nova ficava com o privilégio de antes;
agora a pessoa é relida com `FOR UPDATE` e o token que sai é o do banco, com a linha travada até o `commit` (um `UPDATE`
direto, a primeira tentativa, o portão dos escritores do horário recusa), e o remover ganhou transação; (2) a troca de
grupo e o cadastro refaziam as linhas pelo molde depois do `commit`, uma a uma -- agora
na mesma transação; (3) as frases da tela antiga, uma por porta, ditas como o servidor faz, e desenhadas num caso; (4)
o desenho de Grupos lista o `saem`. Sete casos novos (14 no `PrivilegioDerrubaASessaoTest`, 30 no
`MoldePelosControllersTest`) e 13 mutantes, todos vermelhos. O Artifact do roadmap é do ciclo.

**Não provado:** a tela no navegador (a frase nova foi medida pelo vitest e pelas páginas desenhadas no caso, e a sessão
que cai, em processo e por HTTP); o login no meio de uma gravação com dois pedidos de verdade ao mesmo tempo (o caso o
simula gravando o token por fora do objeto, que é o que o login faz); a personificação (o D514); e a caracterização
inteira, que é do ciclo.

## 17/10/2026 -- PRIVILEGIOS-GEMEOS

**O que mudou para quem usa.** Nada que se veja na tela: ninguém ganhou nem perdeu acesso. Por baixo, cada pessoa passou a
ter no máximo uma linha de privilégio por funcionalidade, e cada grupo no máximo um molde, e o banco recusa a segunda. É a
resposta 125 (a) do dono (12/10/2026), *"As duas regras, com os 117 pares juntados"*, a fatia 11 da fila das telas que
faltam (o D289 e o D374). Quando duas gravações do mesmo privilégio chegam juntas -- duas abas, a tela nova e a antiga --, a
segunda já não grava uma linha gêmea calada: a tela nova recebe o 409 de sempre e relê, e a janela de permissões e o "Novo"
do molde no ERP dizem "Outra gravação mudou este mesmo privilégio ao mesmo tempo, e nada foi gravado: confira e tente de
novo." O molde novo que esbarra nisso volta atrás inteiro, com a propagação.

**Nas duas cópias locais, já.** Contado antes, só contagens: 117 pares de pessoa e funcionalidade com mais de uma linha,
235 linhas, 118 a mais -- um par com valores diferentes, um de três linhas, nenhum molde repetido --, iguais no MySQL e no
PostgreSQL. Ensaiado num banco descartável de cada um (apagado em seguida) e aplicado numa transação: fica a linha mais
antiga de cada par com o OU delas, que é o que o `Acl` já somava; 15.503 linhas viraram 15.385, e o efetivo de cada pessoa
deu o mesmo resumo antes e depois. As chaves: `uq_privilegios_grupo_funcionalidade` e
`uq_privilegios_usuarios_usuario_funcionalidade` no MySQL e no `mysql.sql`; no PostgreSQL a das pessoas, pela lista
`$unicidades` do gerador (a do molde ele já tinha).

**Na produção, no passo 3 do corte**, a etapa 2 pela resposta 192 (a) -- nunca no laço do passo 0, que a etapa 1 copia:
`scripts/migracao-privilegios-unicos.sql`, depois da limpeza. A chave do molde nasce primeiro, e um molde repetido de valores
diferentes para o roteiro antes de apagar qualquer linha -- desde a verificação, de verdade: o bloco 5 da limpeza, que roda
antes, só tira o repetido de valores iguais; e o procedimento manda contar o de valores diferentes antes, na réplica. O portão do passo 0 aprendeu a aceitar a chave única que só o passo
3 cria, com as duas metades: a migração a cria, e o gerador rodado sobre o dump de produção ainda a emite (a afirmação 8).
Até o passo 3 a produção segue com as gêmeas, e o código continua a somá-las ao ler e a juntá-las ao gravar.

**O que mudou por dentro.** A linha nova do par nasce direto pela conexão, depois da trava da pessoa
(`UsuarioService::linhaNova`), e a recusa do banco vira `ParGravadoNoMeio`, que a API, a janela do ERP, a API dos grupos e o
"Novo" do molde transformam em resposta; pelo `flush`, a recusa fecharia o EntityManager. O gerador não emite duas vezes a
chave que a lista já declara. Os casos que gravam gêmeas as gravam numa tabela temporária sem a chave, que esconde a de
verdade dentro da transação do caso (desde a verificação; a primeira versão as lia em memória, e nada podia ficar vermelho).
D289 e D374 PREPARADOS; D515 registrado e PARCIAL (falta o ensaio geral do corte). Débitos de pé: 134 (eram 135).

**Provado**, em série, na primeira versão: a caracterização inteira, 3.898 casos em cada banco, saída 0; os vizinhos da
fatia, 239 casos nos dois bancos; o `ensaiar-sql-do-passo-3.php` com o caso novo (a gêmea igual, a diferente, a de três
linhas e a de ids fora da ordem; a segunda rodada; a gêmea seguinte recusada; o molde repetido parando o roteiro antes de
juntar), no 8.4.11; os ensaios por HTTP no MySQL que tocam privilégio
(`ensaiar-usuarios-adm-api` 19 de 19, com o caso 07 numa linha só; `ensaiar-sessao-do-erp` 24 de 24;
`ensaiar-permissao-adm-api`; `ensaiar-recusas-adm-api` 221 de 221); e os portões que a fatia toca, saída 0: o passo 0, a
unicidade do schema, a limpeza por trás da unicidade, as chaves únicas nos dados, o schema-tool, a autorização privilegiada,
as referências dos docs, os débitos repetidos, os segredos e a marca das fixtures. Dezesseis mutantes, um de cada vez e
restaurados byte a byte: quinze vermelhos no caso ou no portão deles; o do ponto de salvamento em volta do `INSERT` ficou verde
nos dois bancos -- a verificação mostrou que ele não tinha como ficar vermelho (abaixo). O front não mudou.

**Não provado:** dois pedidos de verdade ao mesmo tempo (a corrida é simulada por um dublê que lê "sem linha" com a linha da
outra já gravada); os ensaios no PostgreSQL e o contrato, que são do ciclo; o `ensaiar-sql-do-passo-3.php` num MySQL 8.0 (a
réplica é 8.0.46); e a migração no tamanho e no banco de produção. O ensaio geral do corte (`ensaiar-corte.sh`) não roda a
migração -- a cópia já está juntada (o D515). O Artifact do roadmap é do ciclo.

**A verificação, no mesmo dia, confirmou seis achados, e os seis foram tratados.**

- **O passo 3 nunca parava no molde repetido de valores diferentes** (média): o bloco 5 de `pre-migracao-limpeza.sql`, que
  roda antes da migração, agrupava só por (grupo, funcionalidade) e tirava qualquer molde repetido, ficando o mais antigo
  sem ninguém decidir; o ensaio só via a parada porque rodava a migração sozinha. O bloco 5 passou a tirar só o de valores
  iguais (o que a aprovação de 26/08/2026 cobre), o ensaio roda o bloco 5 antes, como o passo 3 (o de valores iguais sai e
  a migração segue; o de valores diferentes fica, e a chave para o roteiro), e o procedimento manda contá-lo antes, na
  réplica. O que fica, se aparecer um, foi ao lote (parar e o dono decidir, que é o que está; o que concede mais; ou o
  mais antigo).
- **O código das gêmeas tinha perdido todo caso que podia ficar vermelho** (média): a gêmea em memória era uma entidade
  nova, e o `remove` dela não faz nada. As gêmeas voltaram aos casos, gravadas numa tabela temporária sem a chave
  (`CaracterizacaoTestCase::semAChaveDasPessoas`; no MySQL o `CREATE TEMPORARY TABLE` não fecha a transação, no PostgreSQL o
  DDL é transacional), com os casos tirados de volta e um novo, o da lista com o par somado. O D515 e o D289 diziam que esse
  código não tinha mudado e estava provado; os dois foram corrigidos.
- **A troca de grupo, o cadastro e a reativação davam 500 numa corrida, no PostgreSQL** (baixa): a linha nova de quem é do
  grupo de administrador, ou de um nível que não muda o efetivo, não travava a pessoa, e o `DELETE` da troca de grupo não via
  a linha ainda sem `commit`. A linha nova passou a travar a pessoa sempre (`UsuarioService::travarAPessoa`): toda porta que
  cria linha da pessoa segura a trava, e uma espera a outra.
- **O caso da corrida não vigiava "nenhuma consulta entre a recusa e o `rollback`"** (baixa): a suíte punha um ponto de
  salvamento em todo nível, que a produção não tem. A porta da pessoa passou a rodar aninhada como na produção
  (`CaracterizacaoTestCase::aninhadaComoNaProducao`), e a consulta posta no meio fica vermelha no PostgreSQL.
- **A metade "o gerador sobre o dump" da afirmação 8 do passo 0 não tinha falsificador** (baixa): o dump vai ao gerador com
  uma tabela de marca, e o DDL sem ela é do gerador que leu outro arquivo -- o portão fica vermelho.
- **A tabela da volta atrás não dizia o que a migração apaga e muda** (baixa): ganhou a linha das gêmeas e das chaves, e a
  conta, as 118 linhas.

**Provado na verificação**, em série: a caracterização inteira, 3.903 casos em cada banco (11 pulados), saída 0; os casos de
privilégio e de molde (`--filter "Privilegio|Molde"`), 238 em cada banco, saída 0; os ensaios por HTTP no MySQL, depois de
`montar-banco-contrato.sh` (`ensaiar-usuarios-adm-api` 19 de 19, `ensaiar-sessao-do-erp` 24 de 24,
`ensaiar-permissao-adm-api` 71 de 71); o `ensaiar-sql-do-passo-3.php` no 8.4.11; e os portões que a correção toca. Treze mutantes novos, um de cada vez e
restaurados pelo sha1, e quatro controles: os seis das gêmeas, vermelhos nos dois bancos; o da linha nova sem a trava, vermelho nos dois; os três
da consulta entre a recusa e o `rollback`, vermelhos no PostgreSQL e verdes no MySQL, como devem (no MySQL a recusa não
aborta a transação), e os dois do molde verdes também no PostgreSQL com a suíte de antes -- o controle; o do bloco 5 de antes, vermelho no
ensaio; e os dois do gerador que ignora `--mysql-sql`, vermelhos no passo 0, e verdes com o portão de antes.

## 17/10/2026 -- ACL-TELA

A fatia 12 da fila das telas que faltam: a F2 do desenho de Grupos e privilégios (`docs/f3-telas-restantes/tela-acl.md`),
a tela nova sobre o servidor das fatias 9 (a API do molde), 10 (quem muda de privilégio sai do sistema, a 102 (b)) e 11 (as
chaves únicas). A P4 segue "como hoje".

**O que muda para quem usa.** Configurações > ACL > "Grupos" e "Privilégios" abrem a tela nova, a mesma, nas duas vistas
("por grupo" e "por funcionalidade"); até aqui caíam na tela "ainda roda na versão antiga". A lista diz de cada grupo os
papéis (só se leem), as pessoas ("181 ativas de 1.749, em 45 unidades"), quantas funcionalidades o molde dá, com o selo
"de rede", os ajustes e se quem não é administrador põe pessoas ali; o de administrador "passa por tudo, sem molde". O grupo
aberto é o catálogo inteiro, uma linha por funcionalidade, com o nível do molde -- nada, ler, ler e escrever -- e as pessoas
ativas que acompanham, as ajustadas e as sem linha (as repetidas só quando há), com um filtro ("todas", "no molde", "com
ajustes") e a busca pelo nome. **Escolher o nível não grava**: abre, na linha, o que o servidor calcula -- "Mudar «Assistente
IA - Usuários» no grupo Colaborador de ler e escrever para ler: 177 pessoas ativas, em 44 unidades, acompanham o molde e passam
a só ler." --, quem tem o ajuste igual ao nível novo (e passa a acompanhá-lo, como hoje), quem fica com o ajuste, quem continua
sem a linha, no "nada" quantas pessoas a recebem se ela voltar ao molde, os inativos ("não mudam agora; quem for reativado
volta com o molde novo", a 212 (b)) e quem sai do sistema e entra de novo com a senha (a 102 (b)). Numa funcionalidade de rede
que o molde passa a dar (ou a dar com escrita), o aviso em vermelho diz a quantas pessoas ela abre as outras franquias e que
quem não é administrador deixa de pôr pessoas no grupo; reduzir a de rede que já está no molde só diz que o alcance continua. O
"gravar" é ouro quando concede e vermelho quando tira; "não gravar" volta ao nível de antes. Depois de gravar, a linha diz o
que mudou ("177 pessoas passaram a só ler. 177 pessoas saíram do sistema…"); se outra aba ou a tela antiga gravou no meio, a
tela não grava por cima, relê o grupo e diz. A vista por funcionalidade mostra, de uma funcionalidade, o nível de cada grupo,
as contagens e quantas pessoas a têm sem molde no grupo; dali cada grupo abre já procurando por ela. Só o administrador com o
privilégio altera; quem tem o privilégio sem ser administrador ouve por quê. A lista de moldes de 20 em 20 segue na tela
antiga, até o corte.

**O que mudou no código.** No front, `api/grupos.ts` (os tipos e os seis pedidos), `alcanceDoMolde.ts` (as frases, a classe do
"gravar", os níveis e os filtros, puras), `Acl.tsx` (a tela) e `AclLigado.tsx` (o invólucro: só a resposta do último pedido
vale, uma escrita por vez, o 409 que relê por baixo), o ramo `ehAcl` no `Console.tsx`, o CSS (a grade vira lista a 40rem) e a
prévia `Acl` em `frontend/.design-sync/previews` (`_acl.ts`, com o catálogo de `_configuracoes.ts`, agora exportado; o envio
ao Claude Design ainda não foi feito). No servidor, a lista
`GET /adm/api/grupos` ganhou a busca `?funcionalidade=` (cada grupo com a linha da grade daquela funcionalidade, e a
funcionalidade no envelope; 422 pelo nome para o que não é algarismo, 404 para a que não existe e a fora de uso) e o
`catalogo` no envelope, sempre (`GrupoJson::naFuncionalidade` e `::catalogo`; `RegraDoMolde::doFiltroDaLista`). E o
`Adm\GruposController` só consulta o id que cabe na coluna `integer`: o grupo e a funcionalidade 99999999999 eram 500 no
PostgreSQL, e são 404 -- o resto da `/adm/api` tem o mesmo defeito (o D516, novo, parcial). O leitor das interfaces do front
saiu do `UnidadeEscritaApiTest` para o arnês `tests/Caracterizacao/TiposDaTelaNova.php` (com `extends` e `| null`), e prende
também os tipos de `api/grupos.ts`. Sem esquema novo. D513 parcial (falta o "ativar" dos usuários perguntar).

**Provado**, em série: os testes da fatia e os vizinhos (o filtro
`Molde|Unidade|Privilegio|UsuarioAtivacao|Grupo|Barreira|Origem|ForaDoMapeamento`), 1.000 no MySQL e 1.000 no PostgreSQL, 3
pulados em cada, saída 0 -- entre eles os casos novos do `MoldePelosControllersTest` (a lista por funcionalidade, a busca que
não serve, os tipos da tela e as frases da tela, e o 404 do número grande) e do `RegraDoMoldeTest`; o front inteiro, 2.024
casos (eram 1.971: `AclLigado` 24, o contrato 9, `alcanceDoMolde` 13 e 7 no Console), os da tela com
`AMPLIFICAR_EFEITOS_MS=50`, o build e o lint (os seis avisos de antes); o tipo das prévias; os ensaios nos dois bancos de
contrato, o de permissão inteiro ("recusou 72 de 72") e o de recusas nos casos 163 a 177 (15 de 15, os quatro novos: a busca
de texto, "Vendas caixa", o grupo 99999999999 e o controle); 36 mutantes, um por vez e restaurados byte a byte -- 26 do
front e 10 do servidor --, todos vermelhos no caso dele (os dois do número grande só no PostgreSQL, como devem); e
o `conferir-tudo.sh`, 63 portões ok, o vermelho conhecido do D50 e o de débitos repetidos, que pedia tirar o par D336|D513 da lista (o título do D513 ganhou a marca) -- tirado, e saída 0 sozinho, com o `contar-debitos.php` (135 de pé: o D516 é novo). **Não provado:** a tela num navegador, a 375 px e nos dois temas, contra o banco de contrato; a
caracterização inteira, o contrato e os ensaios inteiros, que são do ciclo; a prévia no Claude Design.

**A verificação** (17/10/2026) confirmou 10 apontamentos, todos tratados. Na tela: o aviso forte da funcionalidade de rede
aparecia também na redução ("passa a alcançar" o que já alcançava, ao lado de um "gravar" vermelho) e sem o N do desenho --
agora é só do que concede, com quantas pessoas, e a redução diz que o alcance continua; a grade vazia dizia "com esta busca"
também quando quem a esvaziou foi o filtro; e a funcionalidade que não chegava, na vista por funcionalidade, não oferecia
"tentar de novo" (escolher a mesma opção do `<select>` não pede nada). No servidor: o D516 contava 37 chamadas e eram 38, e
uma delas era da própria `/adm/api/grupos` -- o `GET /adm/api/grupos/:grupo/privilegios`, do `Adm\PrivilegiosController` --,
que ganhou a guarda do número grande, um caso nos dois bancos e o caso 178 do ensaio de recusas; e esta entrada dizia "a
prévia no Claude Design", que ainda não foi enviada. E faltavam casos: voltar depois de gravar na vista por funcionalidade
(relê a funcionalidade e devolve o foco ao grupo), o "atualizar" com a pergunta aberta, os "carregando", o grupo que não
abre (404 e rede), a funcionalidade que não chega, a lista vazia, a coluna das repetidas quando vem, o 409 seguido de
"voltar", as duas listas no ar fora de ordem e o 409 com a releitura que falha. **Provado**, em série: o filtro da fatia,
1.001 no MySQL e 1.001 no PostgreSQL (3 pulados em cada, saída 0); o front inteiro, 2.040 casos (`AclLigado` 37, o contrato
11, `alcanceDoMolde` 14), os três arquivos da tela com `AMPLIFICAR_EFEITOS_MS=50` (62) e os 28 casos do Console que o filtro
`Grupos|Privilégios|grupos|privilégio` escolhe, o build, o lint (os seis avisos de antes) e o tipo das prévias; o ensaio de recusas nos casos 174 a 178, 5 de 5
nos dois bancos de contrato; 25 mutantes novos, um por vez e restaurados pelo sha256, todos vermelhos -- 24 do front (um
sobreviveu na primeira rodada: o foco do "tentar de novo" da funcionalidade, que a lista relida devolvia ao título de
qualquer jeito; o caso passou a prender o pedido e a conferir o foco durante a carga, e ficou vermelho) e o do servidor (o
`doGrupo` sem a guarda: erro no PostgreSQL, verde no MySQL, como deve; e pelo HTTP, o caso 178 recebe 500 no PostgreSQL);
o `conferir-tudo.sh`, 64 portões ok e o vermelho conhecido do D50, e o `contar-debitos.php` (135 de pé).

**No navegador** (17/10/2026), contra o banco de contrato, com um administrador temporário, a 1.024 e a 375 px e nos dois
temas: a lista dos quatro grupos (o de administrador "passa por tudo, sem molde" e, aberto, sem grade); o filtro "no molde"
vazio com a frase do filtro; no grupo de teste, as setas abrindo o alcance sem nenhum PUT ("3 pessoas ativas, em 3 unidades,
recebem a funcionalidade…" e "3 pessoas… saem do sistema"), o "não gravar" com o nível e o foco de volta, o gravar (a frase
verde, o molde em "1 funcionalidade" e 3 acompanhando), a exclusão (o `POST …/exclusao`, "saiu do molde", o "gravar"
vermelho), a de rede com o aviso forte e o N, a redução da de rede sem o aviso e com a frase de que continua, o 409 com o
molde mudado por fora (a tela relê e marca o nível do banco), o "atualizar" com a pergunta aberta e a vista por
funcionalidade (o grupo aberto já procurando por ela, gravar ali e voltar com o nível novo e o foco no grupo). A página não
rola de lado a 375 px. Achou dois defeitos, consertados: depois do 409 o foco ia ao rádio marcado antes de a releitura
chegar -- o de origem -- e ficava nele, desmarcado (o caso novo "o 409 com a releitura que demora" prende a releitura, e era
vermelho antes do conserto); e o aviso forte dizia "hoje, 0 pessoas cadastram colegas", que agora é "hoje, ninguém cadastra
colegas sem ser administrador" (com um caso no `alcanceDoMolde.teste.ts`).

**A resposta 236 (a) do dono** (17/10/2026) veio junto: quem só lê as unidades vê a ficha inteira, com o faturamento e os
sócios de todas, como o desenho. Nada muda no código; o D512 fechou como DECIDIDO (o royalty do ciclo segue só ao
administrador). **Provado no ciclo:** caracterização 3.909 em cada banco, contrato 74 e 74, os ensaios nos dois bancos sem
vermelho, 64 portões, front 2.041; depois do ciclo, só o texto do D512 mudou, e o `contar-debitos.php` (134 de pé), o
`conferir-debitos-repetidos.php` e o `conferir-referencias-docs.php` saem 0.

## 17/10/2026 -- USUARIOS-LER-AGENDA-E-COTA

A fatia 13 da fila das telas que faltam: a F1 do desenho do resto de Usuários
(`docs/f3-telas-restantes/tela-usuarios-resto.md`), só leitura. Gravar a agenda é a fatia 14, os dados a 15 e a cota-parte a
16.

**O que muda para quem usa.** A ficha do colaborador na tela nova de Usuários ganha duas abas, "Agenda" e "Cota-parte",
entre "Dados" e "Privilégios", só para quem pode alterar os usuários, como na tela antiga. Entre elas, a Agenda aparece só
para quem o cadastro põe em alguma agenda, como na tela antiga; a Cota-parte, para todos. A Agenda mostra a semana -- o turno, o almoço, as vagas de cada dia (as que o aplicativo e a grade
oferecem) e, ao lado, o horário da loja --, o tempo de atendimento, as férias, as áreas, os serviços que as agendas oferecem
(com o inativo e o de fora das áreas marcadas ditos), a ordem no aplicativo com quem empata nela, e as duas cores; e avisa
sem o operador procurar: quem aparece sem horário nenhum ("ninguém consegue marcar"), o dia aberto sem o tempo de
atendimento, quem hoje não aparece em agenda nenhuma, e o dia que passa do limite de 71 horários da agenda (o fim dele não
aparece, nem no aplicativo). A Cota-parte mostra o percentual de serviços e de produtos e as extras por produto, com a que nunca vale (a
de pacote) e a de produto inativo marcadas, e diz que o cadastro guarda só o percentual de hoje (a resposta 134 (b) do dono)
e que a Cota-parte do menu só lê a base; a própria pessoa não ganha o link de alterar (a resposta 219 (a)).
No telefone, cada dia da semana vira um cartão. Alterar as duas continua na tela antiga, pelo link de cada aba.

**O que mudou no código.** No servidor, `GET /adm/api/usuarios/:id/agenda` e `/cota-parte` (`Adm\UsuariosController`,
`UsuarioJson::daAgenda` e `::daCotaParte`, `UsuarioComissaoExtraRepository::daCotaParte`), com a guarda da tela antiga
(`escrever` e o `alcanca`) e o número que a coluna não guarda como 404 (o D516); as vagas e o limite de 71 perguntados ao
gerador, e os serviços oferecidos perguntados a quem monta as agendas (`popularComboboxServicos`); o
`RetratoDoHorario::NO_APLICATIVO`, conferido contra a lista que o aplicativo recebe. No front, `api/usuarios.ts` (os tipos,
presos ao recurso pelo arnês `TiposDaTelaNova`), `agendaDoUsuario.ts` (as frases, puras), `Usuarios.tsx` e
`UsuariosLigado.tsx` (cada aba pede o seu recurso quando abre), o CSS e duas prévias novas em `Usuarios`
(`AgendaDoColaborador`, `CotaParteDoColaborador`; o envio ao Claude Design ainda não foi feito). Sem esquema novo. D517
novo: a comanda nova oferece como quem atendeu quem o cadastro tira do PDV. D518 novo: quem só lê os usuários vê a agenda e
a cota-parte? -- o desenho dizia que sim, a tela antiga diz que não; a tela nova segue a antiga, e a pergunta foi ao lote.

**Provado**, em série: o caso novo `UsuarioAgendaECotaParteApiTest` (10) e os vizinhos (23 arquivos, 423 casos) no MySQL e
no PostgreSQL, saída 0; o front inteiro, 2.064 casos (eram 2.041: 16 em `UsuariosLigado`, 7 em `agendaDoUsuario`), os da
tela também com `AMPLIFICAR_EFEITOS_MS=50`, o build, o lint (os avisos de antes) e o tipo das prévias; o
`ensaiar-usuarios-adm-api.php` nos dois bancos de contrato, 25 de 25 (os seis casos novos, 20 a 25); 31 mutantes, um por
vez e restaurados pelo sha1 -- 20 do servidor e 11 do front --, todos vermelhos no caso dele, menos o do número grande no
MySQL, que é verde como deve (o do PostgreSQL cai); e os portões que a fatia toca, saída 0 (as rotas sem login, o recorte
por unidade e a cobertura por par com as linhas novas). **Não provado:** as abas no navegador, a 375 px e nos dois temas,
contra o banco de contrato; a caracterização inteira, o contrato e os ensaios inteiros, que são do ciclo; a prévia no Claude
Design.

**A verificação em três lentes** confirmou 22 apontamentos (19 achados distintos), e 18 foram tratados no código e nos
documentos; o do roadmap publicado é do commit. As duas leituras passaram de `ler` a `escrever`, como a tela antiga (o D518);
os serviços dizem se as agendas os oferecem, e a frase deixou de dizer "o aplicativo" de quem só está na agenda do ERP; cada
dia diz se passa do limite do gerador; a própria cota-parte não leva à tela antiga; a nota da cota-parte diz que o menu só lê
a base; a região viva da seção fica na árvore; o foco que caía no `body` com a releitura vai ao título da seção; a Agenda que
sai da lista volta aos Dados no estado; o 401 e o 404 não oferecem "tentar de novo"; a nota dos Dados diz as abas que
existem; e ganharam caso o atualizar na Cota-parte, a outra aba esquecida, a agenda esquecida e relida depois de mudar o
estado (também no 409), os campos da agenda que nenhum caso conferia, o tempo 0, os sete dias da semana de referência, as
ordens das listas e o aviso da pessoa inativa. O desenho (a F2 e a F4) e os blocos 14 e 16 da fila dizem o que o `GET`
ainda ganha. **Provado na correção**, em série: o `UsuarioAgendaECotaParteApiTest` (12 casos) e os vizinhos (23 arquivos, 425
casos) nos dois bancos; o front inteiro, 2.083 casos, e os da tela também com `AMPLIFICAR_EFEITOS_MS=50`; o build, o lint (os
avisos de antes) e o tipo das prévias; o `ensaiar-usuarios-adm-api.php` nos dois bancos de contrato, 25 de 25; 34 mutantes novos (17 do servidor e 17 do front), um por vez e restaurados pelo sha1, todos vermelhos; e os portões que a
fatia toca. **Não provado:** o navegador, que é de quem faz o ciclo.

**No navegador** (17/10/2026), contra o banco de contrato, com um administrador temporário, a 1.024 e a 375 px e nos dois
temas: a aba Agenda com um pedido só e o foco nela, as setas indo e voltando entre as abas sem pedido novo, a Cota-parte com
o seu pedido, o desativar com a Agenda à vista (a agenda relida diz "Hoje não aparece em agenda nenhuma", e o ativar a
devolve), a rede caída na leitura da agenda (o "tentar de novo" relê e leva o foco ao título "Agenda"), a própria ficha
(sem a aba Agenda, que o cadastro diz "nenhuma", e a Cota-parte sem o link: "Ninguém altera a própria cota-parte, nem o
administrador."), o Gerente Teste sem a aba Agenda e com a nota que diz por quê, e os dias em cartões a 375 px sem a página
rolar de lado. Nenhum defeito. A loja da unidade de contrato aparece "fechado" em todos os dias porque o horário dela está
vazio -- o mesmo que a tela de Unidades diz do horário vazio (na cópia, 33 das 36 unidades ativas não têm o domingo).

## 17/10/2026 -- USUARIOS-GRAVAR-AGENDA

A fatia 14 da fila das telas que faltam: a F2 do desenho do resto de Usuários
(`docs/f3-telas-restantes/tela-usuarios-resto.md`), gravar a agenda do colaborador. Os dados são a fatia 15 e a cota-parte a
16.

**O que muda para quem usa.** A aba Agenda da ficha do colaborador ganha o "alterar a agenda", que abre o formulário no
lugar da leitura: o tempo de atendimento, a semana -- a abertura, o almoço e o fechamento de cada dia, com as vagas que o
dia dá calculadas na hora e o horário da loja ao lado --, o "copiar o horário da loja" e o "copiar para o dia seguinte" (os
dois preenchem e não gravam), as férias, as áreas, os serviços com a procura (cada um diz se as agendas o ofereceriam), a
ordem no aplicativo com quem empata nela hoje, e as duas cores com a amostra. O salvar confere antes de ir e diz, no campo,
o que não serve: o dia de uma ponta só, a abertura igual ou depois do fechamento, o almoço fora do horário do dia ou num dia
fechado, o dia que passa do limite de 71 horários da agenda (com o fechamento que caberia), as férias de uma ponta só ou
invertidas, a ordem fora de 1 a 15, a cor que não é cor, e o serviço que as agendas não ofereceriam -- o sem área, e o de uma
área que não está marcada. O almoço de início igual ao fim é "sem almoço". Gravada, a aba diz que vale a partir de agora (a
ocupação dos meses passados usa o horário da época). Se a tela antiga, ou outra aba, gravou a agenda enquanto o formulário
estava aberto, nada é gravado: o formulário fecha, a aba diz, e a agenda é relida. A tela antiga passa a recusar pela mesma
regra -- a frase do campo no campo, e a do dia no topo, com o nome do dia; dos 175 ativos com agenda da cópia, 15 têm hoje
alguma coisa que a regra recusa, e nada muda para eles até alguém salvar. No telefone, cada dia vira um cartão.

**O que mudou no código.** No servidor, `PUT /adm/api/usuarios/:id/agenda` (`Adm\UsuariosController::gravarAgendaAction`),
com a guarda da leitura (`escrever` e o `alcanca`) e o `antes` obrigatório; a regra pura `RegraDaAgenda`, das duas portas;
o `UsuarioService::editarAgenda` com a pessoa travada primeiro, o `antes` comparado com o banco (409 `mudou`), as ligações
conferidas e gravadas na mesma transação (`AgendaMudouNoMeio`, `AgendaRecusada`; as opções em `opcoesDaAgenda`, pela lista
nova `ProdutoRepository::servicosDaAgenda`); o `editarAgendaAction` da tela antiga pela mesma regra; e o `GET` com as
opções do formulário. D519 e D520 registrados e corrigidos (o D-H e o D-I do desenho). No front, `api/usuarios.ts`
(`gravarAgenda`, `CorpoDaAgenda`, `OpcoesDaAgenda`), `formularioDaAgenda.ts` (puro: as vagas pelo passo do gerador, a mesma
regra, o corpo, o `antes`, o copiar), `AlterarAgenda.tsx`, `Usuarios.tsx` e `UsuariosLigado.tsx`, o CSS, e quatro prévias
novas em `Usuarios` (`AgendaEmEdicao`, `AgendaRecusadaNoFormulario`, `AgendaGravada`, `AgendaQueMudouPorFora`; o envio ao
Claude Design ainda não foi feito). Sem esquema novo; sem escritor novo do horário (a `RECEPTORES_CONHECIDOS` não mudou).

**Provado**, em série: os casos novos `RegraDaAgendaTest` (13, sem banco) e `UsuarioAgendaPelosControllersTest` (13), com os
vizinhos (usuários, privilégios, moldes, teto de rede, cota-parte, barreira, agenda e produtos: 1.095 casos) no MySQL e no
PostgreSQL, saída 0; o front inteiro, 2.117 casos (eram 2.083: 12 em `UsuariosLigado`, 22 em `formularioDaAgenda`), os da
tela também com `AMPLIFICAR_EFEITOS_MS=50`, o build, o lint (os avisos de antes) e o tipo da prévia; o
`ensaiar-usuarios-adm-api.php` nos dois bancos de contrato, 29 de 29 (os quatro casos novos, 26 a 29: o `antes` velho, o dia
invertido, a gravação com a vigência da porta `agenda` e a devolução, com a última vigência igual ao retrato do cadastro); 35
mutantes, um por vez e restaurados pelo sha1 -- 23 do servidor e 12 do front --, todos vermelhos (um sobreviveu na primeira
rodada, a forma do `antes` por cima, e ganhou caso); e os 64 portões (`conferir-tudo.sh`, o vermelho conhecido de sempre) e o
de segredos. **Não provado:** o formulário no navegador, a 375 px e nos dois temas, contra o banco de contrato (com o 409 da
tela antiga gravando no meio); a espera da trava entre duas conexões, que a suíte não alcança; a caracterização inteira, o
contrato e os ensaios inteiros, que são do ciclo; a prévia no Claude Design.

**A verificação do mesmo dia** (23 apontamentos, 21 achados distintos; todos tratados, nenhum ao lote). *Para quem usa:* o
almoço de início igual ao fim é "sem almoço" também no dia fechado, como o desenho diz; a ordem no aplicativo diz com quem o
número escolhido empataria, na dica e em cada número da lista (o `GET` leva os colegas do aplicativo com o número de cada um,
com o recorte dos empates); o "copiar o horário da loja" deixa como estava o dia sem horário na loja (29 das 32 lojas ativas
da cópia não têm domingo, e 6 colaboradores ativos com agenda, em 4 lojas sem domingo, trabalham nele), e a loja sem horário
em dia nenhum não tem o que copiar; na tela antiga, o "Copiar horários da unidade" só põe o almoço de 12:00 às 13:00 quando ele
cabe no turno, e deixa como estava o dia sem horário na loja (nas 3 lojas que abrem no domingo à tarde, 21 colaboradores, o
domingo copiado não salvava mais), e o `<select>` dos serviços diz a área de cada um; abrir o formulário desfaz a pergunta do
"desativar"; o "alterar" espera também a troca de estado no ar; com o salvar no ar ou a releitura do 409, as abas dizem que
esperam, e o foco não vai a um rádio que não se marca; a releitura diz "Relendo a agenda…", diz quando chegou e diz quando
falha; a Agenda que sai da lista depois de gravar volta aos Dados com o foco na aba e a frase do porquê; o foco da recusa
chega ao campo já marcado, e a procura dos serviços e a primeira área levam a frase na descrição. A cor e a cor da letra seguem
obrigatórias (as fixtures as têm nulas: no navegador, escolha as duas também na tela antiga). *No código:*
`RegraDaAgenda::conferirODia` e o espelho do front; `Adm\UsuariosController::colegasNoAplicativo` (a consulta saiu do
`empatesNaOrdem`, e o `conferir-recorte-por-unidade.php` vigia o método novo, falsificado); `UsuarioJson::daAgenda` com
`opcoes.ordensDosColegas`; o `editarAgendaAction` do ERP com as opções da regra no `<select>` (o `conferir-cobertura-por-par.php`
desceu o ISConfiguracao de 155 para 153 chamadas; os pares e o sha1 não mudam); `usuariosCopiarHorariosUnidade` no
`mod-configuracoes.js` (`?cache=165`); o `docs/inventario-acoes.csv` regenerado (o `editarAgenda` do ERP passou a tocar só o
`Usuario`); `useGrupoDeRadio` com o `travado`; `AlterarAgenda`, `Usuarios`, `UsuariosLigado`,
`formularioDaAgenda`; uma prévia nova, `AgendaQueNaoFoiRelida`. *Provado,* em série: os casos novos no servidor -- a recusa
que o serviço acha no meio (422 na API, a frase no campo na tela antiga), a trava de verdade com outra conexão (a gravação para
no `FOR UPDATE`), a tela antiga sem o `flashMessenger` e com o topo escapado, o `<select>` com a área, os colegas da ordem, o
dia fechado com o almoço igual, e o caso dos dois editores da F4.1 pela porta do cadastro (o da agenda deixou de separar o
banco do objeto) -- e, com os vizinhos, 1.114 casos no MySQL e no PostgreSQL, saída 0; o front, 2.139 (eram 2.117: 13 em `UsuariosLigado`, 3 em `formularioDaAgenda` e 6 no `copiarHorariosDaTelaAntiga`, novo) casos com
`--maxWorkers=4` (os de `UsuariosLigado` também com `AMPLIFICAR_EFEITOS_MS=50`), o build, o lint (os 6 avisos de antes) e o
tipo das prévias; 36 mutantes novos, um por vez e devolvidos pelo sha1 -- 11 (10 do código e a falsificação do portão do recorte) do servidor e 25 do front --,
todos vermelhos; os 64 portões (`conferir-tudo.sh`, com o vermelho conhecido de sempre, o D50) e o de segredos; e o `conferir-vigencia-do-horario.php` no `franquiabv_dev` (2.432
usuários, 0 e 0, saída 0), com a mesma medição no `franquiabv_contrato` depois do ensaio: a 90001000, a única gravada, com a
última vigência igual ao cadastro, e as outras 4 fixtures sem linha (entram por SQL). *Não provado:* o formulário no navegador,
a 375 px e nos dois temas, contra o banco de contrato -- com o 409 da tela antiga gravando no meio --; o
`conferir-vigencia-do-horario.php` em si contra o banco de contrato (ele lê o `local.php`); a caracterização inteira, o
contrato e os ensaios inteiros, que são do ciclo; a prévia no Claude Design.

**No navegador** (18/10/2026), contra o banco de contrato, com um administrador temporário, a 1.024 e a 375 px e nos dois
temas, com a tela nova e a tela antiga abertas: o "alterar a agenda" leva o foco ao Tempo e desfaz a pergunta do
"desativar" que estava aberta; o "copiar o horário da loja" fica apagado, com a nota, na loja sem horário; as cores
preenchidas (as fixtures as têm vazias), o domingo fechado com almoço de 12:00 às 12:00 gravado sem almoço, e o foco de volta
no "alterar"; o serviço de área não marcada recusado na própria tela, sem pedido, com o foco na procura dos serviços e a
frase com o nome dele; o 409 com a tela antiga gravando o tempo no meio ("mudou desde que a tela a leu", "Relendo a
agenda…", a agenda de agora com o tempo da tela antiga, e o "alterar" apagado até a releitura chegar, com o foco nele); o
salvar sem mudança nenhuma diz "Nada foi alterado." e não pede nada; a rede caída no salvar (o alerta, o que foi digitado
fica e o foco no salvar); e, na tela antiga, o almoço invertido de terça recusado ("A agenda não foi salva. Terça-feira: …",
com as aspas certas), nada gravado, e a mensagem não volta ao recarregar, com o `<select>` dos serviços dizendo a área de cada
um. Achou dois detalhes, consertados: a 375 px o motivo de cada serviço ("de uma área que não está marcada…") saía cortado
na caixa, porque herdava o `nowrap` do selo "2 linhas" (`.usuario-servicos .usuario-linhas` quebra a linha); e a dica da ordem
dizia "Ninguém mais dela tem este número", que passou a "Ninguém mais da unidade tem este número". A sessão injetada no ERP
dura menos de um minuto: os gestos da tela antiga foram feitos de uma vez, cada um.

## 18/10/2026 -- USUARIOS-DADOS

A fatia 15 da fila das telas que faltam: a F3 do desenho do resto de Usuários
(`docs/f3-telas-restantes/tela-usuarios-resto.md`), gravar os dados do colaborador; e o D513, o "ativar" que pergunta. A
cota-parte é a 16.

**O que muda para quem usa.** A aba Dados da ficha do colaborador ganha o "alterar os dados", que abre o formulário no lugar
da leitura: o nome, o e-mail (é para onde vai o link do "Esqueci a senha"; quem altera usuários troca o do colega, como hoje
-- a resposta 101 (c) --, e nada cai), o grupo entre os que quem pede pode dar, o acesso ao Profissional Parceiro (a marca
"Acesso dashboard" da tela antiga), o telefone com o DDI e o número, onde a pessoa aparece (na agenda, no PDV) e a contratação.
Só o que mudar vai ao salvar. Antes de salvar, o formulário diz o que
não se vê no campo: trocar o grupo muda os privilégios pelo molde do grupo novo, os ajustes feitos à mão saem, e a pessoa sai
do sistema e entra de novo com a senha; mudar o acesso ao Profissional Parceiro também tira a pessoa do sistema; tirar a
pessoa de agenda nenhuma tira a aba Agenda. O salvar confere antes de ir e
diz, no campo, o que não serve (o e-mail sem forma, o telefone brasileiro sem o DDD, a contratação que não existe), e o
servidor diz o resto no mesmo lugar: o e-mail que já é de outra pessoa ("E-mail já cadastrado no sistema.", a frase da tela
antiga) e o grupo que quem pede não pode dar. Se a tela antiga, ou outra aba, gravou no meio, nada é gravado: o formulário
fecha, a aba diz, e os dados são relidos. Ninguém troca o próprio grupo nem o próprio acesso ao Profissional Parceiro (os
campos nem se oferecem). O login, a senha, a foto e os
dados pessoais continuam na tela antiga. E O "ATIVAR" PERGUNTA NO PRÓPRIO BOTÃO, como o "desativar": o primeiro clique diz
que a pessoa volta com os privilégios do molde de hoje do grupo -- os de quando saiu, e os ajustes feitos à mão, não voltam --,
e o segundo ativa; a tela antiga diz o mesmo junto do "Status" de quem está inativo, e no aviso depois de salvar. Na tela
antiga, a recusa da edição e do cadastro (o e-mail ou o login de outra pessoa, o grupo, o próprio estado, a reativação de
rede) deixa de aparecer de novo na página seguinte; e a edição recusa, no campo, o e-mail sem forma, o nome com emoji e a data
que não existe, como a tela nova.

**O que mudou no código.** No servidor, `PUT /adm/api/usuarios/:id` (`Adm\UsuariosController::atualizarAction`), com a guarda
da tela antiga (`escrever` e o `alcanca`), só o que mudou e o `antes` obrigatório; a regra pura `RegraDosDadosDoUsuario` (a
forma do corpo, as chaves que moram noutro endereço, o `estado` do `antes`, as duas partes do telefone); a
`UsuarioService::atualizarDados`, a porta `cadastro` das duas telas, travando a pessoa primeiro, comparando o `antes`
(`DadosMudaramNoMeio`, 409 `mudou`) e gravando só as chaves presentes (`gravarDados`); o e-mail livre numa pergunta só
(`emailDeOutraPessoa`, que o `validarEmail` da tela antiga passou a usar); o `GET` com o telefone em duas partes e o país da
unidade; o `ver`, a ativação e a desativação pelo `usuarioDaRota` (o D516, parcial); e, na tela antiga, a recusa na página e
não no `flashMessenger` (`recusaNaPagina`; o D521, novo, parcial) e a frase do molde (`RegraDoUsuario::FRASE_DA_VOLTA_PELO_MOLDE`,
o D513, fechado). No front, `api/usuarios.ts` (`gravarDados`, `CorpoDosDados`, `DadosDoAntes`, `UnidadeDoUsuario`),
`formularioDoUsuario.ts` (puro), `AlterarDados.tsx`, `Usuarios.tsx` (a aba Dados e o "ativar" que pergunta), `UsuariosLigado.tsx`,
o CSS, e cinco prévias em `Usuarios` (`DadosEmEdicao`, `DadosGravadosComOGrupoNovo`, `DadosQueMudaramPorFora`,
`PerguntandoAoAtivar`, e a `DadosDoColaborador` com o "alterar"; o envio ao Claude Design ainda não foi feito). Os portões
`conferir-rotas-sem-login.php` (a rota nova, medida: 401 sem cookie, 106 bytes) e `conferir-recorte-por-unidade.php` (o
`atualizarAction`, falsificado). Sem esquema novo; sem escritor novo do horário. O caso dos dois editores da F4.1
(`UsuarioHorarioVigenciaTest`) mudou para a porta do estado, a única que ainda não relê a pessoa.

**Provado**, em série: o caso novo `UsuarioDadosPelaApiTest` (18), com os vizinhos (usuários, privilégios, moldes, grupos,
teto de rede, personificação, senha, barreira e assistentes: 672 casos) no MySQL e no PostgreSQL, saída 0; o front inteiro,
2.171 casos (eram 2.139: 15 em `formularioDoUsuario`, 16 em `UsuariosLigado.dados`, novo, e 1 em `UsuariosLigado`), os da
tela também com `AMPLIFICAR_EFEITOS_MS=50`, o build, o lint (os 6 avisos de antes) e o tipo das prévias; o
`ensaiar-usuarios-adm-api.php` nos dois bancos de contrato, 33 de 33 (os casos novos 30 a 33: o `antes` velho, o e-mail de
outra pessoa, a gravação só do nome e do PDV com o resto da linha como estava, e a devolução); 42 mutantes, um por vez e
devolvidos pelo sha1 -- 27 do servidor (3 só no PostgreSQL, o número que a coluna não guarda) e 15 do front --, todos
vermelhos, e os portões do recorte e dos escritores do horário falsificados; os portões (`conferir-tudo.sh`, os 64, com o vermelho conhecido de sempre, o D50; o `conferir-cobertura-por-par.php` subiu o ISConfiguracao de 153 para 154 chamadas -- o `selecionarPorEmail` pelo nome --, com os pares e o sha1 iguais)
e o de segredos. **Não provado:** o formulário e o "ativar" no navegador, a 375 px e nos dois temas, contra o banco de
contrato (com o 409 da tela antiga gravando no meio); a espera da trava entre duas conexões, que a suíte não alcança; a
caracterização inteira, o contrato e os ensaios inteiros, que são do ciclo; a prévia no Claude Design.

**A verificação** (cinco lentes; 18 apontamentos, 16 achados distintos) achou, e foi consertado no mesmo dia:
- **o "No painel" era acesso.** O campo grava o `visivel_dashboard`, a marca "*Acesso dashboard" da tela antiga, que abre o
  Profissional Parceiro, com os próprios números, a quem não é gerente nem administrador -- e nenhum número da unidade a lê. A
  dica dizia "Se entra nos números do painel da unidade.": o gerente que tirasse alguém "dos números" tirava dele o acesso. O
  campo foi para o Acesso como "Acesso ao Profissional Parceiro", com a dica do que faz, e a leitura também. E, sendo acesso,
  MUDÁ-LA TIRA A PESSOA DO SISTEMA, pelas duas portas e na mesma gravação (a resposta 102 (b)), e NINGUÉM MUDA A PRÓPRIA (a
  regra 1, como o grupo) -- o D523, corrigido. No `franquiabv_dev`, 188 dos 252 ativos que não são gerentes nem
  administradores têm a marca;
- **duas portas, duas regras**: a edição da tela antiga gravava o que o `PUT` recusa (o e-mail sem forma, o nome com emoji, que
  o MySQL recusa com erro, e a data que não existe). Passou pela mesma `RegraDosDadosDoUsuario`, no que mudou
  (`doFormularioDoErp`); o telefone dela e o cadastro novo ficam no D522, novo e parcial -- a ida e volta do telefone na tela
  antiga põe o DDD no DDI, e conferir ali recusaria quem nem tocou nele;
- os avisos de antes e de depois do salvar tratavam o valor fora da lista (o `visivel_agenda` "sim" do colaborador do
  contrato) como "nenhuma" -- "a aba Agenda voltou", e ela nunca saiu -- e diziam a quem está inativo que sai do sistema ou
  que entra nas agendas;
- a dica do "No PDV" prometia o que só o PDV antigo faz (o D517 segue aberto);
- o foco da recusa ia ao telefone antes do grupo, que está acima; depois do 409, os privilégios eram relidos com os de antes à
  vista (e, se a releitura falhava, ficavam lá, como se fossem de agora); o aviso da troca de estado e o da gravação dos dados
  ficavam juntos, e o status do detalhe anunciava de novo "Usuário Fulano aberto" com o nome gravado; quem só lê tinha uma
  faixa vazia acima dos cartões; e o aviso do salvar perdia a margem para a regra da leitura (`.cartao .aviso.usuario-texto`);
- os casos que faltavam: o complemento, o CNPJ e o INSS de quem não os mandou (o `?? null` de antes os apagaria pelo `PUT` sem
  nada vermelho); a troca de grupo medida depois da trava; a recusa da tela antiga fora do `flashMessenger` nos ramos do grupo,
  do próprio estado e da reativação de rede (os ajudantes do `UsuarioAtivacaoTest` e do `LoginSemCaixaTest` liam a página, que
  junta as do `flashMessenger`); e os ramos do formulário que nenhum caso desenhava (o "o de hoje, fora da lista", o "sem
  grupo", a lista de grupos carregando e com erro, a dica do inativo, a pergunta do "ativar" desfeita ao abrir o formulário, o
  foco depois de "em agenda nenhuma").
- **o desenho** dizia, na tabela e no título da F3, que o `PUT` muda o estado e fica sem o e-mail: passou a dizer o que foi feito
  (o estado tem as rotas dele, a seção 4.2 da fila; o e-mail, pela 101 (c)).

**Provado depois da verificação**, em série: o `UsuarioDadosPelaApiTest`, 21 casos (eram 18); os vizinhos (543 casos, os
testes que despacham as duas telas de usuários, os privilégios, os moldes, o Profissional Parceiro e a sessão) no MySQL e no
PostgreSQL; a caracterização inteira no MySQL, 3.973 casos (11 pulados); o front inteiro, 2.186 (eram 2.171: 5 em
`formularioDoUsuario`, 10 em `UsuariosLigado.dados`), os da tela também com `AMPLIFICAR_EFEITOS_MS=50` (duas rodadas), o build,
o lint (os 6 avisos de antes) e o tipo das prévias; o `ensaiar-usuarios-adm-api.php` nos dois bancos de contrato, 33 de 33; 28
mutantes novos, um por vez e devolvidos pelo sha1 (14 do servidor e 14 do front), todos vermelhos -- um deles, a reativação
de rede de volta ao `flashMessenger`, ficou verde na primeira rodada, porque o primeiro `addMessage` de um controller novo
move as mensagens que já estão no recipiente, e a recusa igual à do despacho anterior o deixava como estava: o ajudante do
`UsuarioAtivacaoTest` passou a esvaziar o recipiente antes de cada despacho, e ele ficou vermelho --; os 64 portões
(`conferir-tudo.sh`, com o vermelho conhecido de sempre, o D50) e o de segredos. O `conferir-debitos-repetidos.php` ganhou os
pares D439|D522 e D521|D522, com o motivo. **Não provado:** o navegador; o Artifact do roadmap, que recebe esta entrada junto
do commit, com os números do ciclo.

**No navegador** (18/10/2026), contra o banco de contrato, com um administrador temporário, a 1.024 e a 375 px e nos dois
temas: o "alterar os dados" leva o foco ao Nome; a troca de grupo avisa antes de salvar que os privilégios passam a ser os do
molde e que a pessoa sai do sistema, e depois de gravar diz "Dados gravados. … saiu do sistema" e relê os privilégios; o
e-mail de outra pessoa volta com "E-mail já cadastrado no sistema." no campo e o foco nele; o 409 com o nome mudado por fora
(no banco de contrato, como outra aba) diz a frase, "Relendo os dados…" e mostra o nome de agora, com o "alterar" apagado até
a releitura; desativar, e o "ativar" pergunta com a frase do molde e do «_old», o "não ativar" devolve o foco ao botão, e o
confirmado reativa com o login sem o «_old»; na própria ficha o Grupo e o Acesso ao Profissional Parceiro só se leem; "em agenda
nenhuma" avisa antes e, salvo, tira a aba Agenda; e, na tela antiga, a frase do molde sob o "Status" de quem está inativo.
Achou um detalhe, consertado: a 375 px o seletor "Na agenda" cortava o valor ("em agenda nenhur") na meia coluna, e passou à
linha inteira. A recusa do e-mail na tela antiga não foi vista no navegador: as fixtures não têm os campos que o formulário
antigo exige (o nascimento, a contratação), e o próprio navegador barra o envio antes; o caso
`testATelaAntigaMostraARecusaNaPagina…` a cobre.

## 18/10/2026 -- USUARIOS-COTA-PARTE

A fatia 16 da fila das telas que faltam: a F4 do desenho do resto de Usuários
(`docs/f3-telas-restantes/tela-usuarios-resto.md`), gravar a cota-parte do colaborador -- sem vigência (a resposta 134 (b) do
dono), e com ninguém alterando a própria (a 219 (a)). O cadastro de gente nova e a senha são a 17.

**O que muda para quem usa.** A aba Cota-parte da ficha do colaborador ganha o "alterar a cota-parte", que abre o formulário no
lugar da leitura: a base de serviços e a de produtos (de 0 a 100; vazio, ou 0, é sem cota-parte), cada extra com o percentual e o
"tirar" (o "manter" a devolve), e o "acrescentar" com os serviços e os produtos ativos da unidade que ainda não têm extra -- o
pacote não entra, porque o cálculo não o lê. A extra que o cálculo não lê (a de pacote, a de produto inativo) não se edita: fica à
vista com o porquê, e só sai se o operador tirar. Antes de salvar, a tela diz desde quando vale: para o que for lançado nas
comandas a partir de agora, e para o item de comanda aberta refeito depois -- o ajustado, e o que herdar a sessão de pacote de um
item tirado; fora isso, o que as comandas já gravaram não muda; a Cota-parte do menu refaz com o percentual de hoje, também nos
meses passados, e só com a base -- menos as sessões de pacote no PDF, que saem com a cota-parte gravada. Só o que mudar vai ao
salvar; a extra só entra pelo "acrescentar" (o Enter no percentual dela acrescenta, e o salvar com uma escolhida que não entrou
recusa, dizendo); o salvar confere
antes de ir, e o servidor diz o resto no mesmo campo. Se a tela antiga, ou outra aba, gravou no meio, nada é gravado: o
formulário fecha, a aba diz, e a cota-parte é relida. NINGUÉM ALTERA A PRÓPRIA COTA-PARTE, NEM O ADMINISTRADOR: a tela nova não
oferece, e a tela antiga diz na página, sem o salvar, o "Nova", o editar e o tirar -- e recusa se pedirem assim mesmo. A tela
antiga passou a dizer também desde quando vale, com a mesma frase, acima da base e das extras e na janela da extra.

**O que mudou no código.** No servidor, `PUT /adm/api/usuarios/:id/cota-parte` (`Adm\UsuariosController::gravarCotaParteAction`),
com a guarda da leitura e da tela antiga (`escrever` e o `alcanca`), 403 `proprio` para a própria (também a quem entrou, na
personificação), só o que mudou e o `antes` obrigatório: a base e as extras inteiras, como devem ficar (a lida com o `id`, a nova
sem; a que sumiu sai). A regra pura é a `RegraDaCotaParte`, crescida: confere só o que mudou, o produto de uma extra lida não se
troca, um par por linha, e a mesma pergunta da própria nas duas portas (`recusaDaPropria`). A porta é
`UsuarioService::gravarCotaParte`, das duas telas: trava a pessoa, compara o `antes` (`CotaParteMudouNoMeio`, 409 `mudou`), confere
as extras de novo na transação (`CotaParteRecusada`, 422) e grava -- a lida com outro percentual no lugar. A janela das extras da
tela antiga (`comissaoExtraSalvar`, `comissaoExtraRemover`) também passou a travar a pessoa, com o par conferido de novo na trava:
sem isso, o `antes` do `PUT` não valeria. O `GET` ganhou as opções de produto da extra (a mesma lista da janela antiga) e o
`proprio`. Nenhum cálculo de comissão mudou. No front, `AlterarCotaParte.tsx`, `formularioDaCotaParte.ts` (o espelho da regra,
com a frase de desde quando vale e os números do servidor), a aba em `Usuarios.tsx` e a gravação em `UsuariosLigado.tsx`, no
molde da agenda; o link "alterar na tela antiga" saiu. D524 registrado e corrigido (o D-F do desenho: a própria cota-parte, e a
janela fora da trava). D525 registrado: a soma da base com a extra não tem teto (100% e 100% gravam 200% do item, como sempre
gravaram; 1 combinação acima de 100% na cópia, de inativo) -- é regra de negócio, e foi ao lote (TELAS-RESTANTES [vendas]).

**Provado**, em série: o `UsuarioCotaPartePelaApiTest` novo, 17 casos, nos dois bancos; os vizinhos (os de usuários, da
cota-parte, dos privilégios e dos moldes, o teto de rede, a barreira de origem, o D204, o D363/D465, o Profissional Parceiro, a
senha trocada e o D368), 792 casos em cada banco; o front inteiro, 2.214 (eram 2.186: 14 em `formularioDaCotaParte`, 14 em
`UsuariosLigado.cota`), os da cota-parte também com `AMPLIFICAR_EFEITOS_MS=50`, o build, o lint (os 6 avisos de antes) e o tipo das
prévias; o `ensaiar-usuarios-adm-api.php` nos dois bancos de contrato, 38 de 38 (os 5 novos: a própria, o `antes` velho, a base
acima de 100, a gravação com uma extra nova e a devolução); 49 mutantes, um por vez e devolvidos pelo sha1 (30 do servidor, 3 do
contrato entre o front e o servidor e 16 do front), todos vermelhos; os portões que a fatia toca (rotas sem login -- a rota nova
entrou na lista, medida sem cookie: 401 -- ACL da API, recorte por unidade, guarda do ERP, barreira de origem, remoção por POST,
links mortos, escritores do horário, autorização privilegiada, rotas órfãs, cobertura por par, injeção de DQL, os de banco no
contêiner, referências dos docs, débitos repetidos e segredos), todos com saída 0. **Não provado:** o navegador; a caracterização
inteira, o contrato do aplicativo, o `conferir-tudo.sh` e os outros ensaios (o ciclo); a prévia no Claude Design; e o Artifact do
roadmap, que recebe esta entrada junto do commit.

**A verificação** (cinco lentes) confirmou 19 apontamentos -- 18 baixos e um médio --, todos tratados. O médio: o Enter no
percentual da extra nova enviava o formulário sem a extra, e o salvar com uma extra escolhida que não entrou na lista a jogava fora
calada; agora o Enter acrescenta, e o salvar recusa, dizendo na escolha. A frase de desde quando vale estava incompleta em duas
pontas de dinheiro, conferidas no código: o item de comanda aberta que herda a sessão de pacote de um item tirado também é refeito
com o percentual de hoje (`VendaProdutoService::remover`, o D453), e o PDF da Cota-parte soma a cota-parte gravada nas sessões de
pacote (`PdfVendasVersao1.php`); e o desenho dava à resposta 81 (a) a pergunta 5 inteira, quando ela respondeu só a metade da
agenda -- a extra na recalculada é a escolha 1 de `docs/f3-relatorios/spec-cota-parte.md`. A frase foi corrigida nas duas telas e
na nota da aba, e ficou presa ao relatório de hoje por um caso que fica vermelho quando a CP1 da Cota-parte nascer, ou quando o PDF
ou a consulta do menu mudarem. Na tela antiga, a janela da extra pergunta de novo, com a pessoa travada, se a extra que edita ainda
existe (o `PUT` podia tê-la tirado no meio, e ela dizia "salvo"). Na tela nova, a recusa do produto de uma linha descreve o
"tirar", que leva o foco, e o status da seção fica calado depois de gravar e da releitura do 409 (dizia "lida" junto do aviso da
gravação). Os documentos dizem o que a fatia não fez: o produto de uma extra lida não se troca na nova (a janela antiga troca), o
"se a cota é gerada" virou o aviso da aba, e o par repetido é 422. E os casos que faltavam: a base da tela antiga pela mesma regra,
a página desenhada sem os botões da própria, o par criado no meio na janela, a personificação na tela antiga, todas as frases do
front presas às do servidor, o salvar depois do 409 e depois de gravar, o duplo clique, o "manter" e cada fonte do "própria".
Provado, em série: o `UsuarioCotaPartePelaApiTest` com 22 casos (eram 17), e ele com os vizinhos (os de usuários, da cota-parte,
dos privilégios, o teto de rede, a barreira de origem, o D203, o D363/D465, o D453, o D368 e os relatórios de vendas e de
colaboradores), 512 casos em cada banco; o front inteiro, 2.227 (eram 2.214), os da cota-parte também com
`AMPLIFICAR_EFEITOS_MS=50`, o build, o lint (os 6 avisos de antes) e o tipo das prévias; 30 mutantes da correção, um por vez e
devolvidos pelo sha1 (18 do servidor e 12 do front), todos vermelhos; e os portões que a correção toca, todos com saída 0.

**No navegador** (18/10/2026), contra o banco de contrato, com um administrador temporário, a 1.024 e a 375 px e nos dois
temas: o "alterar a cota-parte" leva o foco aos Serviços; a base de 40% e 10%; a extra do Corte acrescentada com o Enter, sem
salvar, e o foco de volta à escolha; a extra escolhida e não acrescentada recusada na própria tela, sem pedido, com a frase e o
foco na escolha; o salvar diz "Cota-parte gravada." com a frase de desde quando vale, e o foco volta ao "alterar"; o 409 com o
percentual dos produtos mudado por fora (no banco de contrato, como outra aba) diz a frase e mostra a cota-parte de agora, sem
gravar o que se digitou; a própria ficha não oferece o "alterar" e diz que ninguém altera a própria; a página antiga mostra a
frase de desde quando vale acima das duas seções e na janela das extras; e a 375 px o formulário não rola de lado, com a escolha
da extra na linha inteira. Nenhum defeito.

**No ciclo**, o portão do inventário caiu: a ação `comissao` da tela antiga passou a gravar pelo `UsuarioService::gravarCotaParte`
(e toca só o `Usuario`), e o `docs/inventario-acoes.csv` foi gerado de novo pelo `inventariar-acoes.php` (uma linha); a etapa
dos portões rodou de novo, inteira.

## 18/10/2026 -- RESPOSTAS-237-238

A fatia 16b da fila das telas que faltam: duas respostas do dono, a 237 (c), de 17/10/2026, e a 238 (c), de 18/10/2026.

**O que muda para quem usa.** Na tela nova de Usuários, quem só consulta os usuários (o "ler" de Configurações usuários, sem o
"escrever") passa a ver a aba Agenda de cada colega da unidade -- a semana com as vagas e o horário da loja, o tempo, as férias,
as áreas, os serviços, a ordem no aplicativo e as cores --, só para consulta, sem o "alterar a agenda"; a Cota-parte continua só
para quem altera (a 238 (c), *"A agenda a quem lê, a cota-parte só a quem altera"*; fecha o D518). A nota dos Dados de quem só
lê diz isso, e não manda mais à tela antiga, que a quem só lê respondia "não encontrado". Hoje ninguém além dos administradores
tem só o "ler" de usuários (0 na cópia; 107 alteram): vale para quando alguém der só o "ler". E na noite do corte (o passo 3 da
etapa 2), o molde de privilégio de um grupo repetido com valores diferentes -- um "só ler" e um "ler e escrever" na mesma
funcionalidade -- se resolve sozinho, ficando o mais antigo, como a limpeza fazia antes de 17/10/2026; a noite não para mais
nele (a 237 (c), *"Fica o mais antigo"*; o D374 segue preparado). Quem entrar no grupo depois do corte recebe o molde que
ficou; quem já está nele não muda. Na cópia de 16/08/2026 não há nenhum.

**O que mudou no código.** No servidor, `GET /adm/api/usuarios/:id/agenda` pede `configuracoes/usuarios/ler`
(`Adm\UsuariosController::agendaAction`), com o mesmo `alcanca` -- a unidade, o teto do D204 e o de rede do D346 -- e a mesma
resposta; o `GET .../cota-parte` e todas as escritas seguem com `escrever`, e a recusa da cota-parte diz só dela. No front,
`abasDe` (`Usuarios.tsx`) põe a Agenda pelo `temAgenda` e a Cota-parte pelo `comCotaParte` (o `podeEditar` do invólucro); a
Agenda de quem só lê fica sem a faixa da gravação e com a nota de consulta. A prévia `SoParaConsultar` passou a mostrar a aba
Agenda de quem só lê, com as três abas na fileira, e os privilégios de quem só lê, que ela mostrava, passaram à
`PrivilegiosParaConsultar`, nova (o envio ao Claude Design é do ciclo, as duas células). No SQL do passo 3, o bloco 5 de
`scripts/pre-migracao-limpeza.sql` volta a tirar todo molde repetido, ficando o de menor id, e lista antes o de valores
diferentes (o grupo, a funcionalidade, o que fica e o que sai); `scripts/migracao-privilegios-unicos.sql` faz o mesmo com o que
chegar a ela, na transação das gêmeas das pessoas, e só depois cria as duas chaves (antes, a chave do molde nascia primeiro e
parava o roteiro). `docs/procedimento-de-corte.md` e `docs/procedimento-de-corte-etapa-1.md` dizem a regra nova. Nada rodou nas
cópias locais nem entrou no laço do passo 0.

**Provado**, em série: `UsuarioAgendaECotaParteApiTest` com dois casos novos (a agenda a quem só lê, igual à de quem escreve,
e a cota-parte recusada; a quem só lê, a de outra unidade, a do administrador e a de quem tem rede são 404), e os vizinhos (os
de usuários, privilégios, moldes, grupos, teto de rede, D204, barreira de origem, regras puras, Profissional Parceiro e "Vendas
caixa"), 966 casos em cada banco (1 pulado nos dois, o de sempre, que pede a cópia da produção); o front inteiro, 2.228 (eram 2.227: o
caso novo da agenda de quem só lê; o da nota dos Dados mudou), os 161 de Usuários também com `AMPLIFICAR_EFEITOS_MS=50`, o build, o lint (os 6 avisos de antes) e o tipo das prévias de Usuários
(falsificado com o nome antigo da propriedade: erro). `scripts/ensaiar-sql-do-passo-3.php`, saída 0: com o bloco 5 antes, os
moldes repetidos de valores iguais e diferentes -- um em que o mais antigo dá menos, outro em que dá mais -- ficam no mais
antigo, os de valores diferentes listados, e a migração segue; sem o bloco 5, a migração resolve o mesmo sozinha, lista os três,
e a segunda vez não muda nada. Num ensaio à parte, em bancos descartáveis (dois no MySQL e um no PostgreSQL, apagados no fim):
7 moldes (1 repetido igual, 2 diferentes) e 3 linhas de pessoas antes; 4 moldes, 0 repetidos, 2 linhas e as 2 chaves depois,
igual na segunda rodada; no PostgreSQL, com as restrições do `schema.sql`, a carga de antes para no molde repetido e a de depois
entra inteira. 18 mutantes, um por vez e devolvidos pelo sha1, todos vermelhos (saída 1): 4 do servidor, 7 do front e 7 do SQL
do passo 3 -- e o ensaio do passo 3 ficou mais firme por dois deles, que saíam com 2 ("não consegui medir") e escondiam a falha:
a migração que não passa agora é falha, e o último caso volta os moldes ao começo antes de carregar. Os portões que a fatia
toca, todos com saída 0: passo 0 do corte, SQL do passo 3, limpeza por trás da unicidade, unicidade do schema, ACL da API,
recorte por unidade, rotas sem login, débitos repetidos (com a marca nova do D374 só de palavras curtas, para o par D289|D374
não cair) e segredos. **Não provado:** o navegador; a caracterização inteira, o contrato, os ensaios HTTP e o `conferir-tudo.sh`
(o ciclo); o MySQL 8.0 da réplica no ensaio do passo 3; e quantos moldes repetidos a réplica tem.

**A verificação da fatia** (em cinco lentes) confirmou 10 apontamentos, 7 achados distintos, todos baixos e todos tratados; nenhum
mudou a regra. (1) A data da 237 (c) é 17/10/2026 (a resposta foi gravada às 19:41 daquele dia), e os roteiros do passo 3, o
ensaio dele e os dois procedimentos diziam 18/10/2026; o D374 dizia "17 e 18". (2) O desenho de Grupos (`tela-acl.md`, o
D-ACL-3) e o parágrafo do passo 3 do D289 ainda diziam que o molde repetido de valores diferentes parava a noite; passaram a
dizer a regra nova, com a de 17 a 18/10/2026 como passado (e a linha do bloco 5 citada no desenho, de 120-136 para 104-174).
(3) Textos que davam à leitura da agenda a guarda `escrever`: o caso da gravação em `UsuarioAgendaPelosControllersTest`
(renomeado para `testAGuardaEADeQuemEscreveEAPropriaPessoaGrava`, com o cabeçalho do arquivo), o "Herda da F1" do desenho
de Usuários e o comentário das opções do formulário em `UsuarioJson::daAgenda`, que agora vão também a quem só lê. (4) A
prévia `SoParaConsultar` abria nos Privilégios, e a Agenda estava numa célula à parte: passou a mostrar a aba Agenda de quem
só lê, e os privilégios de quem só lê foram para a `PrivilegiosParaConsultar`, nova (a `AgendaParaConsultar` saiu). (5) O
`scripts/ensaiar-sql-do-passo-3.php` perdia as falhas quando a migração não criava uma das chaves: o caso seguinte tirava as
chaves com `DROP INDEX` direto, a da chave que não existia falhava, e o portão saía com 2 ("não consegui medir") sem imprimir
o que já tinha medido. Desde a verificação ele tira só a chave que existe (e a gêmea 30 antes de carregá-la de novo), e o
"não consegui medir" que vem depois de uma falha medida sai com 1 e com a lista. (6) A nota dos Dados de quem altera e tem
agenda não tinha caso: o "a nota dos Dados diz as abas que existem" começa por ela.

**Provado, na verificação**, em série: `UsuarioAgendaPelosControllersTest`, `UsuarioAgendaECotaParteApiTest` e
`UsuarioCotaPartePelaApiTest`, 52 em cada banco; o front inteiro, o build e o lint; os 161 de Usuários com
`AMPLIFICAR_EFEITOS_MS=50`; o tipo da prévia de Usuários; o `ensaiar-sql-do-passo-3.php`, saída 0; e os portões do passo 0, da
limpeza por trás da unicidade, da unicidade do schema, do ACL da API, do recorte por unidade, das rotas sem login, das
referências dos docs, dos links mortos, dos débitos repetidos e dos segredos, todos 0. Quatro mutantes novos, um por vez,
todos vermelhos: C1 (a migração guarda o texto da chave do molde e não a cria) e C2 (o mesmo na das pessoas), saída 1 com a
chave que faltou -- com o portão de antes da verificação, os dois saíam com 2; F8 (a nota de quem altera e tem agenda vira a
de quem não tem) e F9 (a frase dela sem a cota-parte), no caso da nota dos Dados. Os sete do SQL do passo 3 (Q1 a Q7)
rodaram de novo contra o portão corrigido: saída 1 nos sete.

**No navegador** (18/10/2026), contra o banco de contrato, entrando como a fixture "Colaborador Teste" com só o ler de
"Configurações usuários": o Barbeiro Evasao Teste abre com Dados, Agenda e Privilégios, sem a Cota-parte e sem nenhum
"alterar"; a Agenda se lê inteira, com a nota "Só para consulta: a agenda se altera por quem altera os usuários."; o
`GET .../cota-parte` responde 403; e o Gerente Teste, que não aparece em agenda nenhuma, abre com Dados e Privilégios, com a
nota que diz por quê e sem o link da tela antiga. Nenhum defeito.

## 18/10/2026 -- USUARIOS-CADASTRO-E-SENHA

A fatia 17 da fila das telas que faltam: a F5 e a F6 do desenho do resto de Usuários
(`docs/f3-telas-restantes/tela-usuarios-resto.md`), o cadastro de gente nova e a senha de outra pessoa pela tela nova -- os dois
como hoje (a resposta 101 (c) do dono: "Os gerentes definem e trocam, e nada cai"), sem coluna nova.

**O que muda para quem usa.** A lista de Usuários ganha o "cadastrar usuário" (o ouro dela), que abre o formulário no lugar da
lista: o nome, o gênero, o login, o e-mail, a unidade (a do gerente, já escolhida; o administrador escolhe entre as ativas), o
grupo entre os que quem cadastra pode dar, o acesso ao Profissional Parceiro, o telefone, onde a pessoa aparece, a contratação e
a primeira senha com a confirmação, em campos que o navegador não preenche sozinho. Nada vem escolhido, como o "Selecione" da tela
antiga; antes de cadastrar, a tela diz que a pessoa nasce ativa e já entra com o login e a senha dados, com os privilégios do molde
do grupo, que quem vai à agenda entra nela sem horário (a aba Agenda o grava depois), e que o nascimento, o endereço e os documentos
ficam na tela antiga. O login e o e-mail de outra pessoa voltam no campo, com a frase da tela antiga; cadastrar abre a ficha da
pessoa nova. Na aba Dados, ao lado do "alterar os dados", o "definir a senha" define a de outra pessoa: antes de salvar, a tela diz
que a senha vale na próxima vez que a pessoa a digitar e que ninguém sai do sistema; na própria ficha ele não aparece -- a própria
senha se troca em "Meus dados", com a atual (personificando, a da pessoa assumida se define ali, como na ficha antiga, e a de quem
entrou se troca depois de voltar à conta dele). Na tela antiga: o cadastro passa pela mesma regra da nova (a frase no campo), não
aceita mais o login terminado em "_old", a aba "Senha" da ficha aceita a senha com espaços nas pontas ou com "<" e ">" (que ela
recusava, embora gravasse) e diz o "Senha atualizada" uma vez só, e "Meus dados" não repete a recusa na página seguinte.

**O que mudou no código.** `POST /adm/api/usuarios` (`criarAction`) e `PUT /adm/api/usuarios/:id/senha` (`definirSenhaAction`), com
a guarda da tela antiga (`escrever`; na senha, também o `alcanca` com os tetos do D204 e do D346, e 403 `proprio` para a de quem
entrou -- a da pessoa assumida, na personificação, é a de outra pessoa, como na ficha). Duas regras puras novas: `RegraDaSenha` (de 6 a 20, a confirmação igual, a senha crua -- a da tela
antiga, num ponto só: o `UsuarioDadosFilter` e o `UsuarioSenhaFilter` passaram a ler dela) e `RegraDoCadastroDoUsuario` (os campos
dos Dados pela `RegraDosDadosDoUsuario::conferirOsCampos`, o login, o gênero, a unidade e a senha), que o `novoAction` também usa
(o resto do D522). A unidade é a da lista do filtro (só as ativas, a 132 (b)); o grupo, pela `recusaDoGrupo` (a 131 (b) e a 26
(a)); o login livre é a pergunta nova `UsuarioService::loginDeOutraPessoa`, das duas portas. As portas são as de sempre
(`inserir`, com o grupo, a vigência `inclusao` e o molde numa transação; `updateMeusDadosSenha`, que deixa o token), e nenhuma
resposta leva a senha nem o hash. No front, `CadastrarUsuario.tsx`, `DefinirSenha.tsx` e `formularioDoCadastro.ts` (o espelho, com
os números que o PHP confere), e a prévia de Usuários com seis células novas e duas mudadas.

**Débitos.** O D521 e o D522 ganharam a marca da fatia (o D522 segue com o telefone da edição); novos: o D526 (quem é cadastrado
pela nova não tem os dados pessoais que a ficha antiga obriga -- foi ao lote, [cadastro]), o D527 (as portas da senha: a ficha e o
filtro de "Meus dados", consertados; o mínimo de "Meus dados", um caractere, e o do link, 6 bytes sem teto, foram ao lote,
[seguranca]), o D528 (o login com o sufixo `_old`: o cadastro, consertado; falta a troca do login na ficha antiga) e o D529 (o
byte nulo na senha dava 500 em todas as portas; corrigido na verificação).

**Provado** (o implementador; o ciclo inteiro é do orquestrador): a caracterização da fatia e dos vizinhos (`Usuario`, `Senha`,
`LoginSemCaixa`, `TetoDeRede`, `D204Edicao`, `PrivilegioDerruba`, `GuardaDeLogin`, `PrivilegiosPelosControllers`,
`Personificacao`), 339 em cada banco, verdes; o front inteiro, 2.250 em 68 arquivos, o build e o lint; os de Usuários com
`AMPLIFICAR_EFEITOS_MS=50`; o tipo da prévia. 23 mutantes do servidor (dois só no PostgreSQL: o login sem o `LOWER`) e 15 do
front, um por vez, todos vermelhos. Os portões que a fatia toca, saída 0 -- o das rotas sem login com as duas rotas novas medidas
por HTTP no `franquiabv_dev`, sem cookie (401 `nao_autenticado`), e o da cobertura por par de 154 para 156 chamadas. **Não
provado:** a tela no navegador (é do orquestrador, a 375 px e nos dois temas, sem criar usuário nem senha), e o ensaio HTTP.

**A verificação** (18/10/2026, em lentes, com 20 apontamentos confirmados -- dois deles o mesmo, a frase do "alterar os
dados"; um, da nota do orquestrador, não era achado: a senha de outra pessoa NÃO tira ninguém do sistema, pela 101 (c), e o
código seguiu o dono). Tratados todos:

- **A personificação, como na ficha antiga** (o D347): a senha da pessoa assumida é a de outra pessoa -- o `definirSenhaAction`
  compara só com quem entrou (`senhaDeQuemEntrou`), e não com o `ehASiMesmo`. A leitura diz onde cada senha se troca
  (`senhaSeTroca`: nula, `meus_dados` ou `fora_da_personificacao`), e a tela não manda quem personifica ao "Meus dados" da pessoa
  assumida. O `senhaSeTroca` entrou no `UsuarioCompleto` e no `FORA` do `PUT` dos Dados (da leitura).
- **O D527 acertado:** o link do "Esqueci a senha" não usa o `SenhaConfirmarFilter` -- confere na ação, sem filtro, 6 bytes e sem
  teto --, e a pergunta do lote dizia que ele aceitava um caractere; o texto foi corrigido. O filtro de "Meus dados", que conferia a
  filtrada e gravava a crua (o defeito do D348), foi consertado sem decisão; o mínimo continua com o dono.
- **O D529 (novo, corrigido):** o byte nulo passava pela `RegraDaSenha`, e o `password_hash` do PHP 8 lança `ValueError` -- 500, com
  a senha crua na pilha, nas seis portas. A regra o recusa no campo, e a ficha, o cadastro e "Meus dados" (um `Callback`) e o link
  (uma linha) leem `RegraDaSenha::temONulo`.
- **O "Inativo" que o cadastro novo não oferece** ganhou a linha e o motivo no desenho: o estado tem as rotas dele, que põem e tiram
  o `_old` e gravam a vigência da porta `estado`.
- **O front:** o título do usuário recém-cadastrado tem o aviso como descrição (a região viva nasce cheia e não é lida); as listas de
  grupos e de unidades que falharam se pedem de novo ao abrir o cadastro (a frase "volte e cadastre de novo" não pedia nada); a
  dica da unidade fala ao administrador pelo papel, e não pelo tamanho da lista; o aviso da senha definida ficou sem gênero
  ("Ana continua conectado"); o "alterar os dados" deixou de dizer que a senha mora na tela antiga; o espelho recusa o nulo.
- **O servidor:** o `PUT` dos Dados manda a senha a `/senha`, e não à tela antiga.
- **A prova que faltava:** o duplo clique do cadastrar e do salvar a senha, e as abas esperando a senha; as listas carregando e
  falhando; o teto do login (101 recusado, 100 cadastrado, e o teto igual ao `length` da coluna e ao do espelho); o 403
  `grupo_de_administrador` no campo; a vigência do cadastro na personificação (quem entrou, em nome de quem); as senhas literais
  dos testes do front viraram constantes; e o comentário do `StripTags` (quatro caracteres, e não cinco, medido no contêiner).
- **Os portões de lista:** o `conferir-recorte-por-unidade.php` vigia o `alcanca` do `definirSenhaAction` e o `popularCombobox` do
  `criarAction`, e o `conferir-autorizacao-privilegiada.php`, a `recusaDoGrupo` do `criarAction` e do `atualizarAction` (este, da
  fatia 15, também faltava). Falsificados arrancando o código: saída 1 nos três.

**O campo novo não termina na palavra da credencial:** o portão de segredos lê a palavra seguida de dois-pontos e de um texto entre
aspas, e o primeiro nome do campo, que terminava nela, casava com o valor -- o campo se chama `senhaSeTroca`, e o tipo,
`OndeASenhaSeTroca`.

**Provado na verificação:** a caracterização da fatia e dos vizinhos (o filtro do implementador mais `Autenticacao`), 359 em cada
banco, verdes; o front inteiro, 2.259 em 68 arquivos; os de Usuários com `AMPLIFICAR_EFEITOS_MS=50` (o amplificador achou um caso
que clicava antes das listas da montagem, e o caso passou a esperá-las); o build, o lint e o tipo da prévia; 30 mutantes novos, um
por vez, todos vermelhos -- 12 do servidor, 15 do front e 3 dos portões. Os portões que a fatia toca, saída 0. **Não provado:** a
tela no navegador e o ensaio HTTP (do orquestrador).

**No navegador** (18/10/2026), contra o banco de contrato, com um administrador temporário, na largura do painel e a 375 px e
nos dois temas, com senhas geradas na própria página e nunca escritas: o "cadastrar usuário" abre com o foco no nome, as duas senhas com o
`new-password`, o grupo pela lista que se pode dar e o aviso do molde antes de cadastrar; o login de outra pessoa volta 409 com a
frase da tela antiga no campo, com o foco nele; o cadastro certo abre a ficha com o foco no título e o aviso do login e do molde,
sem a aba Agenda ("em agenda nenhuma"); na ficha dela, o "definir a senha" recusa a confirmação diferente e a senha de cinco
caracteres no campo, e a certa diz "Ninguém saiu do sistema", com o foco de volta no botão; na própria ficha, nenhum "definir a
senha", e o link para "Meus dados". O formulário vazio recusa cada campo, com o foco no primeiro. Nenhum defeito da fatia. Achou
um da lista que ela herdou: o seletor de unidade é o da tela antiga (o país, o estado, a cidade e o bairro, e não o nome), e duas
unidades do mesmo bairro saem iguais -- no `franquiabv_dev`, 36 ativas dão 34 rótulos. Registrado, sem conserto (o D530).

## 19/10/2026 -- RESPOSTA-239

A fatia 17b da fila das telas que faltam: a resposta 239 (b) do dono, de 18/10/2026. Fecha o D525.

**O que muda para quem usa.** A cota-parte de um colaborador num item -- a base de serviços ou de produtos somada à extra
naquele serviço ou produto -- não passa mais de 100% do valor do item, nas duas telas (a 239 (b), *"A soma não passa de
100%"*). Quem põe uma extra nova, sobe o percentual de uma extra ou sobe a base de modo que alguma soma passe de 100% recebe
a recusa, que diz o item e a conta -- "Corte: 70% de base mais 40% de extra dão 110%; a soma não passa de 100%." -- no campo
que subiu, e nada se grava: na tela nova, antes do pedido, com o foco no campo; na página antiga da cota-parte, no campo da
base; e na janela das extras da antiga, debaixo do percentual. 100% exatos passam. A soma é a que a comanda faz: o serviço e a Lava
VIP com a base de serviços, o produto da barbearia e o do empório com a de produtos; a extra de pacote, que o cálculo não lê,
não conta, e a de produto inativo conta. O que já está gravado acima de 100% fica como está até alguém mexer nele: o salvar
que o deixa igual, ou o desce, passa. Na cópia de 16/08/2026, medida de novo em 19/10/2026, é uma linha só (107%, de pessoa e serviço inativos); entre os
ativos, a maior soma é 68% -- ninguém ativo perde nada. Nenhum cálculo de comissão mudou: as comandas, a Cota-parte do menu e
o PDF ficam como estavam.

**O que mudou no código.** A regra é uma só, pura: `RegraDaCotaParte::conferirASoma` (com o `TETO_DA_SOMA`, o
`PARTE_DO_TIPO` e a `FRASE_DA_SOMA`), e as três escritas a chamam com a pessoa travada, contra o banco de agora --
`UsuarioService::gravarCotaParte` (o `PUT /adm/api/usuarios/:id/cota-parte`, 422 com o campo, e o `comissaoAction` da tela
antiga, que agora põe a recusa no campo da base, na página) e `UsuarioService::comissaoExtraSalvar` (a janela, com a extra
dela no lugar da que edita, ou no fim). A janela a escreve debaixo do percentual (o servidor diz o campo, e o
`mod-configuracoes.js` passou a `?cache=166`), como HTML, e a frase traz o nome do produto: vai escapada. No front, `formularioDaCotaParte.ts` ganhou o espelho (`conferirASoma`, `fraseDaSoma`) e o
`conferirCotaParte` o chama depois das outras recusas; a nota das extras diz que a soma não passa de 100% do item.

**Provado**, em série: a caracterização da fatia e dos vizinhos (os de usuários, da cota-parte, a barreira de origem, as
comandas pela agenda e a sessão de pacote, e os relatórios de colaboradores e de vendas), 513 em cada banco, verdes (1 pulado
nos dois, o de sempre); o front inteiro, 2.266 em 68 arquivos (eram 2.259: 7 casos novos), os de Usuários também com
`AMPLIFICAR_EFEITOS_MS=50`, o build, o lint e o tipo da prévia de Usuários; 24 mutantes, um por vez e devolvidos byte a byte,
todos vermelhos -- 8 da regra, 5 do serviço, 2 da tela antiga, 7 do front e 2 da frase e do mapa do front presos aos do
servidor; e os 65 portões (`conferir-tudo.sh`), saída 0, com o vermelho conhecido do agendamento (o D50). A conta dos débitos:
141 de pé (eram 141: o D525 fechou, e o D531 nasceu na verificação). **Não provado:** a tela no navegador (do orquestrador).

**A verificação** confirmou 9 apontamentos, tratados: a trava não tinha prova -- conferir a soma da janela antes da trava, ou com a
base lida antes dela, deixava a suíte verde; um caso novo chama as portas pelo serviço com a pessoa lida antes da gravação de
outro pedido, e a soma é a do banco de agora --; o espelho do front somava com a base fora da faixa e punha na extra uma conta que
o servidor nunca manda, e agora soma só com os números que se gravam; a frase da soma ficava velha quando o operador corrigia pelo
outro lado da conta, e agora sai com a mudança, com o alerta; a janela das extras da tela antiga mostrava a frase num aviso de 5 s,
e agora a escreve debaixo do percentual, onde fica até abrir ou salvar de novo (o servidor diz o campo, e o `mod-configuracoes.js`
passou a `?cache=166`); a base escolhida que a página antiga guarda depois da recusa e a frase do teto na nota das extras ganharam
caso. Ficou ao dono uma porta de outra tela: a troca de tipo do produto na tela antiga de produtos muda a soma sem conferi-la (o
D531; na cópia, nenhuma extra em insumo). A mudança do D526 que já estava no `debitos-tecnicos.md` antes da fatia, de outro
trabalho, ficou intacta. Provado de novo, em série: a caracterização da fatia (o filtro "CotaParte"), 56 em cada banco; a fatia e os
vizinhos, com os scripts servidos (o D90), 528 em cada banco (1 pulado nos dois, o do D453, que pede uma coluna da base do teste
do BVIP); o front inteiro, 2.276 em 69 arquivos (um novo, `cotaParteDaTelaAntiga.teste.ts`, a janela do arquivo servido), os de
Usuários também com `AMPLIFICAR_EFEITOS_MS=50`, o build, o lint e o tipo da prévia de Usuários; e 19 mutantes da correção, um por vez
e devolvidos byte a byte, todos vermelhos.

**No navegador** (19/10/2026), contra o banco de contrato, com um administrador temporário, na largura do painel e a 375 px e nos
dois temas, no Barbeiro Evasao Teste: na tela nova, 70% de base com o Corte a 40% foi recusado antes do pedido, com a frase no
percentual da extra e o foco nele; baixar a base para 60 tirou a frase, e os 100% exatos gravaram; subir a base para 75 foi
recusado no campo da base; com 70% posto à mão no banco (a soma de 110% já gravada), alterar só os produtos gravou sem recusa. Na
tela antiga, a base de 95% foi recusada no campo, na página, sem o toastr, com os 95 no formulário; a janela das extras recusou a
Barba a 50% (120%) com a frase debaixo do percentual. O banco, conferido depois, ficou como antes das recusas. A 375 px, a frase
quebra dentro da coluna, sem rolar de lado. Nenhum defeito.

## 19/10/2026 -- ETIQUETAS-ERP

A fatia 18 da fila das telas que faltam: a S1b do desenho das Etiquetas (`docs/f3-telas-restantes/tela-tags.md`), só a tela
antiga. Os rótulos DT-B a DT-G ganharam número: D532 a D536, todos corrigidos. O DT-E é a linha das Etiquetas do D361, que
continua parcial: restam Banners, Arquivos e VIP Class. O D344 fecha com o medidor do D203. A API `/adm/api/etiquetas` e a tela
nova ficam para a fatia 19. Nenhuma pergunta nova ao dono: a P2 e a P3 do desenho não foram tocadas.

**O que muda para quem usa** (Configurações > Serviços Tags, que hoje só os administradores abrem):

- **Só o administrador altera etiqueta** (o D532). O novo, o editar, o desativar e ativar, o Gerenciar e o lote recusam os
  outros com "Só o administrador altera etiqueta: ela vale na rede inteira.". Na cópia, ninguém além dos administradores tem a
  funcionalidade, e ninguém perde nada.
- **A lista diz "Desativada", e não "Excluída"**, e a caixa pergunta "Deseja realmente desativar esta etiqueta?".
- **O formulário não tem mais o campo "Status"** (o D533). Salvar uma etiqueta aberta antes não desfaz mais a desativação
  feita em outra aba, e a etiqueta nova nasce ativa.
- **O nome tem regra** (o D534): até 100 caracteres, contados como o nome fica gravado (com um espaço depois de cada ponto);
  sem emoji, sem `<` nem `>` e sem quebra de linha. O nome também não se repete, nem com outra caixa ou acento. A recusa
  aparece no campo, e nada é gravado. Antes, o nome longo ou com emoji dava erro, e o repetido passava. As 8 etiquetas da
  cópia já cumprem a regra.
- **O Gerenciar lista só serviços** (o D535): 3.499 na cópia, contra 9.509 itens de todos os tipos antes. O link de cada linha
  abre o serviço (antes dava "página não encontrada"). Aplicar em lote confere o token e aceita só etiqueta ativa e só
  serviço, e grava todos ou nenhum. A caixa "selecionar todas" deixou de ir no lote, e o `mod-configuracoes.js` subiu para
  `?cache=167`.
- **O cadastro de serviço da tela antiga guarda a etiqueta inativa** que o serviço já tem, com "(inativa)" no rótulo, e recusa
  o id forjado (o D536).
- **Os filtros que davam erro 500 no PostgreSQL agora são ignorados** (o D361): `status=abc` e o vazio na lista, e um
  `status`, uma unidade ou uma etiqueta fora da lista no Gerenciar.

**O que mudou no código:**

- a regra pura nova, `RegraDaEtiqueta` (o nome, a unicidade, os filtros, as opções do serviço), que a fatia 19 vai usar na API;
- no `ProdutoTagService`, o `cadastrar` e o `atualizar` conferem a unicidade com as linhas travadas, o `definirEstado`
  substitui o `alterStatus`, e o `aplicar` faz o lote tudo ou nada;
- o portão novo `scripts/conferir-escape-nas-views.php`, no `conferir-tudo.sh` e no CI. É o medidor do D203 para toda forma:
  o índice de arranjo, o getter e a variável impressos sem escape. Vigia as quatro views das etiquetas, e o `--todas` só mede:
  hoje, 550 impressões cruas em 96 das 166 views do ERP (549 antes da verificação), e não é tudo texto digitado;
- o `docs/inventario-acoes.csv` gerado de novo (três linhas: os métodos novos do serviço).

**Provado**, em série, nos dois bancos:

- **a caracterização:** a fatia e os vizinhos, 270 em cada banco, verdes. Os 3 pulados são os de sempre do D199. Os casos novos
  ou estendidos são três: o `RegraDaEtiquetaTest` (12, puro), o `EtiquetasPeloControllerTest` (14, em processo, cada recusa
  conferida no banco) e o `TelasDasEtiquetasDoErpTest` (7). O `D199AtivarRemoverPorPostTest` e o `D202EdicaoPelaRotaNoErpTest`
  foram ajustados à frase nova e à guarda de administrador.
- **o front:** 2.280 em 70 arquivos, contra 2.276 antes. O caso novo, `etiquetasDaTelaAntiga.teste.ts`, roda o arquivo servido.
  O build e o lint saíram 0.
- **os mutantes:** 41, um por vez e devolvidos byte a byte; 39 ficaram vermelhos. Os da situação, da unidade e da etiqueta crus
  deram no PostgreSQL o 22P02 que o D361 descreve, e o do `status` fora da lista deu "too few parameters". Dois sobreviveram,
  os dois equivalentes. Na chave da unicidade sem a minúscula, a normalização já põe a caixa. No `ativarRemover` sem o
  `isPost()`, o GET não traz o estado no corpo e cai no 404 do estado.
- **os portões:** o `conferir-tudo.sh` teve 64 verdes, com o vermelho conhecido do agendamento (o D50), e o do inventário
  vermelho. O inventário foi gerado de novo e o portão, rodado sozinho, saiu 0.
- **a conta dos débitos:** 140 de pé, contra 141 antes. O D344 fechou; os D532 a D536 nasceram e fecharam juntos.

**A verificação** confirmou 14 apontamentos (12 achados: dois vieram em dobro), e todos foram tratados.

- **O teto do nome conta só o gravado**, como a seção 2.3 do desenho diz. Ele contava também o digitado: "Corte", 92 espaços e
  "Barba" eram recusados, e gravariam "Corte Barba".
- **O `id` no corpo da API** recebe "Não se altera por aqui.", como nos convênios, e não a frase do estado. A tabela 2.2 do
  desenho foi corrigida, e há caso para a fatia 19 herdar.
- **A transação e as travas do `ProdutoTagService` têm caso.** Os testes rodam dentro de uma transação, e por isso tirar a do
  serviço passava verde, e daria 500 em toda escrita das etiquetas fora deles. Agora um outro processo trava as linhas, grava
  "Corrida Teste" e confirma depois de 1,5 s; o cadastro de "corrida teste" espera e recusa, nos dois bancos. É a corrida que
  o implementador não tinha provado, ainda sem dois pedidos HTTP. Outra conexão segurando a etiqueta ou o serviço faz o
  desativar e o lote pararem na leitura com trava, e não no `UPDATE` adiante.
- **A lista das inativas volta a ela** depois do desativar e na página 2: o `status=0` tem caso.
- **O formulário de serviço grava a etiqueta e a área conferidas**, e não o POST cru. Antes, "<b>90001700</b>" passava pela
  lista e dava 500. O "Selecione" diz uma frase só. A área de atuação tem o mesmo defeito do D536 e ficou registrada no
  **D537**: 193 serviços ativos da cópia estão numa área inativa, e o formulário os obriga a trocá-la. Não é deste desenho.
- **O token do lote vale a sessão** (com o sal próprio, no molde do `AgendaRemarcar`), e não vence mais 5 minutos depois de
  abrir o Gerenciar. **A recusa do lote fica na janela**, e não num `toastr` de 5 s.
- **O portão de escape** passou a ver o `printf` e as outras funções que imprimem, e deixou de aceitar o `json_encode`, o `max`
  e o `min`.
- **Ganharam caso** a tela do novo e do editar desenhada depois da recusa (a frase no campo e o valor digitado) e o ativar
  repetido.

Provado de novo, em série: a fatia sozinha, 41 em cada banco (12 da regra, 22 do controller, 7 das telas), e com os vizinhos,
477 em cada banco, com os 3 pulados de sempre; o front, 2.284 em 70 arquivos, e o build e o lint saíram 0. Foram 29 mutantes
novos, todos vermelhos: 20 no PHP, 5 no portão de escape e 4 no JS. A leitura única do `travarOsNomes` foi provada no
PostgreSQL, onde ela importa. Os débitos ficaram em 141 de pé, com o D537.

**Não provado:**

- a tela no navegador (do orquestrador);
- a trava da unicidade com dois pedidos HTTP ao mesmo tempo. O banco esperando de verdade foi provado na verificação.

**No navegador** (19/10/2026), a tela antiga das etiquetas contra o banco de contrato, com um administrador temporário: o desativar
pela caixa pergunta "Deseja realmente desativar esta etiqueta?" e vai por POST com o estado pedido, e o "Sim" clicado duas vezes
deixou a etiqueta inativa, com "Desativada com sucesso."; na lista das inativas, o "Ativar" a reativou e voltou à mesma lista; o
Gerenciar lista só os serviços (com a Lava VIP), cada um com o link para `servicos/editar`, e o lote leva o token: sem etiqueta, a
recusa "Informe uma tag." ficou na janela, e não no toastr; com a etiqueta, gravou só nos dois serviços marcados (conferido no
banco); no "Nova", sem o campo "Status", o nome repetido com outra caixa voltou com a frase no campo e o valor digitado. Nenhum
defeito.

## 19/10/2026 -- RESPOSTAS-240-241-242

A fatia 18b da fila das telas que faltam: as três respostas do dono de 19/10/2026, cada uma seguida à letra. A 241 e a 242 não
eram a recomendação.

**A senha, de 6 a 20 caracteres em todas as portas (a resposta 240 a; fecha o D527).** "Meus dados" aceitava uma senha de um
caractere, e o link do "Esqueci a senha" pedia 6 bytes, sem teto. Agora as duas pedem a mesma regra do cadastro e da ficha, com as
frases "Tamanho mínimo 6 caracteres" e "Tamanho máximo 20 caracteres". Quem já tem uma senha fora da regra continua entrando com
ela e a troca normalmente: a regra vale só para a nova. O link também deixou de dar por iguais "1000000" e "1e6", e o pedido montado
à mão com a senha em lista não dá mais erro 500. O ensaio de sessão punha de volta a senha do contrato (22 caracteres) por "Meus
dados"; agora mede que a tela a recusa e a repõe pelo roteiro da montagem.

**O cadastro novo pede o nascimento e o endereço (a resposta 241 b; fecha o D526).** O "cadastrar usuário" da tela nova ganhou a
data de nascimento e o grupo "Endereço": o CEP, o estado (uma das 27 UFs), a cidade, o bairro, a rua, o número e o complemento, que
pode faltar. São obrigatórios como na ficha da tela antiga, que agora salva quem nasceu pela nova sem ninguém preencher nada. Ficam
de fora o CPF, o CNPJ e o INSS. O endereço não volta na resposta, e o aviso de antes de cadastrar diz que só os documentos ficam na
tela antiga. A recusa vai ao campo, com o foco no primeiro. O cadastro da tela antiga segue com o filtro dela para esses campos: a
lista de estados dela vem da tabela, e não das 27 UFs.

**A tela de produtos também confere a cota-parte (a resposta 242 b; fecha o D531).** Trocar o tipo de um produto na tela antiga
podia levar a cota-parte de alguém acima de 100% naquele item sem aviso: um insumo que volta a ser produto da barbearia faz a extra
de quem a tem voltar a somar. Agora essa troca é recusada no campo do tipo, dizendo quantas pessoas e que se baixe a extra delas
antes, e nada se grava. A conferência roda com as pessoas e o produto travados. Entre a barbearia e o empório a soma é a mesma, e
a troca passa. As telas antigas de produtos e de serviços passaram a aceitar só os tipos da lista delas: um pedido montado à mão
transformava um produto em serviço, e o contrário. Na cópia, nenhuma troca de hoje seria recusada.

Provado em série: a fatia com os vizinhos, 292 casos em cada banco (os 3 pulados de sempre); o front, 2.285 casos em 70 arquivos; o
build, o lint e o tipo das prévias de Usuários saíram 0. O ensaio de sessão passou 24 de 24 nos dois bancos, com o caso 17 novo.
Foram 32 mutantes, todos vermelhos: 23 no PHP e 9 no JS. Os débitos de pé caíram para 138.

**Não provado:** a tela no navegador, que fica com o orquestrador; a troca de tipo com dois pedidos HTTP ao mesmo tempo, porque as
travas foram provadas pela ordem das leituras e não por uma corrida de verdade.

**A verificação (19/10/2026)** confirmou 12 apontamentos. Onze foram consertados no mesmo dia; o do país do CEP não mudou nada, e
o porquê está escrito.

- **A ficha antiga não salvava quem tinha um estado fora da tabela `estados` (o D526).** A lista de estados da ficha vem dessa
  tabela, que é o cache da busca de CEP: quatro UFs na cópia do MySQL, uma nas fixtures. Com a sigla gravada fora dela, o campo
  abria em "Selecione", e salvar recusava o estado. Na cópia, 51 ativos já estavam assim, e quem nasce pela tela nova pode ter
  qualquer uma das 27. A ficha passou a oferecer a sigla que a pessoa já tem, no molde do D416 e do D536. O caso que dizia "salva
  sem preencher nada" agora monta o pedido como o navegador o manda.
- **As frases do cadastro novo são as da ficha.** O que falta no nascimento e no endereço diz "Campo obrigatório"; a forma que a
  ficha não confere tem a frase dela.
- **O complemento e o ano do nascimento passaram a ter caso.** O complemento que veio é gravado, e o que só tem espaços conta como
  não veio. O ano fica entre 1900 e 2099.
- **O link do "Esqueci a senha" (o D527).** A página diz "de 6 a 20 caracteres" antes do envio. A recusa fica escrita na página,
  com o foco na nova senha, e não mais num aviso que some em 5 s. O script vai com o cache novo.
- **Meus dados, a ficha e o cadastro antigo.** A senha vazia ouvia duas frases, e agora ouve só "Informe a senha".
- **A troca de tipo do produto (o D531).** A troca se decide pelo tipo relido com a trava, e não pelo que a tela leu antes. A pessoa
  inativa com extra no produto conta. A página recusada volta com o tipo e o nome digitados.
- **O país do CEP não mudou nada.** A ficha antiga também só aceita o formato brasileiro, pela máscara do CEP e pela lista de UFs.
  Nenhuma unidade ativa da cópia é de fora do Brasil.

Provado em série: a fatia com os vizinhos, 821 casos em cada banco (os 3 pulados de sempre); o front, 2.289 casos em 71 arquivos;
o build, o lint e o tipo da prévia de Usuários saíram 0. Foram 27 mutantes novos, todos vermelhos: 19 no PHP e 8 no front e no
script antigo. O ensaio de sessão não foi repetido: ele só passa por "Meus dados" com senhas não vazias, e o que mudou ali é a frase da vazia. Os portões
saíram 0, só com o vermelho conhecido do D50; na primeira rodada, o do inventário acusou o `senhaConfirmar` como "referenciado" pelo
nome da função e do lugar da recusa no script, que foram renomeados (`mostrarRecusaDoLink`, `#recusa-da-nova-senha`). Os débitos de
pé seguem 138.

**No navegador** (20/10/2026), contra o banco de contrato, com um administrador temporário: na tela antiga de produtos, a Pomada
posta como insumo, com 10% de base e 95% de extra de uma pessoa, voltando a Barbearia foi recusada no campo do tipo ("a cota-parte
de 1 pessoa passaria de 100% neste produto [...] Baixe a extra dela"), com o tipo digitado no formulário; na tela nova, a 375 px, o
"cadastrar usuário" mostra o nascimento e o endereço (o CEP, a UF, a cidade, o bairro, a rua, o número e o complemento), recusa o
CEP de quatro dígitos no campo, e o cadastro certo gravou o CEP como 88010-000 e a UF em sigla. Achou um defeito antigo, consertado
aqui: a ficha antiga dessa pessoa, cadastrada com "Não" no PDV e no dashboard, abria as duas marcas em "Selecione" e recusava
salvar -- o D538 (na cópia, 111 e 122 ativos com "Não"); depois do conserto, "Editado com sucesso." sem preencher nada.


## 20/10/2026 -- ETIQUETAS-TELA

A fatia 19 da fila das telas que faltam: a tela nova "Etiquetas dos serviços" e a `/adm/api/etiquetas` (a S2 de
`docs/f3-telas-restantes/tela-tags.md`). A P1 das etiquetas seguiu a resposta 220 (b), "manter".

**O que muda para quem usa.** Configurações > Serviços Tags abre a tela nova, e não mais o aviso "ainda roda na versão antiga". A
lista mostra as etiquetas ativas ou as inativas, com a busca por nome, e cada linha diz quantos serviços ativos a têm e em quantas
unidades ("351 serviços · 31 unidades"). A etiqueta aberta diz o uso inteiro -- os serviços ativos de unidades ativas e quantos
deles o aplicativo mostra --, quando foi criada e alterada, e tem o link "ver os serviços com esta etiqueta", que abre o Gerenciar da
tela antiga filtrado: aplicar em lote continua lá. Só o administrador cadastra, renomeia, desativa e ativa; quem tem o privilégio
sem ser administrador lê, e a tela diz "Só o administrador altera etiqueta: ela vale na rede inteira." Renomear diz antes quantos
serviços mudam junto. Desativar pergunta no próprio botão e diz que os serviços que a têm continuam com ela e que o cadastro de
serviço deixa de oferecê-la -- o que a tela antiga já fazia (a P3 continua com o dono); o duplo clique e o Enter repetido não
confirmam. O nome repetido, sem diferença de maiúscula nem de acento, volta no campo.

**O servidor** usa a regra e o serviço da fatia 18, sem segunda cópia: a `RegraDaEtiqueta` no corpo, a unicidade com as linhas
travadas no `ProdutoTagService`, e o 409 do estado conferido com a linha travada (o `mudarEstado` novo). Ler pede
`configuracoes/produtosTags/ler`; escrever, o `escrever` e ser administrador. A família entrou na lista de rotas sem login: sem ela,
o pedido sem sessão recebia a página de login com 200, e não o 401 da API. Quem viu foi o ensaio HTTP; o portão das rotas sem login
saía 0, e ganhou a conferência que o pega (o D539, registrado e corrigido).

Provado em série: a fatia com os vizinhos, 522 casos em cada banco; o ensaio novo `ensaiar-etiquetas-adm-api.php`, 21 de 21 no
MySQL e no PostgreSQL, e o de permissão, 78 de 78 nos dois bancos, com as seis rotas novas recusadas; o front, 2.354 casos em 74
arquivos (65 novos); o build, o lint e o tipo da prévia nova saíram 0. Foram 43 mutantes, todos vermelhos: 25 no servidor e 18 no
front. Os portões que a fatia toca saíram 0. Os débitos de pé seguem 138.

**Não provado:** a tela no navegador, que fica com o orquestrador; a corrida de duas abas desativando ao mesmo tempo, provada pela
releitura com a trava (outra escrita por baixo), e não por dois pedidos HTTP.

**A verificação em cinco lentes** confirmou 12 achados, todos tratados. Renomear só mudando a caixa ou os espaços ("corte adulto")
gravava a data de alteração e a tela dizia "Etiqueta renomeada": o `ProdutoTagService::atualizar` passou a comparar o nome
normalizado com o lido com a trava e, sendo o mesmo, não grava nada -- vale também para o editar da tela antiga --, e a tela diz
"Nada foi alterado: gravado como o ERP grava, o nome seria o mesmo.", sem reler a lista ao voltar. O aviso do renomear passou a dizer
que o nome muda em todos os serviços com a etiqueta, também nos inativos ("351 ativos, em 31 unidades ativas, e também os
inativos"). O "buscar" deixou de ser ouro; o "cadastrar etiqueta", a linha e o "renomear" ganharam a guarda do Enter segurado; e a
tabela, a classe `etiquetas-tabela`, sem o mínimo de 30rem que a 375 px cortava o "Uso" (a largura fica para o navegador). Ganharam
caso o que estava sem: a trava e a data da troca de estado, a paginação nos dois lados, o erro ao abrir, as frases de carga, o
"atualizar", o 409 cuja releitura falha, e o foco no voltar e depois do cadastro. O cabeçalho do ensaio deixou de dizer que a faixa
90009700-90009799 é livre: onze testes a usam em outras tabelas, e o ensaio só em quatro onde ninguém mais a usa. Provado: os
testes das etiquetas e os vizinhos, 124 em cada banco; o ensaio das etiquetas, 21 de 21, e o de permissão, 78 de 78, nos dois
bancos; o front, 2.367 casos; o build e o lint, 0; os portões que a fatia toca, 0. Mutantes novos: 24 (5 no servidor, um deles no
PostgreSQL, e 19 no front), todos vermelhos.

**No navegador** (20/10/2026), contra o banco de contrato, com um administrador temporário, na largura do painel e a 375 px e nos
dois temas: Configurações › Serviços Tags abre a tela nova, com o foco no título e um só botão em ouro, o "cadastrar etiqueta"; o
renomear da "Etiqueta Teste" para "ETIQUETA teste" disse "Nada foi alterado: gravado como o ERP grava, o nome seria o mesmo." e a
data de alteração ficou "nunca alterada"; o desativar perguntou no próprio botão ("Nenhum serviço ativo a tem; o cadastro de serviço
deixa de oferecê-la."), com o "não desativar" ao lado, e desativou dizendo que os serviços continuam com ela; o ativar, por um clique
de verdade, voltou com o foco no botão de estado; o cadastro de uma etiqueta de nome longo gravou como o ERP grava, com o foco no
título, e a lista voltou relida; a 375 px, a tabela cabe no cartão, com o nome quebrando em duas linhas e o "Uso" inteiro, sem rolar
de lado. Nenhum defeito.

## 20/10/2026 -- ETIQUETAS-SEM-ETIQUETA

A fatia 20 da fila das telas que faltam: o filtro "sem etiqueta" (a S3 (b) de `docs/f3-telas-restantes/tela-tags.md`). A P2 das
etiquetas segue a recomendação, a (b) -- cada unidade corrige os seus serviços pelo catálogo --, até o dono dizer outra coisa; o
Gerenciar antigo continua, e não houve rota de aplicar nem de tirar etiqueta.

**O que muda para quem usa.** Na tela "Etiquetas dos serviços", quem também abre o catálogo de serviços lê, acima da busca, quantos
serviços ativos da unidade em que está estão sem etiqueta ("12 serviços ativos desta unidade estão sem etiqueta."), com o "ver no
catálogo", que abre Serviços já filtrados; cada um se corrige no cadastro dele, que escolhe a etiqueta. Sem nenhum, a frase diz isso e
não oferece o link. A etiqueta aberta ganhou o "ver os serviços desta unidade com esta etiqueta", que abre o catálogo filtrado por ela
e acha a etiqueta posta no serviço errado. No catálogo de serviços, a busca ganhou o filtro "Etiqueta": "todas", "sem etiqueta" e,
a quem escreve no catálogo, as etiquetas ativas da rede pelo nome -- quem só lê tem as duas primeiras, e todos têm a etiqueta com que
a tela das etiquetas abriu o catálogo. Escolher "Serviços" no menu abre de novo sem o filtro. Os produtos não têm o filtro.

**O servidor:** `GET /adm/api/produtos?catalogo=servicos&etiqueta=sem|<id>`, com o valor em lista fechada
(`RegraDaEtiqueta::filtroDoCatalogo`): o texto, o zero, o id acima do `int4` e a lista são 422 com o campo, e não 500 no
PostgreSQL; nos produtos, também. O recorte da unidade é o de sempre, o da sessão, também para o administrador. A contagem da tela é o
total dessa mesma consulta, e não uma rota nova.

Provado em série: os testes da fatia e os vizinhos (o `CatalogoPorEtiquetaPelaApiTest` novo, o `RegraDaEtiquetaTest`, os das
etiquetas e os do catálogo pela API), 82 em cada banco; o front, 2.381 casos em 74 arquivos (14 novos), e os arquivos da fatia com o
amplificador de efeitos -- o `CatalogoLigado` segue com os 4 vermelhos que já tinha na HEAD (o D471, relido); o build, o lint e o tipo
das duas prévias, 0. Mutantes: 23, todos vermelhos -- 6 no servidor e 17 no front. Os 16 portões que a fatia toca saíram 0. Os
débitos de pé seguem 138: a fatia não fechou nem abriu nenhum.

**Não provado:** a tela no navegador, que fica com o orquestrador.

**A correção da fatia (a verificação em lentes, 20/10/2026).** Doze apontamentos, todos conferidos no código e verdadeiros (três eram
o mesmo espaço). *O filtro não oferecia as etiquetas pelo nome* a quem escreve no catálogo, e a razão dada ("pediria a lista a quem
lê") não valia para ele, que já a recebe: agora o `CatalogoLigado` de quem escreve nos serviços pede `/produtos/catalogos` uma vez ao
montar, depois da lista, oferece as ativas da rede no filtro, e a primeira edição usa as guardadas (não pede de novo); quem só lê não
a pede. *A porta do PDV* (o D168) filtrava por etiqueta e ficava sabendo como os serviços da unidade se agrupam, sem ver etiqueta
nenhuma: o filtro passou a ser 403 a quem não tem `configuracoes/produtos/ler`, antes da lista fechada; a lista sem filtro continua
aberta a ela. *A frase da contagem* passou a ser região viva (o leitor de tela ouve o número e a falha), com o link fora dela. *O
número da cópia*, que a entrada dava por não provado, foi medido (só SELECT agregado): 135 serviços ativos sem etiqueta em 20 das 33
unidades ativas com serviço, até 40 numa só, e 13 com tudo etiquetado; as prévias diziam "31 unidades", e agora dizem 33. *A frase
chega só aos administradores* (M10), cada um na unidade da sessão: está na letra da S3, e os gerentes acham os seus pelo filtro;
escrito no desenho, sem débito. *O espaço* arrancado do `FRASE_DO_TETO` voltou; o comentário do `CATALOGO_DO_DESTINO` no Console
voltou a ser dele. *Cobertura que faltava*: a página 2 com o filtro (no servidor e no front), abrir um serviço e voltar com o filtro,
e o filtro pela query string de verdade -- seis casos novos (22 a 27) no `ensaiar-etiquetas-adm-api.php`, 27 de 27 no MySQL e no
PostgreSQL de contrato. Provado: os testes da fatia e os vizinhos, 158 em cada banco; o front, 2.390 casos em 74 arquivos (9 novos);
com o amplificador, o `EtiquetasLigado`, o `formularioDaEtiqueta` e o `Console` passam inteiros, e o `CatalogoLigado` segue com os 4
do D471 (os 7 casos novos dele passam); o build, o lint e o tipo das duas prévias, 0; os portões que a fatia toca, 0. Mutantes novos:
13, todos vermelhos -- 3 no servidor, 1 no ensaio e 9 no front --, e a primeira montagem já tinha 23.

**No navegador** (20/10/2026), contra o banco de contrato, com um administrador temporário: a tela das etiquetas disse, na região
viva, "3 serviços ativos desta unidade estão sem etiqueta." (o número do banco), com o "ver no catálogo"; o botão abriu Serviços já
filtrados em "sem etiqueta", com os três (dois serviços e a Lava VIP) e "3 serviços ativos com esta busca"; o filtro oferece "todas",
"sem etiqueta" e a etiqueta pelo nome, e com a "Etiqueta Teste" a busca deu "0 serviços ativos com esta busca"; a 375 px, o seletor
não corta o valor e a página não rola de lado. Nenhum defeito.

## 20/10/2026 -- BANNERS-SERVIDOR

A fatia 21 da fila das telas que faltam: o servidor dos banners do aplicativo -- as fatias 1 e 2 de
`docs/f3-telas-restantes/tela-banners.md`, juntas: a tela antiga consertada e a `/adm/api/banners`. A tela nova é a fatia 22.
As perguntas 1 a 3 dos banners não foram respondidas, e o servidor segue o que o desenho decidiu para seguir: não há apagar de
vez (só tirar e pôr no aplicativo), a ordem continua alfabética (com a posição escrita), e a proporção 2:1 é só aviso, com 2% de
tolerância.

**O que muda para quem usa.** Na tela antiga (Configurações > Aplicativo > Banners), o X agora tira o banner do aplicativo de
verdade, perguntando antes ("Tirar este banner do aplicativo? Os clientes deixam de vê-lo na próxima abertura."), e a lista dos
banners fora do aplicativo ganha o "pôr no aplicativo": os dois botões apontavam para uma ação que nunca existiu. Editar um banner
que está fora do aplicativo não o devolve mais aos clientes. A imagem só entra se for um JPEG de verdade, até 5 MB (era 20 MB, e
pelo nome do arquivo); a descrição, até 100 caracteres, sem emoji nem `<` e `>`, com a frase no aviso -- antes, isso derrubava a
tela. Trocar a imagem só apaga a de antes depois de gravar a nova, e o envio que falha não apaga mais o banner. O filtro da lista
com um valor estranho na URL mostra os ativos, e não dá erro. **O aplicativo dos clientes recebe exatamente o que recebia** --
o mesmo endereço, o mesmo formato, a mesma ordem --, e o banner cadastrado pela API nova chega lá do mesmo jeito.

**Para a tela nova** (a fatia 22), a `/adm/api/banners`: a lista por estado (`ativo`, `inativo`, `todos`) e busca, com a posição
de cada um no aplicativo, as dimensões, o peso, o aviso de proporção e a URL pelo armazenamento, e a frase do topo
(`noAplicativo`, `unidadesAtivas`); o banner aberto com os vizinhos na ordem; o cadastro e a troca da imagem por multipart, só em
`POST`; o `PUT` só da descrição; e a desativação e a ativação, com 409. O envio de arquivo ficou escrito como a regra 8 de
`docs/convencao-api.md` (o `422` no campo, e não `413`; o tipo pelo conteúdo; o nome aleatório; "guarda o novo, grava a linha,
apaga o antigo"; o `500 erro_interno` do armazenamento que falha), e o recurso em `docs/f2-fatia-banners.md`.

**Os débitos.** Fechados: o D352 (salvar o inativo o devolvia ao aplicativo) e os quatro sem número do desenho, numerados agora --
o D540 (a descrição sem teto), o D541 (o tipo pelo nome), o D542 (remover o que não existe dava 500) e o D543 (a ordem sem
desempate) -- e o D199, que fechou com a dos banners (**nenhuma remoção do ERP vai mais por GET**, e a trava desceu de 1 para
0; os outros restos dele já tinham fechado em 04/10 e 07/10). Parciais: o D353 (a ordem da troca está no código; restam os 4 órfãos que já estão no disco, para o corte, com a
autorização do dono), a linha dos Banners do D361 (restam Arquivos e VIP Class), o Dη no D377 (a lista antiga e a API usam o
`url()`; o endpoint do aplicativo segue congelado, e um caso o prende) e o D105 (o ativar dos banners; restam cinco mortos). A entrada
`appbanners|ativarRemover` saiu do `conferir-links-mortos.php`.

**Provado.** Nos dois bancos, em série: `RegraDoBannerTest` (15), `TelasDosBannersDoErpTest` (15), `BannersPelaApiTest` (18) e a
linha dos banners do `D199AtivarRemoverPorPostTest`, com os vizinhos (a barreira de origem, a entrada do ERP, as rotas do
aplicativo, o armazenamento confinado, as etiquetas pela API, os arquivos): 454 casos em cada banco. O contrato, 74 e 74. O
ensaio novo, `scripts/ensaiar-banners-adm-api.php` (no CI e no `medir-telas-no-postgres.sh`), 26 de 26 no MySQL e no
PostgreSQL de contrato, com `public/banners` de volta aos 14 arquivos de antes; o de permissão, 85 de 85 nos dois, com as sete
rotas novas. O front não mudou: 2.390 casos. Mutantes: 52, cada um sozinho e restaurado pelos bytes -- 45 nos testes (todos
vermelhos; os dois do desempate, no PostgreSQL, onde a linha nova vai para o fim da tabela), 5 nos portões (o remover e o ativar
por GET, a ação do ativar arrancada, o total cru na lista, a família fora das rotas públicas: todos saem 1), 1 no ensaio (a família
fora das rotas públicas: o caso 1 dá 200) -- e o do desempate no MySQL, que sobrevive lá como o D543 diz (a chave primária
agrupada já devolve pelo id). Os portões que a fatia toca saem 0 (23 no contêiner, mais o inventário, a lacuna do CI, os
segredos, os débitos repetidos). Os débitos de pé descem de 138 para 137.

**Não provado:** a tela antiga no navegador, que fica com o orquestrador.

**A verificação (20/10/2026)** confirmou 11 apontamentos (9 achados, dois repetidos), e todos foram tratados. O D199 fechou: a
seção nova o deixava PARCIAL com um resto que o dono já tinha decidido (a resposta 24) e que foi feito em 07/10. "Só o
administrador escreve" no que é da rede, que a fila pede e o desenho dos banners não seguiu, foi ao lote como o **D544** (a
recomendação é (c): nos banners basta o privilégio, na versão só o administrador); até a resposta, fica o desenho. Na tela
antiga: a recusa do salvar passou do aviso de 5 s do canto para a página, com o título "O banner não foi salvo:" (era "Informe
os campos obrigatórios:", também sobre a imagem que veio), e o `mod-configuracoes.js` foi com o `?cache=168`; o corpo acima do
`post_max_size` diz o tamanho, e não "O formulário expirou". O ensaio dos banners apaga do disco só o que é dele -- o arquivo
do cenário e os que as linhas dele apontam --, e limpa o resíduo de uma rodada morta antes de ler a lista do começo: falsificado
pelo HTTP, com um resíduo plantado e um arquivo alheio criado durante a rodada, a versão de antes saiu 0 deixando o resíduo e
apagando o alheio, e a de agora tirou o resíduo, deixou o alheio e saiu 2 dizendo o nome dele. A tela antiga continua sem o
aviso de proporção: o desenho o põe na tela nova. Casos novos em `TelasDosBannersDoErpTest` (5): as telas do novo e do editar
desenham o token que o salvar aceita, e o lugar da recusa; o token não vence em seis minutos nem pela outra aba; o corpo grande;
a recusa na página; quem só lê não escreve nada na tela antiga. 13 mutantes, cada um sozinho e restaurado pelos bytes, todos
vermelhos: o token arrancado do novo e do editar, o lugar da recusa arrancado, o `getAcesso('ler')` no ativar, no remover, no
salvar, no novo e no editar, o token de 300 s, o token sem o sal, sem a recusa do corpo grande, a recusa de volta ao toastr e o
título de antes. Os quatro arquivos dos banners, 115 casos; com os vizinhos, 219 em cada banco. Os portões que a fatia toca
saem 0.

**No navegador** (20/10/2026), a tela antiga dos banners contra o banco de contrato, com um administrador temporário: o tirar do
aplicativo pela caixa ("Tirar este banner do aplicativo? Os clientes deixam de vê-lo na próxima abertura.") vai por POST com o
estado pedido, e o "Sim" clicado duas vezes deixou o banner fora, com "Banner fora do aplicativo."; na lista dos inativos, o pôr de
volta deu "Banner de volta ao aplicativo."; o "Novo" sem imagem recusou na própria página ("O banner não foi salvo: - Imagem: Escolha
a imagem do banner."), com a descrição digitada, e não no aviso de cinco segundos; com uma JPEG de 800 por 400 feita na página,
salvou com o nome aleatório, e o `/api/banners/selecionar` do aplicativo o devolveu no formato de sempre. O arquivo criado foi
apagado, e `public/banners` voltou aos 14 de antes. Nenhum defeito.

## 20/10/2026 -- BANNERS-TELA

A fatia 22 da fila das telas que faltam: a tela nova "Banners" (a fatia 3 de `docs/f3-telas-restantes/tela-banners.md`), sobre a
`/adm/api/banners` da fatia 21, sem mudar o servidor. As perguntas 1 a 3 de banners seguem sem resposta, e a tela faz o que o
desenho decidiu para seguir; a 243 (o D544, quem escreve) está com o dono, e até lá basta o privilégio.

**O que muda para quem usa.** Configurações > Aplicativo > Banners abre a tela nova, e não mais o aviso "ainda roda na versão
antiga". No topo, a regra numa frase, com os números do servidor: "O aplicativo das 32 unidades mostra os 5 banners ativos, em ordem
alfabética da descrição." A lista mostra os que estão no aplicativo (o padrão), os de fora ou todos, com a busca pela descrição, e
cada linha tem a miniatura, a posição no aplicativo ("3º"), o selo, a data, as dimensões, o peso e, fora de 2:1, o aviso "não é
2:1". O banner aberto mostra a imagem grande, o aviso de proporção com a medida, onde ele está na ordem ("3º no aplicativo, entre
«Assinatura» e «Corte»") e o histórico. Quem tem o privilégio de escrever altera a descrição (a dica diz que a posição segue a
ordem alfabética), troca a imagem, tira do aplicativo e põe de volta -- tirar e pôr perguntam no próprio botão, e o duplo clique não
confirma. Cadastrar e trocar a imagem mostram, antes de enviar, o nome, o peso, as dimensões e a proporção do arquivo escolhido, com
a prévia; o arquivo que não é JPEG pelo conteúdo, ou passa de 5 MB, é recusado já ao escolher; a proporção só avisa, e grava. O
botão diz "Enviando…". Depois de salvar, o banner abre dizendo a posição em que entrou. Não há apagar. Quem só lê vê tudo, sem
botões.

**Por dentro.** O `pedir` ganhou o envio com `FormData` (o campo `formulario`, sem o `Content-Type`, que o navegador põe com o
`boundary`); o 413 do nginx, que volta em HTML, a tela lê como o teto, no campo da imagem. O espelho da regra do servidor é o
`formularioDoBanner.ts` -- o teto da descrição contado como o PHP conta, a ordem e as frases da `RegraDoTexto`, o JPEG pelos três
primeiros bytes, a proporção com as mesmas constantes --, e o `RegraDoBannerTest` lê dele os tetos, a proporção, a tolerância e as
frases. Os tipos de `api/banners.ts` estão presos ao recurso pelo `BannersPelaApiTest`. A leitura da imagem escolhida é injetada (o
`happy-dom` não decodifica imagem); o `blob:` da prévia é solto ao fechar, ao trocar de escolha e ao sair. O proxy `/banners` entrou
no `vite.config.ts`. A prévia nova no Claude Design (`frontend/.design-sync/previews/Banners.tsx`, 12 células); a célula de subgrupo
da tela não migrada trocou os Banners pela Versão. Os destinos do menu que caem na tela não migrada descem de 25 para 24.

**Fora do caminho, consertado:** o portão do relógio nos testes (`conferir-relogio-nos-testes.php`) estava vermelho no HEAD -- o
conserto final da fatia 21 pôs um `time()` no `TelasDosBannersDoErpTest`; passou a `agora()`, a leitura única do D87, e o caso
continua vermelho com o prazo de quatro minutos.

**A verificação em cinco lentes** confirmou 13 apontamentos, todos consertados na fatia (a seção 7 do desenho os lista). Para quem
usa: o aviso de proporção do banner aberto passou a ser o do servidor, como o da lista; a miniatura cujo arquivo falta no servidor
diz "sem imagem", e não aparece quebrada; o banner removido pela tela antiga com o novo aberto faz a lista ser relida ao voltar; o
leitor de tela ouve a leitura da imagem escolhida, o "não é 2:1" e o motivo da recusa; e, durante o envio, o arquivo e a descrição
não mudam. Por dentro: um arquivo de casos comum (`formularioDoBanner.casos.json`, 57 descrições e 17 medidas) roda na regra do
servidor e no espelho da tela, e prende o que as constantes não prendiam -- a fórmula da proporção, as classes de caractere e a
ordem das conferências; o contrato do invólucro foi de 7 a 26 casos. Provado depois da correção: nos dois bancos, os mesmos quatro
arquivos com 118 casos (o novo dos casos comuns), 3 pulados como antes; o front, 2.479 casos em 77 arquivos (31 novos), e os da tela
com `AMPLIFICAR_EFEITOS_MS=50` em seis rodadas sem vermelho -- o amplificador achou uma corrida nos casos de antes: esperar
"natal.jpg · 2 kB" casava também com o "lendo a imagem…", e o salvar no meio da leitura não envia; os casos esperam as medidas;
`tsc`, `build`, `lint` (os 7 avisos de antes) e o tipo das prévias, 0. Mutantes da correção: 32, cada um sozinho e restaurado
pelos bytes, todos vermelhos -- 30 no front e 2 na regra do servidor (a faixa da proporção e o U+061C aceito), que o arquivo de
casos comum pega.

**Os débitos.** Nenhum fechado nem aberto. O D544 ganhou no corpo o que a tela faz (basta o privilégio) e o que muda se a resposta
for a outra. De pé, os mesmos 137.

**Provado.** Nos dois bancos, em série: `RegraDoBannerTest` (16, com o caso novo que lê o front), `BannersPelaApiTest` (19, com o
dos tipos), `TelasDosBannersDoErpTest` e a linha dos banners do `D199AtivarRemoverPorPostTest` -- 117 casos em cada banco, 3
pulados de outras telas (vipclass e mensagens, como antes). O front: 2.448 casos em 77 arquivos (`--maxWorkers=4`), 58 novos; os
da tela também com `AMPLIFICAR_EFEITOS_MS=50`, três rodadas sem vermelho; `tsc`, `npm run build`, `npm run lint` (os 7 avisos de
antes, nenhum nos arquivos novos) e o tipo das duas prévias saem 0. Mutantes: 30, cada um sozinho e restaurado pelos bytes, todos
vermelhos -- 25 no front (o `Content-Type` escrito no multipart, o `FormData` ignorado, a tolerância e o teto da tela, a escolha
sem o teto e sem o JPEG, o JPEG por dois bytes, a descrição sem o teto, o `trim` do JavaScript, o 413 sem ramo, o `PUT` que vai
sem mudar, a miniatura sem o texto alternativo, o tirar sem perguntar, a lista sem a frase do topo, a prévia sem o aviso, a prévia
não solta, a leitura de antes que vale, as duas escritas, o 409 que não relê, o tirar que não marca a lista, a escrita depois de
voltar, a troca por `PUT`, o cadastro sem a descrição, o Console que exige administrador e o Console sem a tela) e 5 no PHP lendo o
front (a tolerância, o teto, a frase do JPEG, uma chave a menos no `BannerDaListagem` e na `FraseDoTopo`); e o do relógio (quatro
minutos no lugar de seis), vermelho. Os portões do `conferir-tudo.sh` saem 0, com o vermelho conhecido de sempre (o D50); os
segredos, os débitos repetidos e as referências dos docs, 0.

**Não provado:** a tela no navegador contra o banco de contrato (cadastrar um 2:1 e um 16:9, trocar a imagem, tirar e pôr conferindo
o `/api/banners/selecionar`, a 375 px e nos dois temas), que fica com o orquestrador; e a leitura de verdade da imagem
(`lerNoNavegador`), que o `happy-dom` não roda.

**No navegador** (20/10/2026), contra o banco de contrato, com um administrador temporário, na largura do painel e a 375 px e nos
dois temas: Configurações › Aplicativo › Banners abre a tela nova, com a frase do topo ("O aplicativo das 5 unidades mostra 1 banner,
o único ativo.") e a nota de que a posição vem da descrição; a miniatura do banner de contrato, cujo arquivo não existe, virou "sem
imagem"; o cadastro de uma JPEG de 1600 por 900 feita na página disse, na região viva, "não é 2:1, e grava assim mesmo", com a prévia e
as medidas, gravou e abriu o banner com o foco no título, "No aplicativo em 2º", depois do de contrato; o tirar perguntou no próprio
botão ("Os clientes deixam de vê-lo na próxima abertura.") e, confirmado, o `/api/banners/selecionar` deixou de devolvê-lo; a 375 px,
a imagem cabe na coluna e a página não rola de lado. O arquivo criado foi apagado, e `public/banners` voltou aos 14 de antes. Nenhum
defeito. Uma nota sobre a fatia 21: o conserto do caso do relógio que eu fiz ali usou `time()` cru, que o portão do relógio nos testes
recusa, e eu só repeti a caracterização -- o aebffa3 subiu com esse portão vermelho; esta fatia trocou a linha pelo ajudante
`$this->agora('U', '+6 minutes')`, e o portão voltou a 0.


## 20/10/2026 -- VERSAO-DO-APLICATIVO

A fatia 23 da fila das telas que faltam: a versão do aplicativo inteira -- as três fatias de
`docs/f3-telas-restantes/tela-versao-app.md` (o gravador consertado nas portas antigas, a API e a tela). A P1 do desenho (o que o
aplicativo faz com o número) ficou de fora, como a fila manda; o Push fica como está (a resposta 76 (c)); e quem publica é quem tem
o privilégio, até a resposta da pergunta 243 (o D544).

**O que muda para quem usa.** Configurações › Aplicativo › Versão abre a tela nova, e não mais o aviso "ainda roda na versão
antiga". Ela mostra a versão que o aplicativo de cada cliente recebe hoje, em destaque, com quem a publicou e quando ("Alterada por
... em 20/10/2026 às 14:03", e "em nome de" na personificação); até a primeira publicação pela tela nova, ela diz que as mudanças
de antes não deixaram rastro. Quem tem o privilégio de escrever vê os três campos (Major · Minor · Patch), a linha "Vai publicar
2.0.9; hoje é 2.0.8." e um botão só, "Publicar 2.0.9", desligado enquanto nada muda ou um campo não serve; o campo vazio, o decimal
e a letra dizem por quê, no campo. Baixar a versão ou trocar o major pede confirmação: o servidor pergunta, a tela mostra a frase
num aviso âmbar, e o botão passa a "Publicar 2.0.7 mesmo assim", com o "voltar" ao lado. Se outra aba publicou depois que a tela
abriu, ela não desfaz em silêncio: diz "A versão mudou desde que a tela abriu: agora é 2.0.9, por ... às 14:03. Confira e publique
de novo.", relê, e guarda o que a pessoa digitou. Sem o arquivo, a tela diz o que o aplicativo recebe e publicar o recria (a antiga
dava 404). Quem só lê vê a publicada, sem campos. O Push, ao lado no menu, continua na tela antiga, em aba nova.

**Na tela antiga** (`/mod-configuracoes/appconfiguracoes/versao` e `.../push`), que continua: salvar em branco não publica mais
0.0.0 para todo o parque -- o campo diz "Obrigatório." --, nem "2.5" vira 2; um byte inválido no status do Push mostra "Erro ao
editar." e não apaga mais o arquivo (e a versão junto); um Push salvo durante uma mudança de versão não a desfaz; salvar a versão
depois de cinco minutos parado não falha mais calado; e cada gravação deixa o rastro de quem fez.

**O que o aplicativo de produção lê não mudou:** o `/api/versao/app` e o `public/app.json` -- o formato, as chaves, a ordem e o
caminho. Os bytes gravados são os de hoje (`[{"push":"inativo","versao_major":"2",...}]`), e o
`VersaoDoAplicativoPelaApiTest::testOAplicativoRecebeOQueSempreRecebeu` despacha o leitor congelado sobre o que a API nova publica.
Nenhum teste toca o `public/app.json` do dev: todos gravam num diretório temporário, e os `tearDown` conferem o sha1 dele.

**Por dentro.** A `RegraDaVersao` (só algarismos, de 0 a 99999, gravados sem zero à esquerda; a `PEDE_CONFIRMACAO`), o
`RegistroDoAplicativo` (o gravador único da versão e do Push: a trava, a releitura dentro dela, a troca só do próprio campo, o
`JSON_THROW_ON_ERROR` antes do disco, o temporário com `rename`, e o histórico em `data/aplicativo/historico.jsonl`, aberto antes da
troca), a `/adm/api/aplicativo/versao` (`GET` e `PUT`, com o 422 por campo, o 409 da tela velha e o que pergunta), e, no front,
`api/aplicativo.ts`, `formularioDaVersao.ts` -- com `formularioDaVersao.casos.json`, rodado pelos dois lados --,
`VersaoDoAplicativo.tsx` e `VersaoDoAplicativoLigado.tsx`. Os destinos do menu que caem na tela não migrada descem de 24 para 23. A
prévia nova no Claude Design (`frontend/.design-sync/previews/VersaoDoAplicativo.tsx`, dez células), e a célula de cadastro da tela
não migrada trocou a Versão pelos Arquivos. Nenhum esquema mudou.

**Débitos:** o D354 corrigido; o D36 parcial (um gravador só, e a tela nova recria o arquivo; a tabela fica para o corte); e os
rótulos do desenho que faltavam ganharam número -- D545 (o TypeError do disco) e D547 (o token de cinco minutos) e D548 (o rastro)
corrigidos, D546 (quem só lê) e D550 (a rebaixa sem aviso) parciais, e o D549 (o `public/app.json` versionado trava o deploy de
teste) registrado, de fora desta fatia.

**Provado:** a caracterização da fatia nos dois bancos, em série (os quatro arquivos novos, 45 casos; com os vizinhos dos banners, 101 em cada banco); o front inteiro, 2.509 casos (os da versão também com `AMPLIFICAR_EFEITOS_MS=50`), o `tsc`, o `oxlint` e o build; 55 mutantes, cada um sozinho -- 52 vermelhos e 3 equivalentes, escritos no desenho (seção 6); e os portões que a fatia toca (rotas sem login, ACL da API, guarda do ERP, relógio nos testes, bytes de controle, rotas órfãs, proteção do webroot, remoção por POST, links mortos, débitos repetidos, referências dos docs). O ciclo inteiro, o contrato, os ensaios HTTP e o navegador ficam com o orquestrador.

**A verificação da fatia.** Cinco lentes confirmaram 16 apontamentos, todos tratados, sem escolha do dono. Para quem usa a tela
nova: o duplo clique e o Enter segurado não confirmam mais a rebaixa sem a pergunta ser lida (o D551); durante a releitura do
409 o botão fica desligado, e digitar não apaga o aviso de quem publicou (o D552); a pergunta é anunciada (`role="alert"`) e
diz o efeito no aplicativo dos clientes -- o número chega a todos na próxima abertura, e, no major, que, se o aplicativo usa o
número para exigir atualização, ninguém com a versão de antes o abre até atualizar --; e o número publicado ganhou o recuo do
cartão. Por dentro, casos novos: as factories de produção (o gravador é um só e grava o que o `/api/versao/app` lê), duas abas
ao mesmo tempo (um segundo processo publica dentro da trava), o `antes: null` com a versão publicada, quem só lê pela linha de
privilégio do banco e a `SessaoAcl` de verdade, a tela antiga com o gravador falhando, o autor do Push e o `status[]`, e a
gravação que só normaliza a escrita; os testes limpam a sessão. Caracterização da fatia 54 em cada banco (803 com os vizinhos),
front 2.514, 24 mutantes novos vermelhos (15 do servidor e 9 do front). Nenhum esquema mudou, e não há ensaio HTTP novo.

**No navegador** (20 e 21/10/2026), contra o banco de contrato, com um administrador temporário, na largura do painel e a 375 px:
Configurações › Aplicativo › Versão abre a tela nova com a publicada (2.0.8) e "Sem registro de quem mudou"; descer o patch para 7
disse antes "O número desce: a tela pede confirmação"; um duplo clique de verdade no "Publicar 2.0.7" levou só à pergunta, num
alerta com o efeito para os clientes, com o foco no "Publicar 2.0.7 mesmo assim", e o `/api/versao/app` seguiu em 2.0.8; um clique
publicou 2.0.7, com "Alterada por Administrador Verificacao", e o `/api/versao/app` passou a 2.0.7; subir para 2.0.8 publicou sem
perguntar. A 375 px, o número fica alinhado com o cartão. O `public/app.json` terminou byte a byte igual ao de antes (o mesmo sha1),
e o histórico criado pelo teste (`data/aplicativo/`) foi apagado. Nenhum defeito.

## 21/10/2026 -- SORTEIOS-LEITURA

A fatia 24 da fila das telas que faltam: a F0 e a F1 de `docs/f3-telas-restantes/tela-sorteios.md` (a leitura
no servidor e a tela só lendo), na vez de Sorteios na ordem das telas (a resposta 36 (c)); o cadastro espera a
F3 (a resposta 1 (c)), e o sortear, o informar e o cancelar são a fatia 25.

**O que muda para quem usa.** Marketing › Sorteios abre a tela nova, e não mais o aviso "ainda roda na versão
antiga". A lista mostra cada sorteio com a janela, um selo -- agendado, recebendo cupons, falta sortear,
sorteado, cancelado -- e a frase dele ("encerrado há 371 dias, falta sortear"), quantas vendas ativas de
quantos clientes participam (cada venda conta uma vez: o cupom em dobro e o de venda cancelada ficam de fora,
e a coluna antiga os contava) e o ganhador; busca por nome e por situação, sem os cancelados por padrão. O
sorteio aberto mostra o prêmio ("sem descrição do prêmio" no Dia das Crianças de 2025), os números e os que não
contam, a tabela por unidade, o ganhador -- com telefone e e-mail só a quem escreve e é administrador -- e
"como funciona"; o sorteio criado depois do início avisa que as vendas de antes não ganharam cupom ("Criado 3
dias depois do início, em 04/10/2023 às 23:52: ..."). Abrir a tela nova não grava nada: a lista antiga passava
todo sorteio vencido a "Aguardando Sorteio" ao ser aberta. A tela nova ainda não sorteia, não informa o
ganhador, não cancela nem cadastra; ao administrador que escreve, ela diz que os três primeiros vêm na próxima
etapa e o cadastrar depois, e que na lista antiga o sortear e o informar ainda mandam um aviso automático ao
ganhador -- sem link para ela (o cadastrar o dono adiou para a tela nova, e o aviso automático ele tirou). A
lista antiga continua no ar pelo endereço.

**No aplicativo dos clientes:** o sorteio vencido que ninguém passou a "Aguardando Sorteio" aparece assim mesmo
"Aguardando Sorteio" nas duas rotas (`/api/sorteios/selecionarCupons/:id` e `/api/v4/clientes/:id/sorteios`),
sem gravar nada; a forma da resposta não mudou (o contrato, 74 de 74, com os snapshots iguais).

**Débitos:** o D334 e o D371 parciais (resta o sortear aceitar o vencido, na fatia 25; e a regra do início, na
F3). Nenhum débito novo, nenhum esquema mudou, e nenhuma pergunta nova ao dono.

**Provado:** a caracterização da fatia nos dois bancos (26 casos novos; 265 com os vizinhos, em cada banco), o
contrato 74 de 74, o ensaio de permissão com as duas rotas novas (403), o front 2.543 (o `tsc`, o `oxlint` e o
build), 44 mutantes vermelhos e os 66 portões. O ciclo e o navegador ficam com o orquestrador.

**A verificação da fatia** confirmou 13 achados, todos tratados: a frase ao administrador deixou de mandá-lo à
lista antiga (o cadastrar de lá contrariava a resposta 1 (c), e o sortear de lá põe o aviso automático que a
217 (c) tirou); o filtro de situação e o vencimento da lista antiga passaram pela própria `RegraDoSorteio`, e
não pela regra reescrita em SQL, com o sorteio que começa hoje no cenário; a API e as duas rotas do aplicativo
leem o "hoje" de um relógio que o caso fixa (nulo é o do servidor), e o caso já não atravessa a meia-noite; o
caso lê a funcionalidade do construtor; o "atualizar" do detalhe não repete o 403 nem o 404; e os casos que
faltavam (o vazio, o "Atualizando", o "Abrindo", as falhas de página e de busca, o 403, o 404 e o 500 do
detalhe, o nome e o `sorteadoEm`, o `max(0)`), mais a célula do Console na prévia, que abre agora Notificações.
Com eles: a caracterização da fatia 30 de 30 em cada banco (329 com os vizinhos), o contrato 74 de 74 com os
snapshots iguais, o front 2.551, e 25 mutantes novos vermelhos (13 do servidor e 12 do front).

**No navegador** (21/10/2026), contra o banco de contrato, com um administrador temporário, na largura do painel e a 375 px:
Marketing › Sorteios abre a tela nova com a frase de que ela ainda só lê e de que a lista antiga avisa o ganhador sozinha, sem convite
a cadastrar nem a sortear lá (as respostas 1 c e 217 c); a lista mostra os dois sorteios das fixtures como "sorteado", com a data e a
participação; o filtro "recebendo cupons" disse "Nenhum sorteio com esta busca."; o sorteio aberto mostra o prêmio, a janela, a
situação, os números (o cupom de venda cancelada fora, dito em "Não contam") e "Nenhuma venda ativa com cupom." por unidade, com o
foco no título; a 375 px, nada passa da largura. Nenhum defeito.


## 21/10/2026 -- SORTEIOS-SORTEAR

A fatia 25 da fila das telas que faltam (a F2 de `docs/f3-telas-restantes/tela-sorteios.md`, seção 6).

**Para quem usa:** no sorteio aberto da tela nova (Marketing › Sorteios), o administrador que escreve tem "O que
fazer": **sortear** (o sistema escolhe entre as vendas ativas, uma chance por venda -- a resposta 216 (a)), **informar
o ganhador** sorteado fora do sistema (o código da venda), **cancelar** o que não tem cupom de venda ativa e **baixar a
planilha** dos participantes (uma linha por venda ativa, com o nome, o e-mail e o telefone -- a P6 (a)). Sortear e
cancelar perguntam no próprio botão (entre quantas vendas e clientes, e o que o aplicativo passa a mostrar; que o
cancelado some do aplicativo), e informar também (a venda que vai, e que não se desfaz); o duplo clique e o Enter
segurado não confirmam. O sorteio que terminou se sorteia mesmo sem ninguém ter aberto a lista antiga (o D334).
**Nada avisa o ganhador sozinho** (a resposta 217 (c)): logo depois de sortear ou informar, a tela mostra o nome, o
telefone e o e-mail dele e diz que o contato é à mão; a lista antiga também deixou de mandar o WhatsApp/push, e a
mensagem dela mostra o telefone e o e-mail. Na
lista antiga, a coluna passou a "Vendas ativas", o "Exportar" só aparece a quem escreve, e a planilha é a mesma.
Cadastrar e alterar continuam para depois (a F3). **Nenhum sorteio real foi sorteado, informado nem cancelado:** o
#4 é do dono, na produção, depois do corte.

**O servidor:** `POST /adm/api/sorteios/:id/sorteio`, `/ganhador` e `/cancelamento`, e `GET /:id/participantes`
(o XLSX, a regra 9 de `docs/convencao-api.md`), só a quem escreve e é administrador; o `SorteioService` sob trava
(o DS-6), com o sorteador injetável; a `RegraDoSorteio` com os motivos (409 de estado, 422 de dado, em
`detalhes.motivo`). A tela antiga passa pelo mesmo serviço. O ensaio novo `scripts/ensaiar-sorteios-adm-api.php`
(no CI, no `medir-telas-no-postgres.sh` e na lacuna do CI).

**Débitos:** o D334 PARCIAL (o GET da lista antiga ainda grava, agora sob a trava); o D553 a D558 (os DS-2, 5, 6, 7 e
11 do desenho, e o aviso automático da 217 (c)) registrados e corrigidos; e, da verificação, o D559 (abrir a lista
antiga podia desfazer um sorteio recém-feito) e o D560 (o sorteio cancelado podia ganhar cupom de uma venda finalizada
no mesmo instante) registrados e corrigidos -- os dois passam pela trava das escritas. Nenhum esquema mudou, e nenhuma
pergunta nova ao dono.

**A verificação** confirmou 16 achados: 15 tratados (os dois escritores fora da trava, o contato na mensagem da tela
antiga, o autor dela com caso, a pergunta do informar, o efeito dito no sortear, o 409 e a recusa do informar ditos, o
informar travado durante o download, o 422 dos números que relê, os zeros à esquerda, os casos do Enter segurado, e os
textos do desenho e dos comentários que as respostas 216 e 217 derrubaram) e um que não procedia (a planilha da API pede
ser administrador porque o desenho já o dizia). 8 mutantes novos do servidor (os cinco da trava também no PostgreSQL) e 12
do front, vermelhos cada um sozinho. Depois da correção: a caracterização inteira, 4.316 em cada banco; o ensaio dos
sorteios, 18 de 18 no MySQL e no PostgreSQL de contrato; o front, 2.578 (os da tela também com
`AMPLIFICAR_EFEITOS_MS=50`), o build e o `oxlint`; e o `docs/inventario-acoes.csv` regerado (a lista antiga chama o
`passarAAguardando`).

**Provado:** a caracterização da fatia nos dois bancos, em série (33 casos novos; 74 com os de sorteio, e 483 com os
vizinhos do marketing, do aplicativo, da V4 e das etiquetas, em cada banco); o ensaio novo pelo HTTP, 18 de 18 no
MySQL e no PostgreSQL de contrato, com a `push_app` sem linha nova em cada caso; o ensaio de permissão, 93 de 93 nos
dois, com as quatro rotas novas em 403; o front, 2.570 (os da tela também com `AMPLIFICAR_EFEITOS_MS=50`), o build e
o `oxlint`; os 67 portões do `conferir-tudo.sh` (o único vermelho é o conhecido do D50); e 38 mutantes vermelhos,
cada um sozinho -- 23 do servidor e 15 do front. O ciclo inteiro, o contrato e o
navegador ficam com o orquestrador.

**No navegador** (o orquestrador, contra o banco de contrato, com um administrador temporário e quatro sorteios de
teste da faixa 90026601-90026604, nenhum dos reais; a 1.280 px e a 337 px): o sortear pergunta no próprio botão e diz o
efeito no aplicativo; o duplo clique abre a pergunta e não envia; o confirmar sorteia uma venda ativa (e não a
cancelada do mesmo sorteio), mostra o contato a quem sorteou com "o contato é feito à mão" e leva o foco ao título; o
informar pergunta antes, recusa no campo a venda de outro sorteio ("Esta venda não tem cupom neste sorteio.", com o
foco nele) e informa a certa; o cancelar do sorteio sem cupom diz que ele saiu do aplicativo; a planilha sai em XLSX
pelo `/participantes`; o "voltar" devolve o foco à linha; e a lista antiga mostra a coluna de vendas ativas, o
"Exportar" a quem escreve e a mensagem do Sorteio Interno com o telefone, o e-mail e o "não avisa". A `push_app` ficou
sem linha nenhuma depois das quatro escritas. **Um defeito, consertado na hora:** no cartão "O que fazer", os botões e
a pergunta encostavam na borda do cartão (o "confirmar o cancelamento" na esquerda e no fundo), porque o `.cartao` não
tem padding; ganharam o recuo de 0.93rem do título e das notas (`App.css`, `.sorteio-botoes-do-cartao` e
`.sorteio-pergunta`, o molde dos cupons). É CSS, que chega vazio no vitest: só o navegador o prova. O banco de
contrato foi remontado depois.

## 21/10/2026 -- Quanto falta e o ritmo, refeitos depois da fatia 25

O dono pediu: "atualize quanto falta e o ritmo". Pela mesma regua de 16/10 (linhas de codigo com os testes, sem documentos,
dumps, carga, fixtures, lockfiles nem o inventario gerado, ate o fim do Corte): **cerca de 55% feito, entre 52% e 57%**
(era cerca de 50% em 16/10).

| Parte | 16/10 | 21/10 |
|---|---:|---:|
| Feito desde a importacao (15/08) | ~352 mil | ~437 mil |
| Falta: as fatias da fila (eram 86; hoje 69 dos 101 blocos) | ~250 mil | ~230 a 260 mil |
| Falta: os debitos de pe fora da fila (eram 113 dos 134; hoje 130 dos 141) | ~80 a 110 mil | ~90 a 125 mil |
| Falta: o preparo do corte (os 13 itens antes da janela da etapa 1) | ~10 a 20 mil | ~10 a 20 mil |
| Falta, no total | ~340 a 380 mil | ~330 a 405 mil |

**Por que a fila quase nao encolheu.** 23 fatias em cinco dias, mas as 32 feitas custaram 93 mil linhas contra 53 mil
estimadas, 1,76 vez (1,46 em 16/10; 1,95 nas dez ultimas); corrigida pela razao nova, a fila que sobra pesa quase o que
pesava. Os relatorios sao 35 das 69 que faltam, cerca de 130 mil linhas, e nenhum foi feito para calibrar.

**Outras reguas.** No menu, 22 dos 51 destinos abrem a tela nova, 7 a mais que em 16/10 (Unidades, Grupos, Privilegios,
Etiquetas, Banners, Versao do aplicativo e Sorteios); o "20 de 48" de 16/10 era outra contagem, e pela de hoje eram 15.
Nos debitos, 417 dos 558 nao estao mais de pe (74,7%).

**O ritmo** (16/10 14h30 a 21/10 12h52, 118 horas de relogio, com as pausas do dono): 23 fatias e 26 commits; 4,2 h
entre uma fatia e a seguinte (mediana; era ~3 h), 5,1 h por fatia em media com as pausas (4,5 h sem a de 18,6 h depois da
fatia 17); ~670 linhas por hora de relogio (a estimativa usava 500). As fatias ficaram maiores (~3.400 linhas reais, contra
~2.000 das 11 primeiras), cada uma passa por cinco lentes, e o ciclo inteiro leva uns 100 minutos (eram uns 45).

**Quanto tempo: de 3 a 8 semanas.** No ritmo destes cinco dias, 3 a 4 semanas (perto de 10 a 17/11; em 16/10, 15 a 25/11);
com pausas maiores, as perguntas e o limite de uso da conta, 5 a 8 semanas (fim de novembro a meados de dezembro). As horas:
as 69 fatias, 290 a 350 h; os debitos fora da fila, 135 a 185 h; as respostas que mudam codigo, 40 a 60 h; o preparo do
corte, 15 a 30 h -- cerca de 480 a 630 h. Fora da conta: as janelas do corte na producao, que dependem do dono, e os
relatorios, se forem mais lentos que as telas.

Medido por um roteiro de leitura, fora do repositorio: a fila pelos blocos de `telas-fila-corrigida.md`, as fatias
feitas pelos commits que as citam, os debitos de pe pela regra do `scripts/contar-debitos.php` (141, conferido contra ele)
cruzados com os citados nas fatias que faltam.

## 21/10/2026 -- SORTEIOS-CADASTRO

A fatia 26 da fila das telas que faltam (a F3 de `docs/f3-telas-restantes/tela-sorteios.md`, seção 6), com o pedido do
dono nesta tarefa: "implemente a opção de gerar um cupom por valor, por serviço" -- "o cadastro escolhe a regra" e
"trocar a regra do banco" (o D561).

**Para quem usa:** em Marketing › Sorteios, o administrador que escreve tem o **"cadastrar sorteio"** e, no sorteio
aberto, o **"alterar"** (até ele ser sorteado ou cancelado). O cadastro escolhe **como a venda ganha cupom**: 1 por venda
(a de sempre, e a de todos os sorteios que existem), **1 a cada R$ X do valor da venda** (a soma dos itens depois dos
descontos -- o débito antigo quitado na venda não conta; R$ 120,00 a cada R$ 50,00 dá 2 cupons, e abaixo de R$ 50,00 nenhum) ou **1 por serviço lançado na venda** (pela
quantidade; os produtos não contam). Cada cupom é uma chance no sorteio. Depois do primeiro cupom, só o nome e o prêmio
mudam -- a janela e a regra aparecem como texto, com o porquê. O formulário avisa que as vendas finalizadas antes do
cadastro não ganham cupom. O sorteio aberto diz a regra, os cupons que contam e os de cada unidade; a planilha ganhou a
coluna "Cupons"; e o aplicativo mostra ao cliente quantos cupons ele tem (nos sorteios de sempre, o mesmo de hoje). A
tela antiga cadastra só "1 por venda", grava só os campos do formulário (o ganhador, o estado e o dia do sorteio que
viessem no POST eram gravados), mostra a regra de cada sorteio na lista, e o "editar" dela, que levava a lugar nenhum,
passou a existir -- em todo sorteio aberto, só ao administrador, e sem desfazer o que outra tela gravou no meio. Nenhum sorteio real foi
alterado além da limpeza dos 4 cupons repetidos (já autorizada) e do número 1 nos cupons de antes.

**O banco:** `sorteios.regra_cupom`, `sorteios.valor_cupom` e `sorteios_vendas.numero` (só acrescentam:
`scripts/migracao-sorteios-regra-do-cupom.sql`, no laço do passo 0 e nas duas cópias locais), e a chave única passou a
(sorteio, venda, numero) -- nas cópias já, na produção no passo 3 (`migracao-sorteios-vendas-unicos.sql`, ensaiada em
`ensaiar-sql-do-passo-3.php`). A finalização dá só os cupons que faltam: a venda finalizada duas vezes não ganha o dobro.

**Débitos:** o D561 (a regra do cupom) PARCIAL -- a produção espera o passo 0 e o passo 3; o D562 (o POST cru do
cadastro, o DS-4) e o D563 (o `update` morto, o DS-12) registrados e corrigidos; da verificação, o D564 (o débito quitado
que dava cupom duas vezes) e o D565 (o editar antigo que desfazia calado o que a tela nova gravou), registrados e
corrigidos; o D370 PARCIAL (a chave nova nas cópias), o D371 PARCIAL (o aviso antes de gravar) e o D105 PARCIAL (o editar
dos sorteios). **Cinco perguntas ao lote:** a venda abaixo de R$ X; a sessão de pacote na regra por serviço; se o sorteio
pode começar antes do cadastro (a P5 do desenho, que fica como hoje, com o aviso); se a permuta e o crédito do programa de
fidelidade contam no valor (contam, até a resposta); e se o Lava VIP conta como serviço (não conta, até a resposta).

**Provado:** 28 mutantes do servidor e 15 do front, vermelhos cada um sozinho (as três guardas do duplo envio do cadastro
são redundantes: cada uma sozinha fica verde; a do envio no ar com a do invólucro, ou as três, ficam vermelhas); a
caracterização da fatia e dos vizinhos (os sorteios, o aplicativo, a V4, o marketing, o finalizar, a fidelidade, o PDV e a
comanda), 656 em cada banco; o contrato, 74 de 74 nos dois, com os retratos `*sorteios*` iguais; o ensaio dos sorteios, 33
de 33 no MySQL e no PostgreSQL de contrato, e o de permissão da `/adm/api` nos dois; o ensaio do passo 3; os 65 portões
(com a cobertura por par, o `schema-tool`, as rotas sem login, os links mortos e a unicidade do schema rebaseados, cada um
com o porquê); o front, 2.607 casos (os da tela também com `AMPLIFICAR_EFEITOS_MS=50`), o build e o `oxlint`; e o
`docs/inventario-acoes.csv` regerado. A caracterização inteira e o navegador ficam com o ciclo.

**A verificação em cinco lentes** confirmou 19 achados, todos tratados: dois defeitos consertados (o D564, o débito
quitado que dava cupom duas vezes, e o D565, o editar antigo que desfazia calado o que a tela nova gravou); duas escolhas
feitas sem o dono que foram ao lote (a permuta e o crédito do programa no valor, e o Lava VIP no serviço); a guarda do
administrador no novo e no editar antigos; a regra na lista antiga e o editar em todo sorteio aberto; a coluna "Cupons"
provada no XLSX; o banco sem as colunas com casos; e, na tela, o foco do 422, os campos travados no envio, o "como
funciona" sem contradição, o exemplo do valor formatado, a nota da planilha, as frases presas ao servidor por um arquivo de
casos e o 409 que diz o que mudou. A verificação achou também o `ColunasForaDoMapeamentoTest` vermelho (as três colunas
novas fora da lista dele, fora do filtro da primeira rodada), consertado. 35 mutantes novos (21 do servidor e 14 do
front), todos vermelhos. Provado depois das últimas mudanças: a caracterização INTEIRA, 4.361 em cada banco (11 pulados);
o contrato, 74 de 74 nos dois; o ensaio dos sorteios, 33 de 33 nos dois, e o de permissão da `/adm/api` nos dois; os 65
portões (a cobertura por par rebaseada de 62 para 63 no ISMarketing: a lista antiga lê a regra); o front, 2.614 casos, o
build e o `oxlint`.

**No navegador** (o orquestrador, contra o banco de contrato, com um administrador temporário; a pane mudou de largura no meio, e a
tela foi vista a 475 px e a 1.280 px): o "cadastrar sorteio" com o foco no nome; a regra por valor que pede o valor, recusa no
campo o vazio ("Obrigatório na regra por valor: de R$ 1,00 a R$ 99.999,99.", com o foco nele) e cadastra "1 cupom a cada R$ 50,00
do valor da venda", com o aviso da criação no próprio dia e o foco no título; o "alterar" que troca a regra para "por serviço" sem
cupom; e, com um cupom posto à mão no sorteio de teste, as datas e a regra fixas com o motivo. Na lista antiga, a regra sob o nome
de cada sorteio; o editar antigo grava o nome. **Um defeito, consertado na hora (o D566):** o editar antigo recusava mudar as
datas de quem tem cupom sem frase nenhuma na página -- a recusa ia ao espaço de erro do flashMessenger, que a view não lê; agora
vai à página, e o caso que só olhava o flashMessenger passou a desenhar a view. O banco de contrato foi remontado depois.

**As respostas 244 a 248 do dono** (21/10/2026, lidas antes do commit): todas "como está" -- a venda abaixo de R$ X não ganha cupom
(244 a), a sessão de pacote conta como serviço (245 a), o sorteio pode começar antes do cadastro, com o aviso (246 c), o valor
dos itens conta inteiro, pago como for (247 a), e o Lava VIP não conta (248 a). Nenhuma linha de código mudou: o D371 fecha como
DECIDIDO, e o D561 fica PREPARADO, esperando as colunas no passo 0 e a chave no passo 3 da janela do corte.

## 22/10/2026 -- ARQUIVOS-SERVIDOR

A fatia 27 da fila das telas que faltam (a F0 e a F1 de `docs/f3-telas-restantes/tela-arquivos.md`, seção 3), só o
servidor; a biblioteca na tela é a fatia 28, e o cadastro, a 29. Pela resposta 135 (a) do dono, os arquivos continuam
abertos a todos que têm "Arquivos exibir", sem recorte.

**Para quem usa:** o endereço direto de um arquivo da biblioteca (`/arquivos/<nome>`), que abria sem login, **fecha**:
o nginx local responde 404, e o arquivo só sai pela tela, com sessão e com o privilégio -- o "abrir" e o "baixar" de
sempre, e a `/adm/api/arquivos/:id/conteudo` da tela nova. Desativar um arquivo agora o tira do ar. Na tela antiga: o
"Inativo" do formulário desativa (o D567); o envio de vários é tudo ou nada e diz qual arquivo falhou, e a troca só
apaga o arquivo antigo depois de gravar (o D568); os filtros e a paginação não dão mais erro com letra no lugar de
número (a linha dos Arquivos do D361); o nome e a descrição vão até 255 caracteres, e não 100 (o D570); e o arquivo de
uma categoria inativa some da biblioteca, como na API. Todo pedido do ERP deixou de ler as categorias quando não desenha
o menu (o D569): medido, `GET /adm/api/sessao` fazia 3 consultas e faz 0; o `/api/paises/selecionar` do aplicativo, 4
e 1. O nginx da produção e do BVIP não foi tocado: o da produção está no vhost da etapa 1, e o do BVIP chega com a
próxima entrega, porque lá quem serve `/arquivos/` é o nginx da pilha, com o `default.conf` deste repositório (o D355
fica PREPARADO).

**A API nova** (`docs/f2-fatia-arquivos.md`; a regra 10 de `docs/convencao-api.md`): a lista, com 422 no filtro que
não serve; as categorias, com as seções e a descrição que se repete uma vez; e o conteúdo, pela mesma entrega do ERP
(`EntregaDoArquivo`). O inativo só a quem escreve e é administrador. O Dnovo-3 do desenho já estava feito (o D199).

**Provado:** os casos da fatia e os vizinhos (as sessões, os logins, a personificação, os privilégios, os menus, os
banners, as etiquetas e os sorteios), 1.040 em cada banco, sem vermelho; os ensaios de arquivos (14 de 14, com três
arquivos postos no disco e apagados, o diretório com os 110 de antes), de permissão (96 de 96) e de recusas (234 de
234, os casos 179 a 186 novos), no MySQL, e os de arquivos e de permissão também no PostgreSQL; o contrato do aplicativo, 74 de 74; 36 mutantes vermelhos (34 em processo
no MySQL e dois no PostgreSQL, com o 22P02) e três do portão do webroot; um sobreviveu e virou simplificação (o
`JsonModel` já é terminal). Os portões que a
fatia toca: o webroot (com a pergunta 4), as rotas sem login, a ACL da API, a injeção de DQL, os booleanos, as remoções
por POST, os links mortos, a cobertura por par (rebaseada de 352 para 354 pares), as referências dos docs, o relógio, os
segredos e os débitos; e o `conferir-tudo.sh` inteiro, 65 portões verdes e o vermelho conhecido do D50 (com a marca das
fixtures regravada, o `inventario-acoes.csv` regerado e a consulta da lista da API anotada no `auditar-group-by.php`). O ensaio novo entrou no CI, no `conferir-lacuna-do-ci.sh` e no `medir-telas-no-postgres.sh`.

**A verificação da fatia** (12 achados confirmados, todos verdadeiros e tratados): o `/arquivos/` fechado no nginx local
reabria em maiúsculas -- `/ARQUIVOS/<nome>` dava 200 `application/pdf` sem sessão, porque o `^~` compara a caixa e o disco
do Windows não --, e o bloco virou `location ~* ^/arquivos/`, a primeira regex (recarregado com `nginx -t` e `nginx -s
reload`; as nove variantes medidas dão 404); o `php -S` do CI deixava passar `/ARQUIVOS/` e `/./arquivos/`, e agora recusa
pelo `realpath`. O "abrir" e o "baixar" do ERP passaram a abrir o inativo e o de categoria inativa para o administrador que
cadastra, como a API (o D571), e o id e a categoria crus das telas antigas de Arquivos, 500 no PostgreSQL, viraram 404 e a
frase (o D572). O D355 dizia "o BVIP no passo 8"; lá quem serve `/arquivos/` é o nginx da pilha, com o `default.conf`
daqui, e o bloco chega na próxima entrega -- o passo de conferir está no passo 8 da etapa 1. E entraram os casos que
faltavam: o D567 no link e no lote, a edição sem arquivo novo, a ordem do link, a categoria que se cria, o `99999999999`
nas duas portas. **Provado:** a fatia e os vizinhos diretos, 120 casos em cada banco; os ensaios de arquivos (24 de 24),
de permissão (96 de 96) e de recusas (235 de 235, o 187 novo), nos DOIS bancos; 12 mutantes vermelhos (três só no
PostgreSQL, com o "out of range for type integer") e três do portão do webroot; o `conferir-tudo.sh` com os 65 portões
verdes e o vermelho conhecido do D50 (os débitos repetidos com os dois pares novos, e o inventário regerado).

**No navegador** (o orquestrador, contra o banco de contrato, com um administrador temporário e uma cópia de um PDF da cópia de
desenvolvimento com nome próprio, apagada depois -- `public/arquivos` voltou a 110): o `/arquivos/<nome>` dá 404 sem sessão e com
sessão, em minúsculas, em maiúsculas e misturado; a `/adm/api/arquivos` lista o arquivo e o `/conteudo` o entrega (200,
`application/pdf`, `nosniff`, `no-store`, o nome original limpo); o id acima do int4 dá 404; na lista antiga do cadastro, o "Abrir"
e o "Baixar" dão 200; e o "Inativo" do formulário tira o arquivo do ar sem apagá-lo do disco (o D567). **Um defeito, consertado na
hora (o D573):** o editar dizia "Inserido com sucesso."; agora diz "Editado com sucesso.", com caso nos dois bancos e o mutante
vermelho. Já era assim antes da fatia.

## 22/10/2026 -- ARQUIVOS-BIBLIOTECA

A fatia 28 da fila das telas que faltam (a F2 de `docs/f3-telas-restantes/tela-arquivos.md`), só o front: a API é a da
fatia 27, sem mudança.

**Para quem usa.** "Arquivos", no menu, abre a biblioteca da franqueadora na tela nova. O grupo do menu, que no ERP tinha um
item por categoria, virou um link só, e as categorias são as abas da tela, cada uma com quantos arquivos tem. Dentro da
categoria, as subcategorias são seções com o total de cada uma, e a descrição que se repetia em todos os arquivos de uma
subcategoria (os Materiais) aparece uma vez, sob o título; nos Manuais, cada documento tem a sua, sob o nome. Cada arquivo diz o
tipo ("PDF", "Planilha"), e tem "abrir" (o PDF, em aba nova) e "baixar", sempre pela API com sessão -- nunca pelo endereço
`/arquivos/<nome>`, que a fatia 27 fechou. O "baixar" que falha diz qual arquivo e por quê, na linha dele. A busca é uma só,
pelo nome, e vale em todas as categorias. Pela resposta 135 (a), quem tem o privilégio de ler vê tudo o que é ativo de
categoria ativa. O cadastro, em Configurações › Arquivos, continua na versão antiga até a fatia 29. Os destinos do menu que
caem na tela não migrada descem de 22 para 21; o menu segue com 15 grupos e 51 destinos.

**Por dentro.** `api/arquivos.ts`, `bibliotecaDosArquivos.ts` (a regra pura), `Arquivos.tsx` e `ArquivosLigado.tsx`, o destino no
`Console.tsx` e o `menu-do-erp.ts`. O `cliente.ts` passou a ler o nome do arquivo baixado do `filename*` do `Content-Disposition`
("Plano de implantação.pdf", e não "Plano de implanta__o.pdf"), e o dublê da API ganhou os cabeçalhos. A prévia nova no
Claude Design (`frontend/.design-sync/previews/Arquivos.tsx`, sete células). Débito novo: o **D574** (o "abrir" em aba nova com a
sessão caída mostra o JSON do 401). Nenhum esquema mudou, e nenhuma pergunta nova ao dono.

**Provado:** o front, 2.668 casos (eram 2.614), o build e o lint, e os casos de efeito com `AMPLIFICAR_EFEITOS_MS=50`; 27
mutantes, todos vermelhos (a recusa do "baixar" fora da linha dele, a descrição sempre na seção, sempre no arquivo, a aba sem a contagem, a busca que não volta à página
1, o vazio da busca, o tamanho da página cravado, a busca que manda a aba, o abrir pelo endereço direto, as duas travas do
"baixar", o nome do `filename*`, o foco, as setas, o 403, a resposta atrasada, o Console e o menu); os casos vizinhos do
servidor (os de arquivos e os que leem o `menu-do-erp.ts`), 251 em cada banco; e o `conferir-tudo.sh`, 65 portões verdes e o
vermelho conhecido do D50.

**No navegador**, contra o banco de contrato, com o gerente das fixtures, o privilégio de ler, uma segunda categoria e três
arquivos postos no disco e apagados depois (`public/arquivos` voltou a 110): as duas abas, as seções, a busca nas duas
categorias, o abrir (o PDF inline) e o baixar (200, com o nome acentuado no `filename*`), o baixar do arquivo que sumiu do disco
(a frase do 404), a 375 px no tema claro e a 1.280 px no escuro. **Três defeitos de tela, consertados na hora:** a aba escolhida
ficava com o texto em ouro sobre o fundo claro com o mouse em cima (o `button:hover` pesava mais); a frase do "baixar" que falhou,
em cima da lista, empurrava a lista para baixo do dedo -- agora vai na linha do arquivo; e o "baixando…" alargava o botão e
empurrava o "abrir". Na busca, a segunda categoria ganhou a linha que a separa da primeira.

**A verificação da fatia** (14 achados confirmados, todos verdadeiros e tratados). **Para quem usa:** na aba, a subcategoria
agora se escolhe -- "todas" e uma por seção, com a contagem --, como o campo "subcategoria" da tela antiga, que o desenho faz
navegação: com 50 por página, Materiais tem 3 páginas, e antes era preciso folhear para achar uma subcategoria. A API não mudou (o
filtro `?subcategoria=` já existia); o filtro exato contra a seção que junta sem a caixa é o **D576**, sem variante hoje. Na busca, a
seção deixou de dizer o total da categoria ("8 arquivos" sob "3 arquivos com «plano»"). Duas recusas do "baixar" ficam as duas, cada
uma na sua linha. "Configurações › Arquivos" virou **"Configurações › Cadastro de arquivos"**: o administrador tinha dois links
"Arquivos" para destinos diferentes. O tipo Link diz "link: não abre nesta tela" (a API não manda o endereço; 0 em uso), e o
**D575** espera a pergunta 5 do desenho, com a fatia 29. **Por dentro:** os casos que faltavam -- a aba e a subcategoria que voltam
à página 1, a falha atrasada da aba de antes, o foco do "tentar de novo" com o pedido no ar --, o nome acessível "baixando…", a nota
da busca como descrição do campo, o `aria-controls` só com o painel na tela e o painel rotulado pela aba escolhida; a prévia com os
números batendo (o Manual da busca é um dos 8) e a célula `UmaSubcategoria`; os comentários do menu com seis grupos com filhos.
**Provado:** o front, 2.683 casos (eram 2.668), o build e o lint, e os casos da biblioteca com
`AMPLIFICAR_EFEITOS_MS=50` (35 de 35); 24 mutantes novos, todos vermelhos, e os 27 da fatia de novo (25 aplicados como eram,
vermelhos; os dois da recusa fora da linha e o do vazio da busca, reescritos para o código novo, vermelhos); os casos do servidor
que leem os arquivos e o `menu-do-erp.ts`, 290 em cada banco; e os portões. Nenhum esquema mudou, a API não mudou, e nenhuma
pergunta nova ao dono (o Link espera a pergunta 5 do desenho, que a fila deixa para depois). Não foi ao navegador: a escolha da
subcategoria e o espaçamento dela no cartão (o recuo de 0.93rem) só o navegador prova.

**No navegador, de novo, depois da correção** (o orquestrador, contra o banco de contrato, com um administrador temporário, uma
segunda categoria e cinco cópias de arquivos da cópia de desenvolvimento com nomes próprios, apagadas depois -- `public/arquivos`
voltou a 110): a 375 px, as abas com a contagem, a escolha nova de subcategoria recuada dentro do cartão (que a correção pôs de
volta e ninguém tinha visto), o título "1 arquivo em Biblioteca de contrato › Gestão do Tempo", a aba dos Manuais sem título de
seção e com a descrição sob o nome, e a busca "plano" e "manual" em todas as categorias, com nenhuma aba escolhida; a 1.280 px no
tema escuro, sem rolagem lateral. O "abrir" é um link `_blank` com `noopener` para o `/conteudo?disposicao=inline`, que entrega o
PDF; o "baixar" pede o `disposicao=anexo`, que sai com o `filename*` do nome original, como a tela antiga; o arquivo que sumiu do
disco dá 404. Nenhum defeito. Uma busca pareceu travada em "Atualizando" por 14 s com o painel do navegador escondido; medida de
novo, a resposta levou 2,97 s e a tela a mostrou aos 3,06 s -- o painel, e não o código.

## 22/10/2026 -- ARQUIVOS-CADASTRO (a fatia 29 da fila das telas que faltam)

**Para quem usa:** "Configurações › Cadastro de arquivos" deixa a tela não migrada e abre a biblioteca nova no modo cadastro --
a mesma tela, com a situação (ativos ou desativados, e a aba conta os desativados dela), a data de envio e, em cada linha,
editar, trocar o arquivo, desativar e ativar. **Só o administrador escreve** (a decisão 1 do desenho): quem tem o privilégio sem
ser administrador lê, e a tela diz por quê. **"Enviar arquivos"**, o único botão de ouro, manda vários de uma vez: a categoria,
a subcategoria (com as que existem sugeridas, e a dica de quando a digitada vai como uma que já existe) e a descrição em comum, e
cada arquivo com o nome já preenchido pelo nome dele, sem a extensão, editável. O que não pode ir -- a extensão fora de PDF,
XLSX e XLSM, o tamanho acima do limite do servidor, o nome que não serve -- é dito na linha, antes de enviar. O envio é uma fila,
um arquivo de cada vez ("enviando 3 de 12"); a recusa de um fica na linha dele e a fila segue; ao fim, o resumo, e "tentar de
novo" manda só os que não entraram. **Desativar pergunta no próprio botão** e diz o efeito: some da biblioteca de quem lê, o
arquivo fica guardado e volta se ativar; ativar é direto. O nome fica como foi digitado: "PNL" não vira mais "Pnl", nem na tela
antiga.

**As respostas 249 a 252 (a) do dono** (foram hoje à página, e voltaram no mesmo dia): desativar guarda e não há
"excluir"; um nome por arquivo; as duas categorias fixas, e a subcategoria que só difere na caixa vira a existente -- nas duas
portas, o que fecha o **D576**; o tipo Link sai da tela nova (a antiga continua oferecendo: o **D575** fica DECIDIDO, sem código).
A **253 voltou em (c)** -- um registro por pessoa de quem abre e de quem baixa --, e é a fatia 29b: nesta, nada se registra. Débito novo: o **D577** (o item do menu e a leitura da tela pedem privilégios distintos; hoje
ninguém fica no meio).

**Por dentro:** a API de escrita (`POST /adm/api/arquivos` em multipart, um arquivo por pedido; `PUT /:id` campo a campo; `POST
/:id/arquivo`, a troca; `/desativacao` e `/ativacao`, com o 409 sob a trava da linha), o tipo pelo conteúdo (`%PDF-`, o ZIP da
planilha), o nome físico aleatório, a ordem guarda-o-novo, grava-a-linha, apaga-o-antigo, e o teto em 422 no campo do arquivo; a
regra dos campos com os casos compartilhados entre a tela e o servidor (`formularioDoArquivo.casos.json`); a prévia com as células
do cadastro. Nenhum esquema mudou.

**Provado:** a caracterização inteira, 4.429 casos em cada banco (eram 4.405), e os casos dos arquivos de novo depois do último
ajuste, 84 em cada banco; o front, 2.718 casos (eram 2.683), o build e o lint, e os do cadastro com `AMPLIFICAR_EFEITOS_MS=50`;
40 mutantes, todos vermelhos (20 do servidor e 20 da tela); o `ensaiar-arquivos-adm-api.php` com o envio multipart de verdade,
32 de 32 no MySQL e no PostgreSQL, apagando o que guardou (`public/arquivos` voltou a 110); o `ensaiar-permissao-adm-api.php`,
101 de 101 nos dois bancos; os casos 179 a 191 do `ensaiar-recusas-adm-api.php`; e os portões. Não foi ao navegador.

**A verificação da fatia 29 (22/10/2026)** confirmou 22 achados, todos tratados. Os que mudam o que a fatia entrega: a tela
antiga também passou a exigir o administrador para escrever (o **D578**; o 403 da API se contornava pelo endereço direto); o nome
do arquivo enviado sai escapado no editar antigo (o **D579**); o editar antigo e o `PUT` conferem o que leram, com a linha
travada, e o formulário velho não desfaz mais calado o que a tela nova gravou (o **D580**); as trocas do conteúdo travam a linha,
e duas ao mesmo tempo não deixam órfão no disco (o **D581**); a porta antiga confere o envio pela regra 8 (o **D582**). Na tela, a
fila ocupa a largura do cartão, o aviso de cada escrita é ouvido, o "tirar" não perde o foco, e o "tentar de novo" só aparece
logo depois de uma rodada. O ensaio dos arquivos passou a achar e apagar também o que não rastreou, e falha se sobrar algo.
**O navegador (o portão da F4), contra o banco de contrato, com um administrador temporário (apagado depois) e a sessão dele
    posta no navegador:** "Configurações › Cadastro de arquivos" abre a tela no modo cadastro; o envio de verdade pelo `FormData`
    do navegador (os arquivos postos no campo `multiple` por um `DataTransfer`: o seletor do sistema não se alcança daqui), quatro
    PDFs e um `.docx` -- o `.docx` recusado na linha, tirado pelo teclado com o foco indo ao nome da linha seguinte; a dica "Vai
    como «Ferramentas de contrato»"; o duplo clique no "enviar" mandou 4 pedidos e não 8; "enviando 1 de 4" a "4 de 4", o texto
    com nome de PDF recusado pelo conteúdo, o resumo "3 enviados e 1 recusado." com o foco nele, o "tentar de novo" mandando só
    o recusado, e o "concluir" com o aviso e o foco no "enviar arquivos"; o editar (o aviso na mesma região viva do "Salvando o
    arquivo…", e "PNL" gravado como foi digitado), o 409 `mudou` com a linha mudada por baixo, a troca por uma planilha (o PDF de
    antes saiu do disco), o desativar que pergunta e diz o efeito, e o ativar na lista dos desativados; o editar antigo com o
    `alterado_em` oculto. A 1.280 px e a 375 px, medido no DOM: a fila na largura do cartão (sem o conserto, 232 de 965 px), sem
    rolagem lateral, e o rótulo de nome comprido quebrando; no tema escuro, as cores da pergunta e das linhas. **Dois defeitos
    novos, consertados na hora:** o "tirar" caía numa linha só dele, esticado na largura do cartão (a segunda coluna da linha
    tinha 0 px), e a 375 px a categoria, numa coluna só, cortava "Biblioteca de contrato" no `<select>`. `public/arquivos` voltou
    a 110, e o banco de contrato foi remontado. **Provado depois da verificação, em série:** a caracterização inteira, 4.440 casos em cada banco (eram 4.429), com os 11 novos
    nos arquivos; os ensaios `ensaiar-arquivos-adm-api` (32 de 32), `ensaiar-permissao-adm-api` (101 de 101) e
    `ensaiar-recusas-adm-api` (239 de 239, os casos 179 a 191 inclusive) no MySQL e no PostgreSQL; o front, 2.727 casos (eram
    2.718), os do cadastro também com `AMPLIFICAR_EFEITOS_MS=50`, o build e o lint (os 7 avisos de antes); o `conferir-tudo.sh`
    inteiro, 65 portões e o vermelho conhecido do D50. Mutantes da verificação, cada um sozinho: 20 do servidor e 12 da tela,
    todos vermelhos; e o ensaio contra o mutante que deixa o gerente cadastrar saiu 1, achando e apagando a linha que não
    rastreou, e com uma sobra plantada de "rodada anterior", apagou-a no começo e saiu verde.

**No navegador, pelo orquestrador, depois da correção** (contra o banco de contrato, com um administrador temporário; o arquivo
que a tela guardou foi apagado depois pelo nome, e `public/arquivos` voltou à lista de antes, 110): a 375 px, "Configurações ›
Cadastro de arquivos" abre a tela nova no modo cadastro, com as ações em cada linha; o envio com um PDF gerado na página e um
`.docx`: o nome preenchido sem a extensão, o `.docx` recusado na linha antes de enviar ("Só PDF, XLSX ou XLSM."), o "tirar" levando
o foco ao nome da linha que ficou, o botão dizendo "enviar o arquivo", o envio de um, o resumo "1 enviado." com o foco e o arquivo
no disco com o nome do servidor; o desativar que pergunta no próprio botão com o efeito ("Some da biblioteca de quem lê; o arquivo
fica guardado e volta se ativar."), a contagem da aba relida, e o ativar na vista dos desativados, com a frase; a 1.280 px, a fila
na largura inteira do formulário (934 de 1.025 px). Nenhum defeito.

## 22/10/2026 -- RESPOSTA-253-QUEM-ABRIU (a fatia 29b da fila das telas que faltam)

**Para quem usa:** pela resposta 253 (c) do dono ("um registro por pessoa: quem abriu, qual arquivo e quando"), cada vez que
alguém abre ou baixa um arquivo da franqueadora -- pela tela antiga ou pela nova -- o sistema anota quem foi, de que unidade,
se abriu ou baixou, e quando. Na personificação, a anotação diz os dois: o administrador que entrou e a pessoa que ele assumiu.
O que não abre (o arquivo que não existe, o que a pessoa não pode ver) não é anotado, e uma falha da anotação não impede o
arquivo de abrir. **No "Cadastro de arquivos", só o administrador** vê, em cada linha, quantas vezes o arquivo foi aberto ou
baixado, e o botão **"quem abriu"**, que mostra a lista -- quando, quem, a unidade, abriu ou baixou --, da mais nova para a
mais antiga, de 50 em 50, com o resumo ("Aberto ou baixado 5 vezes, por 3 pessoas."). O registro começou agora: o que foi
aberto antes dele não está em lugar nenhum, e a tela diz "desde que o registro começou". Quem só lê não vê nada disso. Onde o
banco ainda não tem a tabela (a base do teste do BVIP, a produção antes do passo 0), tudo funciona como antes e a tela diz que
o registro ainda não está ligado.

**O esquema, que só acrescenta** (a resposta 83 (a)): a tabela `arquivos_acessos` (`scripts/migracao-arquivos-acessos.sql`, a
décima quinta do laço do passo 0 do corte e do laço da cópia do ensaio geral), no `mysql.sql`, no `schema.sql` e na carga
(`migrar-dados.sh`) pelos geradores, e **nas duas cópias locais** (MySQL às 22:25, PostgreSQL às 22:26, as duas vezes sem
efeito na segunda, com a forma igual à dos bancos da suíte). **Achado no caminho, o D584:** a carga do corte não tinha sido
regerada na fatia 26 e não copiaria a regra do cupom de sorteio nem o número dos cupons; regerada agora. **A LGPD do registro é o
D583**, anotado como débito futuro pelo dono (o prazo de guarda, quem vê, o aviso aos funcionários), sem pergunta nova.

**Provado:** os casos dos arquivos e dos vizinhos nos dois bancos, em série (131 em cada um; o `QuemAbriuOArquivoTest`, novo, com 11 casos: a
linha nas duas portas, o 404 e a recusa que não gravam, a personificação, a falha que não impede a entrega -- de verdade, pela
chave estrangeira --, o banco sem a tabela, as guardas da consulta, a ordem, os nomes, a página, a contagem só ao
administrador, e os tipos da tela contra a lista e a consulta); o `conferir-tudo.sh`, 65 portões e o vermelho conhecido do D50 (o
`rotas sem login` ganhou a rota, medida com 401 sem cookie, e o `cobertura por par` subiu a base de 3 para 4, lida: o registro chega por
parâmetro, sem repositório); os ensaios `ensaiar-arquivos-adm-api` (39 de 39) e `ensaiar-permissao-adm-api` (102 de 102) no MySQL e no
PostgreSQL; o front, 2.736 casos (eram 2.727), os novos também com `AMPLIFICAR_EFEITOS_MS=50`, o build e o lint (os 7 avisos de
antes). Mutantes, cada um sozinho e devolvido pelo sha: 14 do servidor, todos vermelhos (o que tira o ponto de salvamento, só no
PostgreSQL, com o 25P02 -- no MySQL ele passa, como esperado), e 11 da tela, todos vermelhos; o portão do passo 0 sem a
migração no laço, vermelho. Nada foi aberto num navegador nesta fatia.

**A verificação (22 e 23/10/2026):** cinco lentes confirmaram 13 achados, todos tratados. O mais sério para quem usa: **a 375 px, a
tabela de quem abriu passava da largura** e escondia a coluna "abriu ou baixou" atrás da rolagem -- visto no navegador, consertado
(a data quebra). O painel ganhou o recuo que não tinha; a recusa passou a ser anunciada ao leitor de tela; a página que falha leva o
foco ao título; a contagem da linha acompanha o painel e o "baixar". **O log da falha não copia mais quem abriu** (o D583). **A troca do
arquivo guarda o novo antes de travar a linha** (o **D585**, PARCIAL: o editar antigo ainda trava antes), porque a entrega que grava
quem abriu esperava a troca terminar. O custo do registro na entrega, medido: de 1,2 a 4,2 ms a mais na mediana, contra 2,4 s do
pedido inteiro aqui. Provado: a caracterização inteira, 4.452 casos em cada banco (11 pulados); os ensaios `ensaiar-arquivos-adm-api` (39 de 39),
`ensaiar-permissao-adm-api` (102 de 102) e `ensaiar-recusas-adm-api` (239 de 239) nos dois bancos; o front, 2.740 casos (eram 2.736),
os novos também com `AMPLIFICAR_EFEITOS_MS=50`, o build e o lint (os 7 avisos de antes). Mutantes da correção, cada um sozinho e
devolvido pelo sha: 5 do servidor, vermelhos nos dois bancos, e 5 da tela, vermelhos.

**No navegador, pelo orquestrador, depois da correção** (contra o banco de contrato, com um administrador temporário e três
aberturas gravadas à mão no Manual de contrato, uma delas na personificação; o banco foi remontado depois): a 375 px, a linha diz
"aberto ou baixado 3 vezes" e a planilha "ninguém abriu ainda"; o "quem abriu" abre o painel com o foco no título, "Aberto ou
baixado 3 vezes, por 2 pessoas.", da mais nova para a mais antiga, e a personificação como "Administrador Verificacao, entrando
como Gerente Teste"; a tabela cabe no cartão (344 de 344 px), sem rolagem lateral, e o "voltar à lista" recuado da borda devolve o
foco ao "quem abriu" da linha. Nenhum defeito.

## 23/10/2026 -- WHATSAPP-ESTADO (a fatia 30 da fila das telas que faltam)

**O que muda para quem usa: ainda nada na tela** -- a tela nova do WhatsApp é a fatia 34. O servidor ganhou o
`GET /adm/api/whatsapp`, que diz em palavras como está o WhatsApp da unidade da sessão (a F1 de
`docs/f3-telas-restantes/tela-whatsapp.md`, pela resposta 136 a do dono): conectado, esperando a leitura do código,
iniciando, desconectado, sem chave, desligado ou indisponível -- e, quando indisponível, por quê (tempo esgotado, sem
conexão, chave recusada, instância inexistente, erro do provedor, estado desconhecido), sempre com a frase nossa e nunca o
texto do provedor. **Só lê:** nunca pede o `connect` (o D305), e a unidade fechada, desligada ou sem chave nem chega ao
provedor. O WhatsApp e a chave são os do banco, a cada pedido, e não a cópia da sessão (o D350, PARCIAL de novo). A
unidade fechada é "desligado" (as respostas 132 b e 178 c). Diz também quantos clientes ativos a unidade tem e o que a
pessoa pode fazer: gerar o código quem escreve; desconectar, só gerente ou administrador (a 136 a). Os dois clientes do
provedor ganharam um tempo limite opcional: a consulta da tela desiste em 8 s; o cron segue com os 30 s de sempre.

**Provado:** os 23 casos novos (`RegraDaConexaoWhatsAppTest`, 7, e `WhatsAppPelaApiTest`, 16, com o cliente de mentira --
nada saiu da máquina; os dois casos dos clientes de verdade falam com um socket do próprio teste em 127.0.0.1) e os
vizinhos, 428 casos em cada banco; 26 mutantes, cada um sozinho e devolvido, todos vermelhos (as guardas, o banco antes do
provedor, o `connect` no GET, o tempo limite nos dois clientes, o número do erro de rede, cada motivo, quem desconecta, a
sessão solta antes da rede, a instância no JSON, o alcance e a rota só GET); os portões que a fatia toca (ACL da API,
rotas órfãs, rotas sem login -- a rota medida com 401 sem cookie --, recorte por unidade, guarda do ERP, tempo limite de
rede, relógio nos testes, referências dos documentos, barreira de origem, cobertura por par, links mortos, remoção por POST,
injeção de DQL, inventário, segredos, lacuna do CI, débitos repetidos), todos com 0; o `ensaiar-permissao-adm-api` com a rota
nova, 103 de 103 recusas, no MySQL e no PostgreSQL de contrato, depois do `conferir-provedores-no-loopback.php --conteiner`
verde. O front não mudou: o vitest inteiro deu 2.739 de 2.740, e o que bateu os 5 s (um caso do cadastro de usuários,
arquivo não tocado) passa sozinho, 24 de 24. Nada foi aberto num navegador: não há tela.

**Depois da verificação (23/10/2026, cinco lentes; 12 achados confirmados, todos tratados):** a lista de estados de cada
provedor foi conferida de fato na documentação e no código público deles, sem chamá-los, e o `connecting` do Evolution
passou de "esperando a leitura" a "iniciando" -- é o estado de toda conexão nova, a reconexão sozinha de uma instância já
pareada também, e a consulta não sabe se alguém gerou o código; o pareamento por chave de acesso do WAHA fica, de propósito,
como estado desconhecido. A tela antiga do WhatsApp passou a tratar a unidade fechada como desligada, como a API nova (as
respostas 132 b e 178 c): o administrador gerava o código de uma das 4 fechadas. A resposta torta do WAHA (o estado ou a
mensagem em lista) dava 500 em HTML: os tipos são conferidos no cliente, e o que ainda escapar vira "erro do provedor". A
recusa vale pelo código HTTP também sem JSON (o 401, o 403 e o 404 de um proxy eram "respondeu com erro"). Os casos que
faltavam: o controller pelo construtor de verdade, o 404 da unidade que sumiu, o 401 antes do 403, a unidade da sessão com
outra no pedido, a fechada com o interruptor desligado, a imagem do QR do WAHA, e a resposta torta contra um `php -S` do
próprio caso em 127.0.0.1. **Provado:** os casos da fatia e da tela antiga, 40, e com os vizinhos 482 em cada banco; 19
mutantes da correção, todos vermelhos (um sobreviveu na primeira rodada -- o `status` do WAHA pelo `isset`, escondido pelo
`catch` do controller -- e o caso passou a olhar o cliente sozinho); os portões que a fatia toca, com 0; o
`ensaiar-permissao-adm-api` no MySQL de contrato, de novo verde, com o `conferir-provedores-no-loopback.php --conteiner`
verde antes. Nenhum pedido foi a um provedor de verdade.

**Pelo HTTP, pelo orquestrador** (a fatia não tem tela; contra o banco de contrato, com um administrador temporário, depois do
`conferir-provedores-no-loopback.php --conteiner` com saída 0): `GET /adm/api/whatsapp` sem sessão dá 401; com a sessão, 200 em JSON
em 1,6 s, com a unidade da sessão, `conexao: desligado`, `motivo: whatsapp_desligado`, a frase, e `podeConectar` e `podeDesconectar`
falsos; o POST dá 404; e a resposta não leva a chave nem a instância. O banco de contrato foi remontado depois.

## 23/10/2026 -- WHATSAPP-CONEXAO (a fatia 31 da fila das telas que faltam)

**O que muda para quem usa:** na tela nova, ainda nada -- ela é a fatia 34. O servidor ganhou as duas escritas do WhatsApp da
unidade (a F2 de `docs/f3-telas-restantes/tela-whatsapp.md`, pela resposta 136 a do dono): `POST /adm/api/whatsapp/conexao`
gera o código QR, e `POST /adm/api/whatsapp/desconexao` desconecta. **Só gerente ou administrador desconecta**; o caixa conecta.
A unidade é a da sessão; desligada, fechada ou sem chave é recusada pelo banco, sem falar com o provedor; um pedido por vez por
unidade (a mesma trava do "gerar código" da tela antiga); já conectada não pede o código de novo; o código só sai se for uma
imagem PNG ou JPEG de verdade; e quando o provedor falha a resposta é a nova **502 `provedor_indisponivel`** com o motivo (o
tempo esgotado, a chave recusada...) e a frase nossa, nunca o texto dele (a regra 11 de `docs/convencao-api.md`). **Na tela
antiga**, o caixa deixou de desconectar e não vê mais o botão (o D587, a mesma 136 a). E o "gerar código" do Evolution deixou de
exigir o código de pareamento, que o Evolution só manda com número (o D586: lido no código público dele, e não medido na produção).

**Provado:** 20 casos novos (`WhatsAppPelaApiTest` 16, `RegraDaConexaoWhatsAppTest` 3, `WhatsAppTelaAntigaTest` 1), com o
cliente de mentira e um `php -S` do próprio caso em 127.0.0.1; os 62 da fatia e da tela antiga, e com os vizinhos 607, em cada
banco; 29 mutantes, cada um sozinho e devolvido -- 28 vermelhos, e 1 equivalente (tirar o `releaseLock` do conectar: o PHP fecha
o arquivo da trava quando a ação volta, e a trava se solta igual). O ensaio novo `scripts/ensaiar-whatsapp-adm-api.php`, 21 de
21 no MySQL e no PostgreSQL de contrato, depois do `conferir-provedores-no-loopback.php --conteiner` com saída 0; ele recusou
rodar (saída 2) com uma instância posta à mão na unidade da sessão, e ficou vermelho com a guarda do papel arrancada. O
`ensaiar-permissao-adm-api`, com as duas rotas novas, 105 de 105 nos dois bancos. Os portões que a fatia toca, com 0. O front
não mudou: o vitest inteiro deu 2.739 de 2.740, e o que bateu os 5 s (o cadastro de Unidades, arquivo não tocado) passa sozinho,
46 de 46. **Nenhum pedido foi a um provedor de verdade**, e nada mandou mensagem.

**Depois da verificação (cinco lentes; 10 achados confirmados, 7 distintos, todos tratados):** o "gerar código" pela API nova
passa a criar a sessão do WAHA que ainda não existe, como a tela antiga sempre fez -- a consulta em 404 dava 502 antes do
`connect`, que é quem a cria, e a unidade ficaria em 502 para sempre (o D588); o GET oferece o botão nesse caso. A tela antiga
passa a decidir pela mesma regra da API: o desconectar dela toma a mesma trava da unidade e só desconecta a conectada, e o "gerar
código" deixou de reiniciar o WAHA que se conectava e de conectar com a consulta que falha (o D589); e ela só desenha o QR
conferido, e não qualquer texto do provedor no `<img>` (o D590). A trava tomada é 409 `unidade_ocupada`, e não `em_andamento`, que
é o código do 429 (as regras 2 e 11 da convenção dizem a diferença); o 502 da segunda consulta diz o motivo dela; e o
`ensaiar-permissao-adm-api` exige o 403 sem motivo nas linhas do WhatsApp, porque o 403 `papel` do caixa escondia a guarda de
`escrever` no desconectar. **Provado:** os 66 casos da fatia e da tela antiga, e 704 com os vizinhos, em cada banco; 14 mutantes
novos, cada um sozinho e devolvido, todos vermelhos; os ensaios do WhatsApp (21 de 21) e de permissão (105 de 105) no MySQL e no
PostgreSQL de contrato, depois do `conferir-provedores-no-loopback.php --conteiner` com 0; e o de permissão saiu 1 com a guarda de
`escrever` arrancada (o desconectar acusado como "o 403 de outra guarda"). Nenhum pedido foi a um provedor de verdade.

**Pelo HTTP, pelo orquestrador** (a fatia não tem tela; contra o banco de contrato, com um administrador temporário, depois do
`conferir-provedores-no-loopback.php --conteiner` com saída 0): o `POST /adm/api/whatsapp/conexao` e o `.../desconexao` dão 401 sem
sessão, 403 de outra origem, e 409 `conflito` com `motivo: whatsapp_desligado` para o administrador, a unidade com o WhatsApp
desligado, em menos de 2 s e sem pedido nenhum ao provedor; o GET nas duas rotas dá 404. O banco de contrato foi remontado depois.

## 23/10/2026 -- WHATSAPP-RASTRO (a fatia 32 da fila das telas que faltam)

**O que muda para quem usa:** na tela, ainda nada -- quem mostra é a fatia 34. Mas desde agora **cada "gerar código" e cada
"desconectar" do WhatsApp da unidade fica registrado**: quem pediu (e, quando o administrador está "entrando como" alguém, os
dois), quando, se deu certo, e, quando não deu, o motivo em palavras nossas (o servidor do WhatsApp não respondeu, recusou a
chave...). Vale pelas duas portas, a tela antiga e a API nova. Antes não havia registro nenhum de quem desconectou (o W3 do
desenho, o **D591**). O que não deu em nada não é registrado: a consulta do estado, as recusas (sem permissão, o caixa que tenta
desconectar, a unidade desligada ou sem chave, outro pedido em andamento, a instância que já estava conectada ou desconectada). O
registro nunca guarda a chave, a instância, o código QR nem o texto do provedor. A `GET /adm/api/whatsapp` passa a devolver os
cinco últimos (`ultimos`), para a tela da fatia 34. É dado de funcionário: o prazo de guarda, quem vê e o aviso entraram no débito
futuro da LGPD (o **D583**), sem pergunta nova. A tabela nova `unidades_whatsapp_eventos` só acrescenta (a resposta 83 a): nas duas
cópias locais, no `mysql.sql`, no esquema do PostgreSQL com o RLS, na carga e no passo 0 do corte (`scripts/migracao-unidades-whatsapp-eventos.sql`).

**Provado:** 7 casos novos (`WhatsAppPelaApiTest` 6, `WhatsAppTelaAntigaTest` 1); a caracterização inteira, 4.514 em cada banco,
verde; 20 mutantes, cada um sozinho e devolvido, todos vermelhos (o ponto de salvamento, no PostgreSQL); o
`ensaiar-whatsapp-adm-api.php`, 22 de 22 no MySQL e no PostgreSQL de contrato, depois do `conferir-provedores-no-loopback.php
--conteiner` com 0, e ele saiu 1 com uma recusa gravando o rastro (6 linhas acusadas, e apagadas pelo `finally`); o
`ensaiar-permissao-adm-api`, 105 de 105 nos dois; os portões, todos 0, salvo o vermelho conhecido do agendamento (o D50) e os
testes do front, que caíram em casos de tempo diferentes a cada rodada com a máquina carregada (a fatia não toca o front): com 4
processos, 2.739 de 2.740 (o `Console.teste.tsx` sozinho, 139 de 139); com 2, 2.737 de 2.740 (o `UnidadesLigado.escrita.teste.tsx`
sozinho, 46 de 46). **Nenhum pedido foi a um provedor de verdade**, e nada mandou mensagem.

**Depois da verificação (cinco lentes, 6 achados confirmados, todos tratados):** a linha do rastro passou a ser gravada **com a
trava da unidade ainda tomada, nas duas portas** -- a API soltava a trava antes de gravar, e outro pedido podia gravar primeiro,
trocando a ordem; a tela antiga passou a seguir a regra da API nos dois casos em que diferia: o "gerar código" que dá certo sem
imagem consulta o estado de novo (conectada, a tela diz "já está conectado" e o rastro diz `sucesso`, e não a falha de um código que
funcionou), e o que o cliente do provedor lança vira a falha, na tela (a frase nossa, e não a página de erro) e no rastro (antes, o
pedido caía sem linha, mesmo com o `logout` feito). A F3 da seção 3 do desenho, que ainda dizia esperar a autorização, foi corrigida.
**Provado:** 7 casos novos (`WhatsAppTelaAntigaTest` 6: a personificação, o motivo do desconectar, a leitura e as recusas que
faltavam, a trava, o `connect` sem imagem e o cliente que lança; `WhatsAppPelaApiTest` 1: a trava); 14 mutantes, cada um sozinho e
devolvido byte a byte, todos vermelhos; a caracterização inteira, 4.521 no MySQL e 4.521 no PostgreSQL, verde; os portões que a
fatia toca, com 0. Nenhum pedido foi a um provedor de verdade.

**Pelo HTTP, contra o banco de contrato** (depois do `conferir-provedores-no-loopback.php --conteiner` com 0, e com um
administrador temporário): 401 sem sessão e 403 de outra origem nas duas ações; 409 "desligado" no gerar código e no desconectar
para o administrador; o `GET /adm/api/whatsapp` com 200, o estado desligado e `ultimos: []` (a tabela existe e ninguém pediu), sem
chave nem instância na resposta; e nenhuma linha em `unidades_whatsapp_eventos` depois das seis recusas. O banco de contrato foi
remontado depois. O `sucesso` e a `falha` pelo HTTP ficam para quando houver o provedor de mentira (a fatia 33).

## 24/10/2026 -- WHATSAPP-PROVEDOR-DE-MENTIRA (a fatia 33 da fila das telas que faltam)

**O que muda para quem usa:** nada na tela, nada na produção, nada no cron nem no envio de mensagens. Muda para quem programa e
para os ensaios: **o fluxo inteiro do WhatsApp da unidade -- o estado, o código QR, o conectar, o desconectar, as falhas do
provedor e o registro de quem pediu -- agora se prova sem sair da máquina**, contra um provedor de mentira (a F5 do desenho
`docs/f3-telas-restantes/tela-whatsapp.md`; o **D592**). O endereço dos dois provedores (Evolution e WAHA) continua o de sempre; ele
só pode ser trocado por um `config/autoload/whatsapp.local.php` próprio (o modelo é o `whatsapp.local.php.dist`; nunca o
`local.php`), e **só pelo da própria máquina** (127.0.0.1, ::1 ou localhost, só com a porta). Qualquer outro endereço é ignorado --
fica o de sempre -- e o log diz só que foi ignorado, sem o endereço: nenhuma chave de unidade vai a um host qualquer por engano de
configuração. Só as duas portas da tela (a `/adm/api/whatsapp` e a tela antiga) leem o arquivo; o cron não. O deploy de teste já
recusa qualquer `*.local.php` extra: o arquivo não sobe no BVIP.

**O provedor de mentira** (`scripts/provedor-whatsapp-de-mentira.php`, um `php -S` em 127.0.0.1 dentro do contêiner do app, subido
e parado pelo `provedor-whatsapp-de-mentira-processo.php`) responde nos formatos que os clientes já leem, com um QR em PNG de
verdade e as falhas que o teste escolhe (a chave recusada, a instância que não existe, o erro, a demora acima do tempo limite, a
resposta torta); nunca guarda nem escreve a chave que recebe (compara o sha256), e responde 404 sem fazer nada a qualquer outro
caminho, o envio de mensagem inclusive. O ensaio novo, `scripts/ensaiar-whatsapp-provedor-de-mentira.php`, o sobe, põe uma chave de
teste (uma constante inventada) na unidade de contrato, prova pelo HTTP o GET com cada estado nos dois provedores, o código com o
QR, o desconectar, o 502 com o motivo, a tela antiga pela mesma rede e as linhas do rastro das duas portas na ordem (o `sucesso` e
a `falha` que a fatia 32 deixou para cá), e desfaz tudo no fim; recusa rodar se o endereço configurado não for da máquina, ou se o
cliente que as duas portas montam não chegar ao provedor que ele subiu. Entrou no CI (no job do PostgreSQL), no
`medir-telas-no-postgres.sh`, no `conferir-lacuna-do-ci.sh` e no ciclo.

**Provado:** 14 casos novos (`EnderecoDoProvedorWhatsAppTest` 7, `ProvedorWhatsAppDeMentiraTest` 7) e os vizinhos que falam com os
clientes do WhatsApp, 174 de 174 no MySQL e 174 de 174 no PostgreSQL; o ensaio novo, 30 de 30 no MySQL e no PostgreSQL de contrato,
e o de recusas, 22 de 22 nos dois, depois do `conferir-provedores-no-loopback.php --conteiner` com 0; a recusa do ensaio (saída 2,
sem o endereço na saída) com o endereço de fora, com usuário e senha, com caminho e com os dois endereços diferentes. Os mutantes,
cada um sozinho e devolvido byte a byte:

| Mutante | Vermelho |
|---|---|
| o endereço de fora aceito sem conferir | `testAUrlDeForaEIgnoradaComLogSemAUrl`, `testOLoopbackConfiguradoEAceito`, `testAsDuasPortas...` |
| sem a recusa do usuário e da senha | `testAUrlDeForaEIgnoradaComLogSemAUrl` |
| sem a recusa do caminho | idem |
| qualquer `127.*` vale | idem |
| o log leva o endereço | idem |
| sem o log da recusa | idem |
| o Evolution ignora o endereço | 7 casos, nos dois testes |
| o WAHA ignora o endereço | 7 casos |
| o endereço de sempre do Evolution trocado | 4 casos |
| a API não passa o endereço ao cliente | `testAsDuasPortasDaTelaPassamAConfiguracaoAoCliente` |
| a tela antiga não o passa | idem |
| o provedor escreve a chave que recebe | `testAChaveEAInstanciaSaoConferidasENuncaEscritas` |
| o provedor não confere a chave | idem |
| outro caminho responde 200 | 6 casos |
| o `connect` não muda o estado | `testOEvolutionDeMentiraFalaOQueOClienteLe` |
| o `parar` sem a procura no /proc | `testPararMataOsProcessosOrfaos` (o caso nasceu da verificação: o mutante ficava verde) |
| o PNG sem a assinatura | os dois casos dos formatos |
| a API não grava o código pedido (pelo HTTP) | o ensaio, 28 de 30: o rastro e o `ultimos` |
| o Evolution ignora o endereço (pelo HTTP) | o ensaio, 12 de 30: todo caso do Evolution |
| o ensaio sem a recusa do endereço de fora | a recusa do loopback some da saída |

Os portões (`conferir-tudo.sh`): 64 com 0, o vermelho conhecido do agendamento (o D50), e o dos testes do front, que caiu com 4
processos e a máquina carregada (a fatia não toca o front): com 2, 2.740 de 2.740. Na primeira rodada, o `conferir-segredos.sh`
acusou o nome do cabeçalho da chave no provedor de mentira (foi para uma constante) e o `conferir-referencias-docs.php`, o
`whatsapp.local.php` citado sem existir (explicado na lista dele).

**A verificação (cinco lentes; 16 achados confirmados, dois deles o mesmo), todos tratados** -- o detalhe está na seção 6 do
desenho `tela-whatsapp.md`:

- **O ensaio morto por fora deixava o provedor de pé e a cópia da configuração para sempre.** O `kill` do executor não roda o
  `finally` (o contêiner não tem o pcntl): o `php -S` ficava na porta fixa até o prazo do `timeout` (15 min) e a rodada seguinte saía
  2 com "a porta está ocupada"; o `whatsapp.local.php` copiado ficava, e a rodada seguinte o tomava por de quem programa. E, medido
  ao refazer o caso, **os trabalhadores órfãos seguravam a conexão do ensaio ao banco e a trava dela** (o `proc_open` passa os
  descritores abertos): a rodada seguinte recusava com "outra instância está rodando". Agora o provedor leva o dono no ambiente, o
  `subir` mata o órfão de um dono morto (e recusa o de um dono vivo, e o subido à mão com o comando de parar, novo: `php
  scripts/provedor-whatsapp-de-mentira-processo.php parar <porta>`, escrito no `.dist`), a cópia da configuração leva a marca do dono
  e a que sobrou é adotada e apagada pela seguinte, e o ensaio sobe o provedor antes de abrir o banco. Refeito pelo HTTP: o ensaio
  morto pelo TERM do `matar_orfaos` no meio, duas vezes, e a rodada seguinte -- "6 processo(s) mortos antes de subir este", a cópia
  adotada e apagada, 37 de 37.
- **O `finally` fazia o banco primeiro e sem `try`:** o erro do banco pulava o parar e o apagar da cópia, e a saída era 255. Agora o
  provedor e a cópia vêm antes e cada passo do banco tem o seu `try`; o que falhar é uma linha do "não desfeito", saída 1.
- **O teste e o ensaio iriam ao provedor de verdade se a regra do endereço regredisse.** Os ajudantes do teste conferem o
  `getUrl()` antes de qualquer pedido, e o ensaio confere pela sondagem que o cliente das fábricas de verdade das duas portas chega
  ao provedor que ele subiu, antes de a unidade ganhar a chave.
- **O WAHA de mentira parava em `STOPPED` depois do logout**, e o de verdade reinicia a sessão ("logged out and started from
  scratch", na documentação pública dele): corrigido no roteiro, no ensaio e no teste. A API segue respondendo `desconectado` ao
  POST, e a consulta seguinte diz `iniciando` e depois `aguardando_leitura` -- o que a tela da fatia 34 tem de saber (o **D593**).
- **A tela antiga nunca falava com o provedor de mentira:** agora o ensaio a pede pela rede (o código com o QR desenhado e o que
  falha, o desconectar e o que falha, nos dois provedores), no mesmo rastro, na ordem.
- **A demora só era medida na consulta:** agora também no `connect`, pelo HTTP (o 502 `tempo_esgotado` e a linha `falha`) e em
  processo, e o `logout` em processo.
- **"Nenhum arquivo com a chave" olhava a pasta depois de o registro ser apagado:** agora cada leitura do registro procura a chave,
  a instância e o sha256 antes de apagá-lo.
- **A recusa do ensaio para a URL de fora não tinha guarda automática:** a regra foi para o `enderecoDaConfiguracao` do roteiro de
  processo, medida no teste, que também lê o ensaio sem os comentários e confere que ele passa por ela antes de subir o provedor e
  de escrever no banco.
- **O caso do cron só media o cron sem configuração:** agora também com a configuração do loopback na dele.
- **A F5 aparecia como feita sem o ciclo, e o desenho não dizia o que ficou sem prova:** o ciclo rodou depois da verificação (os
  números abaixo), e a seção 6 ganhou o "o que não se prova"; as referências de linha da F5 foram corrigidas.

**Os testes da correção:** `ProvedorWhatsAppDeMentiraTest` foi a 11 casos (4 novos: o órfão de uma rodada morta recuperado, o
subido à mão recusado com o comando de parar que o para, o endereço que o ensaio aceita, a cópia da configuração que não fica), e
os da fatia, 18 de 18 no MySQL e no PostgreSQL; o ensaio novo, **37 de 37** no MySQL e no PostgreSQL de contrato (7 casos novos), e
o de recusas, 22 de 22, depois do `conferir-provedores-no-loopback.php --conteiner` com 0. Os mutantes da correção, cada um sozinho e
devolvido byte a byte:

| Mutante | Vermelho |
|---|---|
| o `subir` não recupera o órfão de uma rodada morta | `testSubirRecuperaOProvedorDeUmaRodadaMorta` |
| o `subir` recupera o órfão e deixa a pasta dele | idem (a pasta do cenário, que as rodadas mortas deixavam no `/tmp` do contêiner) |
| o `subir` mata o provedor subido à mão | `testOProvedorSubidoAMaoERecusadoEOComandoDePararOPara` |
| o `subir` mata o de um dono vivo | `testSubirSoNoLoopbackEPararSemSobra` |
| o `parar` da linha de comando não para | `testOProvedorSubidoAMao...` |
| o ensaio aceita a URL de fora | `testOEnsaioSoAceitaOLoopbackDaConfiguracao` |
| o ensaio aceita as duas URLs diferentes | idem |
| o ensaio não passa pela conferência do endereço | idem (o ensaio lido sem os comentários) |
| a cópia da configuração sem a marca | `testACopiaDaConfiguracaoDoEnsaioNaoFicaParaSempre` |
| a cópia de uma rodada viva adotada | idem |
| a configuração apagada sem a marca (a de quem programa) | idem |
| o Evolution ignora o endereço | 7 casos; os do provedor param no ajudante, "nenhum pedido saiu" |
| o logout do WAHA para em `STOPPED` | `testOWahaDeMentiraFalaOQueOClienteLe`; pelo HTTP, o ensaio 36 de 37 |
| o cron lê a configuração | `testOCronSegueNoEnderecoDeSempre` |
| o provedor registra a chave crua | `testAChaveEAInstanciaSaoConferidasENuncaEscritas`; pelo HTTP, o ensaio 36 de 37 |
| a tela antiga não passa o endereço (pelo HTTP) | o ensaio recusa rodar (saída 2) antes de a unidade ganhar a chave |
| a tela antiga não grava o desconectar (pelo HTTP) | o ensaio, 36 de 37: o rastro |
| a escrita da API espera 30 s, e não 15 (pelo HTTP) | o ensaio, 35 de 37: o `connect` que demora e o rastro |
| o primeiro passo do banco no `finally` falha | o ensaio sai 1, com o passo no "não desfeito", o provedor parado e a cópia apagada |

**Provado no ciclo, depois da verificação** (em série, em etapas): caracterização 4.539 em cada banco (MySQL e PostgreSQL);
contrato 74 e 74; os 18 ensaios no MySQL e os 18 no PostgreSQL de contrato, mais o `medir-adm-api-no-postgres`, sem vermelho
(o do provedor de mentira, 37 de 37 nos dois; o de recusas do WhatsApp, 22 de 22); 64 portões com 0 e o vermelho conhecido do
agendamento (o D50); e o front, que a fatia não toca: com 4 processos e a máquina carregada, 2 casos de tempo de 2.740 (no
`UsuariosLigado.cadastro`); com 2, 1 de 2.740, em outro arquivo (o `UnidadesLigado.escrita`) -- os dois arquivos sozinhos, 70 de
70 --; o build e o lint, 0.

**Nenhum pedido foi a um provedor de verdade, e nada mandou mensagem.** O que não se prova aqui: os formatos contra o Evolution e o
WAHA de verdade (lidos no código dos clientes e na documentação pública dos provedores, como nas fatias 30 e 31 -- o reinício do
WAHA no logout, inclusive); a tela no navegador, que é a fatia 34 (o comando para subir o provedor à mão, e o de pará-lo, estão no
`.dist`); a demora na tela antiga pela rede (os 30 s dela passam do tempo do ensaio; o cURL 28 dela está em processo, no
`WhatsAppTelaAntigaTest`); e o ensaio morto pelo KILL no meio de uma escrita no banco (o banco de contrato se remonta; o provedor e
a cópia, a rodada seguinte os recupera).

**No ciclo de fechamento, depois da última mudança da correção** (o `conferir-provedores-no-loopback.php --conteiner` com 0 antes):
a caracterização inteira, 4.539 em cada banco; o contrato, 74 e 74; os 19 ensaios no MySQL e os 19 no PostgreSQL de contrato sem
vermelho -- o do provedor de mentira, 37 de 37 --; 65 portões, o do front inclusive; e o front, 2.740 de 2.740. Depois do ciclo, nenhum
processo do provedor ficou no contêiner, o `whatsapp.local.php` copiado foi apagado (só o `.dist` fica), e o `local.php` voltou ao
banco de desenvolvimento.

## 24/10/2026 -- WHATSAPP-TELA (a fatia 34 da fila das telas que faltam)

**O que muda para quem usa:** Configurações › WhatsApp abre a tela nova, e não mais a tela que dizia "ainda roda na versão antiga
do ERP". Ela diz, em palavras, o estado do WhatsApp da unidade -- conectado, esperando a leitura do código, conectando,
desconectado, sem a chave, desligado, ou sem resposta do servidor do WhatsApp com o motivo --, e nunca o texto do provedor, a chave
ou a instância. "Gerar código" mostra o QR com os quatro passos da tela antiga; a tela consulta sozinha, a cada 5 s, até o celular
ler, e quando conecta o QR sai e o foco vai ao estado. Desconectar pergunta no próprio botão e diz o alcance (o que para, os
clientes ativos da unidade, e que as mensagens desse tempo se perdem com a falha registrada, a resposta 115 b); o caixa não vê o
desconectar (a 136 a). Os últimos cinco pedidos aparecem embaixo: quando, quem -- "entrando como" na personificação -- e o que foi
feito. O administrador ganha o link para a unidade em Unidades, na tela antiga, quando falta a chave ou o WhatsApp está desligado.
O servidor é o das fatias 30 a 33; na verificação ganhou só o 409 `unidade_trocada` (abaixo). O desenho é a F4 de `docs/f3-telas-restantes/tela-whatsapp.md`; o que mudou em
relação a ele está na seção 6 de lá (o foco da pergunta fica no botão, como manda `perguntaNoBotao.ts`; depois de toda escrita
respondida a tela consulta de novo, o D593; no Evolution, o "conectando" com o QR desta tela é a leitura que ela espera).

**A consulta:** em fila, um pedido de estado por vez, e só a resposta do último vale; a próxima sai 5 s depois que a anterior
voltou; para ao desmontar, com a aba escondida (e consulta na hora quando ela aparece) e depois de 3 minutos. Uma escrita por vez.

**Débitos:** o **D591** e o **D593** vão a PARCIAL -- o que falta nos dois é ver a tela no navegador; o **D350** ganhou a nota de que
o menu ficou como o desenho manda (o item segue o `whatsapp_ativo` da sessão). Na verificação, o **D594** (a unidade trocada noutra
aba), PARCIAL. Nenhuma pergunta nova ao dono.

**Os testes:** `frontend/src/telas/WhatsAppLigado.teste.tsx` (40 casos), `frontend/src/telas/WhatsAppLigado.contrato.teste.tsx` (7)
e o `frontend/src/telas/Console.teste.tsx` (4 novos, e a conta dos destinos na tela não migrada de 20 para 19), verdes também com o
`AMPLIFICAR_EFEITOS_MS=50`. A prévia do Claude Design, `frontend/.design-sync/previews/WhatsApp.tsx`, tem 14 células com os números
batendo entre si (a VIP Jardins, 6.170 clientes, os pedidos do dia nas horas certas), conferida pelo `tsc` contra os tipos da API.

**Os mutantes**, cada um sozinho e devolvido byte a byte (30, todos vermelhos; o 18 ficou vivo na primeira rodada, porque o foco que
o botão que sai leva ao estado cobria o caso -- ganhou o caso do conectado que chega sem clique, e ficou vermelho):

| Mutante | Vermelho |
|---|---|
| a fila: com uma consulta no ar, a segunda sai na hora | contrato, "a consulta em fila" |
| só a última vale: a resposta velha aplicada | contrato, "a escrita tira a validade da consulta que estava no ar" |
| o intervalo de 5 s vira 1 s | "a próxima só sai 5 s depois que a anterior voltou" (2) |
| o prazo de 3 minutos arrancado | "para depois de 3 minutos" |
| a aba escondida não para a próxima | "com a aba escondida não consulta" |
| a aba que aparece não consulta | idem |
| desmontar não para a próxima | "desmontada, não consulta mais" |
| duas escritas ao mesmo tempo | contrato, "uma escrita por vez" |
| a escrita não invalida a consulta no ar | contrato, "a escrita tira a validade..." |
| depois da escrita não consulta (o 409 não relê, o D593) | 9 casos |
| o 401 e o 403 da escrita releem | o contrato e o "403 do papel" |
| o QR fica com o conectado | "vê o conectado, para, tira o QR" |
| o QR que não é imagem aparece | "o QR que não é imagem não aparece" |
| o "conectando" com o QR não é a leitura esperada | 2 casos |
| sem o "gerar outro código" do Evolution | o caso do QR no Evolution |
| o "gerar outro código" também no WAHA | o caso do WAHA |
| a consulta não acompanha o "conectando" | 10 casos |
| conectou e o foco não vai ao estado | "o conectado que chega sem ninguém ter clicado" |
| o foco órfão fica sem dono | o 409 e o desconectar (2) |
| desconectar sem pergunta | 3 casos |
| o "não" não devolve o foco | "pergunta no próprio botão" |
| o duplo clique pede dois códigos | "o duplo clique pede um código só" |
| o link de Unidades para todos | a tela e o Console (2) |
| o link de Unidades na unidade fechada | "a unidade fechada, nem ao administrador" |
| o 403 oferece "tentar de novo" | "o 403 não oferece" |
| a personificação some dos últimos | "quando, quem e o que foi feito" |
| o `null` dos últimos vira "ninguém pediu" | "o nulo" |
| a consulta que falha apaga a tela pronta | "a consulta que falha com a tela pronta" |
| o Console volta a abrir a `TelaNaoMigrada` | Console (5) |
| o administrador não chega do Console | Console (2) |

**Provado:** o front inteiro, 2.791 de 2.791 com 4 processos; o build e o lint, 0 (nenhum aviso novo). Os portões que a fatia toca, 0:
`contar-debitos.php` (de pé, 144), `conferir-debitos-repetidos.php`, `conferir-referencias-docs.php`, `conferir-fosseis.php`,
`conferir-links-mortos.php` e `conferir-segredos.sh`. O servidor não mudou, e a caracterização não foi rodada de novo.

**O que não se prova aqui:** a tela no navegador -- a 375 px, nos dois temas, contra o banco de contrato, sem o provedor
(`sem_credencial`, `desligado`, as recusas) e com o provedor de mentira da fatia 33 (o QR, o conectado, o desconectar e o reinício do
WAHA) --, que é o passo seguinte, de quem coordena a fila; e os formatos contra o Evolution e o WAHA de verdade. Nenhum pedido foi a
um provedor: o front só fala com o `fetch` dublado dos testes.

### A verificação da fatia 34 (24/10/2026)

Cinco lentes; **14 achados confirmados, 12 distintos** (o "consultar de novo" que some com o foco veio três vezes), todos
tratados. O desenho ganhou o bloco "A verificação" na seção 6 de `docs/f3-telas-restantes/tela-whatsapp.md`.

- **O título do indisponível segue o motivo:** "sem resposta do servidor do WhatsApp" só no tempo esgotado e na rede; nos outros
  quatro motivos o provedor respondeu, e o título é "não deu para saber o estado".
- **A nota diz de onde veio o estado:** no desligado e no sem a chave, "conferido no cadastro da unidade", e não "consultado pelo
  Evolution" -- o servidor nem constrói o cliente.
- **O alcance do desconectar** diz a tabela inteira da seção 1 do desenho, o "cliente chegou" ao colaborador, e sem o "a os 6.170".
- **O "consultar de novo" entrega o foco ao estado** quando some com o clique ou com a resposta.
- **A consulta que não disse nada no meio da espera** (a rede, o 500, e o indisponível do tempo esgotado ou da rede) deixa o estado
  de antes e o QR, e a série segue até o prazo; **no prazo, o QR sai** e o "gerar outro código" fica.
- **A unidade trocada noutra aba (o D594, novo, PARCIAL):** as escritas levam o id da unidade que a tela mostra (`unidadeNaTela`), e
  a API responde 409 `unidade_trocada` antes do banco, da trava e da rede. **A única mudança no servidor**; a tela antiga fica no
  D594. A consulta que traz outra unidade tira o QR.
- **No `StrictMode`**, a tela abre com um GET só (a remontagem adota o do ar; eram dois em série).
- **O recuo de 0.93rem** deixou de somar duas vezes; **a prévia** passou a bater as horas (o conectado às 14:00, a consulta parada às
  14:01 sem o QR, o sem a chave e o desligado noutras unidades).
- **Os casos que faltavam:** a aba que esconde com a consulta no ar; a desmontagem com a consulta na fila e no meio de cada escrita.

**Os testes:** `WhatsAppLigado.teste.tsx` de 40 para 65 casos, `WhatsAppLigado.contrato.teste.tsx` de 7 para 14, todos verdes também
com o `AMPLIFICAR_EFEITOS_MS=50` (que pegou duas esperas de foco escritas sem `waitFor`). No servidor, o caso novo
`WhatsAppPelaApiTest::testATelaQueMostraOutraUnidadeNaoEscreve` e a regra no `RegraDaConexaoWhatsAppTest`: os três arquivos do
WhatsApp, 81 de 81 no MySQL das fixtures e 81 de 81 no PostgreSQL.

**Os mutantes**, cada um sozinho e devolvido byte a byte: **29 novos do front** (31 a 59: o título, a nota, o alcance, o foco do
"consultar de novo", a aba escondida, as cinco guardas da desmontagem, o `StrictMode` nos dois sentidos, o id nas escritas, o QR de
outra unidade, a falha no meio da espera, o QR vencido; e o 15 e o 16 da fatia, de texto novo), **29 vermelhos**; os 28 da fatia que
ainda se aplicam, de novo, 28 vermelhos; e **5 do servidor** (a conferência arrancada, os dois ramos da regra, o "sem o campo
recusa" e a conferência posta depois do 409 do banco), 5 vermelhos. As tabelas estão no rascunho da sessão
(`mutantes-whatsapp-verificacao.txt`, `mutantes-whatsapp-tela-depois-da-verificacao.txt` e `mutantes-whatsapp-servidor-34.txt`).

**Provado:** o front inteiro com 2 processos, 2.822 de 2.823 duas vezes -- o vermelho, um caso diferente a cada rodada em arquivo que
a fatia não toca, por tempo (`UnidadesLigado.escrita` e `UsuariosLigado.cadastro`, este também sozinho, a 5,04 s do teto de 5 s;
sozinhos, passam); o build e o lint, 0, sem aviso novo; a prévia, pelo `tsc` contra os tipos (com o erro de controle pego). O
`conferir-tudo.sh`: o "inventário atualizado" caiu na rodada carregada e saiu 0 sozinho; o do front, pelo tempo acima; o resto, 0.

**O que não se prova aqui:** a tela no navegador, como antes; e a troca de unidade com duas abas de verdade.

**No navegador, depois da verificação** (24/10/2026), contra o banco de contrato e com o provedor de mentira da fatia 33 de pé no
contêiner (o `conferir-provedores-no-loopback.php --conteiner` com 0 antes; uma chave de teste aleatória na unidade 90001000, nunca
impressa), como um administrador temporário, a 375 px, a 1.280 px e nos dois temas, com cliques e teclado de verdade:
- **No Evolution:** o desconectado com o gerar código; o código com o QR de 280 px (o texto alternativo diz o que é), os quatro
  passos e o "gerar outro código" com o foco; o celular que "lê" (o `cenario.json` em `open`), visto pela consulta, que tirou o QR,
  levou o foco ao estado e trouxe o desconectar; o desconectar que pergunta no próprio botão, com o alcance (8 clientes ativos) e
  o "não desconectar" que devolve o foco; e o "sim", com o "desconectando…" e o desconectado relido.
- **No WAHA:** depois do desconectar, a tela consultou de novo e mostrou "Esperando a leitura do código" com a nota de que a sessão
  recomeça sozinha (o D593).
- **Os últimos pedidos:** o código pedido e os dois desconectar, do administrador, na ordem, iguais às três linhas da tabela. Nenhum
  arquivo do provedor de mentira guardou a chave.
- **Dois defeitos, consertados aqui:** no Evolution, a frase do desconectar saía duas vezes, a segunda em verde, porque a resposta do
  POST é a mesma frase do "desconectado" relido logo depois (a tela não repete mais a frase do estado; um caso novo, com o mutante
  vermelho); e a nota "Consultado às…" encostava no botão de baixo (o recuo de baixo, no `App.css`).
- Cada pedido levou de 10 a 13 s, também a `/adm/api/sessao` e o 401 sem sessão, com os contêineres ociosos e a CPU da máquina em
  83% por outros programas: é a máquina, e não a tela (o provedor de mentira respondia em 20 ms).

Depois, o provedor foi parado (sem processo no `/proc`), a pasta dele e o `whatsapp.local.php` apagados, a sessão expirada, o
`local.php` devolvido ao `franquiabv_dev` e o banco de contrato remontado. O D591 e o D593 ficam CORRIGIDOS.

## 24/10/2026 -- PESQUISAS-API (a fatia 35 da fila das telas que faltam)

A pesquisa da etapa 5 da adesão ao programa de fidelidade -- uma só para a rede, e não o NPS (a resposta 211 (a)) -- ganha a
API nova, e a tela antiga termina os consertos da fatia B do desenho (`docs/f3-telas-restantes/tela-pesquisas.md`; o recorte em
`docs/f2-fatia-pesquisas.md`). Nada de tela nova: é a fatia 36.

**O que muda para quem usa:**
- **Na tela antiga** (Configurações > Pesquisas de Satisfação): o "Status" do novo e do editar saiu -- ele nunca gravou (o D595); o
  estado muda pelo ativar e desativar da lista. A pergunta nova vai para a pesquisa da tela, e não para a aberta por último noutra
  aba, e editar, excluir e ler uma pergunta ou opção só valem para as da pesquisa aberta (o D597; o `mod-configuracoes.js` em
  `?cache=169`). Mudar ou excluir pergunta e opção passa a gravar a data (o D596, PARCIAL até a tela nova mostrar as excluídas).
- **Na pesquisa em uso**, nas duas portas: a penúltima opção de uma pergunta não sai quando há outra pergunta respondível -- ela
  sumiria da etapa 5 calada --, e a frase diz para acrescentar outra antes. O estado e as exclusões passam a travar a pesquisa
  antes de ler: duas abas não deixam mais a adesão com uma opção, nem com uma pesquisa inativa.
- **A API** `/adm/api/pesquisas`, doze endereços: a lista (com a pesquisa que a etapa 5 pergunta agora, ou `null`), o cadastro, a
  pesquisa com as perguntas, os votos, a porcentagem sobre as opções ativas e as excluídas à parte, o título, os dois do estado, o
  `/adesao` (a troca numa transação; o 409 diz o que falta), a pergunta nova com duas a dez opções numa escrita só, o texto e a
  ordem da pergunta e da opção, e as duas exclusões, lógicas. A pergunta de outra pesquisa e a opção de outra pergunta são 404.
  Quem escreve é quem tem o privilégio de escrever, como hoje (a P2 do desenho fica com o dono).

**Achado no caminho:** a família `adm-api-pesquisas` não estava nas rotas que chegam ao controller sem sessão
(`module/ISConfiguracao/config/rotas-publicas.php`), e o pedido sem login recebia a página de login com 200; o caso em processo não
passa pelo listener do ERP, e foi o ensaio pelo HTTP que pegou. Entrou na lista, com as treze rotas no `conferir-rotas-sem-login.php`.

**Mutantes:** 25, cada um sozinho e devolvido byte a byte, todos vermelhos (a tabela em `mutantes-log-2.txt` do rascunho): as guardas (a funcionalidade do construtor, o ler, o escrever, o escrever virando ler), o aninhamento nas duas portas (a pergunta e a opção de qualquer pesquisa, a sessão de volta na pergunta nova), os 409 (o mesmo estado, o D58, o `ja_na_adesao`, o `ja_excluida` da pergunta e da opção, o `excluida` das duas, a pergunta da adesão com uma opção), o 400 e o 422 da pergunta com uma opção, a trava (sem o `FOR UPDATE`, sem a transação), o carimbo, a porcentagem e o filtro "todas". Na primeira rodada, dois sobreviveram: a funcionalidade do construtor (o caso montava o controller com ela posta a mão -- passou a usar o construtor de verdade, como o do WhatsApp) e a conferência do `idPergunta` do editar antigo (que não protegia nada: o serviço não troca a pergunta da opção -- saiu).

**Provado:** `PesquisaApiTest` (novo), `PesquisasPelosControllersTest` e `RegraDaPesquisaTest`, com os vizinhos (o D199, a adesão, a fidelidade, a barreira de origem, a guarda de login, o mapeamento), 558 de 558 no MySQL e 558 no PostgreSQL (3 pulados, os de sempre); o `ensaiar-pesquisas-adm-api.php` (novo), 29 de 29 (21 recusas e 8 controles) nos dois bancos de contrato; o `ensaiar-permissao-adm-api.php`, 118 de 118 nos dois; os portões da ACL, das rotas órfãs e sem login, da guarda do ERP, da remoção por POST, da injeção de DQL, da cobertura por par (de 355 para 360 pares), dos booleanos, da ordenação, do relógio, dos links mortos, das referências, do inventário (regerado), da lacuna do CI e dos segredos, todos 0; os débitos, 0; o front, 2.824 de 2.824 com 2 processos, o build e o lint, 0.

**A verificação em lentes confirmou 11 achados (6 distintos), todos tratados:**
- **As recusas do que já foi excluído moravam só no controller da API, antes da trava** (o D599): a tela antiga editava a pergunta
  e a opção excluídas e excluía de novo, recarimbando a exclusão, e duas abas na API recebiam dois 200. Saíram para o
  `PesquisaService`, lidas depois da trava, com a mesma frase nas duas portas; o `ja_na_adesao` também.
- **Duas trocas da adesão ao mesmo tempo, em pesquisas diferentes** (o D598): no PostgreSQL ficavam as duas marcadas, e no MySQL uma
  travava na outra (500). A troca agora trava todas as pesquisas em ordem de id antes de ler (`PesquisaRepository::travarTodas`).
  Provado com outro processo segurando as linhas (o instrumento do D321): vermelho no PostgreSQL sem o conserto.
- **O ensaio não provava a regra nova pelo HTTP:** o caso 16 era o da última pergunta. Entraram os casos 30 (a penúltima opção com
  outra pergunta respondível, `pergunta_com_uma_opcao`), 31 (excluir de novo) e 32 (alterar a pergunta excluída e pôr opção nela),
  e o 16 passou a se chamar pelo que prova. Arrancado o ramo da regra nova, o caso 30 sai vermelho pelo HTTP.
- **O inventário perdeu a coluna `repositorios` de seis ações** da tela antiga, que passaram a ler pelos ajudantes: o
  `inventariar-acoes.php` passou a seguir os métodos do próprio controller (e a mesma cegueira valia para outras 71 linhas, que
  ganharam os repositórios que de fato leem).
- **O desenho** escrevia as P2, P3, P4 e P6 como se fossem respostas: agora diz que são o "enquanto isso", e a P1 ganhou o
  "Respondida em 15/10/2026: (A)".

**Mutantes da verificação:** 14, cada um sozinho e devolvido byte a byte, todos vermelhos: cada recusa arrancada do serviço (as cinco
do excluído e o `ja_na_adesao`), o "Sim" do editar recusando o `ja_na_adesao`, a tela antiga dizendo "sucesso" na recusa (o editar e
a opção), o motivo trocado, e o `travarTodas` arrancado, sem trava e sem ordem -- os dois primeiros também no PostgreSQL, onde o caso
das duas trocas fica vermelho. E o ramo da `pergunta_com_uma_opcao`, arrancado, deixa o caso 30 do ensaio vermelho pelo HTTP.

**Provado de novo, depois da verificação:** a fatia e os vizinhos (`Pesquisa`, o D199, a adesão, a fidelidade, a barreira de
origem, a guarda de login, o mapeamento), 620 de 620 no MySQL e 620 no PostgreSQL (3 pulados); o `ensaiar-pesquisas-adm-api.php`,
32 de 32 (24 recusas e 8 controles), e o `ensaiar-permissao-adm-api.php`, 118 de 118, nos dois bancos de contrato.

**O que não se prova aqui:** a tela nova (a fatia 36) e o navegador; a corrida de duas abas pelo HTTP (a das exclusões está provada
pelo serviço, com a entidade lida antes e a outra aba no meio, e a da adesão, com outro processo segurando as linhas); o deadlock do
MySQL, que pediria as duas trocas no protocolo antigo e não foi reproduzido -- o conserto é a ordem, conferida pela primeira leitura;
e a lista no `medir-adm-api-no-postgres.php`, que exige dados que as fixtures não têm.

**Pelo HTTP, contra o banco de contrato, depois da verificação** (24/10/2026, com um administrador temporário): 401 sem sessão e
403 de outra origem; uma pesquisa nova com 201, ativa e fora da adesão, com o "falta uma pergunta"; o PUT vazio, 400; a pergunta com
uma opção, 422 com o nome de cada campo; pôr na adesão a incompleta, 409 "incompleta" dizendo o que falta; a pergunta de outra
pesquisa, 404; a pergunta com duas opções, 201, e pôr na adesão, 200, com a lista trazendo a pesquisa da adesão no envelope; e 409,
cada um com a frase, na adesão repetida, no desativar da pesquisa da adesão, na penúltima opção e na última pergunta. A lista mostra
as ativas por padrão (o `estado=todas` traz as outras). **E a tela antiga, no navegador**, com o mesmo administrador: o
cadastro sem o "Status" (o DP-5), o editar com o `pesquisa_id` e sem o "Status", a pergunta nova e a opção nova pelos modais, a
opção aberta para editar e a exclusão confirmada -- cada ação agora manda a pesquisa da tela (o DP-9) --, e a opção excluída ficou
na tabela com o status 0 e a data. Nenhum defeito. O banco de contrato foi remontado depois.

## 24/10/2026 -- PESQUISAS-TELA (a fatia 36 da fila das telas que faltam)

**O que muda para quem usa:** Configurações › Pesquisas de Satisfação abre a tela nova, e não mais a tela que dizia "ainda roda na
versão antiga do ERP". A tela diz logo no topo que a pesquisa vale para a rede inteira e que é a pergunta da etapa 5 da adesão ao
programa de fidelidade -- não é o NPS (a resposta 211 a). O cartão "Na adesão agora" mostra a pesquisa que a etapa 5 pergunta, a
pergunta dela (desde a verificação, abaixo) e quantas vezes foi respondida; sem nenhuma, avisa que a etapa 5 passa em branco em todas as unidades. A lista tem a
busca por título e situação (ativas, inativas, todas) e o selo de cada uma -- "na adesão", "ativa", "inativa" e "incompleta". A
pesquisa aberta mostra o que o número é (um contador, desde a criação da opção, sem data nem unidade), cada pergunta com as barras,
a porcentagem com uma casa e os votos de cada opção, o N, e as opções excluídas à parte, "fora da pesquisa", com os votos delas. Quem
tem o privilégio de escrever -- administrador ou não, como hoje (a P2 segue com o dono) -- cadastra (só o título), altera o título,
acrescenta pergunta já com as opções (duas a dez, num formulário só), altera e acrescenta opção, exclui pergunta e opção, desativa,
ativa e põe na adesão. Excluir, desativar e pôr na adesão perguntam no próprio botão e dizem o alcance: quantos votos saem da
porcentagem; o que a etapa 5 passa a perguntar e qual pesquisa sai, com os votos dela. A pesquisa da adesão não tem "desativar" --
"Para tirar esta pesquisa da adesão, ponha outra no lugar." (a P4, no "enquanto isso"). Alterar uma opção ou pergunta com votos diz
antes quantos votos passam a dizer o texto novo (a P3, no "enquanto isso" (B)). As recusas do servidor aparecem onde se age: o 422
no campo (a opção na linha dela), o 409 na linha ou no corpo, com a pesquisa relida por baixo, e o 403 como "Não permitido.". O
servidor é o da fatia 35, sem mudança; o desenho é a fatia D de `docs/f3-telas-restantes/tela-pesquisas.md`, com o que mudou dele
escrito lá.

**Débitos:** o **D596** vai a um segundo PARCIAL -- a tela mostra as excluídas com os votos; falta vê-la no navegador. Nenhum débito
novo, e nenhuma pergunta nova ao dono (as P2, P3, P4 e P6 continuam com ele, no "enquanto isso" de cada uma).

**Os testes:** `frontend/src/telas/PesquisasLigado.teste.tsx` (36 casos), `PesquisasLigado.contrato.teste.tsx` (13),
`formularioDaPesquisa.teste.ts` (14) e o `Console.teste.tsx` (5 novos; a conta dos destinos na tela não migrada de 19 para 18, e o
caso do WhatsApp, que partia das pesquisas, passou a partir das Mensagens), verdes também com o `AMPLIFICAR_EFEITOS_MS=50`. A regra
da tela (`formularioDaPesquisa.ts`) tem os tetos e as frases do servidor, presos pelo caso novo
`RegraDaPesquisaTest::testOFormularioDaTelaDizOQueARegraDiz`, que lê o arquivo da tela. A prévia do Claude Design,
`frontend/.design-sync/previews/Pesquisas.tsx`, tem 11 células com os números da cópia batendo entre si (25.471 votos; 85,8%, 13,3% e
0,9%; excluída a "não sou mais cliente", 25.233 no N e 86,6% e 13,4%), conferida pelo `tsc` contra os tipos da API; a célula do
cadastro não migrado da `TelaNaoMigrada.tsx` passou das Pesquisas às Mensagens.

**Os mutantes**, cada um sozinho e devolvido byte a byte (30, todos vermelhos):

| Mutante | Vermelho |
|---|---|
| o cartão da adesão sem pesquisa não avisa | "a adesão sem pesquisa leva o aviso" |
| as excluídas não aparecem | o detalhe e a exclusão (2) |
| a barra não segue a porcentagem | o detalhe e a regra (2) |
| a pesquisa da adesão ganha o "desativar" | o detalhe |
| a incompleta vai para a adesão | "a incompleta diz o que falta" e o 409 da incompleta (2) |
| a opção vazia passa na pergunta nova | a regra e "exige os dois" (2) |
| a pergunta nova com uma opção passa | a regra |
| a ordem da opção nova não é a posição | a regra e o corpo do POST (2) |
| o aviso dos votos não diz o texto novo | a regra e o alterar a opção (2) |
| excluir não pergunta | 6 casos |
| o "não excluir" não devolve o foco | "o não devolve o foco" |
| o foco que a resposta derruba fica no `body` | a exclusão e o pôr na adesão (2) |
| o 409 do botão não relê | o contrato (2) |
| o 403 do botão relê | o contrato e a tela (2) |
| dois botões ao mesmo tempo | contrato, "uma escrita por vez" |
| o formulário abre com um botão no ar | contrato |
| o que não mudou pede | o contrato e a tela (2) |
| a gravação não marca a lista para releitura | contrato (2) |
| a troca da adesão não some do alcance | contrato, "a de antes deixa de ser nomeada" |
| o 422 não vai ao campo | a regra e a tela (3) |
| o 409 do formulário não relê | "o 409 da opção excluída noutra aba" |
| a releitura do 409 passa por cima do formulário aberto | contrato |
| voltar não devolve o foco à linha | "voltar sem mudar nada" |
| o Console não abre as pesquisas | o Console (6) |
| só o administrador escreve | o Console, "com escrever sem ser administrador" |
| a pergunta no botão não se desfaz quando a pesquisa muda | "a pergunta é do estado em que foi feita" |
| a resposta velha da pesquisa aberta vale | contrato |
| a frase do título com `<` muda só na tela | `RegraDaPesquisaTest` |
| o teto da pergunta muda só na tela | `RegraDaPesquisaTest` |
| o teto de opções muda só na tela | `RegraDaPesquisaTest` |

**Provado:** o front inteiro, 2.891 de 2.891 com 2 processos (a rodada saiu antes do último caso da tela, o da pergunta que se desfaz ao reler, que passou depois com os vizinhos: os quatro arquivos da fatia, 211 de 211), o build e o lint, 0; os casos da fatia com o
amplificador; os 180 casos de `Pesquisa` (a regra, a API, a tela antiga, a adesão) no MySQL e no PostgreSQL. **O que não se provou
aqui:** a tela no navegador, contra o banco de contrato, a 375 px e nos dois temas (a lista, o detalhe, cadastrar com duas opções,
pôr na adesão e a página pública da adesão mostrando a pergunta nova) -- é a próxima etapa, e o D596 espera por ela; e a revisão em
lentes, que não rodou nesta rodada.

**A verificação (24/10/2026, a fatia E do desenho, em parte):** os céticos por lente acharam 18 defeitos, todos confirmados no código
e todos consertados, cada um com o caso que o pega. **Para quem usa:** o 409 de uma pergunta ou opção que outra aba já excluiu
aparece no corpo, e o título o descreve (antes a linha sumia com a releitura, e a frase com ela); o "pôr na adesão" com a lista
velha que ainda aponta a própria pesquisa não diz mais "a adesão está sem pesquisa"; a coluna "Alterada em" diz "nunca" na pesquisa
sem alteração, como o histórico do detalhe (antes mostrava a criação); o cartão "Na adesão agora" diz a pergunta da pesquisa em uso
-- o envelope da lista passou a trazer a da adesão inteira, na forma do detalhe (`PesquisasController::indexAction`, só acrescenta;
`PesquisaApiTest::testAListaEOCartaoDaAdesao` prende); voltar do detalhe aberto pelo cartão devolve o foco ao botão do cartão;
depois do 409 num formulário, o formulário espera a releitura (o "desistir" e o "salvar" travados até ela chegar), e desistir mostra
a pesquisa de agora, e não a de antes; os avisos de alterar e de excluir uma opção, e a dica do formulário, contam os votos que o
servidor devolveu, e não os do retrato de quando o formulário abriu; o alcance e a recusa da exclusão nascem depois dos botões, e o
"confirmar" não desce entre os dois toques; e, no CSS, o texto longo sem espaço quebra (o título da pergunta, as excluídas, o cartão
da adesão e a legenda), o "tirar" da opção nova fica ao lado do campo, e a recusa da opção não leva margem dobrada. A prévia: a
célula da troca da adesão não mostra mais uma "Última alteração" que o servidor não gravaria, e o cartão das células da lista diz a
pergunta. **Nas provas:** filtrar volta à página 1, a resposta velha da lista não cobre a nova, o "não pôr" devolve o foco, o 422
do alterar da pergunta e da opção vai ao campo, e os dois endereços que nenhum teste pedia -- alterar a pergunta
(`PUT /pesquisas/:id/perguntas/:p`) e excluí-la (`POST .../exclusao`), com o 409 da última pergunta da adesão -- passam a ser pedidos.
**Os mutantes novos**, cada um sozinho e devolvido byte a byte (conferido por sha256): 25 do front e 1 do servidor (o envelope de
volta à forma da linha), todos vermelhos, cada um no caso escrito para ele. **Provado:** o front inteiro com 2 processos (2.910 de
2.910), os casos da fatia com o `AMPLIFICAR_EFEITOS_MS=50` (81 de 81), o build, o lint e o `tsc` da prévia; os 180 casos de `Pesquisa` no MySQL e no
PostgreSQL. **O que não se provou aqui:** o CSS (no vitest ele chega vazio: as quebras, o "tirar" ao lado e a margem da recusa
precisam do navegador), o navegador inteiro da fatia D, o ciclo em série e o Artifact do roadmap. Fica um risco aceito do desenho,
sem pergunta nova: o "pôr na adesão" nomeia a pesquisa que sai pela lista, e se outra aba trocou a adesão por uma terceira, a frase
nomeia a de antes até a lista ser relida (a troca em si é a do servidor, numa transação).

**No navegador, depois da verificação** (24/10/2026), contra o banco de contrato, como um administrador temporário, a 375 px, a
1.280 px e nos dois temas, com cliques e teclado de verdade: a lista vazia com o cartão "a adesão está sem pesquisa"; cadastrar a
pesquisa (o foco no título do detalhe, "ativa" e "incompleta" com o que falta); a pergunta nova recusando a opção vazia no próprio campo
(o foco nele, o erro ligado pelo `aria-describedby`), a terceira opção com o "tirar" ao lado; votos de teste no banco (30, 15 e 5), e o
"atualizar" com 60%, 30% e 10% e o N = 50; pôr na adesão, com a pergunta no botão dizendo o que a etapa 5 passa a perguntar, e o
desativar que some, com o porquê; excluir a opção de 5 votos, que foi para "Fora da pesquisa" com a porcentagem refeita sobre 45; a
penúltima opção da adesão recusada pelo servidor, com a frase na linha e o foco no botão; e voltar à lista, com o cartão "Na adesão
agora" mostrando a pergunta e o foco na linha. **Um defeito, consertado aqui:** o aviso dizia "Incompleta: Falta uma pergunta: ...",
com dois-pontos seguidos; agora é "Incompleta. Falta uma pergunta: ...". O D596 fica CORRIGIDO. Não vi a página pública da etapa 5,
que pede um cliente no meio da adesão (o `AdesaoComPesquisaTest` a prova em processo, nos dois bancos). O banco de contrato foi
remontado depois.

**Na prévia do Claude Design, depois do commit** (`1cbb609`): em tela larga, a pergunta nova deixava o "mais uma opção" ao lado da
opção 1, porque o `.clientes-campos` tem 4 colunas e o `.campo-largo` ocupa 2 (a 375 px, com 2 colunas, não aparecia). A lista das
opções e o botão passam a tomar a linha inteira; a captura refeita mostra o botão embaixo da lista. As 11 células das pesquisas e as
3 da tela não migrada foram ao projeto do Claude Design.

## 25/10/2026 -- MENSAGENS-API (a fatia 37 da fila das telas que faltam)

**O que muda para quem usa:** nada de tela nova ainda (a fatia 38). A tela antiga de Configurações › Mensagens -- o mural de
avisos no topo da agenda (a resposta 211 a: é este o "mensagens" da ordem de 27/09) -- mudou em três pontos: o editar não tem
mais o "Status" (salvar não desfaz mais a desativação feita noutra tela -- o D600), e o "Ativar" passou para a lista dos
desativados, como nos convênios e nas pesquisas; salvar um aviso ligado a uma unidade ou a um grupo desativado não tira mais a
ligação (os avisos 28 e 29 da cópia perdiam 1 e 2 unidades a cada salvar); e o título e o texto com tabulação passam a ser
recusados no campo, como na API; e o aviso com o dia que passou também (o de ontem nascia ativo e nunca ia ao ar -- o
D601), menos o mesmo dia de volta no editar, para o aviso passado continuar editável. Desativar o que já está desativado diz "O aviso já está desativado.", e não "sucesso". E
nasce a API `/adm/api/mensagens` (a F1 do desenho `docs/f3-telas-restantes/tela-mensagens.md`; o recorte em
`docs/f2-fatia-mensagens.md`): a lista com o selo de cada aviso pelo relógio do servidor (no ar, agendado, passou, desativado,
e o "todo dia" do aviso sem dia), os filtros por título, situação, momento, unidade, grupo e dia (cada valor fora é 422 com o
nome), o "para quem" por extenso, com as unidades e os grupos inativos marcados, o grupo que não abre a agenda e o grupo sem
ninguém ativo, as opções do formulário, o cadastro, a alteração campo a campo e os dois endereços do estado, com o 409 no mesmo
estado. Só o administrador escreve, e quem só lê vê o que chega à própria unidade -- o "enquanto isso" da seção 4 do desenho
(as perguntas 3, 4 e 5 seguem sem resposta, e não há pergunta nova): o dia é obrigatório na API nova e ainda pode ficar vazio no
ERP; o dia no passado é recusado, nas duas portas; o re-datar é o de hoje; sem autor. Os tipos em `frontend/src/api/mensagens.ts`.

**Débitos:** o **D601** nasce e fecha na verificação (a tela antiga gravava o dia que passou); o **D600** nasce e fecha (o lado D259 do M5: o editar desfazia a desativação, e o estado era gravado sem reler);
o **D342** vai a um segundo PARCIAL -- as mensagens inteiras (a forma do D256 e o "só o que mudou", o M9), e faltam os
assistentes e a VIP Class. De pé, pelo `contar-debitos.php`: 143.

**Os testes**, nos dois bancos: `MensagemApiTest` (novo: o 401, o 403 de ler, de escrever e do não administrador em cada escrita,
a outra origem, o 404, o 409, o 422 de cada campo e de cada filtro, o 400, o momento pelo relógio com os dias relativos a hoje, o
aviso sem dia, os filtros que acham, o recorte de quem só lê -- e, na verificação, o `ultimoQueFoiAoAr` com os filtros e o
recorte, e as unidades ligadas que quem só lê vê --, o cadastro, o detalhe, as opções e os ajudantes do repositório);
`RegraDaMensagemTest`, `MensagemAlvosTest` (os ids das linhas não giram, a chave que não vem, o estado relido com a trava),
`MensagensPeloControllerTest` (o D600, o D256, a tabulação, o dia que passou) e `TelasDasMensagensDoErpTest` (o editar sem a situação e o
"Ativar" da lista). Os da fatia com os da `RegraDoTexto` e do D344, depois da verificação, 281 de 281 no MySQL e no PostgreSQL (1 pulado, de
antes). E o ensaio novo
`scripts/ensaiar-mensagens-adm-api.php`, 31 de 31 (22 recusas e 9 controles, conferidos no banco) contra o banco de contrato
no MySQL e no PostgreSQL, registrado no CI, na lacuna, no `medir-telas-no-postgres.sh` e no ciclo; as sete rotas, no
`ensaiar-permissao-adm-api.php`, no `conferir-rotas-sem-login.php` e no `module/ISConfiguracao/config/rotas-publicas.php`; nove linhas novas no
`conferir-recorte-por-unidade.php` (seis da API, a do editar do ERP e as duas da verificação, cada uma arrancada para ver o
vermelho); o `conferir-cobertura-por-par.php` de 361 para 370 pares (e 122 sem menção, de 124).

**Os mutantes**, cada um sozinho e devolvido byte a byte (39, todos vermelhos -- os 13 últimos da verificação):

| Mutante | Vermelho |
|---|---|
| a guarda de administrador do cadastro, do estado e do PUT, cada uma arrancada | `testEscreverPedeOPrivilegioEOAdministrador` (3) |
| a guarda de ler da lista arrancada | `testLerPedeOPrivilegioDeLer` |
| a guarda de escrever do cadastro arrancada | `testEscreverPedeOPrivilegioEOAdministrador` |
| o 409 do mesmo estado arrancado no serviço | `testOMesmoEstadoE409`, `testOEstadoERelidoComATrava`, `testOAtivarDaListaEOMesmoEstado` |
| o `status` aceito no corpo | 4 casos do 422, nas duas regras |
| o `id` aceito no corpo | 4 casos do 422 e `testOCadastroExigeTodos` |
| o dia sem conferir se existe | o 422 do filtro e do corpo |
| a cor fora da lista aceita | o 422 da cor (e o 500 de 19 casos que gravam) |
| a unidade não oferecida aceita | 4 casos do 422 |
| o PUT sem as ligadas | `testOPutMantemAInativaLigada` |
| o momento com `>` no lugar de `>=` | `testOMomentoPeloRelogio` e 3 outros |
| o dia de hoje recusado | `RegraDaMensagemTest::testOCorpoQueServe` |
| o recorte do detalhe e o da lista de quem só lê, cada um arrancado | `testQuemSoLeVeOQueChegaAPropriaUnidade` (2) |
| a edição do ERP repassando o status (o D600) | `testAEdicaoNaoDesfazADesativacao`, `testOFormularioDoErp` |
| o editar do ERP sem as inativas ligadas (o D256) | `testAEdicaoNaoTiraAInativaLigada` |
| apagar e regravar todos os alvos (o M9) | `testOsIdsDasLinhasNaoGiram` |
| a chave que não vem apagando os alvos | `testSemAChaveOsAlvosFicam`, `testOPutMantemAInativaLigada` |
| o estado sem a releitura com trava | `testOEstadoERelidoComATrava` |
| o "Ativar" da lista trocado | `testOEstadoMudaSoPelaLista` |
| o formulário do ERP sem os validadores da API | 6 casos do 422 do título |
| o mesmo apelido nas duas subconsultas da unidade (abaixo) | `testQuemSoLeVeOQueChegaAPropriaUnidade` |
| o recorte da sessão arrancado do repositório | `testQuemSoLeVeOQueChegaAPropriaUnidade` |
| o momento com `>` no repositório reescrito (abaixo) | `testOMomentoPeloRelogio` e 3 outros |
| o validador do dia que passou arrancado do formulário do ERP | `testODiaQuePassouERecusadoComoNaApi`, `testODiaQuePassouNoFormularioDoErp` |
| a `diaQuePassou` sem o dia atual | os dois acima e `testODiaQuePassouSoServeSeForOMesmo` |
| o editar do ERP sem o dia atual | `testODiaQuePassouERecusadoComoNaApi` |
| o `ultimoQueFoiAoAr` sem o recorte, sem a unidade, sem o grupo, com o desativado (4) | `testOUltimoQueFoiAoArSegueOsFiltrosEORecorte` |
| o controller passando o `ultimoQueFoiAoAr` sem o recorte | `testOUltimoQueFoiAoArSegueOsFiltrosEORecorte` |
| o recurso de quem só lê com todas as unidades; o `outrasUnidades` sempre 0 (2) | `testQuemSoLeVeSoAPropriaUnidadeEntreAsLigadas` |
| a `RegraDoTexto` recusando o sinal também na exceção; a regra da mensagem voltando a recusá-lo (2) | `testOCadastroNasceAtivoEDizParaQuem`, `testOCorpoQueServe` |
| a `RegraDoTexto` deixando de recusar o sinal no `conferir` de sempre | 8 casos do catálogo, dos pacotes e da `RegraDoTexto` |

**O que o ciclo achou, e foi consertado:** o primeiro `conferir-tudo.sh` saiu com quatro portões vermelhos, todos pela consulta
da lista da API -- a concatenação de fragmentos (o portão do D73, de 25 para 29), a contagem e a lista lidas como um `GROUP BY`
(o auditor do PostgreSQL), o apelido sem `FROM` no ajudante (o portão do booleano em coluna inteira) -- e por um caminho de
documento sem a pasta do módulo. A consulta passou a ser montada num método só, no molde do `ArquivoRepository::listagemDaApi`,
com a marca de agrupamento conferido; e, na releitura, um defeito de verdade: o filtro de unidade e o recorte de quem só lê
usavam o mesmo apelido nas subconsultas, e a lista de quem só lê filtrada pela própria unidade daria erro da DQL (500). Agora
são duas constantes com apelidos próprios, e o caso entrou no `MensagemApiTest`.

**A verificação da fatia** (sete achados, todos confirmados no código e tratados): (1 e 3, o mesmo) a tela antiga aceitava o
dia que passou, e o `f2` dizia "a mesma regra da tela antiga" -- a recusa passou a morar na `RegraDaMensagem::diaQuePassou`,
chamada pela API e pelo `MensagemFilter` (o D601); (2) o "Provado no ciclo" daqui media o repositório de antes da reescrita
da consulta, e o texto dizia seis linhas novas no portão do recorte, quando eram sete -- o ciclo inteiro rodou de novo
depois de todos os consertos (abaixo); (4 e 7) o `ultimoQueFoiAoAr` ignorava os filtros da lista, e o recorte dele não tinha
caso -- passou a sair da mesma consulta da lista, com o título, a unidade, o grupo e o recorte, e ganhou caso, mutantes e
linha no portão; (5) quem só lê via as outras franquias ligadas ao aviso -- agora vê só a dele pelo nome, e as outras
contadas em `outrasUnidades` (a leitura da decisão 2, que se desfaz tirando um parâmetro); (6) a convenção da API não dizia
que os avisos aceitam `<` e `>`, e a regra deles copiava a `RegraDoTexto` -- a exceção está em `docs/convencao-api.md`, e a
regra chama a peça (`RegraDoTexto::conferirComSinalDeTag`). Sem pergunta nova ao dono.

**O que não se prova aqui:** a tela (a fatia 38) e o navegador; a faixa na agenda nova e o `GET /adm/api/agendas/avisos` (a
39); a corrida de duas abas pelo HTTP (o estado relido depois da trava está provado pelo serviço, com a desativação de outra
aba entre a leitura do controller e a escrita); e a cópia de produção (o ensaio e os testes rodam nas fixtures, que não têm
avisos: cada caso cria os seus).

**Provado no ciclo, depois de todos os consertos** (o da consulta e os sete da verificação; a primeira rodada, de 4.696
testes, media o repositório de antes da reescrita da consulta, e por isso não conta): caracterização 4.700 em cada banco (11
pulados), contrato 74 e 74, os 21 ensaios nos dois bancos com saída 0 (e o `medir-adm-api-no-postgres`), o `conferir-tudo.sh`
com 65 portões verdes e o vermelho conhecido de sempre (o D50), e o front, 2.910 de 2.910, com o build e o lint em 0. À
parte, os portões que a fatia toca, cada um com saída 0: o da ACL da API, a cobertura por par, o inventário, as rotas órfãs,
as referências dos documentos, a remoção por POST, o recorte por unidade, a guarda do ERP, as rotas sem login, os dois de
booleano, a injeção de DQL, o `GROUP BY`, os escritores do horário e o passo 0 do corte.

**Pelo HTTP, contra o banco de contrato, depois da verificação** (25/10/2026, com um administrador temporário): 401 sem sessão e
403 de outra origem; a lista e as opções com 200; o cadastro com o dia de ontem, 422 no `dia` ("de hoje em diante"); com o de amanhã,
201, o texto `<b>` guardado como veio; o `status` no corpo do PUT, 422 dizendo por onde o estado muda; o PUT vazio, 400; o título,
200; desativar, 200, e de novo, 409 `mesmo_estado`; o aviso que não existe, 404. **E a agenda antiga:** um aviso de hoje com `<i>` no
título e `<script>` no texto saiu escapado no mural (`&lt;script&gt;`), com a cor "urgente" na classe `note-danger` que a tela antiga
imprime; na lista antiga dos desativados, o "Ativar" é um formulário POST, e o desativar continua pelo `modalRemoverPorPost`. Nenhum
defeito. O banco de contrato foi remontado depois.

## 25/10/2026 -- MENSAGENS-TELA (a fatia 38 da fila das telas que faltam)

**O que muda para quem usa:** Configurações › Mensagens abre a tela nova, e não mais a tela que dizia "ainda roda na versão
antiga do ERP". A primeira linha diz o que ela é -- "Avisos que aparecem no topo da agenda da equipe, no dia marcado." (o mural
da agenda, e não marketing: a resposta 211 a). A lista abre nos ativos de hoje em diante; cada linha diz o dia, o selo do
momento ("no ar hoje", "agendado", "passou", "desativado", e "todo dia" no aviso sem dia que a tela antiga ainda grava), a cor
pelo nome e pela forma do trilho (Informação fina, Atenção grossa, Urgente dupla) e para quem, curto ("toda a equipe, em todas
as unidades", "Gerente unidade, em 3 unidades · 1 inativa"). Vazia, ela diz "Nenhum aviso de hoje em diante. O último foi ao ar
em 13/07/2026." e oferece "ver os que passaram". A busca tem título, quando, situação, a unidade onde aparece, o grupo e o dia
(pelo calendário). O aviso aberto mostra a prévia -- como sai no topo da agenda, com as quebras de linha e o `<` como texto --, o
dia por extenso, as unidades com a inativa marcada, os grupos com "não abre a agenda" e "ninguém ativo", e o aviso quando ninguém
o vê. Só o administrador com o privilégio de escrever cadastra, altera, desativa e ativa; quem não é administrador vê o que chega
à unidade dele (as outras unidades só contadas) e lê por que não altera. O formulário pede título, texto, cor, dia, onde aparece
e para quem -- as duas escolhas obrigatórias, nenhuma marcada no cadastro --, com a prévia viva embaixo; re-datar é trocar o dia
(o "enquanto isso" da pergunta 3). Desativar pergunta no próprio botão e diz o alcance ("Sai do topo da agenda agora, para toda a
equipe, em todas as unidades."); ativar não pergunta. O 422 vai ao campo (o dia que passou, pela frase do servidor), o 409 relê o
aviso, o 403 diz "Não permitido.". O servidor é o da fatia 37, sem mudança. A faixa na agenda nova é a fatia 39.

**Débitos:** o **D377** ganha um PARCIAL -- na tela nova, os grupos que não abrem a agenda vêm marcados; a tela antiga segue sem a
marca. Um débito novo, o **D602**: cinco formulários (`Catalogo`, `Clientes`, `Convenios`, `Cupons`, `Sorteios`) levam o foco ao
campo recusado antes de a recusa estar na tela, e o leitor de tela anuncia o campo sem a frase -- achado aqui pelo amplificador dos
efeitos e consertado nos avisos. Nenhuma pergunta nova ao dono: as perguntas 3, 4 e 5 do desenho seguem no "enquanto isso".

**Os testes:** `frontend/src/telas/formularioDaMensagem.teste.ts` (21 casos), `MensagensLigado.teste.tsx` (30),
`MensagensLigado.contrato.teste.tsx` (12) e o `Console.teste.tsx` (6 novos; a conta dos destinos na tela não migrada de 18 para 17,
e os 13 casos que partiam das Mensagens passaram a partir de Configurações › VIP Class, ainda não migrada), verdes também com o
`AMPLIFICAR_EFEITOS_MS=50`. As constantes e as frases da regra da tela ficam presas ao servidor pelo caso novo
`RegraDaMensagemTest::testOFormularioDaTelaDizOQueARegraDiz`, que lê o arquivo da tela. A prévia do Claude Design,
`frontend/.design-sync/previews/Mensagens.tsx`, tem 10 células num cenário só (25/10/2026: a reunião no ar hoje, o inventário de
03/11 em 3 unidades com a Trindade fechada, a campanha de 01/12; os passados de 07/09 e 13/07, e a busca "coloração" que diz o
13/07), conferida pelo `tsc` contra os tipos da API; a célula do cadastro não migrado da `TelaNaoMigrada.tsx` passou das
Mensagens à VIP Class.

**Os mutantes**, cada um sozinho e devolvido byte a byte (40, todos vermelhos): o selo de cada um dos cinco momentos (5); a lista
vazia sem o último e com o último apesar do dia no filtro (2); o "para quem" curto sem a inativa, sem as outras unidades de quem só
lê, sem o nome do grupo e sem "toda a equipe" (4); o por extenso sem a inativa marcada, sem "não abre a agenda" e sem "ninguém
ativo" (3); o "ninguém vê" das unidades desativadas e sem conferir as outras (2); a escolha das unidades e a dos grupos vindo
marcadas, e os grupos sem a escolha conferida (3); a prévia sem a quebra de linha e a viva com a cor fixa (2); desativar sem
perguntar, o alcance fora da descrição e com o texto errado (3); o "não" sem devolver o foco, o voltar sem o foco na linha, o "ver
os que passaram" sem o foco no título (vermelho só com o amplificador), a busca que não renasce, o desistir do novo e o sair da
edição sem o foco (6); o foco no erro antes da marca -- o conserto do D602 desfeito, vermelho com o amplificador (1); o aviso "todo
dia" mandando o dia vazio, os grupos sem o "todos", a cor sem o nome (3); o 409 que não relê (1); o Console sem a rota, escrevendo
sem ser administrador e sem dizer por quê (3); e, no servidor, o teto do texto e a frase do controle mudados na tela (2,
`testOFormularioDaTelaDizOQueARegraDiz`).

**O que não se prova aqui:** o navegador (a tela aberta contra o banco de contrato, a 375 e a 1.280 px e nos dois temas, é do
orquestrador, depois); o upload ao Claude Design; a faixa na agenda nova (a 39).

**Provado:** a caracterização dos avisos e das pesquisas (`RegraDaMensagem`, `MensagemApi`, `MensagemAlvos`,
`MensagensPeloController`, `TelasDasMensagens`, `RegraDaPesquisa`), 170 de 170 no MySQL e 170 de 170 no PostgreSQL; o front
inteiro, 2.979 de 2.979 (`--maxWorkers=2`), com o build e o lint em 0; os portões das referências dos documentos, dos fósseis, dos
links mortos, da guarda do caixa, da injeção de DQL, dos segredos e dos débitos repetidos, com saída 0.

**Depois da verificação** (as cinco lentes confirmaram 14 achados, 13 distintos; todos tratados): os passados ficam a um clique
também com a lista cheia, como o desenho pede; a lista vazia dos desativados não cita o último que foi ao ar, que é sempre um
ativo; a página que deixa de existir depois de desativar recua para a última que existe, em vez de dizer "nenhum" com o total
acima de zero (o irmão nos arquivos é o **D603**, novo); a tela relê a lista e o aviso aberto na virada do dia, sem "carregando" e
sem passar por cima do formulário nem de uma gravação; a 375 px a tabela não rola mais de lado (sem o mínimo de 30rem do catálogo,
e a cor sai da lista no estreito); o "sem título" e o "sem texto" da prévia no `--tinta-2` (3,96 e 4,24:1 no escuro, agora 6,04 e
6,48); o título da prévia viva passou a h2; a lista dos grupos ganhou o espaço de baixo no cartão "Para quem"; e o título do caso
do `Console.teste` e a crônica dos destinos dizem 17. Casos novos: a prévia viva que segue o texto (no cadastro e na edição), o
grupo desativado já ligado no detalhe e nas caixas, os passados com a lista cheia, a frase dos desativados, a resposta velha do
detalhe (depois de abrir outro e depois de voltar, também a falha), a página que recua, e a virada do dia (a hora, a releitura, o
reagendar, o formulário aberto, voltar e a gravação no meio). Mutantes novos, cada um sozinho e devolvido byte a byte: 19, todos vermelhos -- o "ver os que passaram" fora da lista cheia; o último citado nos desativados; a prévia viva com o texto vazio e com o texto antigo (2); a resposta do detalhe sem a guarda, a falha sem a guarda e o voltar sem gastar o número (3); o grupo sem a marca "inativo" no detalhe e como ativo nas caixas (2); a prévia viva em h3; sem o recuo da página e o recuo de uma só (2); a virada sem agendar, sem reagendar, sem reler o aviso, sem reler a lista, relendo por baixo do formulário, sem a guarda da escrita e sem os 5 s (7).
O CSS não se mede no vitest (o `App.css` chega vazio): a tabela a 375 px, o contraste e o espaço do cartão ficam para o navegador.

**No navegador, depois da verificação** (25/10/2026), contra o banco de contrato, como um administrador temporário, com três avisos
semeados pela API (um de hoje, um de amanhã só para uma unidade e um grupo, e um mudado no banco para 3 dias atrás), a 375 px, a
1.280 px e nos dois temas: a lista em "de hoje em diante" com os selos "no ar hoje" e "agendado" e o "para quem"; o "ver os que
passaram" com o foco no título; o detalhe com a prévia como sai na agenda; desativar pela pergunta no botão ("já foi ao ar: desativar
só o tira dos ativos"), com o foco no "ativar"; o cadastro recusando no próprio campo o dia, as unidades e os grupos não escolhidos, a
prévia viva com o `<` como texto, o formulário largo bem distribuído, e o aviso cadastrado com a frase e o foco no título. **Um
defeito, consertado aqui:** a 375 px a tabela ainda rolava de lado dentro do cartão, com o "para quem" fora da vista e o título
quebrado no meio da palavra -- tirar a coluna da cor não bastava (título, dia, selo e "para quem" somavam mais de 400 px num cartão de
~315). No estreito só fica a coluna do título, e o dia, o selo e o "para quem" descem para debaixo dele; no largo esse resumo não
existe, e o leitor de tela lê um ou outro. Medido: a tabela de 344 px num contêiner de 344, sem rolagem; a 1.280 px, as cinco colunas e
nenhum resumo à vista. Um caso novo confere o resumo (o mutante sem o "para quem", vermelho). O banco de contrato foi remontado depois.

## 25/10/2026 -- MENSAGENS-MURAL (a fatia 39 da fila das telas que faltam)

**Para quem usa:** os avisos do mural voltam a aparecer para quem trabalha na agenda nova -- a resposta 210 (a) do dono. Nos
dias com aviso, uma faixa acima da grade mostra cada aviso numa linha, como o administrador o vê na prévia de Configurações ›
Mensagens: a cor pelo nome e pelo trilho, o título e o texto. Nos dias sem aviso, a faixa não existe. Cada pessoa pode
recolhê-la ("recolher até amanhã"), e ela volta aberta no dia seguinte. O mural é sempre o de hoje, como na agenda antiga: com
a grade noutro dia, a faixa diz "São os avisos de hoje, ...: o mural não muda com o dia da agenda." A grade desconta a altura
da faixa, e a página não passa a rolar junto com ela (o D322 não piora). O D604 (o M11 do desenho) fica PARCIAL até o navegador.

**O que mudou:**
- `GET /adm/api/agendas/avisos` (`Adm\AgendasController::avisosAction`), com a guarda da grade (`agendas/visualizar/ler`) e
  a mesma consulta do mural antigo (`MensagemRepository::listagemIndexUsuarios`, agora com o desempate pelo id e o dia de
  hoje lido uma vez pelo controller): os ativos de hoje -- ou sem dia -- para a unidade e o grupo da sessão; nada vem da
  query. O aviso sai por `MensagemJson::doMural`, como texto, com a cor pelo nome.
- A peça `AvisosDoDia` (a `PreviaDoAviso` da fatia 38) e o `mural` da `AgendaDeHoje`, depois do cabeçalho e antes da grade,
  com a altura medida em `--altura-da-faixa`, que o `max-height` da grade desconta no Console e na Home.
- O `AgendaLigada` pede o mural UMA VEZ ao abrir, antes da grade, e na virada do dia -- e não a cada minuto. O que falhou
  vai de novo a cada minuto com a aba à vista, até vir, e uma linha diz que os avisos não chegaram; a grade que diz outro dia
  do servidor o pede de novo, e a virada que ainda traz o dia de antes também; a falha tira a faixa; só a resposta do último
  pedido vale. O recolhido guarda o dia do servidor no navegador, por pessoa e unidade.
- O dublê do `fetch` ganhou o `foraDaFila`: o pedido a mais não anda as filas do `AgendaLigada.teste`, do `Console.teste` e
  do `ConsoleLigado.teste`.
- As prévias: a `AgendaDeHoje` com um e com dois avisos (a `ManhaDeSabado` é a sem aviso), e a `AvisosDoDia` (um, dois,
  recolhida, a grade noutro dia). O upload ao Claude Design fica com o orquestrador.
- Os portões: a rota no `conferir-rotas-sem-login.php` e no `ensaiar-permissao-adm-api.php`; a linha nova do
  `conferir-recorte-por-unidade.php` (o mural lê a unidade e o grupo da sessão); a cobertura por par (o ISCadastro de 130 para
  131, os sem menção de 122 para 119, e os dois homônimos `listagemIndexUsuarios` de Arquivo e de Assistente nos falsos
  positivos). O ensaio das mensagens ganhou os casos 32 a 34 (34 de 34).

**Provado:** `AgendaAvisosTest` (8 casos) nos dois bancos, e os vizinhos -- os avisos, a grade, a agenda antiga, o recorte da
agenda, o texto das telas -- 293 de 293 no MySQL e 293 de 293 no PostgreSQL (2 pulados, os de sempre); pelo HTTP, contra o
banco de contrato, o `ensaiar-mensagens-adm-api.php` 34 de 34 e o `ensaiar-permissao-adm-api.php` 126 de 126, nos dois bancos;
o front inteiro, 3.019 de 3.019 (`--maxWorkers=2`), com o build e o lint em 0, e os arquivos da agenda com o amplificador dos
efeitos (`AMPLIFICAR_EFEITOS_MS=50`), 209 de 209. Mutantes, cada um sozinho e devolvido, 22, todos vermelhos: no servidor, as
duas guardas, a unidade vinda da query, o recorte da unidade e o do grupo abertos, o dia de hoje virando "de hoje em diante",
o desativado entrando, o desempate invertido, o dia pedido ignorado e a cor crua (10); no front, o mural a cada minuto, sem a
virada, recolhida em qualquer dia, a altura sem a variável e o CSS sem o desconto, a falha que deixa o de ontem, o que falhou
sem voltar, a resposta velha vencendo, a faixa nos dias sem aviso, sem a frase do outro dia, a grade de outro dia sem repedir
e a frase que fica com a faixa recolhida (12).

**O que não se prova aqui:** o navegador -- a altura da grade com a faixa a 375 e a 1.280 px, os dois temas, o contraste da
faixa --, que é do orquestrador; o CSS não se mede no vitest, só a regra lida do disco. **Fica de fora:** a F4 do desenho (o
"usar de novo" ou o de/até), que espera a pergunta 3; as perguntas 3, 4 e 5 seguem no "enquanto isso".

**A verificação** (dez achados, oito distintos, todos confirmados e consertados antes do commit): o recolhido era do navegador, e
no balcão quem recolhesse escondia o aviso de quem entrasse depois -- agora é por pessoa e unidade, pelo `quem` que o
`ConsoleLigado` passa; o mural saía depois da grade e, com a sessão do PHP em fila, chegava acima da grade já pintada e a
empurrava -- agora sai antes, e a faixa é medida antes de pintar; a falha era muda e só voltava com a recarga da grade de hoje --
agora uma linha a diz, e o pedido volta a cada minuto; o relógio adiantado deixava o mural de ontem o dia todo -- agora a virada
que traz o mesmo dia repete; dois casos que não prendiam o vão da coluna e a altura que volta a zero; a prévia dos dois avisos
na ordem que o servidor não devolve; e um comentário de teste que prometia casos inexistentes. O D604 voltou de CORRIGIDO para
PARCIAL: o que só o navegador prova ainda não foi visto.

**No navegador, depois da verificação** (25/10/2026), contra o banco de contrato, como um administrador temporário, com dois avisos de hoje semeados pela API (um com `<b>` no texto), a 375 px e a 1.280 px e nos dois temas: a faixa acima da grade, os dois avisos na ordem do servidor, a cor pelo nome e o `<b>` como texto; o pedido dos avisos saindo antes do da grade; a altura da grade descontando a faixa (`--altura-da-faixa` de 182 px no celular; a 1.280x800 a grade termina em 777 px, sem rolar a página); o "recolher até amanhã" com o foco no "mostrar", a marca guardada por pessoa e unidade e a grade de volta à altura toda; e, com a grade no dia seguinte, a frase "São os avisos de hoje ...: o mural não muda com o dia da agenda", sem pedir o mural de novo. Nenhum defeito. A 375 px a grade ainda passa da dobra, como antes da faixa: o cabeçalho da agenda quebra em mais linhas que os 10rem da conta -- não é desta fatia. O D604 fica CORRIGIDO; o banco de contrato foi remontado depois.

## 25/10/2026 -- NOTIFICACOES-DESENHO (a fatia 40 da fila das telas que faltam)

**Só documentação: nada muda para quem usa.** Marketing › Notificações -- as campanhas, a mensagem que a unidade agenda para
uma lista de clientes -- ganhou o desenho próprio que o consolidado pedia: `docs/f3-telas-restantes/tela-notificacoes.md`.
A tela antiga esperava a decisão do WhatsApp, e a 136 (a) a deu em 11/10/2026; a conexão da unidade já está na tela nova
(as fatias 30 a 34).

**O que o desenho decide:** a tela nova cria campanhas de WhatsApp a clientes da unidade da sessão, em três partes numa
página só (a mensagem com a prévia como chega ao cliente e as etiquetas trocadas, os clientes com o "Contar" que devolve só
o número, e o conferir com o estado do WhatsApp da unidade); a lista de clientes não vai mais ao navegador (o D297); a
situação em palavras pelo servidor (agendada, atrasada, saindo com "N de M", parada, enviada, cancelada); o "Não receberam"
pela regra de hoje, como a 170 (c) mandou; editar só o que não começou a sair, sem mudar a lista; cancelar pela pergunta no
botão, com o alcance. A 147 vale só para o código do aplicativo: a campanha segue pelo WhatsApp da unidade. O push (o D461),
o SMS e a LGPD ficam fora, por ordem do dono. A fatia 92 se parte em três: o ERP antigo (F0), a API `/adm/api/notificacoes`
(F1) e a tela (F2), ~8.000 a 9.000 linhas no total (o número corrigido pelo precedente dos avisos, que saiu 1,5 a 2,1 vezes acima do previsto).

**Medido, só com SELECT agregado na `franquiabv_dev`** (MySQL e PostgreSQL batem): 2.787 campanhas; em 2026, 156 de WhatsApp
(16.679 envios, 10 unidades) e 49 de push (67.568 envios, 7 unidades), feitas por 16 pessoas em 12 unidades; 94 das 205
criadas de 0 a 59 minutos antes de sair (e 1 depois da própria hora); 4 com exatamente 200 contatos e 12 entre 190 e 199; 106 pessoas que não são
administradoras abrem a tela, todas com ler e escrever. O texto das campanhas é anonimizado na cópia e não se mede.

**Débito novo:** o D605 (o link da campanha não é conferido e vai ao `href` da lista e ao cliente). Os outros achados são
rótulos do desenho, ou já tinham número (o D418, a edição sem condição; o D419, a campanha presa em "enviando").

**Ao dono:** cinco perguntas no lote -- o push na tela nova, a campanha para colaboradores, o administrador e as outras
unidades, a pausa e a campanha que travou --, cada uma com o "enquanto isso"; nenhuma trava a tela.

**Provado:** os portões de documento, todos com saída 0 -- `conferir-referencias-docs.php`, `conferir-links-mortos.php`,
`contar-debitos.php` (603 débitos; 146 continuam de pé, o D605 entre os abertos) e `conferir-debitos-repetidos.php` (o
primeiro título do D605 casava com o D143, o D273 e o D590, assuntos distintos; o título trocou de palavras, e nenhum par
novo) --, e o `conferir-segredos.sh`. Nenhum teste rodou, porque nenhum código mudou.

**Depois da verificação** (25/10/2026, a mesma fatia; 31 achados confirmados, 27 distintos, todos tratados -- num
deles, uma das referências dadas por erradas, a do D605 no `CronJobService.php:1366-1368`, estava certa). Tudo continua só documentação. **O que mudou no desenho:** a F0 deixa de tirar a pausa da tela antiga -- o estado
fica numa lista fechada dos editáveis, só para o administrador, que é quem pausa hoje (o D418) --; a regra do link
(aparado, e só `http` ou `https`) vale só na campanha de WhatsApp, e o push fica como está, por ordem do dono (o D605 vira
PARCIAL depois da F0); a hora fora da lista e a etiqueta desconhecida deixam de ser recusadas na tela antiga (a nova avisa
a etiqueta); o estado do WhatsApp vem por um endereço das campanhas, e não pelo `GET /adm/api/whatsapp`, que dava 403 aos 2
gerentes do grupo 7 e abriria o rastro com os nomes; a `parada` só com a rede inteira sem envio, porque a rodada manda uma
campanha por vez e as outras esperam; o aviso das campanhas do mesmo dia, o dos 30 minutos, uma escrita por vez e o 409
`repetida`; a ponte "na tela antiga" e o `toHaveLength(17)` do Console na F2; o tamanho, ~8.000 a 9.000 linhas. **Medidas
corrigidas:** o intervalo entre dois envios fica gravado (o maior de 2026, 36 min; o desenho dizia que não se media);
editadas são no máximo 32, e não 48 (cancelar também grava a alteração); 94, e não 95, criadas na última hora; a última SMS
criada é de 2023; o alcance fora da unidade não é teto nas campanhas do administrador; o apêndice escreve cada consulta
inteira, e a do PostgreSQL que falhava foi consertada. **Débitos novos:** o D606 (a LGPD das campanhas) e o D607 (o SMS),
anotados pela ordem do dono; o D608 (a campanha sai pelo WhatsApp desligado: a rotina olha só a unidade fechada; o conserto
entra na F0); e o D609 (a atrasada sai dias depois, sem limite). **Ao dono, mais quatro perguntas** no lote: medir o texto
na produção, o limite por unidade e por dia, o envio de teste e o prazo da atrasada; e as cinco primeiras corrigidas (a
pausa é só do administrador; a parada tem caso medido). **Provado:** os portões de documento, todos com saída 0 --
`conferir-referencias-docs.php`, `conferir-links-mortos.php`, `contar-debitos.php` (607 débitos; 148 continuam de pé,
com o D608 e o D609 entre os abertos e o D606 e o D607 entre os adiados) e `conferir-debitos-repetidos.php` (os primeiros
títulos dos quatro casavam por palavras com o D273, o D274, o D300, o D351, o D543 e o D583, assuntos distintos; os títulos
trocaram de palavras, e nenhum par novo) --, e o `conferir-segredos.sh`. Nenhum teste rodou, porque nenhum código mudou.

## 25/10/2026 -- FIDELIZACAO-SAI (a fatia 41 da fila das telas que faltam)

**O que mudou para quem usa:** nada que se visse -- e era esse o ponto. Pela resposta 138 (a) do dono (*"A Fidelização sai,
junto com a rotina; a fidelização ao colaborador já está no Profissional Parceiro"*), saíram do ERP antigo o item escondido
"Fidelização de Clientes" do menu Relatórios, que levava a um controlador que nunca existiu, e a rotina das 03:10
(`relatoriosFidelizacaoAtualizar`), que gravava toda madrugada um arquivo por unidade que nenhuma tela lia. A fidelização ao
colaborador é a do Profissional Parceiro, na tela nova. Saíram junto a ação, a rota (o pedido é 404), a documentação da API,
a linha das rotas públicas, o `Form/Fidelizacao.php` órfão, a linha do `Rel-fidelizacao` no `menuAtivo` e as duas consultas
que ficaram sem chamador (`VendaRepository::fidelizacaoPorColaborador` e `ClienteRepository::fidelizacao`). **Ficaram** a
`fidelizacaoAtualizar` das 23:50, que é outra rotina (desfaz a fidelização do cliente sem retorno); o privilégio no cadastro
dos grupos; e os arquivos já gravados na produção -- nada foi apagado (o D610, com a pergunta no lote).

**No corte:** o primeiro bloco do passo 9 de `docs/procedimento-de-corte.md` e o recuo do passo 12 de
`docs/procedimento-de-corte-etapa-1.md` tiram a linha das 03:10 do crontab guardado da produção, como as duas de
13/10/2026 (fica com 22 das 25 linhas lidas em 27/08/2026); a tabela de `docs/f1-6-agendamento.md` a tirou. A produção e o
BVIP não foram tocados.

**Débitos:** o D105 e o D306 ficam PARCIAIS, com a marca do que foi feito (restam, no D105, os dois mortos escondidos das
fatias 90 e 91); o D610 é novo (os arquivos que ficam). **Ao dono, uma pergunta** no lote: os arquivos que a rotina gravou
na produção ficam ou saem (recomendado: ficam, como as tabelas dos painéis que saíram).

**Provado:** a caracterização inteira, **4.713 em cada banco** (MySQL e PostgreSQL, 11 pulados com o motivo de sempre), com o
`FidelizacaoDeClientesSaiuTest` novo (4 casos: a rota que não casa e a da irmã que casa; a ação, a rotina e o formulário que
não existem, e os da irmã que existem; o layout desenhado com o privilégio na sessão e sem o item; o `menuAtivo`) e o piso das
rotinas com token do `SegredosForaDoCodigoTest` descendo de 24 para 23. Pelo HTTP, sem token: a rota que saiu, 404; a irmã
das 23:50 e a da evasão, 401 (nenhuma rodou). O `ensaiar-crontab-do-passo-9.sh` com a linha das 03:10 e a irmã das 23:50 no
crontab inventado, e o caso novo do `grep -vE` do recuo da etapa 1, sai 0 no Git Bash e no contêiner. Mutantes, um por vez,
restaurados por sha1: a rota de volta (o caso e o `conferir-rotas-orfas.php` vermelhos), a ação de volta (o caso e o
`SegredosForaDoCodigoTest`), o item de volta ao menu (o caso e o `conferir-links-mortos.php`), a linha de volta ao
`menuAtivo` (o caso), a rotina de volta no passo 9, o filtro largo que pega a irmã, e a rotina de volta no recuo da etapa 1
(o ensaio do crontab, saída 1 em cada um). Os portões acompanharam: links mortos (2 conhecidos), cobertura por par (369
pares, 118 sem menção, ISApi 106), injeção de DQL (22), booleanos em consulta (8 inofensivas), caminhos no PostgreSQL (16),
rotas sem login, agendamento (só o vermelho conhecido do D50) e o inventário de ações (352 ações, 26 rotinas).

**Depois da verificação** (catorze achados confirmados, treze distintos, todos tratados): dois eram prova que faltava. A
página `cronjob-fidelizacao-relatorio` podia voltar à documentação da API com tudo verde, porque o `SegredosForaDoCodigoTest`
só cobrava rota sem página; agora cobra também a página sem rota (o mutante, a entrada de volta: vermelho). E a linha das
03:10 de volta à tabela do `docs/f1-6-agendamento.md` ficava escondida no `conferir-tudo.sh` atrás do vermelho conhecido do
D50: o `conferir-agendamento.php` ganhou o `--conhecidas=`, que tira da conta só a `conveniosAtualizarVencimento`, e o
conjunto passa a rodá-lo como portão comum (o mutante: a órfã acusada, saída 1). A view do relatório
(`is-relatorio/fidelizacao/index`, que desenhava o formulário que saiu e que nenhum controlador usava) saiu, com um caso novo
pelo resolvedor de templates (o mutante, a view de volta: vermelho). O D485 ficou PARCIAL: a `ClienteRepository::fidelizacao`
era sobra dele (da rotina das 23:30 do Dash. Colaboradores), e não da Fidelização, e o privilégio "Relatórios de fidelização"
é o quarto sem tela, que fica como os três pela resposta 182 (a). O resto foi texto: a seção 8 do `docs/f3-relatorios.md`
(a 138 não é a decisão 8 do Profissional Parceiro), o `docs/f1-4-consultas.md`, a tabela de destinos do D105, três contagens
e a seção dos portões da etapa 1, e dois comentários de portão. **Provado:** a fatia e os vizinhos (o
`FidelizacaoDeClientesSaiuTest` com 5 casos, o `SegredosForaDoCodigoTest` e o `FidelizacaoTest`), 32 de 32 em cada banco; o contrato do aplicativo, **74 e 74**, depois do `montar-banco-contrato.sh`; e o `conferir-tudo.sh` (66 portões ok, e nenhum
vermelho conhecido: o do D50 agora é tirado pelo nome), os portões de documento e de débitos (608 débitos, 149 de pé) e o ensaio do crontab, saída 0.

## 25/10/2026 -- REL-ESPECIFICACOES-EM-DIA (a fatia 41b da fila das telas que faltam)

**O que mudou para quem usa:** nada -- só documentação; nenhum código, teste nem esquema. As doze especificações dos
relatórios que faltam (`docs/f3-relatorios/spec-*.md`, de 02/10/2026: as onze e a da ordem) ficaram em dia com o que o
código já faz, nos oito desvios que a fila achou, para as fatias 42 a 75 partirem do código de hoje. Cada especificação
ganhou, no começo, um bloco "Em dia com o código", e as linhas do corpo que a 41b reescreveu levam a marca "(41b)" (as
outras citações de linha de código continuam as de 02/10/2026: procure pelo nome); o quadro dos oito está no começo do
`docs/f3-relatorios/spec-ordem.md`. (1) **O ciclo dos Royalties** se chama pelo mês em que começa, como a
`module/ISRelatorio/src/Regra/RegraDosRoyalties.php` e a tabela `royalties_aliquotas` já gravam (o de 16/09 a 15/10/2026 é
"2026-09"), e não pelo do vencimento: a R1 estende essa classe, e os exemplos e casos foram renomeados. (2) **As portas da
Indicação**: desde a 53 (c) o formulário grava a ligação e paga a comissão; a decisão 1 da especificação caiu, a dupla
contagem do amigo com a marca e a ligação foi tirada do texto, e o caminho de volta da I3 -- que apagaria, pela chave
estrangeira em cascata, os clientes amigos -- foi corrigido. (3) **O 4º argumento de `RegraDoPainelDaUnidade::interpretar`**
(o `aceitaComparar` da 137 (a)): o relatório o chamará com três, pelo `RecorteDoRelatorio::mes` que a C0 cria. (4) **As linhas do Console e do menu**: uma tabela de-para na
C5 do `docs/f3-relatorios/spec-comum.md`. (5) **O Parceiro**: a parte comum preserva a `travaDaRede` e a `filaDaRede.ts`; a
C7 que a fila cita é a do `docs/f3-relatorios/spec-ordem.md`, a adoção pelo Parceiro, e com a F4.4 commitada ela é a troca
do que as fatias comuns deixarem. (6) **O motivo dos Descontos**: a origem lê a marca do cupom em qualquer lugar e o prefixo do
convênio que já existe (`RegraDoItemDaComanda::PREFIXO_DO_CONVENIO`), pela 187 (c) e a 177 (c). (7) **O passo 0**: a 83 (a)
respondeu a autorização das cópias, e cada migração segue o laço do `docs/procedimento-de-corte.md`, que a etapa 1 copia, e
a regra das entregas depois da janela (`docs/procedimento-de-corte-etapa-1.md`). (8) **A R3 dos Royalties** está marcada
FEITA (688922c), com o que saiu diferente do escrito (sem semente, sem entidade, o autor em duas colunas), e a 1.5 reescrita:
a conferência pelo retrato continua decidindo, agora com a alíquota do ciclo.

**A verificação** (cinco lentes; 28 achados confirmados, 20 distintos, todos tratados -- um deles, o diff fora de `docs/`,
não é defeito: os pares do `conferir-debitos-repetidos.php` são dados, no molde das fatias de antes). A primeira passada
corrigiu os desvios pela metade: **a R2 tirava o ciclo da fatura pelo mês do vencimento**, o que pela convenção nova é o
ciclo SEGUINTE (o gerente veria a base do mês de depois) -- agora é o mês anterior (`cicloDoVencimento`); **o `montar` da R1
não lia a vigência** e a contagem de consultas do teste não a cabia, e o glossário da alíquota dizia "a de hoje, no
cadastro"; o cenário, o RM4, o RM6 e o `semFatura` seguiam com o nome antigo; **o filtro "sem a ligação" da Indicação não
tinha a coluna** na consulta, nem caso, nem mutante; **o corpo da D1 dos Descontos contradizia o bloco** (a constante nova,
o `'CUPOM VIP:X'` como manual, o `comBalcao` que só o bloco tinha); "não há C7" contradizia o `spec-ordem.md`; o C1 apontava
para as linhas que hoje são a `travaDaRede`; o D612 descrevia mal o risco; duas perguntas prometiam o que o código não faz
(os 183 amigos "já separados pela marca", que a etapa 3 apaga; e o aviso da porcentagem do convênio que o banco não guarda);
e os blocos deslocaram as linhas das especificações, e as 174 citações por linha entre elas e na fila foram remapeadas pela
diferença com o `7ee8ba5`, conferidas texto a texto. Mutantes novos, escritos nas especificações (nenhum roda nesta fatia):
o RM39 (a alíquota só do cadastro), o RM40 (o primeiro ciclo faturado pelo vencimento), o RM41 (o ciclo da fatura por
`substr`), o IND-M39 (o formulário sem o filtro da ligação) e o MD19 a MD22 dos Descontos.

**Débitos:** cinco REGISTRADOS -- o D611 (o `podeAbrir` do Console sem o papel do menu: o D-a da parte comum, latente), o
D612 (a vigência da taxa de royalty sem o portão dos escritores que a R3 pedia), o D613 e o D614 (as duas perguntas) e, na
verificação, o D615 (o amigo sem a ligação, que ainda perde quem indicou na adesão: a decisão 14 pela metade). Os pares que
o `conferir-debitos-repetidos.php` achou com débitos antigos são assuntos distintos, e entraram na lista dele com o motivo.
**Ao dono, três perguntas** no lote: o amigo do formulário com a ligação conta em qual porta (recomendado, depois da
verificação: gravar a porta e fazer dos 183 antigos linhas de convite, a única que guarda o passado separado); a linha do
convênio com o desconto do balcão conta como convênio, como manual ou dividida (recomendado: dividida, pela porcentagem de
hoje, que o banco não guarda da época); e o amigo sem a ligação guarda quem indicou como (recomendado: uma linha de convite
sem comissão, daqui em diante). Nenhuma trava fatia.

**Provado:** medido na cópia (`franquiabv_dev`, MySQL, sessão só de leitura, só contagem): `royalties_aliquotas` com 0
linhas; 183 amigos do formulário com a marca, 0 com a ligação; 10 linhas em `indicacoes`, nenhuma de outubro; 0 linhas
"Convênio X; ..." do balcão e 0 do cupom no item do convênio. Cada citação de arquivo e linha dos blocos foi lida no código
de hoje. Na verificação, também só contagem: dos 183 amigos com a marca, 110 com `status = 0` e 73 com `status = 1`, e 1
com quem indicou inativo hoje; `royalties_aliquotas` ainda com 0 linhas. Os portões: `conferir-referencias-docs.php`,
`conferir-links-mortos.php`, `contar-debitos.php` (154 de pé: 76 abertos, 4 medidos sem conserto, 74 parciais),
`conferir-debitos-repetidos.php` (nenhum par novo), `conferir-passo-zero-do-corte.php` e `conferir-segredos.sh`, todos
saída 0 -- **com a ressalva de que nenhum dos
dois primeiros lê `docs/f3-relatorios/`** (o de referências lê `docs/*.md`, e o de links mortos, o `module/`): o "saem 0"
deles cobre o `docs/debitos-tecnicos.md` e este arquivo, e não as especificações. As especificações foram conferidas por
um roteiro de uma vez, no rascunho: as citações por linha remapeadas apontam para o mesmo texto de antes (237 pontas
iguais; as 17 que diferem são as linhas que a própria 41b reescreveu, conferidas à mão), e os caminhos citados nas linhas
novas existem, menos os que as fatias criam.

## 25/10/2026 -- REL-BASE (a fatia 42 da fila das telas que faltam)

**O que mudou para quem usa:** nada na tela -- nenhum relatório novo, nenhuma rota nova, nenhum esquema. Nasceu a base que os
dez relatórios que faltam vão usar (a C0 e a C1 de `docs/f3-relatorios/spec-comum.md`): quem vê o quê em cada relatório, numa
tabela só (`module/ISRelatorio/src/Regra/PoliticaDoRelatorio.php`, as doze políticas das respostas do dono de 01/10/2026 --
o mês do painel, o caixa só no dia, o colaborador só a si mesmo onde há linha própria, a Cota-parte v2 e os Royalties só do
administrador); a regra que lê a tabela pelas marcas da sessão, a unidade do filtro e o período -- o mês do painel, chamado
com três argumentos, e o intervalo de/até com o teto e o piso (`RecorteDoRelatorio.php`); e o controller-base
(`module/ISRelatorio/src/Controller/Adm/RelatorioAdmController.php`, abstrato e não `final`), com as cinco guardas numa ordem
só (sessão 401, privilégio 403, papel 403 `papel`, período 422, unidade 422), a sessão solta só depois delas, o gancho das
guardas próprias, a lista de unidades do filtro e a fábrica comum. O Profissional Parceiro passou a delegar a essa regra o
escopo, o menu e a unidade do filtro, e a ler o nome das unidades por `UnidadeRepository::nomesDoCadastro`, que era o método
privado dele: o comportamento dele não mudou (os testes dele ficaram verdes sem mudança), e a `travaDaRede` ficou nele. A rota
`adm-api-relatorios` e a linha de `rotas-publicas.php` nascem com o primeiro relatório (a fatia 47).

**Os portões passaram a tirar a lista do `module.config.php`:** o `conferir-acl-da-api.php` segue a guarda até a classe-mãe
(um nível; imprime `delegadas a classe-mae`); o `conferir-recorte-por-unidade.php` ganhou as vigias da regra e da base e o
bloco derivado -- toda ação de todo controller da família tem a guarda da base uma vez, e o conjunto dos controllers é o das
políticas ligadas (senão, saída 2); o `conferir-rotas-sem-login.php` aceita a rota da família quando o controller é da base;
o `ensaiar-permissao-adm-api.php` ganhou a lista derivada e a parte 3 (o privilégio não basta: a política recusa pelo papel,
e o caixa recebe o recorte dele); e o `medir-adm-api-no-postgres.php` concede ao `gerente.teste` o privilégio dos relatórios
antes de entrar -- **o D616, corrigido: as oito rotas do Parceiro passaram de 403 a 200 no PostgreSQL**, pela primeira vez
medindo a consulta --, cobra a medida de toda leitura da família e já tem a parte do administrador (o grupo temporário
90006191) e o caminho com ponto. O `conferir-cobertura-por-par.php` ganhou o par `UnidadeRepository::nomesDoCadastro` (369 →
370, o sha1 volta ao de antes tirando-o) e o ISRelatorio, 98 → 107 chamadas.

**Provado:** `RegrasDoRecorteDoRelatorioTest` (118 casos, sem banco: a matriz das 12 políticas por 8 usuários, os
invariantes, a unidade do filtro nos quatro escopos, o intervalo, o dia, o mês igual ao `interpretar` com o `?comparar=`
ignorado, o despachante) e `RelatorioAdmControllerTest` (17 casos, em processo, com o trait `RelatorioEmProcesso` e os
usuários de `UsuariosDoRelatorio`); com os vizinhos (`RegrasDoProfissionalParceiroTest`, `ProfissionalParceiroTest`,
`SessaoApiTest`, `PainelDaUnidadeTest`, `VendasNoRelatorioTest`, `UnidadeApiTest`), 378 casos nos dois bancos, verdes (1
pulado, de antes: pede a cópia). Os 19 mutantes da especificação (M1 a M19), cada um sozinho e devolvido pelo sha1, ficaram
vermelhos nos casos que ela diz, e os dos portões também (M2, M3, M5 e M11 no portão do recorte, pelo escopo, pela unidade e
pelo período; M16 e M17 pelas vigias da base; M13 no da ACL; M19 com saída 2 no do recorte e saída 1 no ensaio de
permissão, a parte 3 com 404). Os ensaios: o de permissão no MySQL e no PostgreSQL, "recusou 126 de 126", o mesmo N de
antes, e a parte 3 com zero relatórios; o medidor no PostgreSQL saiu 0, com "36 de 197" no trabalho. **O oráculo da tabela
1.1** (por SELECT agregado na cópia, `franquiabv_dev`, numa transação só de leitura; cada combinação de marcas passada pelo
`RecorteDoRelatorio::escopo`): Atendimentos 105 na unidade e 3 só ele; Caixa 105, 60 no dia e 2 recusados; Indicação 99 na
unidade; Descontos, NPS, Produtos e Canceladas com 1 recusado cada (104, 106, 106 e 107 na unidade); Retirada 106; Royalties
ninguém; Cota-parte 108, 58 no dia e 180 só ele; a v2 recusa os 346; o Parceiro 106, 149 só ele e 27 recusados (os do D309).
Bate com a tabela de 02/10/2026, menos o caixa, que mudou nos dados: 62 → 60 no dia do Caixa, e 2 → 1 caixa na Indicação. O
Parceiro da unidade 1, julho de 2026, em processo na cópia: 1,31 s frio e 0,14 s quente (a C1 pedia 1,5 a 2,0 s pelo HTTP;
a medida aqui é em processo). Os 66 portões, nenhuma falha inesperada.

**A verificação (cinco lentes) confirmou 19 achados, e os 19 foram tratados.** Os que mudaram código:

- **O medidor do PostgreSQL voltaria o D616 calado:** o 403 ou o 422 de uma rota da família contava como guarda, com saída 0.
  Agora toda leitura da família que não chega ao 2xx é saída 1, menos o 403 `papel` do gerente num relatório só do
  administrador (que a parte do administrador pede de novo).
- **A concessão do medidor colidiria com a do CI** (o passo das telas e o `medir-telas-no-postgres.sh` já concedem ao
  `gerente.teste` toda funcionalidade `>= 90002000`, e a chave única recusaria a segunda linha): o medidor e a parte 3 do ensaio
  de permissão só inserem o que falta.
- **Uma rodada morta deixaria o gerente administrador** no banco descartável e quebraria as seguintes na chave estrangeira do
  grupo: a volta é o grupo da fixture quando o lido é o temporário, a parte 3 devolve antes de conceder, e a exceção do banco
  é "não medi".
- **Os ramos novos dos portões nunca tinham rodado** (o M13 provava o caminho antigo da ACL): as regras dos instrumentos
  passaram a um arquivo de funções puras (`scripts/regras-da-familia-dos-relatorios.php`), o portão da ACL aceita a raiz no
  argumento e o do recorte ganhou o `--so-a-familia <raiz>`, e o `PortoesDaFamiliaDosRelatoriosTest` (17 casos, sem banco)
  os roda com um relatório de mentira e um defeito por vez.
- **O bloco derivado do recorte só contava a guarda:** agora ela tem de ser a primeira instrução de cada ação, e nenhum método
  do controller da família lê `?unidade=` do pedido.
- **As vigias das regras puras liam até o fim do arquivo** (o fim do método não reconhecia `public static function`): a guarda
  arrancada e colada noutro método ficava verde (o V4 prova o defeito; o V3, o conserto).
- **O `/unidades` consultava as unidades duas vezes:** a lista do filtro é lida uma vez por pedido (`listaDoFiltro`).
- **A lista do filtro não desempatava:** duas duplas de unidades ativas da cópia têm o mesmo país, estado, cidade e bairro; o
  `popularCombobox` termina no id (o D617, registrado e corrigido).

Os de documento: a saída do ensaio de permissão (não é "a pior das três partes": a parte 1 sai antes), o caso da guarda
própria (prova só que o gancho é sobrescrevível; a ordem na base é da C3), o 90006191 na faixa de ids e as 14 chaves, e as 13
citações por linha do `spec-comum.md` que o item 7 deslocou, remapeadas e conferidas texto contra texto. **A medida do
Parceiro** que a C1 pede ficou comparável: o controller do HEAD e o de agora, alternados no mesmo processo na cópia (só
leitura), deram o mesmo corpo e o mesmo tempo (1,3 a 1,4 s na primeira, fria, e 0,14 a 0,16 s quentes, nos dois); pelo
HTTP seria entrar na cópia com a senha de uma pessoa, o que não se faz. O artefato do roadmap recebe esta entrada no commit,
com o hash.

**Provado no ciclo, em série:** a caracterização inteira, 4.867 casos em cada banco (11 pulados, os de antes); o contrato, 74
e 74; os 20 ensaios no MySQL e os 21 no PostgreSQL, todos com saída 0 (o de permissão "recusou 126 de 126" nos dois, a parte
3 vazia; o medidor com "36 de 197" no trabalho, a concessão 1 nova e nenhum resíduo depois: concessões, grupos temporários e
os grupos de volta conferidos no banco); os 66 portões, nenhuma falha inesperada, com os testes do front. Os 14 mutantes da
verificação (V1 a V14), cada um sozinho e devolvido pelo sha1, ficaram vermelhos no caso que os pega -- o V2 (o desempate)
nos dois bancos, e o V4 é o defeito antigo, verde de propósito. Débitos: 154 de pé.

## 25/10/2026 -- RESPOSTAS-254-266 (a fatia 40b da fila das telas que faltam)

**Só documentação, mais um roteiro de leitura**: nenhum código da aplicação, teste, esquema nem rotina mudou; nenhuma
campanha saiu, e nenhum provedor foi chamado. O dono respondeu no mesmo dia as 13 perguntas que as fatias 40, 41 e 41b
mandaram ao lote (254 a 266), e esta rodada as levou aos desenhos e aos débitos.

**O que muda para quem usa, quando as fatias chegarem:**

- **Campanhas (Marketing > Notificações)**, as 254 a 262: a campanha de push **acaba nas duas telas** (as gravadas ficam para
  ler, e a agendada antes da troca ainda sai); a campanha para a equipe fica; o administrador escolhe a unidade, e a
  campanha passa a ser dela, pelo WhatsApp dela -- "a rede inteira" sai; na tela nova, todos que fazem campanhas pausam e
  retomam (na antiga, só o administrador, como hoje); a
  campanha parada se encerra; um limite de mensagens por unidade e por dia, e o mesmo cliente não recebe duas campanhas no
  dia; um "mandar para mim primeiro" pelo WhatsApp da unidade; e a campanha atrasada há mais de 3 horas não sai sozinha. O
  desenho (`docs/f3-telas-restantes/tela-notificacoes.md`) foi refeito nas seções 1 a 5, e a fatia 92 da fila virou sete
  (92a a 92g), com a 92a e a 92b -- o ERP antigo e a rotina -- recomendadas primeiro.
- **O texto das campanhas** (a 259 a): o roteiro `scripts/medir-texto-das-campanhas.sql`, só SELECT numa sessão só de
  leitura, devolve só números (o tamanho, as etiquetas, a etiqueta que não existe, o emoji, as agendadas agora). O comando
  para o dono rodar na produção está no desenho; na cópia ele roda e dá os números do anonimizador (20 caracteres em todos),
  que não valem para a decisão. As expressões foram conferidas com oito textos de exemplo; o `LIKE BINARY` não separava a
  caixa na coluna `utf8mb4_unicode_ci`, e as contagens passaram ao `REGEXP_LIKE(..., 'c')`.
- **Fidelização** (a 263 a): os arquivos da rotina que saiu ficam na produção. Nada a fazer.
- **Programa de Indicação** (a 264 b e a 265 b): a porta do convite gravada daqui em diante; os amigos antigos do formulário
  viram linhas de convite, escritas uma vez, sem comissão, pela migração da I3 no passo 0 da janela (com o sistema parado);
  e o amigo de quem indica inativo ganha a linha sem a ligação. A especificação diz onde, quando e como se prova.
- **Descontos** (a 266 c): a linha do convênio com o desconto do balcão se divide pela porcentagem de hoje, e a tela diz
  isso. A especificação tem a conta, os casos e os mutantes.

**Voltou ao dono, numa pergunta só**: os dois números "por exemplo" das respostas (300 contatos por dia, 3 horas) e a
campanha que espera a vez na fila, que o prazo seguraria sem querer (1 das 13 de 2025). Até a resposta, os exemplos, e a da
fila sai.

**Débitos:** D609, D613, D614 e D615 decididos, com o código por fazer (marca "EM ABERTO", de pé); D610 decidido e fechado
(nada a fazer); D418 e D419 decididos (257 c e 258 a), o conserto na 92a e na 92b; D461 com a campanha de push encerrada e
o resto do push de pé; D297 com as respostas que eram dele. Nenhum débito novo.

**Medido** no `franquiabv_dev`, só SELECT agregado numa sessão `READ ONLY`: o atraso das campanhas por faixa e se a rede
mandava (A1, A2), as unidades do push de campanha (B1), o telefone de quem escreve nas campanhas (C1), o limite do dia com
300 (D1) e o mesmo cliente no dia (D2) -- no apêndice do desenho.

**O portão de repetidos** acusou três pares que as marcas novas aproximaram (D613|D615, D613|D614 e D544|D610, assuntos
distintos) e um par conhecido que deixou de aparecer (D460|D613, o título do D613 cresceu); os três entraram nos
`$conhecidos` do `scripts/conferir-debitos-repetidos.php` com o motivo, e o quarto saiu, como o portão pede. Tirado o
D613|D615 numa cópia do portão, ele volta a sair com 1.

**Provado:** os portões de documento (`conferir-referencias-docs`, `conferir-links-mortos`, `contar-debitos`,
`conferir-debitos-repetidos`) com saída 0 e o `conferir-segredos.sh` com 0; o `contar-debitos` com 153 de pé (69 abertos,
4 medidos, 80 parciais), um a menos que antes (o D610, fechado; os seis decididos com o código por fazer passaram de
abertos a parciais).

**A verificação em cinco lentes confirmou 25 achados (21 distintos), todos tratados, sem código:** a exceção da fila no
prazo da atrasada não tinha janela e soltava a campanha de sábado assim que a primeira de segunda saísse -- agora vale só
enquanto a rotina não parou desde o começo do prazo, e o prazo conta das 9h na campanha marcada fora do horário; a de
push que passa do prazo fica só com o cancelar; a pausa para todos ficou só na tela nova, como a pergunta dizia (a
antiga pausa como hoje); o "mandar para mim" iria ao provedor de verdade no ensaio e no navegador (o envio não lê o
endereço do loopback, e o provedor de mentira não aceita envio) -- a 92d ganhou o endereço, o envio de mentira e a guarda;
o "mesmo cliente no dia" só entre campanhas de clientes (o id do colaborador está na mesma coluna); a "encerrada à mão"
pelo último envio, e não pelo primeiro (medido: 0 enviadas do passado pegas pela régua nova, 1 pela antiga); a unidade da
campanha da equipe do administrador; o teto de 10 indicações por dia que contaria duas vezes o amigo de quem indica
inativo; a regra da lista filtrada dos descontos e as marcas no corpo da D1 a D3; as 31 citações por linha que os
blocos novos deslocaram, remapeadas (o `spec-ordem.md`, o `README.md` e as faixas da fila); a Q9 da fila marcada como
respondida, e a 75 sem a espera; o D297, o D605 e o D608 acertados; e a citação da 263 (a), textual. O roteiro da 259
parava na seção 5 com o cliente em latin1 (o `ERROR 3697`, reproduzido) -- agora é só ASCII, começa por `SET NAMES
utf8mb4`, termina com "=== FIM ===", sai igual em latin1 e em utf8mb4, e o comando do dono passou à forma do
procedimento (pela entrada do `ssh`, a saída na máquina dele); as etiquetas do contexto passaram a doze. Os mesmos
portões de documento com saída 0, e 153 de pé.

## 25/10/2026 -- NOTIFICACOES-ERP-ANTIGO (a fatia 92a da fila das telas que faltam)

*Pedido do dono: "continue, desta vez montando as telas que ainda não foram migradas. continue de forma autonoma até a
exaustão". O desenho é o `docs/f3-telas-restantes/tela-notificacoes.md`, seção 3, "92a", e as respostas 254 a 262.*

**O que muda para quem usa Marketing › Notificações (a tela antiga):**

- **O push de campanha acabou** (a 254 (c)): o formulário só oferece "WhatsApp", e o servidor recusa o push ("A campanha
  de push acabou: crie a campanha pelo WhatsApp."). A de push agendada antes da troca não se edita -- o lápis e o link do
  dia e da hora saem da linha dela --, e se cancela; a rotina ainda a manda, como hoje, e nada do OneSignal mudou.
- **O administrador escolhe a unidade** (a 256 (c)): a unidade da pesquisa do destinatário escolhido (a dos clientes ou a
  dos colaboradores) passa a ser a da campanha, que sai pelo WhatsApp dela e só alcança quem é dela. **"A rede inteira"
  saiu**: sem unidade, ou com uma fechada, a pesquisa e o "Salvar" recusam ("Escolha a unidade da campanha..."). O
  gerente e o caixa gravam a unidade da sessão.
- **O formulário conferido no servidor**: o título até 100 (o de 101 dava erro), o dia que existe, a hora no formato, o
  texto dentro da coluna, e o link aparado e só `http://` ou `https://` (o D605); a lista faz link só do que abre, e o
  `javascript:` gravado vira texto. A etiqueta que a campanha não troca (`#ALGO#`) é avisada, e não recusada.
- **A edição** só grava o que não começou a sair, decidido no banco (o D418): se a rotina assumiu a campanha no meio, a
  tela diz que ela já saiu, e nada se regrava. A situação da edição só entre "Aguardando" e "Agendado", e o `<select>`
  dela continua só ao administrador, como hoje (pausar para todos é da tela nova, a 257 (c)).
- **A unidade com o WhatsApp desligado não manda campanha** (o D608), mesmo com a chave gravada.
- O "Voltar" da etapa dos usuários fecha a etapa certa.

**Débitos:** o D605 e o D608 CORRIGIDOS; o D418 PARCIAL (falta a porta nova, o PUT da 92d). Nenhum débito novo, nenhuma
pergunta nova.

**A verificação** (cinco lentes, 8 achados distintos, todos tratados): o link recusa também o espaço e a quebra de linha
do Unicode no meio e os apara nas pontas; a porta prova que grava o título, o link e a hora aparados; o aviso da
etiqueta, o canal da criação só com "WhatsApp" e a edição sem mudança (agora sem depender do relógio) ganharam casos; o
desenho tinha perdido o título da 92b, que voltou; e o texto dos débitos foi acertado (os mutantes rodaram primeiro só no
MySQL; na verificação, também no PostgreSQL).

**Provado:** a família das campanhas e as vizinhas, 380 casos e 2.432 asserções no MySQL de fixtures e no PostgreSQL; 32
mutantes, um por vez e devolvidos byte a byte, todos vermelhos nos dois bancos, menos o da releitura do 0, que só se
prova no MySQL (o PostgreSQL conta as linhas casadas); o contrato do aplicativo, 74 de 74; os portões do
`conferir-tudo.sh` verdes depois de regerar o inventário e de subir a linha de base da cobertura por par (o par novo
`NotificacaoRepository::editarSeAindaNaoSaiu`); cinco vigias novas no `conferir-recorte-por-unidade.php`. Nenhuma rotina
de envio rodou; nenhum provedor foi chamado. **Não provado no navegador.**

**No navegador, depois da verificação** (25/10/2026), na tela antiga, contra o banco de contrato, como um administrador temporário
(e sem cron em nenhum contêiner, conferido antes): a criação só oferece WhatsApp, e o filtro da lista mantém o push para ler; o
seletor da unidade não tem mais "a rede"; o link `javascript:` recusado no próprio formulário com a frase; a pesquisa sem unidade
recusada com a frase. **Um defeito, consertado aqui:** o "Finalizar e salvar" voltava "Escolha a unidade da campanha" sem gravar, com
a unidade escolhida -- o layout ainda servia o `mod-marketing.js?cache=164`, e o navegador que o guardara mandava o salvar sem a
unidade que o servidor novo exige do administrador. Na produção, todo administrador com o script em cache cairia nisso. O layout
passou ao `?cache=165`, e o `CampanhaAudienciaTest::testOScriptDaTela` confere o número. Depois: o mesmo pedido gravou, a página
recarregada pede o 165, e a campanha aparece na lista com o link `https` em `rel="noopener noreferrer"`. O banco de contrato foi
remontado depois.

## 26/10/2026 -- NOTIFICACOES-ROTINA-DO-DIA (a fatia 92b da fila das telas que faltam)

*Pedido do dono: "continue, desta vez montando as telas que ainda não foram migradas. continue de forma autonoma até a
exaustão". O desenho é o `docs/f3-telas-restantes/tela-notificacoes.md`, seção 3, "92b", e as respostas 258 (a), 260 (c)
e 262 (b).*

**O que muda para quem usa Marketing › Notificações (a tela antiga) e para a rotina das campanhas:**

- **A campanha atrasada tem prazo** (a 262 (b), o D609): a rotina não manda mais a campanha agendada que passou de 3
  horas do começo do prazo dela -- a hora marcada, entre 9h e 19h50 (a última rodada do dia); as 9h seguintes, se foi
  marcada fora disso (a de 05:30 conta das 9h; a de 19:55 e a de 20:30, das 9h do dia seguinte). A exceção é a que esperou a vez com a rotina mandando outras
  sem parar desde então (a fila): ela sai. Vale também para a campanha de push agendada antes da troca. A lista diz
  **"Atrasada"**, com o que fazer no selo (mudar a hora ou cancelar; a de push, só cancelar). Nada se apaga.
- **O mesmo cliente não recebe duas campanhas no mesmo dia** (a 260 (c)): o "Salvar" tira da lista quem já está em
  outra campanha de clientes daquele dia que ainda vai sair (a atrasada também), de qualquer unidade, ou já recebeu uma, e diz quantos saíram depois de
  recarregar; a rotina confere de novo na hora do envio. O colaborador e o cliente de mesmo número nunca se confundem.
- **Cada unidade tem um limite por dia** (a 260 (c)): 300 contatos, somando as campanhas de WhatsApp dela naquele dia
  (de clientes e da equipe). Acima, o "Salvar" recusa: "São N contatos, e cabem M em dd/mm/aaaa ...". A edição para
  outro dia passa pelas duas regras do dia novo.
- **A rotina relê a campanha a cada mensagem** (a 258 (a), o D419): quando a campanha parada for encerrada (o botão
  vem na tela nova), a rodada para no destinatário seguinte. O encerrar só vale se nada saiu desde que a tela leu.
- **No corte**: o `docs/procedimento-de-corte-etapa-1.md` ganhou, no passo 7, a leitura das campanhas na fila antes da
  troca (só SELECT), para o dono saber antes das 9h quantas o código novo vai segurar.

**Os números** (o prazo de 3 horas e o limite de 300) são os exemplos das opções: voltaram ao dono na pergunta 267, e
cada um é uma constante (`RegraDaCampanha::PRAZO_DA_ATRASADA_HORAS`, `::LIMITE_POR_DIA`).

**Débitos:** o D609 e o D419 PARCIAIS (falta a tela nova, a 92c a 92e; no D609, também o número do dono; no D419, a
retomada sem dono entre dois nós); o D297 com a 260 (c) registrada. Nenhum débito novo. Uma pergunta nova ao dono (no
D609): a campanha atrasada continua segurando o dia dos clientes dela e o limite da unidade?

**Provado:** a família das campanhas e as vizinhas, 447 casos e 8.222 asserções no MySQL de fixtures e no PostgreSQL (o
`CampanhaDoDiaTest`, novo, com o relógio do serviço e instantes montados sobre o dia de hoje; e a trava da unidade de
verdade, com outra conexão); 29 mutantes, um por vez e devolvidos byte a byte, todos vermelhos nos dois bancos; os 66
portões do `conferir-tudo.sh` sem falha inesperada, depois de regerar o inventário e de subir a linha de base da cobertura
por par (cinco pares novos do `NotificacaoRepository`). Nenhuma rotina de envio rodou; nenhum provedor foi chamado.
**No navegador**, logo abaixo.

**A verificação (cinco lentes; 18 achados, 11 distintos, todos tratados):**

- **A atrasada da noite com a rotina parada saía de manhã**: a exceção da fila aceitava a rede sem envio nenhum, e a de
  19:30 saía às 9h05 do dia seguinte. Agora a fila exige um envio, e a campanha marcada depois da última rodada do dia
  (das 19:51 às 20:00) conta o prazo das 9h seguintes.
- **A atrasada esquecida lia o histórico inteiro a cada rodada**: a leitura dos envios da rede não tinha teto e ia toda
  para a memória (com um ano, perto do limite de memória do PHP, e então nenhuma campanha da rede sairia). Agora ela vem
  em lotes de 500, e a conta para na primeira quebra.
- **Quem sobrou na lista de uma campanha já enviada ficava barrado no dia** sem ter recebido; agora só contam as
  campanhas que ainda vão sair. As frases dizem "já está em outra campanha de dd/mm/aaaa (por sair, atrasada ou já
  recebida)" e o que fazer.
- **A mensagem que saía no instante do encerrar apagava a marca da encerrada à mão**: agora ela fica contada e o último
  envio volta ao que a tela leu.
- **Os testes aguentam outra resposta da pergunta 267**: verdes com 6 h e 400, 1 h e 100, e 8 h e 150, nos dois bancos.
- **Casos novos** para o relógio do serviço no assumir, a de push de antes da troca (sai como hoje) e o encerrar
  durante um envio que falha; os três casos com o relógio cru, agora no relógio do caso.
- **Números de depois**: a família, 454 casos nos dois bancos; os 29 mutantes refeitos sobre o código novo e 10 novos,
  39 vermelhos no MySQL e no PostgreSQL, um por vez e devolvidos byte a byte; o contrato do aplicativo, 74 nos dois bancos;
  a cobertura por par com o par novo `guardarAMarcaDoEncerrar` (377).

**No navegador** (26/10/2026, depois da verificação), na tela antiga, contra o banco de contrato, como um administrador
temporário e sem cron em nenhum contêiner: com uma campanha de WhatsApp agendada para ontem às 10:00 e outra para
amanhã, a lista diz "Atrasada" (o selo vermelho, com "Passaram mais de 3 horas da hora marcada: a campanha não sai mais
sozinha. Mude a hora ou cancele.") na de ontem e "Agendado" na de amanhã; pelo formulário de três etapas, a campanha A de
amanhã às 14:00 para os dois clientes da unidade gravou, e a B, no mesmo dia às 16:00 e para os mesmos dois, foi recusada
com "Todos desta lista já estão em outra campanha de 27/10/2026 (por sair, atrasada ou já recebida) ...", sem gravar.
Nenhum defeito achado ali. O banco de contrato foi remontado depois.

## 26/10/2026 -- NOTIFICACOES-API-LEITURA (a fatia 92c da fila das telas que faltam)

*Pedido do dono: "continue, desta vez montando as telas que ainda não foram migradas. continue de forma autonoma até a
exaustão". O desenho é o `docs/f3-telas-restantes/tela-notificacoes.md`, seção 3, "92c", e as respostas 254 (c), 255 (b),
256 (c), 258 (a), 260 (c), 262 (b) e 267 (a).*

**O que muda para quem usa:** nada na tela antiga. Nasce a API de LEITURA das campanhas (`/adm/api/notificacoes`), que
a tela nova (as fatias 92e e 92f) vai usar: a lista com filtros e a situação de cada campanha em palavras (agendada,
atrasada -- no prazo ou fora dele --, pausada, saindo, na fila, parada, enviada, encerrada à mão, cancelada), calculada
pelo servidor com a mesma conta da rotina que manda; a campanha aberta, sem a lista de quem recebe; as opções do
cadastro (os horários, as etiquetas, os destinatários de cada um, as unidades só ao administrador, os filtros da
unidade escolhida e os números da regra); o "Contar" da audiência com o dia -- só os números: quantos, quantos já estão
em outra campanha do dia e saem, quantos ficam, se cabe no teto e no limite do dia da unidade --; e o estado do WhatsApp
da unidade da campanha, para quem faz campanhas e não tem o privilégio do WhatsApp. Quem não é administrador só alcança
a unidade da sessão. Nenhuma escrita: criar, alterar, cancelar, pausar, retomar, encerrar e o "mandar para mim primeiro"
são da 92d.

**A resposta 267 (a) do dono** (300 contatos por dia, 3 horas de prazo, e a campanha que só esperou a vez sai) era o que
a 92b já fazia: mudaram só os comentários e o desenho. A pergunta 268 (a atrasada segura o dia?) continua aberta, e o
"Contar" segue a conta de hoje (segura).

**Débitos:** o D609 PARCIAL com a marca nova (o número do dono e a situação na API; falta a tela, a 92e); o D419 e o
D297 com o que a 92c fez (a situação `parada` e o `podeEncerrar` no recurso -- o endereço do encerrar é da 92d --; o
"Contar" sem ids). Um novo, o D618: a busca das campanhas por
título e por texto acha sem acento no MySQL e, no PostgreSQL, só com o acento escrito (da família do D119). Nenhuma
pergunta nova ao dono.

**Provado:** o `NotificacaoApiTest`, novo, 54 casos, verdes no MySQL de fixtures e no PostgreSQL -- entre eles cada um
dos 23 filtros da pesquisa de clientes da tela antiga, que nunca tinham rodado no PostgreSQL, e todos serviram --; a
família das campanhas, do WhatsApp, dos cupons e dos sorteios e as vizinhas, 742 casos em cada banco; 31 mutantes, um
por vez e devolvidos byte a byte, vermelhos nos dois bancos; o ensaio de permissão da `/adm/api` (recusou 131 de 131) e o
das rotas públicas, nos dois bancos; os portões do `conferir-tudo.sh` (as rotas novas, seis vigias de recorte, a
cobertura por par de 377 para 381); o contrato do aplicativo, 74 de 74; o front, 3.051 de 3.051, build e lint 0. Nenhuma rotina de envio rodou; nenhum provedor foi chamado
(o estado do WhatsApp nos testes pelo cliente que anota). **Não visto no navegador**: a fatia não tem tela.

**A verificação (26/10/2026)**, em cinco lentes, confirmou 10 achados, todos tratados. O mais caro: **a rede lida uma vez**.
Depois da 92b a campanha atrasada fora do prazo fica até alguém agir, e cada uma lia os envios da rede desde o começo do
prazo dela, numa coluna sem índice -- vinte esquecidas eram vinte varreduras (de 0,14 s a 0,8 s cada, no MySQL da cópia)
em cada página da lista, que as põe na página 1, e em cada rodada de dez minutos da rotina. Agora a página e a rodada leem
a rede uma vez, de agora para trás, em janelas de um dia, e cada campanha passa o mesmo trecho à mesma conta
(`RegraDaCampanha::trechoDaRede`; provado igual sobre envios sorteados, e contra a rodada). Nenhum índice novo. E mais:
o filtro `fidelizado_unidade`, que a tela antiga nunca mostrou, saiu da audiência (23 filtros, comparados com a tela); a
busca da lista escreve a coluna no literal, e o `conferir-injecao-dql.php` passou a contar o `sprintf` com `%s` numa
consulta; o estado do WhatsApp das campanhas ganhou o caso que confere o endereço do provedor (a terceira porta, no
`EnderecoDoProvedorWhatsAppTest`); casos novos para a noite no envio da rede, o retomar e o encerrar de quem só lê, e o
lote do "Contar" acima do teto do PostgreSQL; e o texto (o desenho, seção 4, e esta entrada, que dava à 92c o encerrar).
Provado depois dos consertos: os casos da fatia (o `NotificacaoApiTest`, agora 58, o `RegrasDoMarketingTest`, o
`EnderecoDoProvedorWhatsAppTest` e o `CampanhaDoDiaTest`), 118 em cada banco, e a família das campanhas, do WhatsApp, dos
cupons, dos sorteios, dos portões e do molde, 705 casos em cada banco; 19 mutantes novos, um por vez e devolvidos byte a
byte, vermelhos nos dois bancos (o do lote do "Contar" só no PostgreSQL, o banco do teto); os 66 portões, sem falha
inesperada (a cobertura por par de 381 para 380); o front, 3.051 de 3.051, build e lint 0. Os débitos seguem 616, com 151
de pé; nenhuma pergunta nova ao dono.

**Pelo HTTP** (26/10/2026, depois da verificação), contra o banco de contrato, como um administrador temporário, sem cron
em nenhum contêiner e com o `conferir-provedores-no-loopback.php --conteiner` verde antes: as cinco leituras responderam
200 com JSON (a campanha de ontem às 10:00 como `atrasada` com `foraDoPrazo`, a de amanhã `agendada`; a audiência de
amanhã só com números, `noDia` com o limite 300 e quanto ainda cabe; o WhatsApp só com `{conexao, motivo}`), o filtro
fora da lista 422 com o campo, e sem sessão 401. **Um defeito achado ali e consertado:** a mesma unidade saía com dois
nomes -- "Unidade Teste Centro" (o `nome` do cadastro) na lista e no detalhe, e "Brasil - SC - Florianopolis - Centro"
(o rótulo do `<select>` do ERP) na lista das unidades que o administrador escolhe. Agora o nome é o rótulo nos três
lugares, como nos cupons (`NotificacaoJson::rotuloDaUnidade`), com três casos que o conferem e um mutante (o rótulo só
com a cidade) vermelho em três deles; o `NotificacaoApiTest`, 58 casos, verde nos dois bancos.

## 26/10/2026 -- NOTIFICACOES-API-ESCRITA (a fatia 92d da fila das telas que faltam)

*Pedido do dono: "continue, desta vez montando as telas que ainda não foram migradas. continue de forma autonoma até a
exaustão". O desenho é o `docs/f3-telas-restantes/tela-notificacoes.md`, seção 3, "92d", e as respostas 255 (b), 256 (c),
257 (c), 258 (a), 260 (c), 261 (b), 267 (a) e 268 (a).*

**O que muda para quem usa:** nada na tela antiga. Nasce a API de ESCRITA das campanhas, que a tela nova (as fatias 92e
e 92f) vai usar: agendar uma campanha de WhatsApp para os clientes ou para a equipe -- mandando os filtros, e não a lista,
que o servidor refaz --, com o limite do dia, o "mesmo cliente no dia" e o teto de 200; o clique duplo no "Agendar" não
grava duas (409 com a que já existe); alterar o nome, o texto, o link, o dia e a hora do que ainda não saiu; cancelar;
**pausar e retomar, agora também para o gerente** (a 257 c; a retomada só com a hora por vir); encerrar a campanha
parada (a 258 a); e o **"mandar para mim primeiro"** (a 261 b): a mensagem como a prévia a mostra, com a linha "Teste
da campanha ..." em cima, pelo WhatsApp da unidade, ao telefone de quem pede, um por minuto, sem gravar nada -- e com o
WhatsApp da unidade desligado ou desconectado, a recusa antes de mandar. Quem não é administrador só escreve na unidade
da sessão; o administrador escolhe a unidade (a 256 c). Toda escrita confere a unidade que a tela mostra.

**A resposta 268 (a) do dono** (a atrasada segura o dia): era o que o código já fazia; mudaram só os comentários, o D609
e o desenho.

**Débitos:** o D418 CORRIGIDO (a porta nova também grava só o que não saiu, e nunca o `status` do corpo); o D419 e o
D609 PARCIAIS, com a marca nova (o encerrar na API; a 268 registrada; falta a tela, a 92e); o D297 com o que a 92d fez.
Nenhum débito novo, nenhuma pergunta nova ao dono. Os débitos seguem 616, com 150 de pé.

**Provado:** a família das campanhas, do WhatsApp, dos cupons, dos sorteios, dos portões e do molde, 645 casos em cada
banco (o `NotificacaoApiTest` com 78, o `TesteDaCampanhaTest` novo, o envio do provedor de mentira sem o número nem o
texto no registro); 34 mutantes, um por vez e devolvidos byte a byte, vermelhos nos dois bancos; o ensaio HTTP novo
(`scripts/ensaiar-notificacoes-adm-api.php`), 19 de 19 nos dois bancos, contra o banco de contrato e o provedor de mentira
no loopback -- e, com a configuração apontando para o provedor de verdade, ele recusa rodar (saída 2) antes de qualquer
pedido; o ensaio de permissão (recusou 138 de 138) nos dois bancos; os 66 portões do `conferir-tudo.sh`, sem falha (a
cobertura por par de 380 para 384, o inventário regerado); o contrato do aplicativo, 74 de 74 nos dois bancos; o front,
3.051 de 3.051, build e lint 0. Nenhuma rotina de envio rodou, nenhum provedor de verdade foi chamado, nada do OneSignal
nem do crontab mudou, e nenhum esquema. **Não visto no navegador**: a fatia não tem tela.

**A verificação da 92d (26/10/2026)** confirmou 13 achados; 11 consertados, um fica como o desenho escreve e um foi ao
dono. **O que muda para quem usa**: na tela antiga, a edição de uma campanha não desfaz mais a pausa (ou a retomada)
feita por outra pessoa na tela nova -- o formulário manda a situação que leu, e o servidor só a muda quando o `<select>`
mudou (o script no `?cache=166`). Na API nova: o PUT só com a `unidadeNaTela` é 400; no mesmo dia, a alteração não confere
o limite do dia (ela não acrescenta ninguém, e corrigir o nome de uma campanha de um dia que já passava de 300 dava 422);
duas alterações juntas não se desfazem -- a que chega depois da outra recebe 409 `alterada` e nada grava (a campanha é
relida sob a trava da unidade, com `FOR UPDATE`); a cancelada é 409 `ja_cancelada`, e não "já começou a ser enviada".
Ganharam caso o teste na personificação (vai a quem entrou), a pausa e a retomada do caixa, a trava de uma escrita por vez
e a situação no `UPDATE` da retomada. **Fica**: a trava de um minuto do teste é da sessão (a decisão 18; sair e entrar a
zera, e a mensagem só vai ao telefone de quem entrou). **Ao dono**: o caixa altera, pausa e cancela a campanha da equipe que
ele não pode criar, como na tela antiga -- o **D619** novo e a pergunta no lote; até a resposta, como hoje. O D418 segue
CORRIGIDO, com os três restos no corpo. Os débitos ficam 617, com 151 de pé.

**Provado na verificação:** a caracterização inteira, 5.024 casos em cada banco (o `NotificacaoApiTest` com 84); 16
mutantes novos, um por vez e devolvidos byte a byte, vermelhos nos dois bancos (e os 35 da 92d, 70 de 70, com o resumo refeito
da saída de cada um); o ensaio `ensaiar-notificacoes-adm-api`, 19 de 19, e o de permissão, 138 de 138, nos dois bancos, com
o provedor conferido no loopback antes; os 66 portões (a cobertura por par de 384 para 385, o `::camposDaEdicao`); o
contrato do aplicativo, 74 de 74 nos dois; o front, 3.051 de 3.051, build e lint 0. Nenhuma rotina de envio rodou, nenhum
provedor de verdade foi chamado, e nada do esquema, do crontab nem do OneSignal mudou.

**Pelo HTTP e no navegador** (26/10/2026, depois da verificação), contra o banco de contrato, como um administrador
temporário e sem cron em nenhum contêiner (o "mandar para mim" não foi pedido aqui: só o ensaio o pede, e só ao provedor
de mentira): a pausa pela API nova deu 200 e a campanha virou `pausada`; a segunda pausa, 409 `ja_pausada`; a unidade
trocada na tela, 409 `unidade_trocada`; a pausa por GET não existe (404). Na tela antiga, com o `mod-marketing.js?cache=166`
servido, a mesma campanha aparece "Aguardando"; editar só o título gravou e a deixou pausada (pela API, `pausada`) -- o
conserto do `status_lido` --, e o administrador que troca o `<select>` para "Agendado" a retoma, como antes. O
cancelamento pela API, 200, e o segundo, 409 `ja_cancelada`. Nenhum defeito achado ali. O banco de contrato foi remontado
depois.

## 26/10/2026 -- NOTIFICACOES-TELA-LISTA (a fatia 92e da fila das telas que faltam)

*Pedido do dono: "continue, desta vez montando as telas que ainda não foram migradas. continue de forma autonoma até a
exaustão". O desenho é o `docs/f3-telas-restantes/tela-notificacoes.md`, seção 1 e seção 3, "92e", e as respostas 254 (c),
255 (b), 256 (c), 257 (c), 258 (a), 262 (b), 267 (a) e 269 (a).*

**O que muda para quem usa:** "Notificações", em Marketing, abre a tela nova, a página "Campanhas": a lista das campanhas
da unidade (o administrador escolhe a unidade, ou a rede), com o selo e a frase de cada situação -- agendada, atrasada (no
prazo, ou "não sai mais sozinha: mude a hora ou cancele"), pausada, saindo, na fila, parada, enviada, encerrada à mão,
cancelada --, para quem, o canal, os contatos, os envios e quem não recebeu; e a campanha aberta, com a mensagem como chega
no WhatsApp (as etiquetas pelos exemplos, o link no fim, o título fora), os números com o que querem dizer e os motivos do
"não receberam". Quem escreve pausa e retoma no mesmo botão (a pausada com a hora passada pede o dia e a hora novos antes
de retomar), muda a hora da atrasada que passou do prazo, cancela dizendo quantos deixam de receber, e encerra a campanha
parada dizendo quantos ficam sem a mensagem; se a campanha voltou a sair, a tela diz e relê. As de push e de SMS aparecem
só para ler (a de push agendada, só com o cancelar). Agendar uma nova e alterar o texto continuam na tela antiga até a 92f
(a tela diz onde). **E a resposta 269 (a)**, nas duas telas: a campanha para a equipe só se altera, pausa, retoma, cancela
e encerra pelo gerente e pelo administrador -- o caixa a vê, sem os botões, e o servidor recusa (403 na API; na tela antiga,
sem o editar e o cancelar na linha, e o "Salvar" e o cancelar recusados com a frase). O D609 e o D619 CORRIGIDOS; o D419
PARCIAL (resta a retomada entre dois nós). Os débitos ficam 617, com 149 de pé.

**A verificação em cinco lentes** confirmou 13 achados, todos pequenos; 12 consertados e um registrado. As canceladas ficam
**a um clique**, como o desenho escreve: "mostrar também as canceladas", logo abaixo da contagem que diz "sem as
canceladas", pede a mesma busca com elas, e o filtro da situação acompanha. A retomada com a hora passada abre com a frase
do desenho ("Escolha a hora nova: a marcada já passou."). No celular, o resumo da linha diz "0 de 184 enviados" e quantos
não receberam -- sem os cabeçalhos, o número sozinho não dizia nada. A prévia do Claude Design ganhou as três situações que
faltavam (na fila, encerrada à mão sem envio, cancelada), e o desenho e os comentários do código passaram a dizer a recusa
da campanha da equipe (o 403). Os casos que faltavam entraram, cada um com o mutante dele vermelho: a carga, a falha e o 403
da lista, a página seguinte, a campanha que não abre e o "atualizar", os horários que não vêm, a de push agendada, as frases
sem o último envio, a nota do push, o "todos já receberam", a unidade fora da linha do gerente, os motivos só quando alguém
não recebeu, o 409 da hora nova, e o foco do "desistir", do "não encerrar" e da recusa da hora. **Registrado, sem mudar**: as
ações da tela nova ainda não mandam a unidade que a tela mostra (a conferência do 409 `unidade_trocada`); sem dano hoje --
quem não é administrador e trocou de unidade em outra aba recebe 404 --, e a 92f a manda, com as opções do cadastro.

**Provado:** a família das campanhas, do WhatsApp, dos cupons e dos sorteios, 663 casos e 6.937 asserções em cada banco
(o `NotificacaoApiTest` com o caso novo do caixa e as constantes da tela comparadas às da regra; o `CampanhaAudienciaTest`
com a lista antiga renderizada); 75 mutantes (47, e 28 da verificação, todos da tela), um por vez e devolvidos byte a byte -- 16 do servidor, vermelhos nos dois bancos (o `pode*` da lista e do detalhe ao caixa, a recusa da rota tirada e cada uma das cinco escritas sem ela, a regra que sempre mexe, o papel sem o gerente, os dois botões da tela antiga de volta, o salvar e o cancelar antigos sem a recusa, e as constantes da tela trocadas), e 31 da tela (cada frase, o N de M, a na fila, o encerrar fora da parada e sem o número, o 409 sem a releitura, o botão que só pausa, a retomada sem a hora nova, o cancelar sem o alcance, o push com o "mude a hora", a prévia da equipe com todas as etiquetas, os motivos, a unidade a todos, cada foco de volta, duas escritas, a resposta velha que vale, o Console sem a tela, o `PUT` sem o dia, o encerrar sem o lido); os portões 66 do `conferir-tudo.sh`, sem falha inesperada (e as referências dos documentos, os débitos repetidos, a cobertura por par e os segredos à parte); o contrato do aplicativo, 74 de 74; o front, 3.127 de 3.127 (73 casos novos e três no Console, 20 deles da verificação), com o
`AMPLIFICAR_EFEITOS_MS=50` nos arquivos novos, build e lint 0. Nenhuma rotina de envio rodou, nenhum provedor foi chamado,
nenhum "mandar para mim" se clicou nem se chamou, e nada do esquema, do crontab nem do OneSignal mudou. **Não visto no
navegador** nesta fatia: o navegador final é do orquestrador.

**No navegador** (26/10/2026, depois da verificação), na tela nova, contra o banco de contrato, sem cron em nenhum
contêiner e com o privilégio das campanhas concedido só ali ao `gerente.teste` e ao `colab.teste` (as fixtures não o
têm): como gerente, a lista com a atrasada ("Passaram mais de 3 horas ... Mude a hora ou cancele."), a de amanhã e a da
equipe, cada uma com o selo e a frase; o detalhe com a prévia, os números, o para quem e o histórico; pausar pela tela
deu "PAUSADA", anunciou a frase e levou o foco a "retomar"; o "cancelar" pergunta no próprio botão com o alcance ("Os 2
clientes deixam de receber. Cancelar não se desfaz."), e o "não cancelar" desfaz a pergunta e devolve o foco ao
"cancelar" (o Escape não a desfaz, como a peça comum escreve para todas as telas); ao voltar, o foco no nome da campanha;
a 375 px a lista cabe sem rolar de lado, com o resumo de cada linha, nos dois temas; a 1024 px a tabela rola dentro da
caixa dela. Como colaborador, a campanha da equipe só com "atualizar" (a 269 a). Nenhum defeito achado ali; fica para a
92f uma frase que diga ao caixa por que a da equipe não tem os botões. O banco de contrato foi remontado depois.

## 26/10/2026 -- NOTIFICACOES-TELA-CADASTRO (a fatia 92f da fila das telas que faltam)

*Pedido do dono: "continue, desta vez montando as telas que ainda não foram migradas. continue de forma autonoma até a
exaustão". O desenho é o `docs/f3-telas-restantes/tela-notificacoes.md`, seção 1 ("O cadastro" e "Editar") e seção 3,
"92f", e as respostas 255 (b), 256 (c), 260 (c), 261 (b), 267 (a), 268 (a) e 269 (a).*

**O que muda para quem usa:** na página "Campanhas", quem escreve agenda a campanha na tela nova -- o "agendar campanha
nova" abre o cadastro numa página só: para quem (clientes, ou a equipe para o gerente e o administrador) e, ao
administrador, a unidade, que passa a ser a da campanha; a mensagem, com o nome (só a equipe vê), o texto com os botões das
etiquetas, a contagem dos caracteres, o aviso da etiqueta que a campanha não troca, o link, o dia e a hora (a mais próxima
sugerida, e o aviso quando ela sai em menos de 30 minutos) e a **prévia viva**, como chega no WhatsApp; o **"mandar para mim
primeiro"**, que manda essa mensagem ao WhatsApp de quem cria e diz "Mandamos para o seu WhatsApp terminado em 1234." (ou
por que não saiu: sem telefone, o WhatsApp desconectado, um por minuto); quem recebe, com os filtros da pesquisa de hoje e o
**"Contar"**, que diz quantos recebem, quantos saem da lista por já receber outra campanha no dia e quanto cabe no limite
da unidade (300 por dia); e o **estado do WhatsApp da unidade** e o resumo ("Sai em 26/10/2026 às 15:00, pelo WhatsApp da
unidade ..., para 172 clientes.") antes de agendar. A campanha agendada abre com quando sai, para quantos e quem saiu da
lista; a gêmea agendada há pouco abre a que já existe. Na campanha que ainda não saiu, o **"alterar"** muda o nome, o
texto, o link, o dia e a hora, com a prévia e o teste, sem acrescentar ninguém à lista. A tela antiga deixa de ser
oferecida (os links "tela antiga" saíram). Toda escrita leva a unidade que a tela mostra: com a sessão trocada noutra aba,
nada se grava, e a tela passa à unidade de agora. E os dois pontos vistos na 92e: o caixa, que escreve e não cria a
campanha da equipe, lê na campanha dela por que não há botões; e a coluna da unidade da lista do administrador não quebra
mais o nome em quatro linhas.

**Provado:** o front, 3.184 de 3.184 (57 casos novos: os 40 do `NotificacoesLigado.cadastro.teste.tsx`, 14 da regra pura, o da coluna e dois no Console), e os arquivos das campanhas também com o `AMPLIFICAR_EFEITOS_MS=50`; build, lint e tsc 0, e o tipo da prévia do Claude Design 0; 56 mutantes da tela, um por vez e devolvidos byte a byte, todos vermelhos (a prévia com cada etiqueta, com o título e a da equipe; o aviso da etiqueta; o cursor; os 30 minutos; a hora sugerida; o "Contar" com a unidade de quem não é administrador e o corpo com a lista; quem sai da lista, o "cabem", o teto e o limite; a contagem que não vence e a chave sem a unidade; o colaborador da outra unidade; o agendar sem contar; o WhatsApp desconectado, o estado que não veio e o link a quem não abre a tela dele; os quatro dígitos, as recusas e o 429 do teste; a `unidadeNaTela` tirada de cada escrita; o 409 de outra aba sem reler; a unidade no corpo do gerente; a gêmea; os dois cliques; os focos; a equipe a todos; a edição sem a nota, com tudo no corpo, sem quem saiu, com o "nada mudou" pedindo, sem a hora gravada, e o 409 sem reler; a nota da equipe tirada, a quem a cria e na que saiu; e a coluna da unidade sem a classe e sem os mínimos); no servidor nada mudou, e o `NotificacaoApiTest` (84 casos, com o dos tipos da tela, que lê o `api/notificacoes.ts`) passou nos dois bancos; a coluna da unidade medida numa página com o CSS do build: duas linhas a 900 px sem rolar de lado, e fora a 375 px; os 66 portões do `conferir-tudo.sh` ok (e as referências dos documentos, os débitos repetidos e os segredos à parte, saída 0). Os débitos não mudam (617, 149 de pé): a 92f não fecha nem abre nenhum; o D297 ganhou o parágrafo da tela nova, e o D419 segue PARCIAL pela retomada entre dois nós, que não é da tela. Nenhuma rotina de envio rodou, nenhum provedor foi chamado, nenhum "mandar para mim" se clicou
num navegador nem se chamou pelo HTTP (só pelo dublê do vitest), e nada do servidor, do esquema, do crontab nem do OneSignal
mudou. **Não visto no navegador** nesta fatia: o navegador final é do orquestrador.

**A verificação da 92f (cinco lentes) confirmou 19 achados (17 distintos), todos tratados na mesma fatia.** Os que mudam
o que se vê: o administrador que estava na unidade da sessão, depois da sessão trocada noutra aba, passa à unidade de
agora junto com o resumo e o WhatsApp (antes, o seletor e a gravação ficavam na de antes, e o resumo dizia a de agora); o
"mandar para mim" do administrador sai pelo WhatsApp da unidade escolhida; a recusa de um filtro marca o filtro, com o
rótulo e o foco (antes, nenhum era marcado, e o "Contar" mostrava o nome interno); a recusa da audiência na edição (todos
já recebem no dia novo) se diz, com o foco nela (antes, sumia calada); o consumo "1.500,00" vai como 1500.00; a frase do
teto é a mesma da tela antiga; o estado do WhatsApp se anuncia ao leitor de tela; o link para a tela do WhatsApp segue o
menu (com o WhatsApp da unidade desligado, não aparece); a nota da campanha da equipe só onde o gerente teria botão (não
na que está saindo); e a frase da situação desconhecida não manda mais à tela antiga. Os casos novos cobrem o 409 de
outra aba no teste e na edição, a carga e a falha do cadastro e da edição, e as guardas do invólucro uma a uma; a prévia do
Claude Design ganhou três células (o cadastro do caixa, o da equipe e a recusa com o campo marcado) e a hora sugerida
certa. 37 mutantes novos (36 da tela e um do servidor: a frase do teto trocada no `api/notificacoes.ts`), um por vez e
devolvidos byte a byte, todos vermelhos. No servidor, só o caso dos tipos do `NotificacaoApiTest` passou a conferir a
frase do teto (`FRASE_TETO`). **Provado:** o front, 3.225 de 3.225 (41 casos novos) com `--maxWorkers=4`, e os arquivos
das campanhas e os casos delas no Console com o `AMPLIFICAR_EFEITOS_MS=50`; build e lint 0, e o tipo da prévia do Claude
Design 0; o `NotificacaoApiTest`, 84 casos e 1.218 asserções, nos dois bancos; os 66 portões do `conferir-tudo.sh`, as
referências dos documentos, os débitos repetidos e os segredos, saída 0. Os débitos não mudam (617, 149 de pé). Nenhum
"mandar para mim" se clicou nem se chamou fora do dublê do vitest.

**No navegador** (26/10/2026, depois da verificação), na tela nova, contra o banco de contrato, sem cron em nenhum
contêiner, com o privilégio das campanhas concedido só ali ao gerente e ao colaborador de teste, e **sem clicar no
"mandar para mim"** (o provedor de mentira não estava de pé; o envio dele já fora provado pelo ensaio da 92d, e o botão
aqui pelo dublê do vitest): como gerente, a campanha nova pelo formulário -- o nome, o texto digitado com uma etiqueta
posta pelo botão, a etiqueta inventada avisada ("não é uma etiqueta: vai ao cliente como está escrito"), a prévia viva com
o exemplo e o link no fim, o "Contar" com o dia e o limite --, agendada, com o foco no título; o "alterar" do nome gravou
("Campanha alterada."). Como administrador, a segunda campanha do mesmo dia para os mesmos clientes: "2 já recebem outra
campanha em 27/10/2026 e saem da lista: ficam 0"; noutro dia, cabe. Como colaborador, a da equipe com a nota "só o gerente
e o administrador mexem". A 375 px, nada passa da tela. **Um defeito achado ali e consertado:** com o WhatsApp da unidade
desligado, o aviso dizia "a campanha não sairia" e o resumo, logo abaixo, "Sai em 27/10/2026 às 14:00, pelo WhatsApp da
unidade"; agora o resumo diz "Fica agendada para ... : só sai se o WhatsApp da unidade ... estiver conectado até lá"
(`resumoDoAgendamento` com o `conectado`; um caso puro e dois da tela, que um mutante -- a chamada sem o estado -- deixa
vermelhos). E a coluna da unidade do administrador, que quebrava o rótulo em quatro linhas, passou a três com o
"Florianopolis" (duas com os rótulos mais curtos), e a tabela segue rolando dentro da caixa dela: tirar a coluna e pôr a
unidade sob o nome é mudança de desenho, que fica anotada. O banco de contrato foi remontado depois.

Nas capturas do Claude Design da 92f (26/10/2026), dois pontos, consertados antes do envio: na campanha para a equipe, a
nota antes do "Contar" prometia dizer "quem sai da lista por já receber outra campanha no dia", conta que só as de
clientes fazem (a decisão 14; a API devolve 0 na da equipe) -- agora a nota depende de para quem (`notaDoContar`, com um
caso puro e um da tela, que o mutante do texto fixo deixa vermelho); e a prévia não passava o `acoesDaEdicao`, e o
detalhe da agendada aparecia sem o "alterar". Portões 66 e front 3.228; o pacote enviado é o a3ef89c727a5.

## 26/10/2026 -- REL-CONSULTA-E-EXPORTACAO (a fatia 43 da fila das telas que faltam)

**Para quem usa, nada muda ainda:** é a segunda fatia da base comum dos relatórios da tela nova (a C2 e a C3 de
`docs/f3-relatorios/spec-comum.md`), e nenhum relatório liga -- a família `adm-api-relatorios` nasce na 47. O painel e o
Profissional Parceiro respondem byte a byte o que respondiam. O que fica pronto para os dez relatórios:

- **A consulta dos relatórios** (`VendaRepository::relatorioItens`): a mesma do painel, com oito colunas a mais -- o
  item, o motivo do desconto, o convênio, a cota gravada, o percentual de hoje do colaborador (serviço e produto), o
  caixa e a etiqueta -- e as vendas concluídas OU as canceladas. As peças da resposta num lugar só
  (`NumerosDaResposta`, `ContasDoPainelDaUnidade::formas` e `INTERNAS`), e os clientes novos de um recorte
  (`NovosDoRecorte`), que o Parceiro já usa.
- **Desde quando o PDV grava cada coluna** que os relatórios esperam (`ColunasDoPdv`, com as seis chaves): a tabela nova
  `colunas_gravadas` (nome, desde), **criada vazia** nas duas cópias locais, no `mysql.sql`, no `postgres/schema.sql`, na
  carga e no laço do passo 0 do corte (`scripts/migracao-colunas-gravadas.sql`; a resposta 83 (a) do dono: só acrescenta).
  Nenhuma linha entrou: cada migração da fatia do PDV grava a sua. Sem linha, o relatório diz "ainda não gravado".
- **A exportação "como hoje"** (a pergunta 10): o PDF que abre no navegador, a planilha em `.xls` ou `.xlsx` (a extensão de
  hoje de cada relatório) e o TXT dos contatos do NPS, gerados da MESMA conta que a tela mostra, com as guardas na ordem
  "a da base, a própria, soltar a sessão"; na tela, `BotoesDeExportacao` (o PDF num link em aba nova, a planilha e o TXT
  baixando, "Gerando o arquivo…", a recusa em alerta), com a prévia para o Claude Design.

**Medido, na cópia (`franquiabv_dev`, só leitura, `innodb_buffer_pool_size` de 128 MB, o mesmo da base do desenho), julho
de 2026, `relatorioItens` e `painelItens` alternados, sete rodadas de cada, cada uma num processo:** na unidade 1, o painel
de 0,57 a 1,19 s (mediana 0,79) e o relatório de 0,75 a 1,01 s (mediana 0,86); na rede, o painel de 1,02 a 1,50 s (mediana
1,19) e o relatório de 1,21 a 1,79 s (mediana 1,43). **O relatório fica acima dos 20% que o desenho tolera:** +33% no
mínimo da unidade (+9% na mediana) e +18 a +21% na rede -- registrado aqui, como o desenho manda, e seguido. Os dois métodos
mediram 2 a 3 vezes a base de 0,28 e 0,42 s do `f3-relatorios.md`, com o host carregado (a mesma consulta do painel, sem
mudança, deu de 0,57 a 1,19 s): a comparação que vale é a alternada, e ela diz que as oito colunas custam de 0,1 a 0,2 s
na rede. O pico de memória: 22,6 MB contra 22,4 na unidade, 76,6 contra 75,5 na rede (a base: 88 MB do Parceiro). **O
oráculo bateu ao centavo** contra SQL independente em tudo menos a cota: unidade 1, concluídas, 4.416 itens, 3.101 vendas,
R$ 286.189,40, R$ 21.198,60 de desconto, 396 com motivo, 1.300 em convênio; canceladas, 108 itens, 69 vendas, R$
8.045,00, R$ 572,00, R$ 3.124,15 de cota, 1 e 35; a rede, concluídas, 31.898 itens, 2.525 com motivo e 3.329 em convênio,
canceladas 791, 56 e 98 -- os números do desenho. **A cota NÃO bate com a soma SQL, e não tem de bater** (corrigido na
verificação: a primeira versão deste parágrafo dava a da unidade 1 como batida): a linha arredonda cada item para centavos
(o "arredondamento por item" que o `spec-ordem.md`, seção 2, item 3, manda), e a SQL arredonda a soma. A unidade 1 soma
R$ 102.170,12 contra os R$ 102.170,06 do desenho (13 itens com a comissão em três casas); a rede, R$ 654.911,28 contra
654.910,48 (350 itens, os 13 dentro); as canceladas da rede, R$ 17.050,07 contra 17.050,03. Medido de novo na cópia, só
leitura, pelo mesmo roteiro. Na tela, a Cota-parte vai dizer que o total pode diferir em centavos (a nota
`arredondamentoDaCota` que o `spec-cota-parte.md` desenha). **O documento sintético de 5.000 x 15**, no
contêiner, `memory_limit` 512M (e o padrão de produção, 128M, acima do pico): o XLS em 5,3 s e 56 MB, o XLSX em 5,5 a 6,0 s e
72 MB, o PDF em 0,6 a 0,7 s e 23 MB. Não subi o limite.

**Achado na verificação e consertado:** o mutante `Output('I')` no PDF passava VERDE -- o `exit` do `FPDFTable` matava o
PHPUnit, que saía com 0 e sem resumo. O `PdfDoRelatorio` agora recusa qualquer modo que não seja o `S` (lança), e o caso
tem uma função de saída que diz o que houve e sai com 1 se o processo morrer dentro do `Output`. E o `FPDFTable::Output`
passou a devolver os bytes do modo `S` (engolia; ninguém o pedia).

**Provado:** os casos novos e os vizinhos (`ItensDoRelatorioTest`, `NumerosDaRespostaTest`, `NovosDoRecorteTest`,
`ColunasDoPdvTest`, `ExportacaoDoRelatorioTest`, e o painel, o Parceiro, a base da 42, as colunas fora do mapeamento, os
sorteios e os PDFs), 487 casos verdes no MySQL e no PostgreSQL; o front inteiro, 3.244, o build e o lint; 27 mutantes do
servidor, 8 do front e 5 dos portões, cada um sozinho e devolvido byte a byte, todos vermelhos; os portões da fatia (o
recorte por unidade com as vigias novas, a cobertura por par com a base de 4 para 6 e o ISRelatorio de 107 para 106, o
booleano em coluna inteira de 13 para 17 -- e de volta a 13 na verificação, que o ensinou a ler as constantes da consulta --, cada linha de base lida à mão, o passo 0, a injeção de DQL, o group by, o buffer
de saída, os segredos, as referências e os débitos) e o `conferir-tudo.sh`, os 66 portões, saída 0. O banco de contrato foi remontado com a tabela.
Não rodei o contrato nem os ensaios HTTP: nenhuma rota nova responde, e a família nasce na 47. Os débitos D620 (o Líder sem a
marca de gerente, o D-c da parte comum), D621 (o `conferir-rotas-orfas.php` sem as ações das rotas de `/adm/api`, o D-d) e
D622 (a chave de `colunas_gravadas` no PostgreSQL sem o inquilino) registrados: 620 débitos, 152 de pé.

**A verificação da fatia 43** confirmou 17 achados (14 distintos), todos tratados, nenhum ao dono. O de mais peso: **o
medidor do PostgreSQL pulava a `.../exportacao`** -- a C3 mandava pedir com o formato e conferir o arquivo, e nada o
fazia; na 47, a rota responderia 422 sem o `formato`, contaria como guarda, e o medidor sairia 0 sem o arquivo nunca ter
sido gerado no PostgreSQL. Agora ele a pede uma vez por formato da política, nas duas partes (a do gerente e a do
administrador), e só o arquivo mede: o tipo do formato, a assinatura no byte zero e as bordas limpas
(`pedidosDaExportacao` e `exportacaoQueNaoGerou`, regras puras com caso); o laço em si só roda com a família de pé, na 47.
E: a cota do oráculo, corrigida acima; a `FORMATOS` reprovada em todo `module/` por um caso, e as oito especificações que
ainda a mandavam declarar -- e a `unidadeDaVenda`, que não entrou -- com a nota onde elas mandam; o nome do arquivo com cada
forma do período escrita (o ano e o ciclo são erro de programa até a fatia deles) e o caso do acento e do mês; o TXT
soltando a sessão antes de montar, conferido; o filtro de colaborador do `relatorioItens` com caso; o portão do booleano em
coluna inteira lendo a constante literal da classe (de 17 de volta para 13 não resolvidas, e o `ven.status = :status` do
painel vigiado de novo: um `'status' => true` ali sai 1); o comentário do `taxa()` de volta ao lugar; e, na tela, o link
do PDF que diz "(abre em outra aba)" e que, desligado, continua na ordem do Tab com o foco, o "um pedido de cada vez" entre
formatos e a região viva já montada antes do pedido. O M25 só fica vermelho no `NumerosDaRespostaTest` (o painel não tem
pagamento em `PACOTE`): a tabela do desenho foi corrigida. E um ponto cego que já existia antes da fatia: o portão do
booleano não julgava a comparação com ASSOCIAÇÃO (`usu.unidade = :unidade`) com valor nenhum -- agora o booleano do próprio
método ligado a ela sai 1 (o que vem de quem chama segue fora, escrito no cabeçalho dele). 14 mutantes novos (9 do servidor,
5 do front) e 2 do portão (o `'status' => true` e o `'unidade' => true` no `painelItens`), cada um sozinho e devolvido
byte a byte, todos vermelhos; a base do portão não mudou (0 quebram, 13 não resolvidas, 622 ligações conferidas). Provado: os casos
da fatia e os vizinhos nos dois bancos, o front inteiro (3.246), o build, o lint e os portões que a correção toca.

**A medida de novo, com a máquina calma (26/10/2026, depois da verificação, fora do fluxo):** o mesmo roteiro, só
leitura na cópia, buffer pool de 128 MB, os dois métodos alternados, cinco rodadas de cada, cada uma num processo. Na
unidade 1, o painel de 0,50 a 0,53 s (mediana) e o relatório de 0,63 a 0,67 s: **+26%**, acima dos 20% que o desenho
tolera; na rede, 0,89 a 0,91 s contra 1,03 a 1,07 s: **+18%**. O painel não muda (a consulta dele é o texto de antes); só
os relatórios novos pagam as oito colunas, uns 0,13 s na unidade. Registrado, como o desenho manda, e seguido.

**Os testes desta fatia**, pela decisão do dono de 26/10/2026 (as fatias ímpares rodam só o que tocam; o ciclo inteiro, nas
pares): os casos novos e os vizinhos do código tocado, 566 no MySQL e 566 no PostgreSQL, os 66 portões, o tsc, o lint, o
build e os 86 casos do front da `api` e dos botões. O ciclo inteiro fica para a 44.

## 26/10/2026 -- Quanto falta, o ritmo e o tempo, refeitos depois da fatia 92f

O dono pediu: "atualize e compare as partes quanto falta, ritmo e quanto tempo de projecao". Pela mesma regua de 16/10 e
21/10 (linhas de codigo com os testes, sem documentos, dumps, carga, fixtures, lockfiles nem o inventario gerado, ate o fim
do Corte): **cerca de 62% feito, entre 59% e 65%** (55% em 21/10, 50% em 16/10).

| Parte | 16/10 | 21/10 | 26/10 |
|---|---:|---:|---:|
| Feito desde a importacao (15/08) | ~352 mil | ~437 mil | ~510 mil |
| Falta: as fatias da fila (86; 69; hoje 51, com a 43 em andamento) | ~250 mil | ~230 a 260 mil | ~170 a 200 mil |
| Falta: os debitos de pe fora da fila (113 dos 134; 130 dos 141; 141 dos 149) | ~80 a 110 mil | ~90 a 125 mil | ~100 a 135 mil |
| Falta: o preparo do corte (os 13 itens antes da janela da etapa 1) | ~10 a 20 mil | ~10 a 20 mil | ~10 a 20 mil |
| Falta, no total | ~340 a 380 mil | ~330 a 405 mil | ~280 a 355 mil |

**A fila encolheu.** 26 fatias em cinco dias e oito horas. As 11 com estimativa na fila feitas desde 21/10 custaram 1,94
vez o previsto; todas as da fila desde 14/10, 1,81 (era 1,76). As seis das campanhas (92a a 92f), estimadas num desenho
proprio, sairam 1,21 vez (17,7 mil contra 14,7 mil), e a base dos relatorios (42), 1,59: a faixa de baixo usa 1,64 (a razao
das fatias com desenho) e a de cima 1,94. Os relatorios sao 33 das 51, ~115 a 135 mil linhas. Os debitos fora da fila sao
141 dos 149 de pe, com o custo por debito de 21/10 (700 a 960 linhas): a parte menos firme.

**Outras reguas.** No menu, 27 dos 51 destinos abrem a tela nova (mais Arquivos, WhatsApp, Pesquisas da adesao, Avisos do
mural e Notificacoes). Nos debitos, 468 dos 617 nao estao mais de pe (75,9%; eram 74,7%).

**O ritmo** (21/10 12h52 a 26/10 21h05, 128 horas de relogio): 26 fatias; 4,5 h entre uma e a seguinte (mediana; 4,2 em
21/10), 4,9 h por fatia em media com as pausas (5,1); ~2.700 linhas por fatia (~3.400); ~555 linhas por hora (~670). Mais
fatias e menores -- tres so de documentacao (40, 40b, 41b), e as campanhas partidas em seis --, e o custo fixo das cinco
lentes, do ciclo e do navegador pesa mais na fatia pequena. A maior pausa, 12,6 h, depois da fatia 32.

**Quanto tempo: de 3 a 8 semanas**, como em 21/10, com o fim uma semana adiante. No ritmo destes cinco dias, 3 a 4 semanas
(perto de 15 a 24/11; era 10 a 17/11 em 21/10 e 15 a 25/11 em 16/10); com pausas maiores, as perguntas e o limite de uso da
conta, 5 a 8 semanas (fim de novembro a fim de dezembro). As horas: as 51 fatias, 250 a 360 h; os debitos fora da fila, 175 a
245 h; as respostas que mudam codigo, 40 a 60 h; o preparo do corte, 15 a 30 h -- cerca de 480 a 700 h (eram 480 a 630).
Fora da conta: as janelas do corte na producao e o texto das campanhas (92g), que dependem do dono.

Medido por roteiros de leitura, fora do repositorio: o mesmo da estimativa de 21/10 com o corte do ritmo em 21/10 12h52 (o
bloco 92, partido em sete, sai da conta; a 92g conta como pequena), os debitos de pe pela regra do
`scripts/contar-debitos.php` no HEAD (149, conferido contra ele) cruzados com os citados nas fatias que faltam, e os destinos
do menu pelo `MENU_DO_ERP`.

## 27/10/2026 -- REL-COMPONENTES (a fatia 44 da fila das telas que faltam)

**Para quem usa, nada muda:** é a terceira fatia da base comum dos relatórios da tela nova (a C4 de
`docs/f3-relatorios/spec-comum.md`), e nenhum relatório liga ainda -- a família `adm-api-relatorios` nasce na 47. O Painel,
o Profissional Parceiro e o histórico de cada colaborador mostram o que mostravam: o "Como os números são contados" com o
mesmo texto, caractere a caractere; a escolha da unidade do administrador, a navegação de mês e a região viva iguais; a
tabela do histórico rolando dentro do cartão. O que fica pronto para os dez relatórios, na tela (nada do servidor mudou):

- **Os pedidos** (`pedidosDoRelatorio.ts`): os números com a resposta atrasada descartada e o "atualizando" com os de antes
  na tela; o segundo pedido ("os números, depois os clientes") com o período que os números aplicaram, zerado na troca de
  recorte; e o "só vale do mesmo recorte". O Parceiro não os usa, de propósito: a fila da rede dele fica.
- **O período:** a navegação de mês, movida do Parceiro; a escolha do intervalo -- um dia, uma semana (de segunda a
  domingo, e a tela o diz), uma quinzena (1 a 15, 16 ao fim), um mês, ou de um dia a outro com o teto do relatório --,
  lembrada neste aparelho por relatório e por unidade; e o "Hoje, dd/mm/aaaa" do caixa.
- **O cabeçalho, a região viva, a recusa e a tabela:** o cabeçalho com o "de onde" e as vagas do filtro, do período e da
  exportação (com a recusa, só o título); a recusa que diz se o relatório é do gerente e do administrador, só do
  administrador, ou se falta o privilégio -- e que entrar de novo não resolve; a tabela que rola de lado dentro do cartão,
  com a primeira coluna presa; o "…" e o "—" do que chega depois; o "ainda não gravado" das seis colunas que o PDV ainda não
  grava; e o link da ficha do cliente, em outra aba.
- **O glossário** (`glossario.tsx`): os 24 parágrafos do painel e do Parceiro, cada um com uma chave (`painel.*`,
  `parceiro.*`; os relatórios acrescentam `<rota>.<nome>`), e o `ComoSeConta` que monta a lista de cada tela.

O que saiu diferente do desenho, e o que a 45 precisa saber, está no item 9 do bloco "Em dia" do `spec-comum.md`.

**O texto do glossário foi medido, e não copiado à mão:** no HEAD a8693eb7, antes de qualquer mudança, as duas funções de
então renderizadas num caso temporário e o `textContent` de cada parágrafo gravado (8 do painel, 16 do Parceiro); o
`glossario.tsx` gerado do código-fonte do HEAD por roteiro; e o `glossario.teste.tsx` compara parágrafo a parágrafo.

**Medido:** o pacote do build, comprimido, de 301,53 para 302,28 KB o JS (+0,75 KB) e de 15,88 para 15,99 KB o CSS. Esse
número só pega o que o Painel, o Parceiro e o histórico já usam: o build descarta o que ninguém importa, e a escolha do
intervalo, a recusa, o "ainda não gravado", o link da ficha e o dia de hoje não estão no pacote de hoje. Medidas à parte (o
app com as peças exportadas da entrada, contra o app sozinho, a mesma régua dos dois lados), elas somam +3,33 KB; a C4
inteira fica em ~4,1 KB, dentro dos 10 KB que o desenho tolera. A 47 mede de novo, com o primeiro relatório ligado.

**Os mutantes**, cada um sozinho e devolvido byte a byte (o hash conferido), todos vermelhos (os do desenho com o prefixo
C4-, porque M37 a M43 a 43 já usou):

| # | Mudança | Caso vermelho |
|---|---|---|
| C4-M37 | sem o `vigente` nos números | a resposta velha não sobrescreve a nova |
| C4-M38 | `doMesmoRecorte` sem a unidade | com outra unidade, carregando |
| C4-M39 | a segunda quinzena de 16 a 15 do mês seguinte | a quinzena de 16/02/2028 (e o vizinho de 1–15) |
| C4-M40 | a memória sem a unidade na chave | escolher "quinzena" grava com a unidade; reabrir com outra unidade |
| C4-M41 | o › ativo no ciclo em andamento | o "›" desligado no ciclo em andamento |
| C4-M42 | a tabela sem `tabIndex` | o da `TabelaQueRola` |
| C4-M43 | uma vírgula a mais num parágrafo do painel | o do Painel, caractere a caractere |
| C4-X1 | sem o `vigente` no pedido de depois | trocar o recorte zera o segundo pedido |
| C4-X2 | "relendo" também depois da falha | com dados, relendo; sem dados, carregando |
| C4-X3 | sem o `aoTrocar` | trocar o recorte zera o segundo pedido |
| C4-X4 | o ciclo que termina hoje fora do andamento | o ciclo que termina hoje |
| C4-X5 | a semana de domingo a sábado | os seis casos da semana |
| C4-X6 | o vizinho sem refazer o ciclo | o vizinho do ciclo cortado em hoje |
| C4-X7 | o "tentar de novo" também no 403 | a recusa sem "tentar de novo" (dois casos) |
| C4-X8 | o foco puxado sem ter estado na tela | quem não focou nada não tem o foco puxado |
| C4-X9 | as duas frases do papel trocadas | o motivo papel |
| C4-X10 | o "Gravado desde" ignorado | sem data e com data |
| C4-X11 | o link da ficha sem `rel` | a ficha em outra aba |
| C4-X12 | o cabeçalho com as vagas na recusa | a recusa só com o título |
| C4-X13 | "todas as unidades" sempre | sem a rede (`comTodas` falso) |
| C4-X14 | o livre sem o teto | o teto mostra a frase, e além dele não pede |
| C4-X15 | o "…" sem o nome "contando" | o valor de depois |
| C4-X16 | duas colunas do PDV fora da ordem do servidor | as seis colunas são as do `ColunasDoPdv::CHAVES` |
| C4-X17 | o "tentar de novo" sem levar o foco ao título | o erro com o "tentar de novo" (o caso estava verde com o mutante: o `expect` dentro do clique não chegava ao caso; agora o foco é anotado e conferido depois) |
| C4-X18 | a escolha do atalho sem gravar | escolher "quinzena" grava; reabrir lembra |

**Provado no ciclo** (a fatia é par, pela decisão de 26/10): caracterização 5.067 em cada banco, contrato 74 e 74, os
22 ensaios nos dois bancos sem vermelho, os 66 portões (o `conferir-segredos.sh`, 0) e o front inteiro, 3.327 casos, sem
mudar o `ProfissionalParceiroLigado`, o `HistoricoDoParceiroLigado` nem o `PainelLigado.teste.tsx`; antes, os casos dos
arquivos de efeito com `AMPLIFICAR_EFEITOS_MS=50`, o tsc, o lint (os 8 avisos de antes, nenhum novo), o build e o tsc das
prévias tocadas. No navegador, contra o banco de contrato: o Parceiro de março de 2026 como o administrador de teste, com
uma região viva só, a troca da unidade que guarda o mês, e a nota com os mesmos 16 parágrafos; o histórico de um
colaborador com a tabela rolando dentro do cartão e a coluna do mês presa, a 1.280 e a 375 px (esta também no tema claro), sem a
página rolar de lado; e o gerente de teste sem o Parceiro no menu, no Painel, com a nota de 8 parágrafos e uma região viva
só. Nenhum defeito achado ali. As prévias para o Claude Design: `CabecalhoDoRelatorio`, `EscolhaDoIntervalo`,
`RecusaDoRelatorio`, `TabelaQueRola` e `AindaNaoGravado`, com o cenário em `_relatorios.ts`; o envio fica para a 47. Nada
do servidor, do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; nenhum débito aberto ou fechado.

**A verificação, em três lentes** (a primeira fatia com três, pela decisão de 26/10), confirmou 13 achados (11 distintos),
todos tratados:

- **Trocar a unidade na mesma tela mudava só o `<select>`:** o nome do ciclo e o ‹ › passavam a descrever uma semana
  enquanto os números eram do mês. Agora a escolha volta ao atalho guardado da unidade nova e o pede; sem guardado nela, o
  da tela fica e nada sai. O item 9 do bloco "Em dia" diz ao invólucro de cada relatório como passar a unidade.
- **O erro do intervalo livre não chegava ao leitor de tela:** o campo errado ganhou `aria-invalid`, a frase o
  `aria-describedby`, numa região educada do próprio campo.
- **O glossário era provado pela constante, e não pela tela:** a lista errada no Painel passava verde. Agora o Painel, o
  Parceiro e o histórico são montados com números de dublê e o texto inteiro da nota comparado.
- **O `painel.comparacao` não serve a relatório** (oferece o ano anterior e fala do retorno): o comentário e o item 9
  dizem que o "mês em andamento" do relatório é uma chave nova, da primeira fatia que o mostrar.
- **Casos que faltavam:** uma região viva só também no erro e nas recusas; a falha velha que não sobrescreve a nova, nos
  dois ganchos; o `aria-label` do "—", que o `title` escondia.
- **O `DiaDeHoje` sem regra no CSS**, as prévias com a Cota-parte por mês (ela é por intervalo) e com um "gravado desde"
  depois do hoje do cenário, e a medida do pacote que não dizia o que não media.

Os mutantes da verificação, cada um sozinho e devolvido, vermelhos: C4-X19 (a troca da unidade sem pedir), C4-X21 (sem
guardado, volta ao padrão), C4-X22 a C4-X25 (o `aria-invalid`, o `aria-describedby`, o `aria-live` e a região do campo),
C4-X26 a C4-X29 (cada tela com a lista errada), C4-X30 e C4-X31 (o `role="alert"` no erro, o `aria-live` na recusa), C4-X32 e
C4-X33 (o `vigente` no `.catch` de cada gancho) e C4-X34 (o "—" só com o `title`). Um ficou verde, e é equivalente: o C4-X20,
que mostra o atalho guardado já no desenho da troca, antes do efeito -- o efeito o põe no mesmo desenho seguinte, com o
pedido.

## 27/10/2026 -- REL-MENU-E-PARCEIRO (a fatia 45 da fila das telas que faltam)

**Para quem usa, quase nada muda:** é a última fatia da base comum dos relatórios da tela nova (a C5, a C6 e a C7 de
`docs/f3-relatorios/spec-comum.md`), e nenhum relatório liga ainda -- a família `adm-api-relatorios` nasce na 47. O menu de
cada pessoa é o mesmo de antes (15 grupos e 51 destinos, nenhum item com guarda nova); o Profissional Parceiro abre pelo
menu e pelo "ver detalhes" do Painel como abria, no mesmo mês. O que fica pronto:

- **A sessão diz, para cada relatório que falta, se a pessoa o vê** (`/adm/api/sessao`, `menu`: as nove chaves
  `relatorio<Nome>`), pela mesma regra que a API vai conferir -- a tabela 1.1: o caixa vê o Caixa e a Cota-parte do dia, a
  Indicação da unidade e o Atendimentos só dele; o colaborador, o Atendimentos e a Cota-parte só dele. Cada relatório liga
  a sua no menu junto com a tela.
- **O menu da tela nova tem o caminho e a guarda de cada um dos onze**, com os nomes que a política do servidor guarda, e um
  teste só confere os dois lados e o item do ERP antigo.
- **Os atalhos do Console conferem o papel** (o D611, CORRIGIDO): a mesma regra do menu, e não uma cópia que esquecia o
  "só do administrador". Hoje nenhum atalho leva a um destino assim; o Royalties não vai aparecer a quem não é
  administrador quando ganhar atalho. A Cota-parte v2 ainda não tem item no menu, e por isso um atalho para ela não abriria
  para ninguém, nem para o administrador: a regra só acha item do menu, e o item da v2 entra com a fatia da Cota-parte, junto
  com o link (corrigido na verificação: o texto prometia que ela abriria ao administrador).
- **A tabela de caminho para tela** (`TELA_DO_RELATORIO`): cada relatório novo entra com uma linha, e abre com a
  "abertura" que outra tela lhe dá (o mês, o intervalo, a unidade, os filtros). O Parceiro é o primeiro.
- **Vendas Realizadas abre com os filtros do "ver as vendas"** de um relatório (a pergunta 5 do dono), já na primeira
  consulta, e com a unidade do recorte: a tela diz "Abertas pelo relatório: só as vendas da unidade nº 7." e a mantém ao
  filtrar; escolher Vendas Realizadas no menu volta à lista de sempre. A unidade sai pelo número, porque a listagem não traz
  o nome: o D623, para a fatia dos Descontos.

**A C6:** a subseção "A parte comum" no `docs/f3-relatorios.md` (seção 6); as correções do `spec-ordem.md` no
`spec-comum.md` (o controller de relatório sem `final`, a faixa de ids 90008800 a 90008899 que não estava livre, o
`corpoDoProprio` do ensaio). **A C7 só conferiu**, pelo `git log` da 42 à 44: o Parceiro já usa as peças comuns, e o único
resto, o `usuarioDaSessao` privado do controller dele, fica (ele não estende a base; o item 4 do bloco "Em dia" diz por quê).
O que saiu diferente do desenho, e o que a 46 e a 47 precisam saber, está no item 10 do bloco "Em dia" do `spec-comum.md`.

**Os mutantes**, cada um sozinho e devolvido byte a byte (o sha1 conferido), todos vermelhos (os do desenho com o prefixo
C5-):

| # | Mudança | Caso vermelho |
|---|---|---|
| C5-M44 | o `podeAbrir` do Console de volta à cópia de antes, sem o `podeVer` | `ConsoleAbreRelatorios.teste.tsx`: o Console pergunta ao `podeAbrirNoMenu` com os papéis |
| C5-M44b | a cópia de antes dentro do `podeAbrirNoMenu` | "o papel vale" e "é a mesma conta do menu" (`menu-do-erp.teste.ts`) |
| C5-M45 | a guarda da sessão sem o escopo (sempre `true`) | `SessaoApiTest`: o caixa, o colaborador e a sessão sem marca |
| C5-M46 | o `PRIVILEGIO_DE_DESCONTOS` divergente | `RelatoriosNoMenuTest`, a política dos Descontos |
| C5-M47 | o `navegar` sem zerar a abertura | o Parceiro e Vendas pela abertura (`Console.teste.tsx`) e os dois do "ver detalhes" (`ConsoleLigado.teste.tsx`) |
| C5-M48 | os `filtrosIniciais` ignorados | os três de `VendasRealizadasLigada.teste.tsx` e o de Vendas no Console |
| C5-X1 | o Console sem passar os filtros a Vendas | a abertura leva os filtros a Vendas Realizadas |
| C5-X2 | filtrar sem a unidade | filtrar mantém a unidade do relatório |
| C5-X3 | a frase pela unidade pedida, e não a aplicada | a unidade que vale é a que o servidor aplicou |
| C5-X4 | a `key` de Vendas sem os filtros | o menu tira os filtros |
| C5-X5 | o Parceiro da tabela sem o mês da abertura | a abertura leva o mês ao Parceiro; os dois do "ver detalhes" |
| C5-X6 | o Parceiro da tabela sem o `administrador` | o administrador escolhe a unidade |
| C5-X7 | uma guarda a menos na sessão (a do NPS) | as seis linhas da tabela e a lista exata das chaves |
| C5-X8 | o item dos Descontos com o `quando` antes da tela | o `quando` segue a tela nova |
| C5-X9 | o item do NPS com o caminho em texto | o item usa as constantes |
| C5-X10 | o item da Retirada no `layout.phtml` com outro privilégio (o arquivo devolvido e conferido) | o item do layout é guardado pela política |
| C5-X11 | o `relatorioCanceladas` fora do tipo `GuardaDoMenu` | a guarda da política está no tipo da tela |
| C5-X12 | o Royalties com o papel em texto | o item usa as constantes |

**Não provado nesta fatia, por não ter quem chame:** o `podeAbrir` de dentro do `abrirRelatorio`, o contador de aberturas
e o `unidadeId` dos relatórios; e os ramos `naTelaNova` do `RelatoriosNoMenuTest` (o `quando` e o item do layout com o
`veOMenu`), que rodam a partir da 47. O ensaio da sessão não lê as chaves do menu: quem as prende é o `SessaoApiTest`.
O `podeAbrir` de dentro do `abrirRelatorio` é preso pela primeira fatia cuja tela chame o `aoAbrir` (o texto do D611
dizia que ele "só abre com o `podeAbrir`" como se provado, e foi corrigido).

**A verificação** confirmou 7 achados, todos baixos, todos tratados:
- **O caso que a 47 teria de ter e não tinha:** o bloco "Em dia" ensinava que o `RelatoriosNoMenuTest` cobra as quatro
  coisas que a 47 liga juntas, e ele não lia o `Console.tsx` -- esquecer a linha da `TELA_DO_RELATORIO` ficava verde, e o item
  do menu cairia na tela não migrada. Agora o caso `testATabelaDoConsoleTemATelaDeCadaRelatorioLigado` (doze, um por política)
  cobra a linha se e só se `naTelaNova` (o Parceiro, que não é da família, sempre), com a constante como chave.
- **O `if` de fora da Cota-parte no `layout.phtml`** não era lido; o novo `condicoesQueEnvolvem` (o tokenizador do PHP e uma
  pilha de chaves) confere que a guarda da política envolve o item, nos dez pares.
- **Os casos de Vendas aberta por relatório** esperavam a frase, que antes da resposta já diz a unidade pedida; agora
  esperam o título com o período da resposta, e o "filtrar mantém a unidade" confere que o segundo pedido leva o `de`.
- **A documentação:** o "não existem (são da C5)" ganhou a nota FEITO no item 3 do bloco "Em dia" e em dez especificações; a
  C5 ganhou a tabela "As linhas de 27/10/2026"; o recuo do item 10 foi consertado (os subitens caíam fora da lista); o D611
  e o parágrafo acima deixaram de prometer o que não tem caso; e a Cota-parte v2, que não tem item no menu, não abre por
  atalho para ninguém até a fatia dela -- o texto de cima dizia que abriria ao administrador.

| # | Mudança | Caso vermelho |
|---|---|---|
| C5-X13 | a chave do Parceiro na `TELA_DO_RELATORIO` em texto | a tabela do Console, o Parceiro ("a chave é a constante") |
| C5-X14 | a linha do Produtos na tabela antes da tela | a tabela do Console, o Produtos ("a linha entra junto com a tela") |
| C5-X15 | o `if` de fora do item "Vendas" do `layout.phtml` com o privilégio das Canceladas (devolvido e conferido) | o item do layout, a Cota-parte ("a guarda da política envolve o item") |
| C5-X16 | a 47 que liga o `naTelaNova`, o `quando` e o item do layout e esquece a linha da tabela (três arquivos) | só a tabela do Console, o Produtos -- antes da verificação, verde |
| C5-X17 | filtrar Vendas Realizadas sem o período | filtrar mantém a unidade do relatório |

Os cinco aplicados um por vez e devolvidos, o sha1 de cada arquivo conferido. A corrida dos casos de Vendas não se reproduziu
com a âncora antiga, nem com `AMPLIFICAR_EFEITOS_MS=50` (o `type` do `userEvent` leva mais que a resposta): a âncora nova é o
que prova, e não um vermelho que antes faltasse.

**Provado** (a fatia é ímpar, e pela decisão do dono de 26/10/2026 rodaram só os testes do que ela toca; **o ciclo inteiro
fica para a 46**): a caracterização tocada e as vizinhas que leem a sessão, a política ou o menu (`RelatoriosNoMenuTest`,
novo, 52 casos depois da verificação, eram 40; `SessaoApiTest`, com 7 novos; `RegrasDoRecorteDoRelatorioTest`, `RelatorioAdmControllerTest`,
`PainelDaUnidadeTest`, `ProfissionalParceiroTest`, `RegrasDoProfissionalParceiroTest`, `PortoesDaFamiliaDosRelatoriosTest` e
`MenuDosRelatoriosSemAsTelasQueSairamTest`), 424 no MySQL e 424 no PostgreSQL, verdes, e 436 e 436 depois da
verificação; o front dos arquivos tocados (o
Console, o `ConsoleLigado`, o novo `ConsoleAbreRelatorios`, o menu, Vendas Realizadas, o Parceiro, o histórico, o Painel e
os que montam o Console), 488 casos verdes, e com `AMPLIFICAR_EFEITOS_MS=50` só os 3 vermelhos conhecidos do
`DevedoresLigado` (o D471, arquivo que esta fatia não toca); o tsc, o tsc das prévias tocadas, o lint (os 8 avisos de
antes), o build (o JS comprimido em 302,64 KB, +0,36 KB sobre a 44); o ensaio HTTP da sessão do ERP
(`ensaiar-sessao-do-erp.php`) nos dois bancos de contrato, 24 de 24; os portões que a fatia toca (`conferir-acl-como-booleano`
e `conferir-grupos-por-id`, 0) e os 66 do `conferir-tudo.sh`, nenhuma falha inesperada; `conferir-segredos.sh`, 0. As
prévias para o Claude Design: as nove chaves nos menus montados à mão (`MenuPrincipal`, `_console.ts`) e a célula nova
`VendasRealizadas` → `AbertaPeloRelatorio`; o envio fica para a 47. Débitos: o D611 CORRIGIDO e o D623 REGISTRADO. Nada do
esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; o `layout.phtml` não mudou.

**No navegador** (27/10/2026, depois da verificação), contra o banco de contrato: o gerente de teste com o menu de
sempre (Painel, Agendas, Vendas, Clientes, Relatórios só com "Vendas", Faturas) e as nove guardas `relatorio<Nome>` na
sessão; o administrador de teste com os 12 itens de Relatórios e o Parceiro pelo menu; e, com o privilégio do Parceiro
concedido só ali ao gerente, o "ver detalhes" do Painel de março de 2026 que abre o Parceiro em março de 2026, com o foco
no título e uma região viva só, e o Parceiro pelo menu logo depois de volta em outubro (o `navegar` zera a abertura); a
gaveta a 375 px sem a página rolar de lado. Nenhum pedido saiu com 403 ou 409 nesse caminho; nenhum defeito achado ali.

## 27/10/2026 -- PAINEL-DO-ADMINISTRADOR (a fatia 46 da fila das telas que faltam)

**O que mudou para quem usa.** O administrador ganhou o **Painel no menu** (a home dele continua a agenda) e escolhe nele
**a unidade**, como no Profissional Parceiro, um mês por vez e **sem a rede** -- a resposta 181 (b) do dono, que devolve a ele
a comparação com o ano anterior e o movimento por dia da semana de qualquer unidade, que o Radar e o Fluxo davam (o D487). A
troca da unidade guarda o mês e a comparação, e traz os clientes e a semana da unidade nova; o cabeçalho diz de que unidade são
os números; o "ver detalhes" dos colaboradores abre o Profissional Parceiro no mesmo mês e **na mesma unidade**. E todo painel
-- o do gerente também -- ganhou dois números comparados, **o valor dos serviços e o dos produtos**, com o mês anterior ou o ano
anterior, e as duas linhas no "Como os números são contados" (a resposta 183 b; o D488): das mesmas vendas, sem consulta a mais.
Para o gerente, nada mais muda na tela: o menu, a home e os pedidos HTTP são os de antes, e o parâmetro de outra unidade, que era
ignorado, agora é recusado (422). No servidor, cada um dos três pedidos faz uma consulta por chave a mais, o nome da unidade
do cadastro para o recorte, depois de soltar a sessão -- a forma do Parceiro (escrito na seção 3 do `docs/f3-relatorios.md`).
Quem tem o privilégio sem ser gerente nem administrador continua sem o painel.

**Onde.** No servidor: o `SessaoJson::menu` com a guarda nova `painelNoMenu` (o gerente ou o administrador) -- **a
`painelDaUnidade` não mudou de sentido**, porque ela decide a home no `ConsoleLigado`, e uma guarda nova era o caminho mais curto
para o administrador ganhar o item sem mudar de home; o `PainelDaUnidadeController` com as guardas em `guardasDoPainel` (o papel
de gerente OU de administrador) e a unidade em `recorteDoPainel` (`RecorteDoRelatorio::unidadeDoFiltro` com o escopo da unidade
para todos, entre as da lista suspensa de quem pede; a lista só é lida quando o pedido traz a unidade, e a home do gerente
continua sem consulta antes de soltar a sessão); `GET /adm/api/painel/unidades` (a rota nova, nos portões das rotas sem login e
no ensaio de permissão); o recorte das três respostas com a `unidade`; e o `PainelDaUnidadeJson::numeros` com o
`valorDosServicos` e o `valorDosProdutos`. Na tela: o `api/painel.ts` (a unidade nos três pedidos e `lerUnidadesDoPainel`), o
`Painel.tsx` (a `EscolhaDaUnidade` sem "todas", o cabeçalho pelo recorte, os dois cartões e os clientes de outra unidade como
"contando"), o `PainelLigado.tsx` (a lista só para o administrador, a unidade no pedido, a troca no mês do último pedido, e o "ver
detalhes" com a unidade dos números na tela), o `Console.tsx` (o `administrador` ao painel, a unidade na abertura do Parceiro),
o `ProfissionalParceiroLigado.tsx` (a `unidadeInicial`, só para o administrador), o `menu-do-erp.ts` (o item na guarda nova), o
glossário (duas chaves novas; as oito de antes caractere a caractere) e o `App.css` (**a grade dos números para em sete
colunas**: com catorze cartões, treze colunas -- uma janela de uns 2.520 a 2.692 px, a de um monitor de 2.560 px a 100% --
deixariam um sozinho; de uma a sete, nunca). **O menu continua com 15 grupos e 51 destinos**: o Painel do administrador é o
mesmo destino do gerente, e não um item a mais. O `layout.phtml` não mudou; nenhum outro papel ganhou ou perdeu item.

**O oráculo na cópia** (o `franquiabv_dev`, só SELECT, nada de dado pessoal): o valor dos serviços e o dos produtos do painel,
pela função da API, contra uma SQL independente que soma `vendas_produtos.valor_total` pelo tipo do produto -- cinco unidades
ativas, março de 2026 contra fevereiro e julho de 2026 contra julho de 2025: **dez de dez ao centavo**, o valor e o anterior,
e a soma dos tipos é o faturamento (na unidade 1, março: R$ 241.489,30 de serviços e R$ 6.345,50 de produtos, de R$ 247.834,80).

**Os mutantes**, um por vez, devolvidos byte a byte e conferidos pelo sha256 (e o `git diff --stat` igual antes e depois):

| # | Mudança | Caso vermelho |
|---|---|---|
| P46-M1 | a guarda do papel sem o administrador | o administrador escolhe a unidade; a lista de unidades |
| P46-M2 | as unidades aceitas sem a sessão (o gerente aceitando outra) | o gerente pedindo outra unidade é 422 |
| P46-M3 | o escopo da rede para o administrador, e só o nulo recusado | a rede, a inativa e a que não existe são recusadas |
| P46-M4 | sem a guarda do papel (o caixa aceito) | quem tem o privilégio e não é gerente recebe 403 |
| P46-M5 | a lista de unidades sem a sessão | a lista de unidades é a do filtro |
| P46-M6 | o recorte dos números sem a unidade | os números do mês (o recorte inteiro) |
| P46-M7 | o valor dos serviços sem o anterior | os números do mês; os dois valores contra a SQL |
| P46-M8 | o valor dos produtos com o anterior zero | os números contra o ano anterior |
| P46-M9 | o item do painel sem o administrador, na sessão | o item do painel é do gerente e do administrador |
| P46-M10 | o item do menu de volta na guarda da home | o Painel é o primeiro destino; o administrador tem o Painel no menu (Console) |
| P46-M11 | a troca da unidade sem pedir | trocar de unidade pede a nova; a semana acompanha |
| P46-M12 | a troca da unidade sem o mês da tela | trocar de unidade pede a nova no mês da tela |
| P46-M13 | o "ver detalhes" sem a unidade, no invólucro | o "ver detalhes" leva o mês e a unidade |
| P46-M14 | o "ver detalhes" sem a unidade, no Console | o administrador... o "ver detalhes" leva a unidade escolhida |
| P46-M15 | o Parceiro ignorando a unidade de partida | a unidade de partida vai no pedido do administrador |
| P46-M16 | o Parceiro lendo a unidade de partida para o gerente | idem, "e o gerente a ignora" |
| P46-M17 | a comparação perdendo a unidade escolhida | com a unidade escolhida, trocar de mês e de comparação a mantém |
| P46-M18 | os clientes sem a unidade | trocar de unidade pede a nova, com os clientes dela |
| P46-M19 | a semana sem a unidade | aberta, a semana acompanha a unidade |
| P46-M20 | os clientes de outra unidade misturados aos números | clientes de outra unidade ficam como "contando" |
| P46-M21 | o glossário sem as duas linhas | o do Painel é o de antes, com os dois valores; o Painel da unidade (a tela) |
| P46-M22 | o cartão do valor dos serviços fora da lista | a ordem dos cartões |
| P46-M23 | a lista de unidades pedida também para o gerente | os pedidos do gerente (três casos) |
| P46-M24 | a escolha oferecendo a rede | a escolha não oferece a rede |
| P46-M25 | os números sem a unidade no pedido | trocar de unidade pede a nova; mês e comparação a mantêm |
| P46-M26 | a lista de unidades com as inativas (`popularCombobox($this->sessao, null, true)`) | a lista não traz a inativa (só ele) |
| P46-M27 | o valor dos serviços com variação sem base no anterior | o valor dos serviços sem base e o dos produtos que caiu a zero |
| P46-M28 | o valor em dinheiro sem base mostrado como zero, na tela | os números do mês; o "sem base" dos dois cartões novos |

Os do servidor (P46-M1 a M9, M26 e M27) ficaram vermelhos **nos dois bancos**: na implementação, os nove primeiros rodaram só
no MySQL; a verificação os repetiu no PostgreSQL, um por vez, com os dois novos.

E os portões, falsificados arrancando o código (e não o comentário): sem a guarda do papel, com o escopo da rede, a lista sem a
sessão e a semana sem a chamada das guardas, o `conferir-recorte-por-unidade.php` sai 1 nos quatro, e o `conferir-acl-da-api.php`
sai 1 no último. Os tokens de antes do painel (`empty($this->usuarioGerente)` sozinho e `$unidade = (int) $this->unidadeId;` em
cada ação) saíram com a mudança, e os novos vigiam as quatro ações, as guardas e a regra da unidade. **Os tokens das ações
levam a recusa devolvida** (o molde do Parceiro): `$recusa = $this->guardasDoPainel(); if ($recusa instanceof Response) { return
$recusa; }`, o mesmo do `$pedido` com a desestruturação do recorte, e cada consulta pela `$unidade` dele. A primeira versão
vigiava só a chamada, e arrancar o `if ... return` de uma ação deixava o portão verde (e o da ACL também, que acha o
`getAcesso2` dentro do `guardasDoPainel`). Falsificados de novo, arrancando o código: P46-G5 (a semana sem devolver a recusa),
P46-G6 (os itens dos números pela unidade da sessão), P46-G7 (os clientes sem devolver a recusa da unidade) e P46-G8 (a lista sem
devolver a recusa) -- o recorte por unidade sai 1 nos quatro; o da ACL, 0, como a verificação previa.

**A verificação** (três lentes) confirmou 8 achados, todos baixos, todos tratados: os parágrafos antigos de "Quem vê" e a
tabela "Onde está" da seção 3 do `docs/f3-relatorios.md`, que diziam o painel só do gerente e esqueciam a `/semana` e a
`/unidades`; o comentário da prévia `_console.ts` que usava o Painel como exemplo do que o administrador não vê; o caso da lista
de unidades, que não distinguia as ativas (nos dois bancos de teste todas estão ativas: o caso novo desliga a 90001100 antes, e
o P46-M26 só o deixa vermelho); os mutantes do servidor só no MySQL; o "sem base" do valor dos serviços sem caso (março de 2025
contra fevereiro, no servidor, e os dois cartões na tela); os tokens do portão sem a recusa devolvida; a consulta a mais do
nome da unidade em cada pedido (registrada aqui e na seção 3, a forma do Parceiro -- não mudou o código); e a semana relida a
cada troca de unidade, que o servidor não trava: **medida na cópia, só leitura**, a leitura da rede do mês com a conta da
semana leva 0,58 s de mediana em março de 2026 (29.195 itens) e 0,67 s em julho (31.898), e por isso a semana continua sem
a trava (escrito na seção 7). Rodaram de novo: as 22 classes, **815 no MySQL e 815 no PostgreSQL** (os dois casos novos); os 11
arquivos do front, **558 casos**, e o `PainelLigado.teste.tsx` com `AMPLIFICAR_EFEITOS_MS=50`; o tsc, o tsc das prévias e o lint
(os 8 avisos); o portão do recorte por unidade (273 métodos) e os 66 do `conferir-tudo.sh`.

**Provado** (a fatia é par, e **o ciclo inteiro é do orquestrador, depois da verificação**; aqui rodaram os testes do que ela
toca): a caracterização das classes tocadas e vizinhas (`SessaoApiTest`, `PainelDaUnidadeTest` -- com os casos novos do
administrador em cada unidade, a rede, a inativa e a que não existe recusadas, o gerente pedindo outra unidade, o caixa recusado,
a lista e o oráculo dos dois valores --, `RelatoriosNoMenuTest`, `ProfissionalParceiroTest`, `RegrasDoPainelDaUnidadeTest`,
`ItensDoRelatorioTest`, `NumerosDaRespostaTest`, `NovosDoRecorteTest`, `RelatorioAdmControllerTest`, os das regras do Parceiro e
do recorte, `MenuDosRelatoriosSemAsTelasQueSairamTest`, `PortoesDaFamiliaDosRelatoriosTest`, `MoldePelosControllersTest`,
`PrivilegioApiTest`, `D499TravaDeOutroDonoTest`, `ExportacaoDoRelatorioTest`, `SoltarASessaoTest` e os das rotas), **813 no
MySQL e 813 no PostgreSQL**; o front dos arquivos tocados e vizinhos (o Painel, o glossário, o menu, o Parceiro, o histórico, o
Console, o `ConsoleLigado`, o `ConsoleAbreRelatorios`, as peças dos relatórios, a agenda e os arquivos), **557 casos**, verdes
com `--maxWorkers=2` e com `AMPLIFICAR_EFEITOS_MS=50`; o tsc, o tsc das prévias tocadas, o lint (os 8 avisos de antes), o build
(o JS comprimido em 303,03 KB, +0,39 KB sobre a 45); o `ensaiar-sessao-do-erp` (24 de 24) e o `ensaiar-permissao-adm-api` (a
`/painel/unidades` recusada sem o privilégio) nos dois bancos de contrato, e o `medir-adm-api-no-postgres` depois do ensaio do
fluxo (0; a `/painel/unidades` 200); os 66 portões do `conferir-tudo.sh`, nenhuma falha inesperada (o
`conferir-cobertura-por-par.php` com a base do ISVenda de 134 para 137 -- três chamadas novas de pares que já tinham teste --, e
o último portão, a suíte do front inteira com `--maxWorkers=4`, verde); `conferir-segredos.sh`, `conferir-referencias-docs.php`,
`contar-debitos.php` e `conferir-debitos-repetidos.php`, 0. As prévias: `Painel` → `DoAdministradorComAEscolha` (a escolha sem "todas", a VIP Centro),
os dois valores no cenário (`_relatorios.ts`, `_parceiro-painel.ts`), a guarda nova nos menus montados à mão (`_console.ts`, com
o administrador com o Painel, e `MenuPrincipal`); o envio fica para a 47. Débitos: o D487 e o D488 CORRIGIDOS (de pé: 152 para
150). Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou.

**Provado no ciclo** (a fatia é par, pela decisão de 26/10, depois da verificação e do navegador): caracterização 5.147 em cada
banco (11 pulados, os de antes; 80 a mais que na 44), contrato 74 e 74, os ensaios nos dois bancos sem vermelho (21 no MySQL, e
22 no PostgreSQL com o `medir-adm-api-no-postgres`), os 66 portões sem falha inesperada e o front inteiro, 3.350 casos.

**No navegador** (27/10/2026, depois da verificação, contra o banco de contrato, com o `local.php` devolvido ao `franquiabv_dev`
no fim): o administrador de teste entra na agenda, e o menu tem o Painel; o Painel abre na unidade da sessão com os 14 cartões
(o valor dos serviços e o dos produtos entre eles) e a escolha da unidade sem "todas as unidades", com o foco no título. Em março
de 2026 os números batem com a soma (serviços R$ 119,90 + produtos R$ 12,00 = faturamento R$ 131,90); a troca para Blumenau
guarda março, muda o cabeçalho para o nome da unidade e deixa o foco no seletor; o "ver detalhes" abre o Profissional Parceiro
em Blumenau, em março de 2026; com a semana aberta, a troca da unidade pede a `/painel/semana` de novo com a unidade nova (lido
no log do nginx, todos 200). A 375 px nada passa da largura (medido no DOM). Pela API, o administrador com `todas`, `0` e uma
unidade que não existe recebe 422. O gerente de teste segue como na 45: a home no Painel, sem seletor, o mesmo menu (Painel,
Agendas, Vendas, Clientes, Relatórios com um item, Faturas), os dois cartões novos, nenhum pedido da lista na carga; pedindo
outra unidade ou `todas`, 422, e a `/painel/unidades` lhe devolve só a dele. Nenhum defeito. **Não provado:** a grade de sete
colunas, que só aparece numa janela de uns 2.520 a 2.692 px; a conta está no comentário de `NUMEROS`, e não foi conferida no
Chromium.

## 27/10/2026 -- REL-PRODUTOS (a fatia 47 da fila das telas que faltam)

**O que mudou para quem usa.** Produtos / Serviços abre na tela nova -- o primeiro relatório dela, e o "ver todos" do cartão
"Os serviços que mais faturaram" do Painel. Mostra, numa unidade por vez, quanto se vendeu de cada serviço e de cada produto
no mês, quanto isso faturou, os descontos, o preço médio cobrado e a fatia de cada item, contra o mês anterior; em cinco
seções (Serviços, Produtos vendidos, Cortesias, Pacotes vendidos e Outros, as vazias escondidas), com o subtotal por
categoria nas três primeiras -- todas, zeradas também, e "Sem categoria" por último (a pergunta 11). As sessões de pacote
contam na quantidade e valem zero no faturamento, com o que valiam dito ao lado; o item que só vendeu no mês anterior aparece
com zero e −100,0%. As tabelas rolam dentro do cartão, com a primeira coluna presa. O gerente vê a unidade dele; o
administrador escolhe qualquer unidade ativa, sem "todas" (a rede é a F2); quem não é nenhum dos dois é recusado, e a tela
diz por quê. O "ver todos" do Painel abre no mesmo mês e, para o administrador, na mesma unidade dos números na tela -- a
extensão que a fatia 46 deu ao desenho (o corpo da F1 é anterior a ela). O item do menu da tela nova e o do ERP antigo
aparecem pela mesma regra da API (o gerente e o administrador), e **a tela antiga aplica a mesma política**: o colaborador com
o privilégio, que a abria, recebe a página de não encontrado. Saem da tela nova o "Valor Compra", o "Valor Venda" e o "Lucro"
da antiga (o preço de catálogo de hoje); a antiga continua no ERP até o corte.

**O que entrou** (a F1 de `docs/f3-relatorios/spec-produtos.md`, com o bloco "Em dia" dele, as resoluções do `spec-ordem.md`
e os itens 7 e 10 do bloco "Em dia" do `spec-comum.md`, todos no mesmo commit): no servidor, `ContasDosProdutosVendidos` (as
gavetas, as contas por linha, o subtotal por categoria e a união do mês com a comparação, pela régua do painel), o
`ProdutosVendidosJson` e o `ProdutosVendidosController` (`GET /adm/api/relatorios/produtos` e `.../unidades`, com a guarda da
rede no gancho `guardasDoRelatorio`, antes de soltar a sessão); a regra comum da categoria em `ContasDoPainelDaUnidade`
(`SEM_CATEGORIA` e `categoria`, pelo tipo do item; o `entre` público); a família `adm-api-relatorios` e a linha dela em
`rotas-publicas.php`; o `naTelaNova` do Produtos; a fábrica no `Module.php`; a guarda da tela antiga
(`ProdutosController::indexAction`) e o item do `layout.phtml` pelo `RecorteDoRelatorio::veOMenu`. Na tela,
`api/produtosVendidos.ts`, `ProdutosVendidos.tsx` e `ProdutosVendidosLigado.tsx` (as peças comuns da C4, a `EscolhaDaUnidade`
com `comTodas={false}`, o `usePedidoDoRelatorio`), as chaves `produtos.*` e a `relatorios.mesEmAndamento` do glossário, a linha
da `TELA_DO_RELATORIO`, o `quando` do item do menu (`relatorioProdutos`; continuam 15 grupos e 51 destinos) e o "ver todos" do
Painel com o mês e a unidade. Os portões: as vigias novas do recorte por unidade (a unidade dos números é a do recorte, a
guarda da rede respeitada, a tela antiga pela mesma política), a família com 1 relatório, as rotas de relatório guardadas pela
base (2), a cobertura por par (o ISRelatorio de 106 para 107, nenhum par novo) e o medidor do PostgreSQL (`QUERY` e
`PRECISA_TRAZER` = `gavetas.0.linhas`). As prévias: `ProdutosVendidos` (o gerente com as cinco gavetas, a sessão, a linha só de
junho e "Sem categoria"; o administrador noutra unidade; relendo; sem venda; carregando; a falha; a recusa), com o cenário em
`_relatorios.ts`, montado das linhas para fechar; o envio é do orquestrador. Débitos: o D-p1, o D-p2 e o D-p3 do desenho
registrados como o **D624**, o **D625** e o **D626**, e, na verificação, o **D627**; nenhum fechado (de pé: 150 para 154). A margem continua
fora da tela, como a F1 manda: o dono já disse gravar o custo no item (a 224 b, de 15/10/2026), mas **nenhuma fatia da fila leva
a coluna nem a margem** (as do PDV, 70 a 74, não as têm) -- o D627; o orquestrador a pôs na fila como a **74b**
(PDV-CUSTO-DO-ITEM, depois da 70, da 47 e da 48).

**O oráculo** (Python sobre as linhas cruas da cópia, só SELECT, só ids e números, contra a resposta do controller em processo
como administrador, as unidades 1 e 39, junho e julho de 2026): **395 conferências, nenhuma divergência** -- toda linha, toda
categoria e todo total, ao centavo, e a variação com o arredondamento do PHP (o empate de 31,25% vai a 31,3%). Reproduz a seção
1.8 do desenho: a unidade 1 em julho, R$ 286.189,40 contra R$ 257.223,45, serviços R$ 279.143,50, produtos R$ 7.045,90, 63
linhas; a 39, R$ 71.162,00 contra R$ 63.647,40, com os Outros de julho só com a linha de junho.

**A medida** (a ação inteira em processo, a cópia, buffer pool de 128 MB, unidade 1, julho de 2026, três processos): **1,01 a
1,21 s, 29,8 MB de pico** -- abaixo dos 2 s e dos 64 MB do desenho.

**Os mutantes** (cada um sozinho, devolvido byte a byte e conferido pelo sha1; os do servidor nos dois bancos), todos
vermelhos:

| # | Mudança | Vermelho em |
|---|---|---|
| P47-P1 | a gaveta pergunta produto antes de cortesia | `ProdutosVendidosTest` (o mês à mão, a régua contra o painel) e as regras sem banco |
| P47-P2 | a sessão no faturamento (`producao` no lugar de `centavos`) | a régua contra o painel, o mês à mão, as regras |
| P47-P3 | o desconto derivado (o preço da sessão menos o total) | o mês à mão; a sessão e as identidades sem banco |
| P47-P4 | a ordem sem a quantidade no desempate | o desempate sem banco (ficou VERDE na primeira rodada: os dois casos tinham a maior quantidade no menor id; o caso foi refeito com o maior id) |
| P47-P5 | o preço médio sem tirar as sessões | o Pezinho (`"0.00"` no lugar de `null`) |
| P47-P6 | a categoria crua, sem conferir o catálogo | a Selagem em Serviços; a categoria sem banco |
| P47-P7 | o subtotal sem as categorias zeradas | o subtotal sem banco (falha da asserção); no `ProdutosVendidosTest`, erro, e não falha: o `TypeError` do `paresDasCategorias(array)` com a gaveta sem as categorias |
| P47-P8 | a linha só da comparação omitida | a Barba de junho; a gaveta só da comparação |
| P47-P9 | a comparação pelo mês anterior inteiro | o mês em andamento |
| P47-P10 | sem o 422 de `todas` | o administrador e a contagem das soltas, os dois por ERRO, e não pela asserção: o `TypeError` do `contarOPeriodo(int)` com a unidade nula chega antes do 422 (o P47-X11 prova a asserção) |
| P47-P11 | o 422 de `todas` depois do `soltarASessao()` | o administrador (zero soltas); a contagem |
| P47-P12 | `contarOPeriodo` pelo `fromQuery('unidade')` | o portão do recorte (o bloco derivado e a vigia); os testes ficam verdes, como o desenho prevê |
| P47-P13 | a fatia sobre o faturamento total | o mês à mão, a régua, as regras |
| P47-P14 | a tela antiga sem o `$escopo !== null` | `testATelaAntigaAplicaAMesmaPolitica` |
| P47-P15 | o `layout.phtml` com a condição de antes | `RelatoriosNoMenuTest::testOItemDoLayoutEGuardadoPelaPolitica` |
| P47-P16 | a escolha da unidade com "todas" | o administrador na tela (`ProdutosVendidosLigado.teste.tsx`) |
| P47-P17 | a gaveta sem linha desenhada | o caso das gavetas |
| P47-P18 | o "ver todos" sem o mês | `PainelLigado.teste.tsx` (três casos) |
| P47-P19 | as Cortesias com as colunas de dinheiro | o caso das Cortesias |
| P47-X1 | o "ver todos" sem a unidade dos números | `PainelLigado.teste.tsx` (o administrador e o gerente) |
| P47-X2 | a unidade de partida lida também para o gerente | `ProdutosVendidosLigado.teste.tsx` |
| P47-X3 | a `TELA_DO_RELATORIO` sem a `unidadeInicial` | `ConsoleAbreProdutos.teste.tsx` (o administrador) |
| P47-X4 | o Console abre sem a abertura | `ConsoleAbreProdutos.teste.tsx` (os dois) |
| P47-X5 | o item do menu sem o `quando` | `ConsoleAbreProdutos.teste.tsx` (sem a guarda) |
| P47-X6 | a cortesia e o produto pelo catálogo dos serviços | o mês à mão e as regras |
| P47-X7 | o `naTelaNova` desligado | `RegrasDoRecorteDoRelatorioTest`, `RelatorioAdmControllerTest` (a família), `RelatoriosNoMenuTest` |
| P47-X8 | a coluna "Sessões" sempre | o caso das sessões |
| P47-X9 | a troca da unidade sem guardar o mês | o administrador na tela |
| P47-X10 | a rota das unidades com outro nome | `PortoesDaFamiliaDosRelatoriosTest` (a lista derivada da config) |
| P47-X11 | o P47-P10 com o tipo afrouxado (a unidade nula vira 0 na chamada), da verificação | o administrador (200 no lugar de 422) e a contagem das soltas (1 no lugar de 0), pela asserção, nos dois bancos |

E as falsificações do `conferir-recorte-por-unidade.php`, o código arrancado (e não o comentário), cada uma saída 1: P47-G1 (a
comparação pela unidade da sessão), G2 (a guarda da rede com `if (false)`), G3 (os itens pela unidade da sessão), G4 (a tela
antiga sem o `$escopo !== null`), G5 (a tela antiga pela política de outro relatório) e G6 (a guarda própria chamada e não
respeitada).

**O que rodou** (a fatia é ímpar: pela decisão do dono de 26/10/2026, só o que ela toca; **o ciclo inteiro fica para a 48**):
as classes novas e as tocadas e vizinhas (`ProdutosVendidosTest`, `RegrasDosProdutosVendidosTest`, `RelatoriosNoMenuTest`,
`RelatorioAdmControllerTest`, `SessaoApiTest`, `PortoesDaFamiliaDosRelatoriosTest`, `PainelDaUnidadeTest`,
`ItensDoRelatorioTest`, `ProdutosNoRelatorioTest`, `RetiradasNoRelatorioTest`, as regras do recorte, do painel e do Parceiro,
`ProfissionalParceiroTest`, `NumerosDaRespostaTest`, `NovosDoRecorteTest`, `RegraDoCatalogoTest`, as que leem o `layout.phtml`
e as rotas públicas, a exportação, a solta da sessão, a troca de tipo e as vendas no relatório), **884 no MySQL e 884 no
PostgreSQL**; o front dos arquivos tocados e vizinhos, **462 casos** com `--maxWorkers=2`, e os de efeito (`ProdutosVendidosLigado`,
`ConsoleAbreProdutos`, `PainelLigado`) também com `AMPLIFICAR_EFEITOS_MS=50`; o tsc, o tsc das prévias tocadas, o lint (sem
aviso novo), o build (o JS comprimido em **306,87 KB**, +3,84 KB sobre os 303,03 da 46: a tela, o invólucro e as peças da C4 que
nada usava -- a recusa e o `usePedidoDoRelatorio`); nos dois bancos de contrato, o `ensaiar-permissao-adm-api` (as duas folhas
novas recusadas sem o privilégio, 141 de 141 no MySQL; na parte 3, o colaborador e o caixa com o privilégio recebem 403) e o
`ensaiar-sessao-do-erp` (24 de 24); no PostgreSQL, depois do `ensaiar-fluxo-adm-api`, o `medir-adm-api-no-postgres` (0; as duas
folhas 200, com linhas) e o `medir-relatorios-no-postgres` -- **saída 1, "A MEDIÇÃO FICOU CEGA"**: nenhum relatório quebrou e o
Produtos antigo trouxe a marca "Corte Teste", mas o `rel-atendimentos/gerar` e o `rel-evasao/pdf` voltaram 302 sem a marca (19
de 25 com marca; a causa, medida na verificação, está no parágrafo seguinte) -- este por um roteiro do rascunho que concede os privilégios com `ON CONFLICT DO NOTHING`, porque o
`medir-telas-no-postgres.sh` não monta o banco: a concessão em massa colide com as linhas que as fixtures dão ao
`gerente.teste` (a colisão que o D616 já descreve, de antes desta fatia); o `conferir-tudo.sh` inteiro; `conferir-segredos.sh`,
`conferir-referencias-docs.php`, `contar-debitos.php` e `conferir-debitos-repetidos.php` (dois pares novos de palavras
coincidentes, o D578 e o D611 com o D625, registrados). Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou;
nenhum navegador foi aberto (é do orquestrador).

**A verificação** (três lentes; 9 achados confirmados, 6 distintos, todos tratados):

- **A resposta 224 (b) sem lugar.** O texto da fatia mandava a coluna do custo para "a fatia do PDV", que não existe: as do PDV
  (70 a 74) são o cancelamento, o "lançado por", a gaveta contada, o motivo da retirada e a cota do pacote. Corrigidos o D624, o
  `spec-produtos.md` (a tabela da seção 0 e a seção 3, com a resposta escrita) e o `spec-ordem.md` (a 3.4); registrado o **D627**
  (a coluna, o PDV gravando, o `gravadoDesde`, o "sem registro" e o valor em reais da Retirada), com o par D624|D627 no portão
  de repetidos. A F1 sem margem segue o desenho; o que falta é a fatia, que é da fila.
- **O medidor dos relatórios cego, calado no roadmap e no item 12.** Medida a causa, na mesma corrida e no mesmo banco de
  contrato: o `rel-atendimentos/gerar` e o `rel-evasao/pdf` leem o cache que duas rotinas escrevem
  (`CronJobService::relatoriosAtendimentosAtualizar` e `::relatoriosEvasaoAtualizar`, em `data/cache/relatorios/`); no CI quem as
  roda é o `medir-cron-no-postgres`, antes do medidor dos relatórios, e o roteiro do rascunho apagava o `data/cache` e não rodava
  rotina nenhuma. Sem o cache, saída 1 e os dois com 302 (o "Dados indisponíveis." da tela antiga); depois das DUAS rotinas, e só
  delas (nenhuma outra, nem as de royalty), **saída 0, 21 de 25 com marca**, os dois com a planilha e o PDF. Não é desta fatia;
  o roteiro do rascunho é que media sem o passo das rotinas.
- **P47-P10 e P47-P7 morrem por erro, e não pela asserção.** Dito na tabela; o **P47-X11** (o P10 com a unidade nula virando 0 na
  chamada) prova que a asserção do 422 e a das soltas pegam o mutante sem o tipo, nos dois bancos.
- **A prévia do administrador com o escopo do gerente.** O `produtosDaAmerica` de `_relatorios.ts` levava `escopo: 'unidade'`; o
  servidor dá `rede` ao administrador (o `ProdutosVendidosTest::testOAdministradorEscolheUmaUnidadePorVez`). Agora `rede`. A tela
  não lê o escopo, e nenhum caso pode pegar isso: é o cenário da prévia; o tsc das prévias tocadas, 0.
- **Comentários que diziam que a família não existia**: o `RelatorioAdmController`, o `RelatorioAdmControllerTest` (o docblock e a
  mensagem do caso da família), o `ExportacaoDoRelatorioTest` (o `index` do Produtos é a segunda ação a chamar o gancho), o
  `PortoesDaFamiliaDosRelatoriosTest`, o `ensaiar-permissao-adm-api.php` e o `conferir-rotas-sem-login.php`; e o docblock da
  `CHAVES` do `RegrasDoRecorteDoRelatorioTest` de volta acima dela.

Rodados de novo, em série: as 26 classes tocadas e vizinhas, **884 no MySQL e 884 no PostgreSQL** (1 pulado, de antes); o
tsc, o lint e o tsc das prévias tocadas, 0; o `conferir-tudo.sh` inteiro, **66 portões**, os testes do front entre eles, sem falha
inesperada; o `contar-debitos.php` (de pé: 154) e o `conferir-debitos-repetidos.php`, 0. Nenhum arquivo de `frontend/src` mudou
na correção. O ciclo inteiro continua para a 48.

**No navegador** (27/10/2026, depois da verificação, contra o banco de contrato, com o `local.php` devolvido ao `franquiabv_dev` e
o banco de contrato remontado no fim): o administrador de teste, no Painel de março de 2026, clica no "ver todos" e o Produtos /
Serviços abre em março, na unidade da sessão, com o faturamento do painel (R$ 131,90: serviços R$ 119,90 + produtos R$ 12,00),
as duas gavetas com linha (as sem linha não aparecem), as categorias zeradas (Base, Extra; Barba, Cabelo, Empório) com o Sem
categoria, e a Pomada Teste, que só vendeu em fevereiro, com zero no mês e R$ 90,00 no anterior. A escolha da unidade não tem
"todas"; a troca para Blumenau guarda março, mostra o cartão vazio ("Nenhuma venda com item neste mês.") e deixa o foco no
seletor. Com outra unidade (90001150) e setembro escolhidos no Painel, o "ver todos" abre o relatório nessa unidade e nesse mês,
com o foco no título (lido no log do nginx: `produtos?mes=2026-09&unidade=90001150`, 200). O "Como os números são contados" abre
com os textos do desenho. A 375 px a página não passa da largura (medido no DOM), as quatro tabelas rolam dentro do cartão
(`overflow-x: auto`, com `tabindex`) e a primeira coluna fica presa ao rolar. Pela API: sem sessão 401; o administrador com
`todas` 422 (uma unidade por vez), com `0` ou uma unidade que não existe 422, com `mes=2026-13` 422; a `exportacao` 404. O gerente
de teste não tem o privilégio do relatório nas fixtures: o menu, o Painel (sem o "ver todos", como antes) e a API (403
`sem_permissao`) ficaram como estavam. Com o privilégio concedido só no banco de contrato (descartável): o item Produtos /
Serviços no menu, o "ver todos" abrindo março sem seletor, nenhum pedido da lista de unidades, a dele 200, outra ou `todas` 422 e
a `/produtos/unidades` só com a dele. Nenhum defeito. A margem: a fatia 74b da fila (PDV-CUSTO-DO-ITEM, o D627), posta pelo
orquestrador depois da verificação.

## 27/10/2026 -- REL-RETIRADA (a fatia 48 da fila das telas que faltam)

**O que mudou para quem usa.** A Retirada de Produtos abre na tela nova -- o segundo relatório dela, e o "ver por produto" do
cartão Cortesias do Painel. Mostra, no mês, quanto produto saiu da unidade sem venda: os cartões "Saiu sem receita" (uso
interno, saídas pelo estoque e cortesias), "Uso interno" e "Saída pela tela de estoque" com o número de saídas, e "Cortesias"
com as comandas; a tabela por produto (o tipo, uma coluna por porta, as cortesias, o "saiu sem receita"), com o "ver as N
saídas" de cada linha, que abre ali mesmo as saídas daquele produto (dia e hora, quantidade, como saiu, quem pediu, quem lançou)
e devolve o foco ao botão ao fechar; e, numa unidade, "Pedida por" e "Lançada por", com "Sem quem pediu" por último. A
quantidade quebrada sai com vírgula ("0,5"). Perda, vencimento e acerto ainda não aparecem: a saída do estoque não pede o
motivo, e a tela diz isso (o acerto, quando vier, fica fora do total). O filtro "Pedida por" (todos, sem quem pediu, ou uma
pessoa) tira as cortesias, que não têm quem pediu, e volta a "Todos" ao trocar o mês ou a unidade. O gerente vê a unidade dele;
**o administrador escolhe qualquer unidade ativa ou "todas as unidades"** -- a rede, com a unidade de cada produto e sem as
pessoas, como a tela antiga também lhe mostrava; quem não é nenhum dos dois é recusado, e a tela diz por quê. O "ver por
produto" do Painel abre no mesmo mês e, para o administrador, na unidade dos números na tela. O item do menu da tela nova e o do
ERP antigo aparecem pela mesma regra da API, e **a tela antiga aplica a mesma política**, na lista e no histórico -- que passou
a pedir o privilégio da Retirada, e não o de Produtos / Serviços (o D625). A antiga continua no ERP até o corte.

**O que entrou** (a F0 de `docs/f3-relatorios/spec-retirada.md`, com o bloco "Em dia" dele, as resoluções do `spec-ordem.md` e
os itens 7 a 12 do bloco "Em dia" do `spec-comum.md`, todos no mesmo commit): no servidor, `RegraDaRetirada` (a porta de cada
saída, o filtro "pedida por", o motivo gravado pelo `ColunasDoPdv::semRegistro`), `ContasDaRetirada` (a normalização, as
cortesias pelo booleano do painel, as linhas, as pessoas, os totais), o `RetiradaDeProdutosJson` e o
`RetiradaDeProdutosController` (`GET /adm/api/relatorios/retiradas` e `.../unidades`, o filtro no gancho `guardasDoRelatorio`
antes de soltar a sessão, e o 422 do id que não pediu no recorte depois das consultas); os repositórios
`ProdutoEstoqueRepository::retiradasDoPeriodo` (uma linha por saída, a unidade do produto, a faixa meio-aberta) e
`ProdutoRepository::cadastroDosProdutos` (a unidade e o custo de hoje, que a F1 vai ler); o filho `retiradas` da família, o
`naTelaNova`, a fábrica no `Module.php`; a guarda das duas ações antigas (`retiradasAction` e `retiradasHistoricoAction`) e o item
do `layout.phtml` pelo `RecorteDoRelatorio::veOMenu`. Na tela, `api/retiradas.ts`, `RetiradaDeProdutos.tsx` e
`RetiradaDeProdutosLigado.tsx` (as peças da C4, a `EscolhaDaUnidade` COM "todas", o `AindaNaoGravado` do motivo), as chaves
`retiradas.*` do glossário (o texto do desenho, e um "mês em andamento" próprio, sem a comparação), a linha da
`TELA_DO_RELATORIO`, o `quando` do item do menu (`relatorioRetiradas`; continuam 15 grupos e 51 destinos; 14 na tela não
migrada) e o "ver por produto" do Painel (`aoVerCortesias`, com o mês e a unidade). Os portões: as vigias novas do recorte por
unidade (a unidade das saídas e das cortesias é a do recorte, a do produto no repositório, a guarda própria respeitada antes de
soltar, o filtro só com quem pediu, a tela antiga e o histórico pela política nova), a família com 2 relatórios, as rotas de
relatório guardadas pela base (4), a ACL delegada à base (2 ações), a cobertura por par (385 para 387 pares, os dois novos com
teste; o ISRelatorio de 107 para 111 chamadas; o sha1 sem os dois volta ao de 385, conferido) e o medidor do PostgreSQL
(`QUERY` = `mes=2026-03`; `PRECISA_TRAZER` = `movimentos`, a lista que só a consulta nova enche). As prévias:
`RetiradaDeProdutos` (o gerente; o administrador numa unidade, com o escopo `rede` no recorte, como o servidor responde a ele;
o administrador na rede; "Sem quem pediu"; sem saída; a falha; a recusa), com o cenário em `_relatorios.ts`, somado das
saídas para fechar; a entrada no `componentSrcMap` e o export no `index.ts`. O envio é do orquestrador, na rodada de cinco.

**A rede, contra o roteiro da fatia.** O roteiro mandava recusar `todas` com 422 "como no Produtos" e usar `comTodas={false}`.
A recusa do Produtos existe porque a rede dele é a F2 de lá; o desenho da Retirada tem a rede na F0 (a tabela "As guardas e o
escopo", os casos da rede no `RetiradaDeProdutosTest`, a coluna da unidade), e a tela antiga a mostrava ao administrador --
recusá-la tiraria dele o que tem hoje. Segui o desenho, e escrevi a divergência no `spec-retirada.md` (a nota FEITA da F0) e no
item 13 do bloco "Em dia" do `spec-comum.md`. Se o orquestrador quiser a recusa, é uma linha no gancho e o `comTodas={false}`
(e, desde a verificação, tirar a trava e a fila da rede, que só existem por ela).

**O oráculo** (Python sobre as linhas cruas da cópia, só SELECT, só ids e números, contra a resposta do controller em processo
como administrador; as unidades 1, 10, 26, 32 e 39 e a rede, julho de 2026): **1.299 conferências, nenhuma divergência** --
todo total, toda linha, cada saída uma a uma, as duas listas de pessoas e a ordem pelo total. Reproduz a tabela 1.2 do desenho:
a rede 1.659 (uso interno 129, estoque 105, cortesias 1.425 em 905 comandas; 121 linhas, 186 saídas), a U10 494, a U26 36, a U32
466, a U39 577, a U1 0.

**A medida** (a ação inteira em processo, a cópia, buffer pool de 128 MB, julho de 2026, três processos de cada): a unidade 39
em **1,03 a 1,25 s, 20,0 MB de pico**; **a rede em 1,52 a 1,95 s, 122,5 MB de pico** (72,6 KB de JSON) -- acima dos 64 MB, e
**os dois tempos acima do que o desenho esperava nas três rodadas** (menos de 1 s na unidade, menos de 1,5 s na rede; "acima
disso, registre e me diga": dito ao orquestrador; a primeira versão deste parágrafo só apontava a rede, e no pior caso). Registrado, sem mudar o desenho: o **D631** (o peso é o `painelItens` da rede inteira,
31.898 itens, lido só para as cortesias; com o `memory_limit` de fábrica de 128 MB, um mês maior cairia num erro fatal; o do
repositório é 512 MB, e o da produção ainda não foi lido).

**Os mutantes** (cada um sozinho, devolvido byte a byte e conferido pelo sha1; os do servidor nos dois bancos), todos
vermelhos, e todos pela asserção (nenhum por erro de tipo):

| # | Mudança | Vermelho em |
|---|---|---|
| P48-P1 | `retiradasDoPeriodo` sem o `status` (a saída cancelada entra) | `RetiradaDeProdutosTest`: o mês à mão, a rede, as pessoas, o detalhe, o repositório |
| P48-P2 | sem a `operacao` (a entrada entra) | os mesmos e o filtro |
| P48-P3 | `<= :fim` (a saída de 01/09 entra) | os mesmos e o mês em andamento |
| P48-P4 | sem a unidade do produto (P4 em A) | os mesmos; e o portão do recorte (o P48-G6) |
| P48-P5 | a porta ignora quem pediu | as regras (a porta, a normalização, o acerto, o filtro) e o controller |
| P48-P6 | a cortesia pelo total zero, e não pelo booleano do painel | as cortesias e o cartão do painel, nas regras e nos dois bancos |
| P48-P7 | o acerto no "saiu sem receita" | `RegrasDaRetiradaTest::testOAcertoFicaForaDoSaiuSemReceita` |
| P48-P8 | o filtro mantém as cortesias | o filtro (regras e controller) e a resposta |
| P48-P9 | as opções montadas depois do filtro | `testOFiltroPedidaPor` |
| P48-P10 | sem a conferência do id nas opções | `testOFiltroPedidaPor` (o U, de B) |
| P48-P11 | a quantidade por `(int)` (0,5 vira 0) | as regras e o controller (sete casos) |
| P48-P12 | as comandas com cortesia como soma por produto | `testDuasCortesiasNaMesmaVendaContamUmaComanda` |
| P48-P13 | a tela antiga sem o escopo | `testATelaAntigaAplicaAMesmaPolitica` |
| P48-P14 | o histórico antigo de volta a `getAcesso('ler')` | `testOHistoricoAntigoPedeOPrivilegioDaRetirada` |
| P48-P15 | o `soltarASessao()` antes da guarda do filtro | `testAsConsultasAntesDeSoltarASessao`; e o portão (o P48-G4) |
| P48-P16 | na tela, o filtro não zera ao trocar o mês | `RetiradaDeProdutosLigado.teste.tsx` |
| P48-P17 | na tela, o detalhe sem filtrar pelo produto | `RetiradaDeProdutos.teste.tsx` (dois casos) |
| P48-P18 | a ordem pelo nome, e não pelo total | a ordem (regras), o mês à mão, a rede, o filtro |
| P48-P19 | o `painelItens` com as canceladas | o mês à mão e a rede |
| P48-X1 | a unidade de partida lida também para o gerente | `RetiradaDeProdutosLigado.teste.tsx` |
| P48-X2 | o filtro pela unidade da sessão (a rede aceitaria o "pedida por") | `testOFiltroPedidaPor`; e o portão (o P48-G5) |
| P48-X3 | o item do `layout.phtml` com a condição de antes | `RelatoriosNoMenuTest::testOItemDoLayoutEGuardadoPelaPolitica` |
| P48-X4 | a `TELA_DO_RELATORIO` sem a `unidadeInicial` | `ConsoleAbreRetiradas.teste.tsx` (o administrador) |
| P48-X5 | o item do menu sem o `quando` | `ConsoleAbreRetiradas.teste.tsx` (sem a guarda) |
| P48-X6 | o "ver por produto" sem o mês | `PainelLigado.teste.tsx` (três casos) |
| P48-X7 | o "ver por produto" sem a unidade dos números | `PainelLigado.teste.tsx` (o administrador e o gerente) |
| P48-X8 | o `naTelaNova` desligado | `RelatoriosNoMenuTest` (quatro casos) e `RelatorioAdmControllerTest` (a família) |
| P48-X9 | a escolha da unidade sem "todas" | `RetiradaDeProdutosLigado.teste.tsx` (a rede) |
| P48-X10 | a troca da unidade guardando o filtro | `RetiradaDeProdutosLigado.teste.tsx` |
| P48-X11 | `cadastroDosProdutos` sem o retorno da lista vazia | `testOCadastroDosProdutos` (as consultas contadas) |
| P48-X12 | as portas do motivo sempre nulas | `RegrasDaRetiradaTest::testARespostaSoFormata` |
| P48-X13 | "Sem quem pediu" em primeiro | as pessoas (regras e controller) |
| P48-X14 | o "fechar" sem devolver o foco | `RetiradaDeProdutos.teste.tsx` |
| P48-X15 | o gancho devolve o pedido sem o filtro | `testOFiltroPedidaPor` |
| P48-X16 | o "ver por produto" em outro cartão | `PainelLigado.teste.tsx` |

E as falsificações do `conferir-recorte-por-unidade.php`, o código arrancado (e não o comentário), cada uma saída 1: P48-G1 (as
saídas pela unidade da sessão), G2 (as cortesias pela unidade da sessão), G3 (a conferência do id com `if (false)`), G4 (a guarda
própria chamada e não respeitada), G5 (o filtro pela unidade da sessão), G6 (o repositório sem a unidade do produto), G7 (a tela
antiga pela política de outro relatório) e G8 (o histórico de volta a `getAcesso('ler')`, sem o escopo).

**O que rodou** (a fatia é par: o ciclo inteiro e o navegador são do orquestrador, depois desta entrada; aqui, o que ela toca,
para corrigir cedo): as classes novas e as tocadas e vizinhas (`RetiradaDeProdutosTest`, `RegrasDaRetiradaTest`,
`ProdutosVendidosTest`, `RegrasDosProdutosVendidosTest`, `RelatorioAdmControllerTest`, `RelatoriosNoMenuTest`, `SessaoApiTest`,
`PortoesDaFamiliaDosRelatoriosTest`, `RegrasDoRecorteDoRelatorioTest`, `RetiradasNoRelatorioTest`, `ExportacaoDoRelatorioTest`,
`RegrasDoProfissionalParceiroTest`, `CampanhaAudienciaTest`, `RetiradasDoCaixaTest`, `PainelDaUnidadeTest`), **489 no MySQL e 489
no PostgreSQL**; o front dos arquivos tocados e vizinhos, **355 casos** com `--maxWorkers=2` (os cinco novos e tocados, 58; os
oito vizinhos, 297), e os de efeito (`RetiradaDeProdutosLigado`, `RetiradaDeProdutos`, `ConsoleAbreRetiradas`, `PainelLigado`,
`ConsoleAbreProdutos`, 86 casos) também com `AMPLIFICAR_EFEITOS_MS=50`; o tsc, o tsc de todas as prévias (que achou duas
constantes repetidas no cenário, renomeadas), o lint (sem aviso novo), o build (o JS comprimido em **309,92 KB**, +3,05 KB sobre
os 306,87 da 47); nos dois bancos de contrato, o `ensaiar-permissao-adm-api` (as duas folhas novas recusadas sem o privilégio;
na parte 3, o colaborador e o caixa com o privilégio, 403), o `ensaiar-sessao-do-erp` (24 de 24) e, depois do
`ensaiar-fluxo-adm-api`, o `medir-adm-api-no-postgres` (as duas folhas 200, com saídas), todos com saída 0 nos dois bancos;
o `conferir-tudo.sh` inteiro (os 66 portões; a primeira rodada saiu **1**, só o de débitos repetidos: o par D514|D629, palavras do mesmo assunto e defeitos diferentes, registrado no portão com o motivo; a segunda, **0**); `conferir-segredos.sh`, `contar-debitos.php` e `conferir-debitos-repetidos.php`. **Não rodou** o
`medir-relatorios-no-postgres` (a tela antiga da Retirada é medida lá com o `gerente.teste`, que tem o papel; fica para o ciclo,
que roda antes as duas rotinas do cache). Débitos: o **D625** CORRIGIDO; o D-r7, o D-r8 e o D-r9 do desenho registrados como o
**D628**, o **D629** e o **D630**, e a medida da rede como o **D631**; de pé, de 154 para **157** (o `contar-debitos.php`, o CONTINUAM DE PÉ). Nada do esquema, do crontab, do OneSignal,
da produção nem do BVIP mudou; nenhum navegador foi aberto.

**A verificação em três lentes** confirmou 12 achados (8 distintos), e a correção tratou todos:

- **A rede ficou** (três achados, um deles médio): o desenho a manda na F0 (a tabela "As guardas e o escopo", a 1.5, o caso "A
  rede"), o `spec-ordem.md` não a tira, nenhuma resposta do dono a recusa, e a tela antiga a mostrava ao administrador; a nota
  do roteiro que mandava recusar supunha um conflito com o corpo que não existe. **Ficou como o desenho**, e é o orquestrador
  quem decide outra coisa antes do commit -- para recusar: a linha no `guardasDoRelatorio`, o `comTodas={false}`, o
  `testARede`, as células da rede na prévia, e tirar a trava e a fila abaixo. O mutante pedido "a recusa da rede depois de
  soltar a sessão" não tem objeto (não há recusa); o caso do filtro na rede passou a cobrar `soltas === 0`.
- **A rede sem trava nem fila** (dois achados médios): a regra da base ("o relatório novo que ler a rede inteira em pedido de
  segundos ganha a sua") não fora seguida, e a sessão solta deixava várias leituras da rede da mesma pessoa em paralelo, cada
  uma com até ~122 MB. Entrou a **trava por pessoa no servidor** (`RetiradaDeProdutosController::TRAVA_DA_REDE`, depois de
  soltar a sessão; o segundo pedido da rede da mesma pessoa é o 429 com a frase do Profissional Parceiro; a unidade não trava;
  as consultas foram para o `numeros`, dentro do `try`) e a **fila `retiradas`** na tela (`filaDaRede.ts`, só com `todas`); o
  `usePedidoDoRelatorio` passa ao `ler` o `aindaQuer` do pedido, e ignora o `undefined` do que não saiu. O D631 diz isso.
- **Os tempos acima do desenho** (dois achados): a unidade 39 também passou do 1 s, nas três rodadas, e a rede passou dos 1,5 s
  nas três, e não só no pior caso. Corrigidos o parágrafo "A medida", o D631, a nota FEITA e a seção 6; dito ao orquestrador.
- **O `retiradas.mesEmAndamento`** estava sob um comentário que dizia "o texto do desenho, sem mudar": o comentário agora diz
  que essa frase é da 48 (o desenho pede uma chave do painel que não existe).
- **A fábrica no `Module.php` sem caso**: o `RelatorioAdmControllerTest::testTodoControllerDaFamiliaTemAFabricaNoModule`, com a
  lista derivada das rotas da família, confere a fábrica no `Module::getControllerConfig()` e o `has()` do gerenciador de
  controllers da aplicação montada.
- **A prévia do Painel sem o "ver por produto"**: os tratadores da prévia ganharam o `aoVerCortesias`; o envio é na rodada de
  cinco.
- **Os comentários velhos** ("nenhum item do menu as lê ainda"): no `_console.ts` e no `MenuPrincipal.tsx` das prévias, e, pelo
  grep, também no `Console.teste.tsx`, no `ConsoleLigado.teste.tsx` e no `api/sessao.ts`.
- **O administrador com a inativa e a inexistente**: o caso `testOAdministradorNaoPedeAInativaNemAQueNaoExiste` (a B desligada
  na transação; 422 `unidade` e nenhuma sessão solta).

**Os mutantes da correção** (cada um sozinho, devolvido e conferido pelo sha1; os do servidor nos dois bancos):

| # | Mudança | Resultado |
|---|---|---|
| P48-X17 | a Retirada da rede fora da fila (`pedir()` direto) | vermelho: os dois casos novos do `RetiradaDeProdutosLigado.teste.tsx` |
| P48-X18 | a fila com `aindaQuer` sempre verdadeiro | vermelho: "a trocada nem sai" |
| P48-X19 | o gancho passa `() => true`, e não o `vigente` | vermelho: o mesmo |
| P48-X20 | o `esquecerAsFilasDaRede` sem a `retiradas` | **verde, equivalente hoje**: nenhum caso deixa a rede no ar ao terminar |
| P48-X21 | sem a trava da rede | vermelho nos dois bancos: `testARedeEUmPedidoDeCadaVezPorPessoa` (o 429) |
| P48-X22 | a trava sem o id de quem pede | vermelho nos dois bancos: o mesmo |
| P48-X23 | sem o `liberar` no `finally` | **verde nos dois bancos, equivalente**: o PHP fecha o arquivo, e solta o `flock`, quando o handle sai de escopo (o mesmo vale para a do Parceiro) |
| P48-X24 | a trava antes de soltar a sessão | vermelho nos dois bancos: "a trava vem depois de soltar a sessão" |
| P48-X25 | a linha da Retirada fora do `Module.php` | vermelho nos dois bancos: `testTodoControllerDaFamiliaTemAFabricaNoModule` |
| P48-X26 | as unidades do filtro com as inativas | vermelho nos dois bancos: `testOAdministradorNaoPedeAInativaNemAQueNaoExiste` |

E as falsificações das vigias que mudaram de método (as consultas foram para o `numeros`), o código arrancado, cada uma saída 1:
P48-G9 (`numeros` chamado com outro pedido), G10 (as saídas com `null`) e G11 (as cortesias com `null`).

**O que rodou na correção** (em série): as 16 classes (as 15 de cima e o `ProfissionalParceiroTest`, cuja frase a Retirada
reusa), **563 no MySQL e 563 no PostgreSQL**, saída 0; o front tocado e vizinho com `--maxWorkers=2`, **231 casos** em 11
arquivos e **189** no `Console.teste.tsx` e no `ConsoleLigado.teste.tsx`, e **121** com `AMPLIFICAR_EFEITOS_MS=50`, todos com
saída 0; o tsc, o tsc das prévias tocadas (o Painel, o Console, o MenuPrincipal, o `_console.ts` e a Retirada), o lint (os 8
avisos de antes, nenhum nos arquivos tocados) e o build (o JS comprimido em **310,00 KB**, +0,08 KB sobre a primeira versão da
fatia); nos dois bancos de contrato, o `ensaiar-permissao-adm-api` e o `ensaiar-sessao-do-erp` (24 de 24), saída 0, e no
PostgreSQL o `ensaiar-fluxo-adm-api` e o `medir-adm-api-no-postgres` (as duas folhas 200; a primeira rodada do PostgreSQL foi cortada pelo meu `timeout` no meio do medidor -- não é defeito: o `local.php` foi devolvido à mão e conferido pelo sha1, e a rodada repetida saiu 0); os portões do recorte por unidade e da
cobertura por par, e o `conferir-tudo.sh` inteiro (os 66 portões, saída 0, "Nenhuma falha inesperada"; o `conferir-segredos.sh`, o `contar-debitos.php` -- de pé continua 157 -- e o `conferir-debitos-repetidos.php`, saída 0). O ciclo inteiro e o navegador continuam do orquestrador.

**A rede, decidida pelo dono** (27/10/2026, depois da verificação): "Acredito que o servidor de produção permite 1024mb ou
2048mb para o limite do php. mantenha a rede, siga o desenho". A rede fica como o desenho a escreve, com a trava e a fila da
correção; o D631 diz isso, e que o `memory_limit` da produção continua por ler no item 7 da etapa 1.

**Provado no ciclo** (a fatia é par; o `medir-ciclo-todo.sh` com as seis etapas, cada uma com saída 0): caracterização
**5.197 no MySQL e 5.197 no PostgreSQL** (11 pulados em cada); contrato **74 e 74**; os ensaios por HTTP, **22 de 22 com saída 0
em cada banco** (o `medir-adm-api-no-postgres` entre eles); os **66 portões**, "Nenhuma falha inesperada"; o front inteiro,
**3.405 casos**. O `medir-relatorios-no-postgres` não faz parte do ciclo (só o `medir-telas-no-postgres.sh` o chama), e o
parágrafo de cima o deixava "para o ciclo": rodei-o à parte, num PostgreSQL de contrato montado das fixtures, depois de rodar
**só** o `relatoriosAtendimentosAtualizar` e o `relatoriosEvasaoAtualizar` -- **saída 0**, nenhum relatório quebrou, 21 de 25
com a marca de dado (o mesmo da 47), e a tela antiga da Retirada (`rel-produtos/retiradas`) em 200.

**No navegador**, contra o banco de contrato: o administrador de teste pelo menu (Relatórios > Retirada de Produtos), março de
2026 na unidade em que entrou -- "Saiu sem receita" 5 (uso interno 2 e saída pelo estoque 3, uma saída cada), a Cerveja e a
Pomada, as duas listas de pessoas e o detalhe de uma saída; "todas as unidades" no mesmo mês, com a coluna da unidade e sem as
pessoas nem o "Pedida por"; outra unidade guardando o mês; todos os pedidos 200. O gerente de teste, com o privilégio concedido
só no banco de contrato, pelo "ver por produto" do cartão Cortesias do Painel de março de 2026: a Retirada no mesmo mês, sem a
escolha da unidade; o "Pedida por" numa pessoa tira as cortesias (o travessão e a frase) e a linha só da outra porta; a API lhe
recusa outra unidade e "todas" com 422. O glossário inteiro. **Dois defeitos, consertados no mesmo commit:**

- **A página rolava de lado a 375 px** (644 px de largura): o rótulo "As saídas" do cabeçalho da última coluna é `.sr-apenas`,
  absoluto, e o `.relatorio-rolagem` da `TabelaQueRola` não era posicionado -- o rótulo escapava da rolagem e esticava a
  página. O `.relatorio-rolagem` ganhou `position: relative` (o `App.css`, comum a toda tabela de relatório); depois, 375 px, a
  tabela rolando dentro do cartão (701 em 344) e a coluna do produto presa ao rolar. O vitest não lê o CSS (o `App.css` chega
  vazio), e a prova é esta.
- **"ver as 1 saída"**: com uma saída só, o botão agora diz "ver a saída"; o caso novo no `RetiradaDeProdutos.teste.tsx` (o
  JUNHO das cargas) cobra o texto.

Depois dos consertos: o tsc, o lint (os 8 avisos de antes, nenhum nos arquivos tocados), o build (o JS comprimido em **310,01
KB**) e os quatro arquivos de teste tocados e vizinhos (`RetiradaDeProdutos`, `RetiradaDeProdutosLigado`, `TabelaQueRola`,
`ConsoleAbreRetiradas`), 30 casos, saída 0.

## 27/10/2026 -- REL-RETIRADA-NA-COMANDA (a fatia 49 da fila das telas que faltam)

**O que mudou para quem usa.** Duas coisas. **A Retirada de Produtos mostra o custo atual:** o cartão "Custo atual do que saiu
sem receita" (em reais, "ao custo de hoje do cadastro", e quantos produtos sem custo no cadastro ficaram fora) e a coluna
"Custo atual" na tabela por produto -- a quantidade vezes o preço de compra que o cadastro tem HOJE, e não o da época; o
produto sem preço de compra sai com "—" (e o porquê ao passar o mouse). O acerto de inventário, quando vier, tem custo e fica
fora do total, como a quantidade. Vale numa unidade e na rede; com o filtro "Pedida por", o custo das cortesias some com elas.
O "Como os números são contados" ganhou a frase do custo atual. **A comanda nova retira do estoque:** na lista de comandas,
quem lança na comanda vê "retirar do estoque" ao lado de "nova comanda" -- escolhe o produto (só os contados em estoque e não
esgotados, com o estoque ao lado, e uma procura sem acento), quem pediu (os colaboradores ativos) e a quantidade (inteira, de 1
ao estoque; acima, a tela diz o estoque sem pedir). A retirada lançada diz "Retirada lançada: 2 × Pomada, pedida por Ana.
Estoque agora: 8." e o produto já aparece com o estoque novo na comanda. É a mesma saída do modal "Retirada de produtos" do PDV
antigo, e a Retirada de Produtos a conta como uso interno, com quem pediu e quem lançou. Quem pediu tem de ser alguém ativo da
unidade do produto -- agora também no modal antigo, que aceitava qualquer número mandado à mão (o D468).

**O que entrou** (a F1 e a F2 de `docs/f3-relatorios/spec-retirada.md`, com o bloco "Em dia" dele e a nota FEITA da F0, e o
bloco "Em dia" do `spec-comum.md`): no servidor, `ContasDaRetirada::custo` (em centavos, arredondado por porta, o nulo do
produto sem custo e o `produtosSemCusto`), o `custo` por linha e por porta no `RetiradaDeProdutosJson` (com as regras de nulo
das quantidades) e a chamada no `numeros` do controller, dentro da trava da rede e do mesmo `cadastroDosProdutos`; a
`RegraDaRetiradaDeEstoque` (nova), a ação `retirarDoEstoqueAction` (`POST /adm/api/produtos/:id/estoque/retirada`, a guarda
`vendas/comandasNova/escrever` pelo 4º parâmetro do `produtoDaRota`, a franquia e o "tem estoque" de sempre, o piso do serviço
com o 409 do saldo relido) e a rota `adm-api-produtos/um/estoque/retirada/registrar`; no PDV antigo, a conferência de quem
pediu (`retiradaAction`, com o ajudante `solicitanteDaRetirada`) -- a guarda dele não mudou: já era a do PDV desde o D365. Na
tela, os tipos novos em `api/retiradas.ts`, o cartão e a coluna no `RetiradaDeProdutos`, a chave `retiradas.custoAtual` (o
texto do desenho; o corpo a chamava `retiradaCustoAtual`); o `retirarDoEstoque` em `api/produtos.ts`, o `RetirarDoEstoque`
(novo), a prop `retirada` da `ListaDeComandas` e do `Comanda`, o `ComandasLigada` passando os catálogos que já pedia (uma vez
só) e trocando o saldo do produto pelo devolvido, e o `TIPOS_COM_ESTOQUE` exportado do `catalogoDaComanda.ts`. Os portões e
roteiros: quatro vigias novas no `conferir-recorte-por-unidade.php` (a guarda da comanda na ação, quem pediu nas duas portas e a
unidade na regra), a rota nova no `conferir-rotas-sem-login.php`, a linha nova no `ensaiar-permissao-adm-api.php` (o 403 de
quem não tem a comanda) e os casos 192 a 196 no `ensaiar-recusas-adm-api.php` (o serviço 404, quem pediu de outra unidade 422,
acima do saldo 409, a quantidade em texto 422, e o controle 201 com a linha conferida no banco), com a foto e a devolução do
saldo e das linhas de `produtos_estoque`. As prévias: `RetirarDoEstoque` (quatro células), a `retirada` na base das prévias da
`ListaDeComandas` e da `Comanda`, o custo no cenário da Retirada em `_relatorios.ts` (o Café Especial sem custo), a entrada no
`componentSrcMap` e o export no `index.ts`. O envio é do orquestrador, na rodada de cinco (depois da 52).

**Medido na cópia antes de mexer no PDV antigo** (só SELECT, só contagens; recontado na verificação, sem as fixtures): 542
retiradas com solicitante em 2026, lançadas por 27 pessoas (22 de grupo caixa e 5 de grupo gerente, nenhuma de grupo
administrador; os baldes somam 27), e as 27 têm `vendas/nova/escrever` e `vendas/comandasNova/escrever`: as duas portas cobrem
todas. A primeira contagem dizia 543 e 28, e "1 sem `vendas/nova`": a 543ª linha é a fixture 90001000, lançada pelo usuário de
teste 90001001, que estava na cópia -- ninguém de verdade perde o modal antigo. Quem pediu: 0 das 542 com o solicitante de
outra unidade que a do produto, 47 com o solicitante inativo hoje (a regra vale para a retirada nova). Ninguém que lançou
retirada em 2026 perde a porta.

**O oráculo** (Python sobre as linhas cruas da cópia, só SELECT, contra a resposta do controller em processo como
administrador; as unidades 1, 10 e 39 e a rede, julho de 2026): **360 conferências, 0 divergências** -- o custo unitário e o
custo de cada linha, os totais de cada porta e o `produtosSemCusto` --, e os totais são os da tabela da F1 de 02/10, centavo a
centavo: U10 R$ 1.298,66, U39 R$ 1.314,78, rede R$ 6.571,45, U1 R$ 0,00.

**A medida** (o D631; a cópia, buffer pool de 128 MB, julho de 2026, três processos de cada, e o código da 48 carregado na
mesma hora pelo mesmo instrumento): a unidade 39 em **1,17 a 1,19 s com 20,1 MB** de pico (a 48: 1,18 a 1,25 s, 20,1 MB); a
rede em **1,58 a 1,99 s com 122,6 MB** (a 48: 1,73 a 2,14 s, 122,6 MB). O custo não mudou o pico nem o tempo além do ruído; o
JSON da rede foi de 72.605 a 77.492 bytes. Contra os números da 48 escritos (U39 1,03 a 1,25 s e 20,0 MB; rede 1,52 a 1,95 s e
122,5 MB), a diferença é do instrumento e da hora: o código da 48 mede o mesmo que o de agora.

**Os mutantes**, cada um sozinho, devolvido byte a byte e conferido por sha1, os do servidor nos dois bancos (o mesmo vermelho
no MySQL e no PostgreSQL):

| # | Mudança | Vermelho |
|---|---|---|
| P49-P1 (RC1) | `valor_venda` no lugar de `valor_compra` no cadastro | 5 casos: o mês contado à mão, o custo ao centavo, o de hoje, a rede, o repositório |
| P49-P2 (RC2) | o custo zero vale como custo | 4: o custo zero nulo, a resposta, o ao centavo, a rede |
| P49-P3 (RC3) | o acerto no custo da linha e do total | o acerto fora do total |
| P49-P4 (RC4) | o custo arredondado em reais | 6, entre eles 5,50 × 3 e 0,5 × 3,33 |
| P49-P5 (RE1) | a ação sem o 4º argumento (o catálogo de volta) | 7: o caixa só com a comanda, e todo caso que passava da guarda |
| P49-P6 (RE2) | a guarda da comanda trocada por `true` | o 403 sem a comanda |
| P49-P7 (RE3) | a regra sem a unidade | a regra, o de outra unidade na API e no PDV antigo |
| P49-P8 (RE4) | a regra sem o status | a regra, o inativo na API e no PDV antigo |
| P49-P9 (RE5) | sem ler a recusa do piso | morre por erro de tipo (a movimentação nula): não conta sozinho |
| P49-P9b | o P9 com o tipo afrouxado (a movimentação nula aceita) | o 409, pela asserção do código |
| P49-P10 (RE6) | `is_numeric` no lugar de `is_int` | a quantidade em texto e a quebrada |
| P49-P11 (RE7) | o PDV antigo de volta a `getAcesso()` | 6 casos do PDV antigo (os novos e os do D365) |
| P49-P12 (RE8) | o PDV antigo sem o `empty($produto)` | o produto de outra franquia, por erro fatal |
| P49-P12b | o P12 com o nulo afrouxado (`?->`) | o mesmo caso, pela asserção da frase |
| P49-P13 (RE9) | a frase da recusa vazia | 6 casos com a frase |
| P49-P14 (RE10) | na tela, sem trocar o saldo no catálogo | o saldo novo depois de retirar |
| P49-X1 | o sem custo somado como zero sem contar em `produtosSemCusto` | 4 casos |
| P49-X2 | o custo das cortesias fora | 6 casos |
| P49-X3 | o acerto sem custo na porta dele | o acerto fora do total |
| P49-X4 | o custo fora da rede | o custo na rede |
| P49-X5 | o PDV antigo sem conferir quem pediu | os quatro conjuntos (o inativo e o de outra unidade pela asserção; o que não existe e o texto por erro do banco) |
| P49-X6 | quem pediu conferido contra a unidade DELE, e não a do produto | o de outra unidade na API |
| P49-X7 | a retirada que não pede os catálogos ao abrir | 3 casos da tela |
| P49-X8 | a frase do custo sempre no plural | o cartão do custo |
| P49-X9 | o "—" sem o porquê | a coluna |
| P49-X10 | acima do estoque, pede assim mesmo | a frase sem pedir |
| P49-X11 | os esgotados oferecidos | o que se oferece |
| P49-X12 | os colaboradores inativos oferecidos | o que se oferece |
| P49-X13 | o botão para quem não escreve na comanda | o botão só para quem escreve |
| P49-X14 | o custo das cortesias com o filtro | 2 casos |
| P49-X15 | o custo das portas do motivo sem o motivo gravado | 3 casos |
| P49-X16 | o custo da linha sem custo como R$ 0,00 | 2 casos |
| P49-X17 | desistir sem devolver o foco | o foco no botão |
| P49-X18 | o cartão com o custo de uma porta, e não o total | 2 casos |
| P49-X19 | a quantidade em texto no corpo | o corpo |
| P49-X20 | o 422 no alerta, e não no campo | o 422 no campo |

As falsificações dos portões, arrancando o CÓDIGO e deixando a marca num comentário onde cabe: **P49-G1 a G4** (a guarda da
comanda na ação, a unidade na regra, quem pediu no PDV antigo e na ação nova) -- `conferir-recorte-por-unidade.php` saída 1 em
cada uma; **P49-G5** (a rota fora da lista) -- `conferir-rotas-sem-login.php` saída 1; **P49-G6** (a guarda da comanda arrancada) -- o `ensaiar-permissao-adm-api.php` sai 1, "recusou: 143 de 144" (a linha nova respondeu 422, e não 403); **P49-G7** (`is_numeric`) -- o de recusas, só os casos 192 a 196, sai 1 (o 195 com 201, e o banco devolvido pelo `depois`). Cada um no banco de contrato montado de novo, e o `local.php` devolvido ao `franquiabv_dev`, conferido.

**O que rodou** (a fatia é ÍMPAR: pela decisão do dono de 26/10/2026, só os testes do que ela toca; **o ciclo inteiro fica para
a 50, e o navegador é do orquestrador**): a caracterização das classes tocadas e vizinhas (as da Retirada, a nova `RetiradaDeEstoqueNaComandaTest`, as do catálogo, do estoque, do PDV antigo e do D365, as dos relatórios, do menu, da sessão e dos privilégios), **634 casos no MySQL e 634 no PostgreSQL** (3 pulados de propósito, os do D199), pelo executor `-como-ci` e pelo do PostgreSQL; os ensaios nos dois bancos de contrato -- `ensaiar-permissao-adm-api` 144 de 144, `ensaiar-sessao-do-erp` 24 de 24, `ensaiar-recusas-adm-api` 244 de 244 (180 recusas e 64 controles), `ensaiar-fluxo-adm-api` 22 passos, todos saída 0 nos dois, e o `medir-adm-api-no-postgres` saída 0 (a rota nova, 422 pela guarda, com o corpo vazio); o medidor dos relatórios no PostgreSQL pelo roteiro do rascunho (só as duas rotinas do cache), saída 0 e 21 de 25 com marca; o front -- tsc, o tsc das prévias tocadas, o lint e o build (o JS comprimido de 310,01 KB para **311,39 KB**, +1,38 KB) com saída 0, e **595 casos** dos 12 arquivos tocados e vizinhos (`--maxWorkers=2`); com `AMPLIFICAR_EFEITOS_MS=50`, 183 de 185 nos 5 arquivos de efeito -- os 2 vermelhos ("as pílulas do catálogo andam pelas setas" e "abrir outra comanda tira a frase da encerrada", do `ComandasLigada.teste.tsx`) caem igual no `HEAD` exportado, medido duas vezes: são o D471, e não desta fatia. O `conferir-tudo.sh` inteiro: na primeira rodada, 1 portão vermelho, o `inventario atualizado` -- o `docs/inventario-acoes.csv` defasado, porque a `retiradaAction` antiga passou a ler `Usuario` (o quem pediu); regerado pelo `inventariar-acoes.php` (uma linha), e na segunda rodada, os 66 portões com saída 0 ("Nenhuma falha inesperada").

**Depois da verificação** (três lentes, 7 achados confirmados, todos baixos menos um médio, todos tratados; nenhum mudou o
código que roda, só os testes, um portão e os textos): (1) o item 14 do `spec-comum.md` e o docblock da `ContasDaRetirada`
diziam à 74b que ela não devia reaproveitar a conta do custo, e a fila manda pôr "o custo gravado onde houver" -- os dois
textos agora deixam a escolha com a 74b; (2) e (3), o médio: o `round` do custo unitário não tinha caso (todos os custos dos
testes eram exatos no double), e o de três casas que a faixa prometia não existia -- casos novos, na regra pura (9,70, 18,90 e
1,235) e pelo banco (os produtos 90006856 a 90006858), e o docblock diz por que arredondar: na cópia, 21 dos 483 custos
distintos de produto com movimento de estoque perderiam um centavo pelo `(int)` sozinho (33 dos 965 do cadastro); (4) a linha
só de cortesia, de produto sem custo, não tinha caso em `produtosSemCusto` -- caso novo nos dois níveis; (5) a unidade do
produto como segundo argumento de quem pediu só era provada onde é igual à da sessão -- caso novo com o administrador pedindo
o produto de outra unidade, nas duas portas, e as duas marcas do `conferir-recorte-por-unidade.php` passaram a levar o
argumento; (6) as saídas do MySQL do `ensaiar-permissao-adm-api` e do `ensaiar-recusas-adm-api` no rascunho eram as das
falsificações G6 e G7, que gravam no mesmo arquivo -- rodados de novo no banco de contrato montado de novo, 144 de 144 e 244
de 244 (180 recusas e 64 controles), saída 0, guardados em `p49c-mysql-ensaiar-*.txt`; (7) a medida de quem perde o PDV antigo
contava a fixture 90001000 como pessoa: são 542 retiradas de 27 pessoas, todas com as duas guardas (o parágrafo acima, o
spec e o D468 corrigidos). Mutantes novos, cada um sozinho, devolvido e conferido por sha1, vermelhos pela asserção nos dois
bancos: **P49-X21** (o custo unitário truncado) e **P49-X22** (`floor`), os dois casos de arredondamento; **P49-X23** (a
cortesia fora do `temQuantidade`), os dois casos da cortesia sem custo; **P49-X24** (a ação nova com a unidade da sessão) e
**P49-X25** (o PDV antigo com a unidade da sessão), o caso do administrador. Falsificações: **P49-G8** e **P49-G9** (o
argumento trocado no código, a marca deixada num comentário) -- `conferir-recorte-por-unidade.php` saída 1 em cada. Depois:
as 47 classes tocadas e vizinhas (as que leem o que a fatia mudou, mais o `RelatorioAdmControllerTest`, o
`RelatoriosNoMenuTest` e o `SessaoApiTest`), **823 casos no MySQL e 823 no PostgreSQL** (4 pulados de propósito: os 3 do
D199 e o do D453), saída 0; os 66 portões do `conferir-tudo.sh`, saída 0; `contar-debitos` e `conferir-debitos-repetidos`
saída 0, de pé 156. O front não mudou na verificação.

**O que ficou de fora:** o motivo da saída (a fatia 73, que gravará `uso` nesta retirada sem mudar mais nada) e o custo
gravado no item (a 74b, o D627); o autor de verdade na personificação (o D629, que fica). Nada do esquema, do crontab, do
OneSignal, da produção nem do BVIP mudou. **Débitos:** o D468 CORRIGIDO; o D631 com a medida nova; de pé 157 para 156.

**No navegador** (o orquestrador, 28/10/2026, contra o banco de contrato; um defeito, consertado neste commit): o
administrador de teste na Retirada de março de 2026, na unidade e em "todas as unidades", com o cartão "Custo atual do que
saiu sem receita" em R$ 55,00 -- 2 × R$ 20,00 da pomada e 3 × R$ 5,00 da cerveja, o `valor_compra` lido no banco -- e a coluna
"Custo atual" nas duas linhas; com o custo da cerveja zerado só no banco de contrato, o cartão foi a R$ 40,00 com "1 produto
sem custo no cadastro fica fora", e a linha com "—" e o `title` "sem custo no cadastro"; o glossário com a linha do custo;
a 375 px, só a tabela rola. O gerente de teste (com "Comandas nova" escrever) nas comandas abertas: o botão "retirar do
estoque", o formulário com o foco na procura e os colaboradores ativos da unidade; as fixtures não têm estoque (a `quantidade`
nula, que a tela conta como esgotado, como o catálogo da comanda -- na cópia, 2 de 2.388 produtos ativos), e a pomada ganhou 3
só no banco de contrato. O envio vazio deu as três frases ao lado dos campos; 5 deu "há 3 unidade(s)" sem pedir; com o saldo
baixado a 1 por baixo da tela, 2 deu o 409 do servidor no alerta ("há 1 unidade(s)", o código ao lado como nas outras telas);
1 deu o 201 e "Retirada lançada: 1 × Pomada Teste, pedida por Colaborador Teste. Estoque agora: 0." no `role="status"`, uma
linha só em `produtos_estoque` (`sai`, quem lançou o gerente da sessão, quem pediu o colaborador), e o formulário aberto de novo
já sem a pomada; o "voltar" devolve o foco ao botão. **O defeito:** a procura, os dois seletores e a quantidade saíam crus, com
o fundo claro e o canto reto do navegador, porque a `.retirar-do-estoque-campos` não estava na regra `:is(...)` dos campos das
telas novas (a de 01/10/2026, a do D322) -- entrou nas três listas, e o campo ganhou o da casa e a borda `--perigo` do
recusado, conferidos a 375 px; o `App.css` chega vazio no vitest, e nenhum caso o pegaria. Depois do conserto: o lint e o
build com saída 0 (o JS comprimido em 311,39 KB). O banco de contrato montado de novo no fim e o `local.php` no
`franquiabv_dev`, conferido.


## 28/10/2026 -- REL-DESCONTOS-CONTAS (a fatia 50 da fila das telas que faltam)

**O que mudou para quem usa.** A API dos Descontos existe, antes da tela (a D1 e a D2 do
`docs/f3-relatorios/spec-descontos.md`, com a resposta 266 (c) do dono; a tela é a fatia 51): `GET
/adm/api/relatorios/descontos` (os números do mês com o anterior, por origem, por convênio, por colaborador e por motivo),
`.../itens` (a lista dos itens com desconto, 50 por página, com os filtros e a soma da lista filtrada) e `.../unidades`. O
total é ao centavo o "Descontos concedidos" do painel. **A linha do convênio com o desconto do balcão é dividida** pela
porcentagem de HOJE do convênio (a 266 (c)): a parte do convênio com o teto do desconto, o resto no manual e no motivo
digitado; sem a porcentagem, inteira no convênio e contada à parte -- na cópia não há linha assim, e a conta se prova pelos
casos. **Quem vê:** o gerente, a unidade da sessão; o administrador, a rede ou qualquer unidade ativa (a 1.5 do desenho; o
roteiro da fatia dizia que a rede recusa, lendo a coluna "Gerente" da tabela da C1 -- segui o desenho, como a 48); os outros,
403 `papel`; a rede, um pedido de cada vez por pessoa (o 429). **A tela antiga e os dois menus aplicam a mesma política:**
medido na cópia, só contagens e sem as fixtures, de 366 ativos viam a tela antiga 115 (10 administradores, 104 gerentes e 1
pessoa do grupo 1), e pela política veem 114 -- só aquela uma perde a porta, como o desenho mediu. No menu novo, o item
"Descontos" continua abrindo a tela não migrada (agora pela linha da `TELA_DO_RELATORIO`), só para quem a política deixa.

**Os portões obrigaram dois pedaços da D3, e só eles** (o desenho previa a API um commit antes da tela): o controller na
família exige o `naTelaNova` (o bloco derivado do `conferir-recorte-por-unidade.php`), e o `naTelaNova` exige, no
`RelatoriosNoMenuTest`, o `quando` do item do menu e a linha da `TELA_DO_RELATORIO`. A escolha mínima: o `quando` (a política)
e a linha mostrando a mesma `TelaNaoMigrada`; a 51 a troca pela tela. O D623 (o nome da unidade no "ver as vendas") fica para
a 51, com a frase que o lê.

**O que rodou.** A fatia é par: **o ciclo inteiro é do orquestrador**, depois deste relatório. Rodou aqui o que ela toca:
**844 casos em cada banco** (MySQL e PostgreSQL, saída 0; 1 pulado, de antes) nas 35 classes novas, tocadas e vizinhas
(`RegrasDosDescontosTest`, 48 casos sem banco, e `DescontosTest`, mais as do painel, da família, do menu, da sessão, dos
convênios, dos clientes, do Produtos, da Retirada e do Parceiro); o `conferir-tudo.sh` com saída 0 e os 66 portões; os ensaios
nos dois bancos de contrato e o medidor dos relatórios no PostgreSQL (no MySQL, a permissão 147 de 147, a sessão 24 de 24 e as recusas 244 de 244, todos com saída 0; no PostgreSQL, os mesmos, o fluxo e o medidor da adm-api com saída 0, as três folhas dos Descontos em 200; o medidor dos relatórios no PostgreSQL com saída 0 e 21 de 25 com marca, a tela antiga dos Descontos em 200); o tsc, o lint (só os avisos de antes) e
o build com saída 0 (o JS comprimido em **311,42 KB**, +0,03 KB sobre os 311,39 da 49); os 221 casos do front tocados (o
`menu-do-erp.teste.ts`, com oito destinos de `quando`, e os do Console). Os portões: o recorte com 9 vigias novas (294 para
303 métodos), a cobertura por par de 387 para 390 pares e o `ISRelatorio` de 111 para 120 chamadas, a ACL da API com 4 ações
delegadas (eram 2), as rotas sem login com 7 guardadas pela base (eram 4), o medidor do PostgreSQL com o índice e a lista de
fevereiro de 2026.

**O oráculo e a medida.** O oráculo em Python sobre as linhas cruas da cópia (só SELECT; ids, números e o motivo em HEX, sem
imprimir nome nem texto; as chaves comparadas pelo sha1), contra o controller em processo como administrador e contra o painel
da mesma unidade e mês: **476 conferências, 0 divergências** (as unidades 1, 10 e 39, julho e junho de 2026: o total, cada
origem, cada convênio, cada colaborador -- contra o painel também --, cada motivo, a soma e a ordem inteira da lista). Reproduz
o desenho: a U1 em julho com R$ 21.198,60 (junho R$ 18.856,55), 1.323 itens, 65 convênios e a linha sem convênio (7 itens, R$
98,00), 15 colaboradores, 8 chaves. **A rede** não estava nessas 476 (a verificação o achou: os números dela tinham saído da
própria API); o mesmo oráculo, sem o filtro da unidade e sem o painel, que recusa a rede, bate em **1.228 conferências, 0
divergências**, em julho e junho de 2026: julho com R$ 57.053,19, 4.206 itens, 119 linhas de convênio, 128 colaboradores e 199
chaves. **A medida** (a cópia, buffer pool de 128 MB, em processo, julho de 2026, três processos de cada): o índice da U39 em
1,08 a 1,24 s e o da U1 (a de mais itens com desconto) em 1,16 a 1,41 s, a lista em 1,08 a 1,38 s, com 20,3 e 29,9 MB de pico;
**a rede, o índice em 2,53 a 2,67 s e a lista em 1,62 a 2,37 s, com 122,5 MB de pico** (o D631, o mesmo peso da Retirada).
**Acima do teto do desenho**, dito aqui: a lista da unidade passa do 1 s -- pelo custo fixo do primeiro despacho em processo
(a U39, com UM item com desconto no mês, leva o mesmo); com um pedido antes no mesmo processo, o índice da U1 em 0,27 a 0,31 s,
a lista em 0,13 a 0,14 s, a rede em 1,35 a 1,45 s e 0,65 a 0,73 s. **A tela antiga, para comparar** (a rodada que o "Medir"
da D2 pede; a primeira versão deste parágrafo a dispensou dizendo que pelo HTTP era preciso a senha de alguém, e a verificação
mostrou que ela roda em processo, sem senha, como o `DescontosTest` já a despacha): como administrador, a unidade 1, de 01/07 a
31/07/2026, o `indexAction` com os dois totais e a página que o `index.phtml` percorre, sem desenhar a view: **6,82 a 7,15 s**
fria e 6,01 a 6,69 s quente, 16,3 MB de pico, 1.323 linhas (o desenho media 13,8 a 14,1 s pelo HTTP, com a máquina carregada).

**Os mutantes**, cada um sozinho, devolvido e conferido pelo sha1, nos dois bancos, todos vermelhos pela asserção (nenhum por
erro de tipo): **P50-P1 a P22** (os MD1 a MD22 da D1: a origem por "conv", o sem motivo, o cupom por "cupom", a chave sem
acento e sem espaços, a fatia, os clientes com sentinelas, as vendas contando itens -- este sobreviveu na primeira rodada,
nenhuma venda tinha dois itens descontados, e ganhou caso --, o faturamento das vendas, o colaborador sem o cupom, a ordem, a
página, o motivo por trecho, o nulo por último, a lista nos filtros, motivo e sem motivo juntos, a sessão de pacote, o literal
sem acento, o cupom só no começo, o `comBalcao` sempre falso, o `porMotivo` com a dividida inteira); **P50-P23 a P31** (os MD20
a MD28 da D2: a lista sem a guarda da base, a rede no lugar da unidade, a sessão solta antes dos filtros, os nomes de toda a
lista, o `outros => proprio`, a tela antiga sem a política, o layout de volta ao privilégio, o índice sem a comparação, a
consulta antes de soltar a sessão); e **P50-X1 a X19** (o item 7 -- a dividida inteira no convênio, inteira no manual, sem o
teto, a porcentagem do primeiro convênio do mapa, o `porMotivo` sem a parte do balcão, a chave do motivo inteiro --, o filtro
manual com convênio casando a dividida, a soma sem a parte, o total sem a parte do balcão, a trava da rede arrancada nas duas
ações, o `quando` e a linha da tabela arrancados do front, o `semPorcentagem`, o `porConvenio` com a dividida inteira, a
fábrica do `Module.php`, as porcentagens nunca lidas, a origem sem a dividida; o X7, "a marca do cupom só no começo", é o
P50-P20). As falsificações do portão do recorte, **P50-G1 a G10**, cada vigia nova com o código arrancado: saída 1 em todas. O
"rede aceita" do roteiro não é mutante aqui: a rede é do desenho; o vazamento que importa, o gerente lendo a rede, é o P50-P24.

**A verificação em três lentes** confirmou 11 achados (9 distintos), todos baixos, todos tratados neste commit: (1) a rodada
da tela antiga, feita (acima); (2) a DQL do `ClienteRepository::nomesPorId` e a do `ConvenioRepository::resumoPorId` eram
variáveis locais, e o desenho as escreve como constante privada literal -- viraram `DQL_DOS_NOMES_POR_ID` e
`DQL_DO_RESUMO_POR_ID`, com o caso `testADqlDosRepositoriosNovosEUmaConstantePrivadaLiteral`; (3) o caso do arredondamento só
matava o truncamento: o `ceil` e o `PHP_ROUND_HALF_EVEN` sobreviviam (todas as contas davam número exato ou meio exato) --
ganhou 899,1 (fica 899) e 898,5 (vai a 899); (4) a vigia do MD22 só contava a recusa e o soltar, e o P50-P25 (um
`soltarASessao()` a mais antes dos filtros) a deixava em 0 -- agora a vigia é o começo inteiro do `itensAction`, da guarda da
base ao soltar; (5) a rede no oráculo (acima); (6) os portões de documentação rodaram antes da última edição deste parágrafo --
o `conferir-tudo.sh` rodou de novo depois de todas as edições desta verificação; (7) o D632 não estava na tabela do `README.md`
de `docs/f3-relatorios/` nem na seção 4 do desenho -- está nos dois; (8) o administrador com o `0`, a inativa e a inexistente
não tinha caso no `Adm\DescontosController` de verdade -- ganhou `testOAdministradorNaoPedeO0NemAInativaNemAQueNaoExiste`, nas
duas ações, antes de soltar a sessão; (9) a passagem para a 51 não dizia que o índice e a lista da rede dividem a MESMA trava
(`descontosDaRede-<id>`): a nota está no começo da D3 do desenho -- os dois pedidos da rede pela mesma fila `descontos` do
`filaDaRede.ts`, a lista depois do índice (4 a 5 s ao todo, frio). **Os mutantes novos**, um por vez, devolvidos e conferidos
pelo sha1, nos dois bancos, todos vermelhos pela asserção (nenhum por erro de tipo): P50-X20 (o `ceil`) e X21 (o par mais
próximo), no `testOArredondamentoDeUmCentavo`; X22 (o `unidadesAceitas` sobrescrito com as inativas) e X23 (com o 0 e a
inexistente), no caso novo do administrador (o X22 também no `testARede`); X24 e X25 (a DQL de volta a variável local, um
repositório cada), no caso da constante; e a falsificação P50-G11 (o P50-P25 de novo) com o portão do recorte em 1 e três casos
do `DescontosTest` vermelhos. Depois dos consertos, rodaram de novo, em série: as 35 classes tocadas e vizinhas com **846 casos em cada banco** (MySQL e PostgreSQL, saída 0; 1 pulado, de antes; os dois casos novos são os 846 menos os 844); o portão do recorte com os mesmos 303 métodos e saída 0; e, por último, depois da última edição dos documentos, o `conferir-tudo.sh` com saída 0 e os 66 portões. Nada do front, das rotas nem da resposta da API mudou na verificação: os ensaios HTTP e o build não rodaram de novo.

**Débitos:** o D614 PARCIAL (a conta e a API; falta a tela, a 51); o **D632** registrado (o D-d do desenho: o motivo
"Convênio …" em venda sem o convênio, 7 itens na U1 e 66 na rede em julho; a causa não lida); o D631 e o D623 com a nota da
fatia; o D-c caiu com a C5, o D-b já estava no D212. De pé 156 para 157. Nada do esquema, do crontab, do OneSignal, da produção
nem do BVIP mudou.

**O ciclo inteiro** (o orquestrador, 28/10/2026, depois da verificação; a fatia é par): caracterização **5.306 em cada
banco** (MySQL e PostgreSQL, saída 0, 11 pulados), contrato 74 e 74, os ensaios nos dois bancos de contrato com saída 0 em
cada um (21 no MySQL; 21 e o medidor da adm-api no PostgreSQL), os portões com saída 0 e nenhuma falha inesperada, e o front
com **3.422 casos** em 124 arquivos. Nenhum vermelho.

**No navegador** (o orquestrador, 28/10/2026, contra o banco de contrato): o administrador de teste com o item "Descontos" no
grupo Relatórios abrindo a mesma tela não migrada de antes (a linha da `TELA_DO_RELATORIO`), e a tela antiga em 200; o
gerente de teste, sem o privilégio dos Descontos, sem o item no menu, com 403 `sem_permissao` na API e 404 na tela antiga. A
API pelo navegador da sessão do administrador: em março de 2026, na unidade de teste, R$ 18,00 de desconto, ao centavo o
"Descontos concedidos" do painel da mesma unidade e mês, 3 itens com a soma da lista igual ao total; a rede com o mesmo
total (só essa unidade tem desconto nas fixtures). Duas coisas vistas que não são desta fatia, iguais no Produtos e na
Retirada: o `recorte.escopo` diz o escopo de quem pede (`rede` para o administrador), e não o do pedido; e o nome da
unidade no `recorte` vem de um campo e o da lista `/unidades` de outro ("Unidade Teste Centro" e "Brasil - SC -
Florianopolis - Centro", na mesma unidade das fixtures). Nenhum defeito.

## 28/10/2026 -- REL-DESCONTOS-TELA (a fatia 51 da fila das telas que faltam)

**O que mudou para quem usa.** O item "Descontos" do menu novo abre a tela nova, e não mais a tela não migrada (a D3 do
`docs/f3-relatorios/spec-descontos.md`, com o item 7 dele, a resposta 266 (c) do dono). O gerente vê a unidade dele; o
administrador escolhe a unidade ou "todas as unidades". A tela mostra os nove números do mês contra o anterior (o desconto e as
fatias sem a cor de bom ou ruim, as fatias em pontos); **"De onde veio"**, com a barra e a tabela das três origens, o aviso de que
quem lançou o desconto manual ainda não é gravado e, quando há, a nota das **linhas divididas** (o convênio com o desconto do
balcão: quantas, quanto foi a cada lado, quantas foram divididas pela porcentagem de hoje -- essas contam em cada origem em que
têm parte --, e as que ficaram inteiras sem ela); **por
convênio**, **por colaborador** e **por motivo (só o manual)**, com o "ver os itens" de cada linha; e **os itens com desconto**,
com os filtros (origem, convênio -- "Sem o convênio na venda" inclusive --, colaborador e motivo -- "Sem motivo" inclusive), a
soma da lista que diz de que parte é o desconto quando o filtro escolhe uma parte da linha dividida ("desconto do convênio",
"desconto do balcão"), a linha dividida com as duas partes ("convênio R$ 17,98 + balcão R$ 10,00"), o cliente com o link da
ficha e a paginação de 50. O "Como os números são contados" diz, com a frase do dono, que **a divisão usa a porcentagem de
hoje**. **No Painel**, o cartão "Descontos concedidos" ganhou o "de onde veio", que abre os Descontos no mês e na unidade dos
números. **O "ver as vendas"** e o código da venda abrem Vendas Realizadas no período dos números, com desconto (ou na venda, no
dia dela), **na unidade do recorte** -- a rede é "todas" --, só a quem abre Vendas Realizadas. **E Vendas Realizadas aberta assim
diz a unidade pelo nome** ("Abertas pelo relatório: só as vendas da unidade VIP Centro."), e não mais pelo número (o D623): a
listagem devolve o nome do cadastro da unidade que aplicou, só quando veio pelo filtro.

**Na rede**, o índice e a lista dividem a mesma trava do servidor, e por isso andam na mesma fila da tela (`descontos`, no
`filaDaRede.ts`): a lista sai depois do índice, e a troca de filtro ou de página da rede espera a leitura no ar; a unidade não
espera. O `usePedidoDeDepois` passou a dar ao `ler` o `aindaQuer` (como o primeiro gancho desde a 48), para a lista andar na fila.

**Onde está.** No front: `api/descontos.ts` (os tipos seguem a resposta da 50 onde ela e o desenho divergiam: o `divididas`, as
`partes` só da dividida, a `parte` da lista, o colaborador nulo do item), `telas/Descontos.tsx`, `telas/DescontosLigado.tsx`,
`telas/listaDosDescontos.ts` (a regra pura da lista), as entradas `descontos.*` do `glossario.tsx` e o `ITENS_DOS_DESCONTOS`, a
linha da `TELA_DO_RELATORIO` e o `aoVerDescontos` do `Console.tsx`, o `aoVerDescontos` do `Painel.tsx` e do `PainelLigado.tsx`, a
frase do `VendasRealizadas.tsx` e o tipo do `api/vendas.ts`, e o `App.css` (a barra das origens com as cores dos tokens -- o texto
no segmento de 5,21 a 10,55:1 no claro e de 7,60 a 8,70:1 no escuro -- e os `<select>` dos filtros na regra `:is(...)` dos campos
das telas novas). No servidor, só o D623: `Adm\VendasController::recorteComONome` (a regra da listagem continua sem banco), com a
contagem do `ISVenda` no `conferir-cobertura-por-par.php` de 137 para 138. A prévia `Descontos` (sete células, oito com a `ListaRelendo` da verificação; os nomes
inventados) e o cenário em `_relatorios.ts`; a do Painel ganhou o tratador novo; o `Descontos` no `src/index.ts` e no
`componentSrcMap`. O design-sync não rodou (é do orquestrador, depois da 52).

**O que rodou** (a fatia é ímpar: só os testes do que ela toca; **o ciclo inteiro fica para a 52**). Front, com
`--maxWorkers=2`: os 23 arquivos de casos tocados e vizinhos (os da D3, o `Descontos.teste.tsx`, o `listaDosDescontos.teste.ts`, o `api/descontos.teste.ts`, o `ConsoleAbreDescontos.teste.tsx`, o `PainelLigado`, o `VendasRealizadasLigada`, o `Console` e os `ConsoleAbre*`, o `ConsoleLigado`, o `menu-do-erp`, o `glossario`, o `pedidosDoRelatorio`, e os relatórios que usam a fila e as peças comuns), **550 casos, saída 0**; os três arquivos dos Descontos também com `AMPLIFICAR_EFEITOS_MS=50` (34 casos, saída 0); o vitest inteiro não rodou; o tsc e o lint sem erro (os 4 avisos de antes, em arquivos que a fatia não toca); o build com o JS
comprimido de **316,87 KB** (eram 311,42 KB na 50: +5,45 KB, abaixo dos 12 KB). Servidor, as classes tocadas e vizinhas
(`ListagemDeVendasTest`, `VendaApiTest`, `D428NpsDaListaDeVendasTest`, `D14RaizDoModuloDeVendasTest`, `SessaoApiTest`,
`RelatorioAdmControllerTest`, `DescontosTest`, `RegrasDosDescontosTest`, `DescontosNoRelatorioTest`, `RelatoriosNoMenuTest`,
`PortoesDaFamiliaDosRelatoriosTest`, `RegrasDoRecorteDoRelatorioTest` e `PainelDaUnidadeTest`): **472 casos em cada banco** (MySQL e PostgreSQL, saída 0; 1 pulado, de antes: o `DescontosNoRelatorioTest::testOsTotaisNaoSomamOsDeOutraFranquia`, que pede a cópia da produção), com os quatro casos novos do D623 no `ListagemDeVendasTest` (sete conferências do recorte; até a verificação, esta entrada dizia "seis", e o débito, "cinco"). Os ensaios que
leem a listagem de vendas nos dois bancos de contrato: no MySQL, o `ensaiar-permissao-adm-api` e o `ensaiar-fluxo-adm-api` com saída 0, e o `ensaiar-recusas-adm-api` com saída 0 depois de remontar o banco (a primeira rodada, logo depois do de fluxo, saiu 2: o caixa que o de fluxo fecha e abre, como o próprio ensaio avisa -- não é desta fatia); no PostgreSQL, os três e o `medir-adm-api-no-postgres`, todos com saída 0. Nenhum deles lê o `recorte.unidade` da listagem. Os portões: o `conferir-cobertura-por-par.php` com a contagem nova (saída 0), o `conferir-links-mortos.php` (nenhum link novo, 413 chamadas, saída 0), o `conferir-segredos.sh` (saída 0) e, por último, o `conferir-tudo.sh` inteiro, com **65 dos 66 portões ok**; o 66º, os "testes do front", é o vitest inteiro com `--maxWorkers=4` (`npm test --prefix frontend`), que esta fatia não roda (ímpar, e o Windows estoura com 4): ele foi cortado pelo `timeout 595` do meu roteiro, a saída 124, sem processo órfão -- fica para o ciclo da 52.

**Os mutantes**, um por vez, devolvidos byte a byte e conferidos pelo sha1, todos os que se aplicam vermelhos pela asserção (o
P51-P5 não se aplica; nenhum só por erro de tipo), os do servidor nos dois bancos:

| # | Mutante | Quem ficou vermelho |
|---|---|---|
| P51-P1 (MD29) | os itens pedidos junto com os números, com o mês do pedido | `DescontosLigado`: "a lista só sai depois dos números" e mais cinco |
| P51-P2 (MD30) | sem descartar a resposta velha (o `vigente` do gancho) | `DescontosLigado`: "a resposta velha não sobrescreve a nova" |
| P51-P3 (MD31) | trocar o mês sem zerar os filtros | `DescontosLigado`: "trocar o mês zera os filtros e volta à página 1" |
| P51-P4 (MD32) | o filtro sem voltar à página 1 | `DescontosLigado`: "a paginação pede a página seguinte; um filtro depois volta à página 1" |
| P51-P5 (MD33) | "ver as vendas" sem a condição da unidade da sessão | não se aplica: a condição saiu com a C5 (o `spec-ordem.md`, 1.4, item 1); no lugar, o P51-X6 |
| P51-P6 (MD34) | "ver as vendas" sem `desconto: '1'` | `DescontosLigado`, `listaDosDescontos` e `ConsoleAbreDescontos`, o caso do "ver as vendas" |
| P51-P7 (MD35) | a recusa pedindo os itens | `DescontosLigado`: "o 403 do papel ... não pede a lista" (o caso espera os efeitos antes de conferir a ausência: sem a espera, ficava verde) |
| P51-P8 (MD36) | a falha dos itens trocando a tela inteira pelo erro | `DescontosLigado`: "a falha da lista ... os números ficam" |
| P51-P9 (MD37) | sem o `AindaNaoGravado` | `Descontos`: "de onde veio ... o ainda não gravado" |
| P51-P10 (MD38) | o link do painel sem o `podeAbrir` | `ConsoleAbreDescontos`: sem o privilégio e sem a guarda |
| P51-X1 | a lista da rede numa fila separada | `DescontosLigado`: "na rede, com a lista no ar, o índice do outro mês espera" |
| P51-X2 | a lista da rede fora da fila | o mesmo, e "a troca de filtro com a lista no ar espera a do ar" |
| P51-X3 | a parte da linha dividida trocada | `Descontos`: "a lista mostra as duas partes da linha dividida" |
| P51-X4 | a soma sem dizer de que parte | `listaDosDescontos`, `Descontos` e cinco casos do `DescontosLigado` |
| P51-X5 | o "de onde veio" do Painel sem a unidade | `PainelLigado`: o do administrador e o do gerente |
| P51-X6 | o "ver as vendas" sem a unidade do recorte | `DescontosLigado`, `listaDosDescontos` e `ConsoleAbreDescontos` |
| P51-X7 | a frase do D623 de volta ao número | `VendasRealizadasLigada`: "a frase diz o nome"; `ConsoleAbreDescontos` |
| P51-X8 | o "Sem motivo" mandando o `motivo`, e não o `semMotivo` | `listaDosDescontos`, `Descontos` e `DescontosLigado` |
| P51-X9 | o convênio nulo sem o `sem` | `DescontosLigado`: "sem o convênio na venda pede convenio=sem" |
| P51-X10 | o eco da lista sem a página | `listaDosDescontos`: "o eco só vale com os mesmos filtros e a mesma página" |
| P51-X11 | a paginação chamando nas pontas | `Descontos`: "a paginação desliga o ‹ na primeira página e o › na última" |
| P51-X12 | a nota das divididas sem a condição | `Descontos`: "sem linha dividida, a nota não aparece" |
| P51-X13 | o "inteira no convênio" de volta ao glossário | `glossario` e `Descontos`, o "Como os números são contados" |
| P51-X14 | a lista sem o `doMesmoRecorte` | `DescontosLigado`: "a lista de outro recorte não se mistura" (o caso entrou para ele: sem ele, ficava verde) |
| P51-X15 | a lista do administrador na rede sem o `todas` | `listaDosDescontos` e três do `DescontosLigado` |
| P51-X16 | o índice da rede noutra fila | `DescontosLigado`: "na rede, com a lista no ar, o índice do outro mês espera" |
| P51-X17 | o nome da unidade sempre nulo | `ListagemDeVendasTest::testComOFiltroDaUnidade...`, nos dois bancos |
| P51-X18 | o nome da unidade PEDIDA, e não da aplicada | o mesmo, nos dois bancos (o gerente que pede outra) |
| P51-X19 | a consulta do nome sem a condição do filtro | `testSemOFiltroONomeENuloEARedeContinuaNula`, nos dois bancos |
| P51-X20 | o recorte sem passar pela função do nome | três casos do D623, nos dois bancos |
| P51-X21 | o nome vazio do cadastro como nome | `testONomeVazioNoCadastroFicaNulo`, nos dois bancos |

**A verificação** (três lentes) confirmou onze achados, sete distintos, e todos foram tratados (o servidor não mudou):

1. **Com outro mês ou outra unidade no ar, o "ver os itens" dos grupos e das origens ficava ligado**, e o filtro escolhido no
   mês de antes ia para a lista do mês novo -- filtrada por um id que podia nem estar nele, com o `<select>` dizendo "Todos". Só
   os `<select>` e o "limpar" desligavam. Agora desligam também os "ver os itens" e a paginação. (O achado dizia que o "próxima ›"
   fazia o mesmo; não faz: com outro recorte no ar a lista já espera os números novos, e a paginação nem está na tela -- o caso
   novo o confere.)
2. **A lista não tinha o "relendo" da D3**: cada página (ou filtro) desmontava a tabela e a paginação, e o foco do "próxima ›"
   ia ao `body`. Agora outra página ou outros filtros, no mesmo recorte, deixam a lista de antes na tela, marcada (`aria-busy`),
   com "Atualizando os itens…" no lugar da soma e a paginação desligada por `aria-disabled` (o botão apertado fica, com o foco);
   números novos (outro recorte, o "atualizar") e o "tentar de novo" da lista não a deixam. A página que falha leva o foco, se
   estava na lista, ao título dela (o `useFocoNoTitulo`, agora também na seção da lista).
3. **O "limpar os filtros" sumia ao ser clicado e deixava o foco no `body`**: agora o leva antes ao título da lista.
4. **A nota das linhas divididas dizia "foram divididas" e "cada uma conta nas duas origens" de todas**, também da que ficou
   inteira sem a porcentagem de hoje (o `divididas.itens` conta toda linha `dividida`). Agora: "N linhas do convênio tinham
   também desconto do balcão: R$ x no convênio e R$ y no manual. K foram divididas pela porcentagem de hoje do convênio e contam
   em cada origem em que têm parte, uma vez só nos itens com desconto. M ficaram inteiras no convênio, sem a porcentagem de hoje
   do convênio." -- o "em que têm parte" porque, com o teto, o balcão fica 0 e a linha conta só no convênio.
5. **As variações neutras iam além do desenho**: o implementador tinha tirado a cor também dos itens, das vendas, do
   faturamento delas e dos clientes com desconto, sem desenho nem resposta. A tela voltou ao item 3 da vista (neutros só o
   desconto e as fatias), e a pergunta foi ao lote: o **D633**.
6. **Nenhum caso provava que trocar a unidade zera os filtros** (o código estava certo): entrou o caso, e o P51-X22.
7. **As contagens dos documentos**: o D623 dizia "cinco casos" e esta entrada "seis"; são quatro casos, com sete conferências do
   recorte. O `docs/f3-relatorios.md` dizia os P51-P1 a P51-P10 vermelhos, e o P51-P5 não se aplica. E o relatório da fatia falava
   em "12 classes" e listava 13 (são 13, as desta entrada).

**O que rodou na verificação** (a fatia é ímpar; o ciclo inteiro continua para a 52): os casos novos contra o código de antes,
**10 vermelhos de 39** nos dois arquivos dos Descontos (passam o da unidade, que era só o caso que faltava, e o do "tentar de
novo" e do "atualizar", que vigia o "relendo" novo da lista -- o código de antes não o tinha); depois do conserto, os 9
arquivos de casos tocados e vizinhos (`Descontos`, `DescontosLigado`, `listaDosDescontos`, `api/descontos`,
`ConsoleAbreDescontos`, `PainelLigado`, `Console`, `pedidosDoRelatorio`, `glossario`) com `--maxWorkers=2`: **304 casos, saída
0**; os três dos Descontos com `AMPLIFICAR_EFEITOS_MS=50`: **45 casos, saída 0**; o tsc e o build sem erro, o JS comprimido em
**317,06 KB** (+0,19 KB sobre a fatia, +5,64 KB sobre os 311,42 KB da 50); o lint com saída 0 e 8 avisos, todos em arquivos que a
fatia não toca (o do `Console.tsx` é o `descreverTela`, que já estava no HEAD; o relatório da fatia dizia 4). As 13 classes do
servidor de novo, nos dois bancos: **472 casos em cada, saída 0**, 1 pulado (o de antes). Os portões: o
`conferir-links-mortos.php` (413 chamadas, nenhum link novo), o `conferir-referencias-docs.php`, o `conferir-segredos.sh`, o
`contar-debitos.php` e o `conferir-debitos-repetidos.php`, todos com saída 0; o `conferir-tudo.sh` inteiro, com os **66 portões ok**, os "testes do front" (o vitest inteiro) inclusive,
"Nenhuma falha inesperada", saída 0.

**A correção morreu antes do relatório** (a API fora do ar, `ENOTFOUND`, quando ela esperava o `conferir-tudo.sh`): o
orquestrador leu o transcrito dela, conferiu pelo sha1 que a árvore é a versão final do conserto e repetiu, no código final, o
front tocado e vizinho: **417 casos em 15 arquivos** (os 9 de cima, o `VendasRealizadasLigada`, o `menu-do-erp` e os quatro da
Retirada e do Produtos, que usam o `usePedidoDeDepois`), `--maxWorkers=2`, saída 0; os três dos Descontos com
`AMPLIFICAR_EFEITOS_MS=50`, **45 casos**, saída 0; o tsc, o lint e o build com saída 0, o JS comprimido em **317,06 KB**. A
pergunta do D633 foi à página das perguntas, como a **270**.

**Os mutantes da verificação**, no front, um por vez, devolvidos byte a byte e conferidos pelo sha1, todos vermelhos pela
asserção (nenhum por erro de tipo):

| # | Mutante | Quem ficou vermelho |
|---|---|---|
| P51-X22 | a escolha da unidade sem o `outroRecorte` (sem zerar a lista) | `DescontosLigado`: "trocar a unidade também zera os filtros" |
| P51-X23 | o "ver os itens" dos grupos sem o `disabled` | `Descontos` e `DescontosLigado`, os dois casos de outro recorte no ar |
| P51-X24 | o "ver os itens" das origens sem o `disabled` | `Descontos`: "com outro mês ou outra unidade no ar, ... desligam" |
| P51-X25 | a paginação sem o `ocupado` | `Descontos` (dois) e `DescontosLigado`: "a página seguinte relê ..." |
| P51-X26 | a lista sem o "relendo" (nunca a de antes) | `DescontosLigado`: "a página seguinte relê ..." e "dois filtros seguidos" |
| P51-X27 | a seção da lista sem o `useFocoNoTitulo` | `DescontosLigado`: "a página que falha ... o foco vai ao título da lista" |
| P51-X28 | o "limpar" sem levar o foco antes | `DescontosLigado`: "limpar os filtros ... o foco vai ao título da lista já" (a primeira versão do caso ficava verde: o foco chegava depois, pela lista nova; o caso passou a conferir com a lista no ar) |
| P51-X29 | a nota contando as sem porcentagem como divididas | `Descontos`: "com uma sem a porcentagem de hoje, diz que ficou inteira" |
| P51-X30 | a lista relendo com a soma de antes | `Descontos` e dois do `DescontosLigado` |
| P51-X31 | o faturamento das vendas com desconto neutro de novo | `Descontos`: "as outras variações têm a cor, como o desenho" |
| P51-X32 | os números novos sem esquecer a lista de antes | `DescontosLigado`: "o tentar de novo da lista e o atualizar não mostram a lista de antes" |
| P51-X33 | o "tentar de novo" da lista sem esquecê-la | o mesmo |
| P51-X34 | o "relendo" da lista com os números no ar | o mesmo |
| P51-X35 | a segunda troca de filtro esquecendo a lista de antes | `DescontosLigado`: "dois filtros seguidos" |
| P51-X36 | a lista relendo sem o `aria-busy` | `Descontos`: "a lista relendo fica na tela, marcada" |

**O que não se provou.** O CSS (o vitest lê o `App.css` vazio): a barra, as cores, os campos na regra `:is(...)` e os 375 px são
do navegador. O `if` de dentro do `abrirRelatorio` continua sem caso que o pegue: o "ver as vendas" só existe com o `podeAbrir`
do destino (o item 10 do bloco "Em dia" do `spec-comum.md`). O `dados !== undefined` novo do `usePedidoDeDepois` é defesa: o
pedido que não sai da fila já não é o vigente, e tirá-lo não muda caso nenhum. **Para o navegador** (o orquestrador), contra o
banco de contrato: **fevereiro de 2026**, 1 item, R$ 20,00, convênio, sem motivo (a venda 90001210); **março de 2026**, 3 itens em
2 vendas, R$ 18,00, manual (os R$ 10,00 da 90001010 e os R$ 5,00 e R$ 3,00 da 90001610 -- e não os R$ 10,00 de um item que a
lista "Navegador" da D3 escreve); nenhum outro mês com desconto nas fixtures; o "de onde veio" do painel no mesmo mês, o "ver
as vendas" com a frase pelo nome, a rede do administrador, a lista em 375 px e a barra no claro e no escuro.

**No navegador** (o orquestrador, contra o banco de contrato, remontado antes e depois). O administrador: o item Descontos abre a
tela nova; outubro vazio, com "sem base"; **março de 2026 com R$ 18,00, 3 itens, preço cheio R$ 149,90**, a barra "Manual
100,0%", o colaborador e os três motivos, e a lista com as duas vendas (90001610 e 90001010), igual à API, às fixtures, ao
Painel e a Vendas Realizadas -- as três fontes dão os mesmos R$ 18,00, e por isso o oráculo em Python não rodou de novo (a 50
o rodou na cópia). O "ver os itens" do motivo filtra a lista, leva o foco ao título dela e a soma diz "desconto do balcão R$
5,00"; o "limpar os filtros" volta aos 3 itens com o foco no título; o "ver as vendas" abre Vendas Realizadas com **"só as
vendas da unidade Unidade Teste Centro"** (o D623) e, na rede, "as vendas de todas as unidades", as duas com os mesmos R$ 18,00.
O "de onde veio" do Painel abre os Descontos em março e na mesma unidade. A rede ("todas as unidades") com os mesmos números e a
lista. Os `<select>` com o fundo do tema (na regra `:is(...)`), no escuro e no claro; a 484 e a 375 px nenhum elemento passa da
largura fora das tabelas que rolam. O gerente de teste, sem o privilégio dos Descontos: sem o item no menu e sem o "de onde
veio" no Painel; com o privilégio concedido só no banco de contrato (a linha `ler` da funcionalidade dos Descontos, 90002038):
o "de onde veio" abre março sem a escolha da unidade e sem pedir a lista de unidades, a API dá a dele (200) e recusa a rede e
outra unidade (422), e a `/descontos/unidades` traz só a dele. Nenhum defeito. Não rodou o lançamento de uma comanda com
desconto manual pelo PDV (pede abrir caixa e finalizar uma venda no banco de contrato): a leitura do desconto gravado está
provada pelas fixtures e pelos casos do servidor da 50.

**Débitos:** o **D614 CORRIGIDO** (a tela fecha o que faltava: as duas partes, a soma pela parte e a frase do dono) e o **D623
CORRIGIDO**; na verificação, o **D633** registrado (a cor das contagens do desconto, com a pergunta no lote). De pé 157 para
155, e 156 com o D633. Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou.

## 28/10/2026 -- REL-CANCELADAS-CONTAS (a fatia 52 da fila das telas que faltam)

**O que mudou para quem usa: nada aparece para ninguém.** É a V0 do `docs/f3-relatorios/spec-canceladas.md`: as contas e as
consultas das Vendas Canceladas, sem rota, sem política ligada, sem menu e sem tela (a V1, a fatia 53). Entraram a
`ISRelatorio\Regra\ContasDasVendasCanceladas` (pura: sem banco, sessão nem relógio) e duas consultas no `VendaRepository`, com a
DQL em constante privada literal: `canceladasVendas` (as canceladas do período, uma linha por venda, as sem item inclusive, pela
unidade de quem está na venda) e `canceladasVendasDoDia` (as finalizadas dos clientes no período, em qualquer unidade, em lotes
de mil, com `LEFT JOIN` nos itens). A V0 prova, antes de expor: o tipo pelo caixa (desfeita com caixa, comanda sem); as duas
regras do dono (a C2) -- **corrigida** quando o cliente teve, no mesmo dia e em qualquer unidade, outra venda finalizada com o
mesmo valor de itens OU os mesmos produtos, e a **regra larga** com qualquer outra venda no dia --; as comandas **duplicadas,
abandonadas e do balcão** (a C3); os sentinelas 1 e 2 nunca corrigidos nem duplicados; quem cancelou pelas frases da rotina
(as duas de hoje pela igualdade, a antiga pelo começo, em ASCII); o motivo digitado agrupado sem acento (a C4, por tabela, sem
`iconv`), dez grupos e `outros`; as taxas sobre a base do painel mais o cancelado; e a venda sem item fora da régua. O mês é o
da venda: o dia do cancelamento e quem o autorizou não são gravados (a fatia do PDV), e a V0 não cria coluna. A `unidade` é a
do SELECT comum (a `unidadeDaVenda` do desenho não existe -- o bloco "Em dia"): nenhuma chave do `normalizar` mudou.

**O oráculo.** Em Python, sobre as linhas cruas da cópia (`franquiabv_dev`, só SELECT; ids, números e o motivo em HEX, sem
imprimir nome nem texto; as chaves comparadas pelo sha1), no molde do `vd-08.sql`, contra a `ContasDasVendasCanceladas` rodada
no contêiner sobre as mesmas datas: **1.463 conferências, 0 divergências** -- as unidades 1, 10 e 39 e a rede, junho e julho de
2026 (cada venda com a classe, a regra larga, a frase, os centavos e a chave; o resumo inteiro; a base; as três taxas; os
motivos; os usuários; e, na rede, as unidades em ordem); nenhum motivo com byte inválido. E **os números da 1.3 e da 1.4 do
desenho saem iguais ao centavo**: a unidade 1 em julho com 7 desfeitas (R$ 848,00; 2 corrigidas, R$ 240,00; 3 pela regra larga,
R$ 410,00), 62 comandas (R$ 7.197,00; 27 duplicadas, R$ 2.910,00; 33 abandonadas, R$ 3.512,00; 2 do balcão, R$ 775,00; 62 pela
rotina) e 5 vazias, taxas 0,2%, 0,3% e 2,0%; a rede em julho com 178 desfeitas (R$ 17.722,25; 108 corrigidas, 70 sem nova
venda com 11 do balcão, 131 pela regra larga), 316 comandas (132, 162 e 22; 257 pela rotina e 59 à mão), 2 compras de crédito
desfeitas (R$ 560,00) e 47 vazias, 27 unidades com a 45 primeiro (6,4% de comandas) e 53 usuários; junho igual ao desenho.

**A medida** (a cópia, buffer pool de 128 MB, em processo, junho e julho juntos -- o mês e a comparação --, três processos):
a unidade 1 em 0,55 a 0,83 s fria e 0,24 a 0,25 s quente, 62,0 MB de pico; **a rede fria em 1,50 a 2,17 s** nos seis
processos (três de duas voltas, 2,167, 1,504 e 1,964 s; três de uma, 2,149, 1,986 e 2,098 s), quente em 1,21 a 1,39 s, e
**238,5 MB de pico num pedido** (julho só: 1,38 a 1,63 s e 134,0 MB). Os processos de duas voltas deram 340,5 MB de pico: é o
medidor, com a primeira volta ainda viva enquanto a segunda lê -- medido na verificação, soltas as leituras antes da segunda
volta o pico cai a 238,5 MB (dois processos de cada jeito, rede fria de 2,06 a 2,39 s e quente de 1,46 a 1,83 s, o host mais
carregado). As três consultas novas ficam abaixo de 0,7 s fria e de 0,03 s quente; a das finalizadas
do dia, a que o desenho mandava vigiar, em 0,02 s na rede (733 clientes, 3.286 linhas). O peso, medido, é a base
(`relatorioItens` concluídas da rede, 60.160 itens: 1,05 a 1,19 s mesmo quente) -- **o D634**, no molde do D631: acima do
`memory_limit` de 128 MB de fábrica; o dono disse, em 27/10/2026, que a produção deve ter 1.024 ou 2.048 MB, e o número
continua por ler.

**O que rodou.** A fatia é **par: o ciclo inteiro é do orquestrador**, depois deste relatório. Aqui: os dois testes novos, o
`RegrasDasVendasCanceladasTest` (22 casos sem banco) e o `ConsultasDasCanceladasTest` (9 casos nos dois bancos, o cenário de
março de 2025 por SQL dentro da transação, ids 90008201 a 90008237, sem fixture nova) -- 31 casos, 203 asserções, saída 0 em
cada banco; e, com eles, as classes tocadas e vizinhas (as do painel, da base dos relatórios, da família, do menu, das
canceladas antigas, da comanda fantasma, do cancelamento, da listagem de vendas, dos Descontos, da Retirada, do Produtos, do
Parceiro e o `D90PortoesDosScriptsTest`): **642 casos em cada banco, saída 0** (4.643 asserções no MySQL e 4.642 no
PostgreSQL). Os portões: o recorte com a vigia nova da unidade em `canceladasVendas` (303 para 304 métodos), a injeção de DQL
sem concatenação nova, o GROUP BY, os booleanos, o relógio dos testes, a cobertura por par sem mudança (a V0 não tem controller:
os dois pares entram na V1), os links mortos, os segredos, e o `conferir-tudo.sh` com saída 0 e os 66 portões "ok" (o vitest inteiro inclusive). O front não mudou.

**Os mutantes**, cada um sozinho, devolvido byte a byte e conferido pelo sha1, nos dois bancos (os dois com os mesmos
vermelhos, e nenhum por erro de tipo): os 18 do desenho, P52-P1 a P18 (os MV1 a MV18; o P52-P10, o filtro da unidade
arrancado, deixa também o portão do recorte vermelho; o P52-P3, a guarda dos sentinelas do `classificar`, só fica vermelho sem
banco, porque no banco os sentinelas nem chegam à consulta do dia), e 18 meus, P52-X1 a X18 (entre eles a classe pela regra
larga, o sentinela 2 fora do balcão, a venda sem item com classe, a linha de valor nulo descartada, os produtos sem repetição, a
frase antiga com o acento, o motivo além dos dez sem `outros`, a unidade do colaborador na base, a chave por `<=>`, os
sentinelas e a venda sem item na consulta do dia, e a frase da duplicada pelo começo). Todos vermelhos pela asserção.

**A verificação** (três lentes) confirmou 8 achados (6 distintos), todos baixos e todos tratados, sem mudar código da classe
nem das consultas: o caso da V4 no `ItensDoRelatorioTest` que o desenho pede (a `unidade` de quem fechou contra a
`colaboradorUnidade`), que a primeira montagem deixara só na V13 sem o dizer; quatro casos que faltavam -- a taxa sem base
por último no `porUnidade`, o motivo cru em `vendas` e `classificar` (a frase da rotina com um espaço antes é à mão), o
`valor_total` nulo como zero (a V10 do cenário passou a tê-lo nulo; a cópia tem 6.541 canceladas assim) e o dia pelo relógio
do PHP perto da meia-noite (a F01 às 22:30 e a V03 às 22:00) --; e a medida da rede, que dava só os processos de uma volta e
calava os 340,5 MB (acima). Rodaram de novo, depois dos casos: os três arquivos (`ConsultasDasCanceladasTest`,
`RegrasDasVendasCanceladasTest`, `ItensDoRelatorioTest`) com 44 casos e 283 asserções em cada banco, saída 0; e as 18
classes tocadas e vizinhas: 647 casos em cada banco, saída 0 (4.672 asserções no MySQL e 4.671 no PostgreSQL). Os mutantes novos, cada um sozinho, devolvido
pelo sha1, nos dois bancos com os mesmos vermelhos: P52-X19 e X20 (a guarda da base zero arrancada e invertida), X21 e X22 (o
motivo aparado em `vendas`, com e sem o nulo), X23 e X24 (o valor nulo como exceção e como -1; o X23 morre pela exceção, que é o
que ele prova, e o X24 pela asserção), X25 a X27 (o dia em UTC na cancelada, na finalizada e nas duas); e o P52-P18 de novo,
agora vermelho também no caso da V4.

**O ciclo inteiro** (a fatia é par; o orquestrador, depois da correção, com a máquina recém-reiniciada e as sete travas de
`/tmp` criadas como root pela suíte apagadas antes, o caso do D499): caracterização **5.346 casos em cada banco** (11
pulados de propósito; 40.199 asserções no MySQL e 40.195 no PostgreSQL), contrato 74 e 74, os 22 ensaios em cada banco de
contrato com saída 0, os 66 portões ("Nenhuma falha inesperada") e o front com 3.484 casos em 129 arquivos; todas as etapas
com saída 0. Fora do ciclo, o medidor dos relatórios no PostgreSQL (o roteiro do rascunho, só as duas rotinas do cache), saída
0. Nada para o navegador: a V0 não tem rota nem tela.

**Débitos:** D634 registrado (a base da rede); os V-a a V-e do desenho são da V1 (a seção 4). De pé 156 para 157. Nada do
esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; nenhuma rotina rodou.

## 28/10/2026 -- REL-CANCELADAS-TELA (a fatia 53 da fila das telas que faltam)

**O que mudou para quem usa.** O item **Relatórios › Vendas Canceladas** do menu novo abre a tela nova (era a tela não migrada).
Ela mostra, no mês (‹ ›), **as vendas desfeitas** -- as que foram fechadas no caixa e canceladas depois --, quantas foram
**corrigidas no mesmo dia** (o cliente teve outra venda fechada com o mesmo valor ou os mesmos produtos, em qualquer unidade),
quantas ficaram **sem nova venda** (com as do balcão) e a **regra larga**; e **as comandas não fechadas** (o teto do que estava
aberto, e não receita perdida), separadas em **duplicadas, abandonadas e do balcão**, com quantas a rotina cancelou e quantas foram
à mão. Cada número vem com o do mês anterior, e as taxas (sobre os atendimentos do painel) com a diferença em pontos. Embaixo, "Fora
da conta" (as canceladas sem item), o botão **"Ver as vendas canceladas de <mês> em Vendas Realizadas"** (no período, na situação
"canceladas" e na unidade dos números), e as tabelas **por motivo** (o texto digitado agrupado sem acento, e as frases da rotina) e
**por quem fechou ou abriu**; o administrador escolhe a unidade ou **todas**, e na rede aparece **por unidade**. Logo abaixo do
cabeçalho, o aviso de que **quem cancelou e quando ainda não é gravado** (a fatia do PDV, a 70): a venda conta no mês em que foi
vendida. **Quem vê:** o gerente, a unidade dele; o administrador, a rede ou qualquer unidade; os outros são recusados pelo papel. A
tela antiga e o item do menu antigo passaram a aplicar a mesma política: **medido na cópia, só contagens e sem as fixtures, de 366
ativos viam a tela antiga 118, e pela política veem 117 -- perde a porta só uma pessoa do grupo 1**, como o desenho mediu.

**O servidor:** o `Adm\CanceladasController` (`GET /adm/api/relatorios/canceladas` e `.../unidades`, sem exportação), sobre as contas
e as consultas da 52, e o `VendasCanceladasJson`; a política `canceladas` com o `naTelaNova`, o filho da família, a fábrica no
`Module.php`, a guarda do controller antigo e o `veOMenu` do `layout.phtml`. **A base é lida um mês de cada vez** (o MV22), e por isso
o pico da rede caiu de 238,5 MB (a V0) para 125,5 MB; **a rede anda um pedido de cada vez por pessoa** (o 429) e, na tela, na fila
`canceladas` do `filaDaRede.ts`. **A classificação olha a rede e devolve só a classe:** o id, o valor e a unidade da outra venda não
saem. **A tela:** `api/vendasCanceladas.ts`, `VendasCanceladas.tsx`, `VendasCanceladasLigado.tsx`, as dez chaves `canceladas.*` do
glossário, a linha da `TELA_DO_RELATORIO` e o `quando` do menu. **Onde o desenho e a resposta da V0 divergem, vale a resposta:** a
tabela por motivo tem Vendas e Valor, e não Desfeitas e Comandas (a V0 não as separa); e **o "ver as vendas" leva a unidade do
recorte** (a C5 da 45, como os Descontos da 51): o MV26 não se aplica, e o débito V-e não nasce. O cenário da V0 passou a uma classe
comum (`CenarioDasCanceladas`), que o `ConsultasDasCanceladasTest` e o novo `VendasCanceladasTest` leem.

**O que rodou** (a fatia é ímpar: só os testes do que ela toca; **o ciclo inteiro fica para a 54**). No servidor, as 19 classes
tocadas e vizinhas (as três das canceladas, as da família, do menu, da sessão, dos itens, das colunas do PDV, da exportação, do
painel, da listagem de vendas, dos Descontos, da Retirada, do Produtos e do Parceiro): **689 casos em cada banco, saída 0** (5.095
asserções no MySQL e 5.094 no PostgreSQL); o `VendasCanceladasTest` sozinho tem 16 casos. Os ensaios nos dois bancos de contrato,
depois de apagar as 11 travas de `/tmp` criadas como root pela suíte (o D499) e remontando o banco entre o fluxo e o recusas: no
MySQL, o `ensaiar-permissao-adm-api` (as duas rotas novas recusadas sem o privilégio; o colaborador e o caixa com 403), o
`ensaiar-fluxo-adm-api` e o `ensaiar-recusas-adm-api`, **saída 0** nos três; no PostgreSQL, os mesmos três e o
`medir-adm-api-no-postgres` (200 nas duas folhas, com `porUsuario` em fevereiro de 2026), **saída 0** nos quatro; o medidor dos
relatórios no PostgreSQL (só as duas rotinas do cache), saída 0, com a tela antiga em 200 para o `gerente.teste`. No front, os 22
arquivos tocados e vizinhos: **540 casos, saída 0**; os três novos com `AMPLIFICAR_EFEITOS_MS=50`: 33 casos, saída 0; o tsc, o lint
(sem aviso novo) e o build: **o JS comprimido de 317,06 KB (a 51) para 319,95 KB (+2,89 KB)**. A prévia `VendasCanceladas` (sete
células, nomes inventados) passou no tsc num `tsconfig` do rascunho, com a dos Descontos de controle; nenhuma célula do Console abria
as Vendas Canceladas. Os portões: o recorte por unidade (304 para 312 métodos; quatro relatórios na família), a cobertura por par
(390 para 392 pares, os dois da V0 que só agora têm controller; o ISRelatorio de 120 para 126 chamadas; o sha1 sem os dois volta ao de
390), a ACL da API, as rotas sem login (9 rotas de relatório guardadas pela base), os links mortos, e o `conferir-tudo.sh`
(saída 0, os 66 portões "ok", o vitest inteiro inclusive: "Nenhuma falha inesperada.") e o `conferir-segredos.sh` (saída 0).

**A medida** (a resposta inteira, em processo, na cópia de 128 MB, julho de 2026 com junho, três processos): a unidade 1 em 1,52 a
1,64 s fria e 0,24 a 0,29 s quente, 31,3 MB; a unidade 39 em 1,30 a 2,31 s fria, 21,1 MB; **a rede em 2,48 a 2,68 s fria e 1,31 a
1,33 s quente, 125,5 MB de pico**. Acima de 1 s, e a causa medida por partes: a base de cada mês (0,57 s e 0,63 s, ~30 mil itens e
~50 MB cada) é quase todo o tempo quente da rede, e a fria soma o ~1 s fixo do primeiro despacho em processo. O D634 ficou PARCIAL
com esses números. **O "ver as vendas"** (Vendas Realizadas, as 74 canceladas de julho da unidade 1): 4,78 a 4,87 s, pelo `DATE()` da
listagem -- o D637. **O oráculo** em Python, independente do PHP, contra o JSON do controller campo a campo (as unidades 1 e 39 e a
rede, julho de 2026): **172 conferências, 0 divergências**; os números da 1.3 do desenho saem iguais (a unidade 1 com 7 desfeitas,
R$ 848,00, e 62 comandas, R$ 7.197,00; a rede com 178 e 316).

**Os mutantes**, cada um sozinho, devolvido byte a byte e conferido pelo sha1, os do servidor nos dois bancos (sempre os mesmos
vermelhos), todos pela asserção: os do desenho, **P53-P1 a P11** (os MV19 a MV25 e MV27 a MV30; o MV26 não se aplica): a política
desligada (cinco casos, e o portão do recorte com saída 2), a tela antiga sem a política, o `layout.phtml` com o `getAcl` sozinho, a
base lida nos dois meses, `porUnidade` e a unidade das pessoas fora da rede, o "ver as vendas" sem a situação, a diferença da taxa em
porcentagem, a recusa sem a peça dela, o balcão fora do valor do "sem nova venda" e o 422 antes do 403; e os meus, **P53-X1 a X23**:
a rede noutra fila, a trava arrancada e a trava também na unidade, o "ver as vendas" sem a unidade e com as ativas, o "ainda não
gravado" arrancado, o botão ligado com outro recorte no ar, a tela antiga com a política de outro relatório (o portão do recorte) e
com o escopo forçado, as canceladas sem a unidade (o caso 1 e o portão), a base do mês com as datas da comparação, a sessão solta
depois das consultas, os nomes das unidades de "Por unidade" fora da leitura, o motivo vazio como texto, a taxa anterior trocada pela
de agora, a linha do Console, o `quando` do menu, a unidade de partida do gerente, o "do balcão" sempre, o "Fora da conta" com zero, o
rótulo da frase antiga, a chave do glossário fora da lista, e a rede pelo escopo e não pelo recorte. **Dois sobreviveram na primeira
rodada** -- o X12 (os nomes de "Por unidade") e o X14 (a taxa anterior), porque no cenário toda unidade da tabela tinha pessoa e a
taxa de março era igual à de fevereiro --; o `testARede` ganhou uma unidade só com atendimento e o `testAComparacao` uma segunda
finalizada de fevereiro, e os dois ficaram vermelhos.

**O que fica de fora e por quê:** o dia do cancelamento e quem o autorizou (as colunas são da 70); o gerente que também tem a marca
de caixa, que o "ver as vendas" leva às ativas de hoje -- nenhum grupo real tem as duas marcas, e a regra é do dono: a pergunta foi
ao lote ("Vendas Canceladas: o gerente que também é do caixa, no "ver as vendas" (D639)"), e à página das perguntas como a **271**. **O roteiro do navegador** (a lista
"Navegador" da V1), no banco de contrato, na unidade 90001000 (conferido no `fixtures.sql` e no banco montado em 28/10/2026):
**fevereiro de 2026** com a desfeita 90001211 ("Cliente desistiu", R$ 80,00) em "Sem nova venda" e na regra larga, pela finalizada
90001210 do mesmo cliente no dia 14; a base de fevereiro tem também a 90001002 (14/02) e a 90001020, que é gravada a
`CURRENT_DATE - 8 meses` e cai em 28/02 com o banco montado em 28/10 -- montado em novembro, ela vai para março e os atendimentos e as
taxas de fevereiro mudam; **janeiro de 2026** sem cancelada com item: "Nenhuma venda cancelada com item neste mês." e "Fora da conta:
1 venda sem item (0 compras de crédito, R$ 0,00) não entra nos números." (a 90001000, de 01/01, tem caixa e nenhum item; a 90001150,
também de 01/01, é da unidade 90001150 e não aparece na 90001000). O `gerente.teste` tem a marca de caixa, e o "ver as vendas" dele
cai nas ativas de hoje (o D639) -- a lista das canceladas de fevereiro se vê como administrador na unidade 90001000.

**A verificação** (três lentes) confirmou 5 achados, todos baixos, e a correção tratou todos (o código do servidor não mudou):
(1) a pergunta do D639 no lote dizia que a trava do caixa em Vendas Realizadas fora "confirmada na pergunta 4", que só fala do Caixa e
da Cota-parte: agora diz que é uma regra antiga do sistema; (2) o roteiro do navegador não dizia o que janeiro mostra, nem que
fevereiro depende do dia em que o banco é montado: reescrito acima, conferido no banco; (3) "Fora da conta" escrevia as partes
zeradas ("5 comandas vazias e 0 vendas sem item (0 compras de crédito, R$ 0,00)", a unidade 1 em julho): agora só as partes com
número, com o verbo concordando ("não entra" com uma só), e um caso novo na vista; (4) o `VendasCanceladasTest` só pedia como o
gerente que também é do caixa: o `testOGerenteSemAMarcaDeCaixaVeAUnidadeNaApiENaTelaAntiga` cobre o gerente sem a marca, o dos
grupos reais, na API e na tela antiga (17 casos no arquivo); (5) o "ver as vendas" do gerente com caixa já era o D639, e segue. **Os
mutantes novos**, cada um sozinho e devolvido pelo sha1: **P53-X24** (a comanda vazia zerada de volta na frase), **X25** (a venda
sem item zerada de volta) e **X26** (o verbo sempre no plural), vermelhos no caso novo da vista; **X27** (o escopo do recorte
pedindo a marca de caixa ao gerente) e **X28** (a tela antiga pedindo a marca de caixa a quem não é administrador), nos dois bancos,
vermelhos só no caso novo (403 e 404 contra 200) -- antes dele, o arquivo inteiro passava com os dois. **O que rodou de novo:** as
classes das canceladas e as vizinhas (a família, o menu, o recorte, a sessão, os itens, as colunas do PDV, a exportação, a listagem
de vendas, os Descontos e a Retirada), **606 casos em cada banco, saída 0** (3.353 asserções, 1 pulado em cada); o front tocado
(seis arquivos, 231 casos, saída 0) e os três novos com `AMPLIFICAR_EFEITOS_MS=50` (34 casos, saída 0); o tsc, o lint (sem aviso
novo) e o build, **319,98 KB** comprimidos (+2,92 KB sobre a 51); os portões que a fatia toca e o `conferir-tudo.sh` no fim.

**No navegador** (o orquestrador, contra o banco de contrato, remontado antes e depois). O administrador: o item Vendas
Canceladas abre a tela nova; **fevereiro de 2026** com 1 desfeita de R$ 80,00 em "Sem nova venda" e na regra larga, 33,3% dos
atendimentos e 21,6% do faturamento (a base de R$ 289,90), nenhuma comanda, o motivo "Cliente desistiu" e o Colaborador Teste
com 1 à mão; o aviso de que quem cancelou e quando ainda não é gravado no topo. A linha "Quem cancelou: 0 pela rotina, 0 à mão"
fica dentro do cartão das comandas, como o desenho a conta (só a comanda passa pela rotina), enquanto o "À mão" de "Por quem
fechou ou abriu" conta também as desfeitas. O "ver as vendas" abre Vendas Realizadas com "só as vendas da unidade Unidade Teste
Centro" e a 90001211 cancelada (R$ 80,00). A rede ("todas as unidades") com "Por unidade" e a coluna Unidade em "Por quem
fechou ou abriu". A 375 px nada passa da largura fora das tabelas que rolam; o claro e o escuro. **Um defeito, consertado neste
commit:** o total de cada cartão ("1 venda · R$ 80,00") saía alinhado à direita -- leva o `.num` (os algarismos tabulares), que
alinha à direita --, longe do mês anterior e das taxas que o explicam, à esquerda; a `.canceladas .cartao > .canceladas-total`
ganhou o `text-align: left`, como a `.identificacao dd.num` e a `.agenda-bloco .num` já faziam; depois dele, o build (319,98 KB).
O gerente de teste, sem o privilégio das canceladas: sem o item no menu, 403 na API e 404 na tela antiga; com o privilégio
concedido só no banco de contrato (a funcionalidade 90002026), o item abre a unidade dele sem a escolha da unidade, a API dá a dele
(200) e recusa a rede e outra unidade (422), a tela antiga responde 200, e o "ver as vendas" leva a Vendas Realizadas com "Quem é do
caixa vê só as vendas concluídas de hoje" -- o D639, à espera da 271. Janeiro, só pela API: nenhuma desfeita nem comanda.

**Débitos:** D634 PARCIAL; D635 (o V-a), D637 (o V-c), D638 (o V-d) e D639 registrados; D636 (o V-b) registrado e corrigido (as duas
anotações do status 0). De pé 157 para 161. Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; nenhuma rotina
rodou além das duas do cache dos relatórios no PostgreSQL de contrato.

## 28/10/2026 -- REL-ROYALTIES (a fatia 54 da fila das telas que faltam)

**O que mudou para quem usa.** O item "Royalties" do menu novo, só do administrador, abre a tela nova (era a tela não migrada):
o ciclo da fatura (de 16 a 15, chamado pelo mês em que começa; o padrão é o que vence no mês de hoje, e o ‹ › anda de ciclo em
ciclo, com "16/07 a 15/08/2026 · vence 29/08"), a rede ou uma unidade (as desativadas com retrato ou fatura entram na escolha,
marcadas), a faixa do ciclo ("a fatura sai em 16/08 e vence em 29/08"; no ciclo em andamento, "ontem e hoje ainda estão em
apuração"), quatro cartões (a base, o royalty devido, o faturado e a diferença em palavras), as faturas do ciclo por situação no
Asaas, a tabela por unidade (as cinco categorias, com Lava VIP e Sem alíquota só quando há valor, o faturamento, os descontos
fora da base, o bruto, a fidelidade com a taxa, o devido -- em travessão, com o porquê, quando a alíquota usada na época não é a
do ciclo --, o faturado, a fatura com "abrir a cobrança" em outra aba, "sem fatura" e as repetidas, e a diferença com os "N dias"
que levam à lista), a linha da rede embaixo, e os dias com diferença, cada um com o motivo (em apuração, sem retrato, retrato
zerado, cancelada depois do retrato, crédito, sem explicação) e o royalty faturado e devido do dia, com o aviso de que o
instante do cancelamento ainda não é gravado. A tela antiga e o item do menu antigo agora são só do administrador: de 366 ativos,
viam 10 e veem 10 -- ninguém perde a porta. Nada escreve, e nada fala com o Asaas.

**No servidor:** `GET /adm/api/relatorios/royalties` e `.../unidades` (o filho `royalties` da família, a fábrica no `Module.php`,
o `naTelaNova`, a linha da `TELA_DO_RELATORIO` e o item do `layout.phtml` no mesmo commit; o menu com o `exige` do papel, sem
`quando`), o `Adm\RoyaltiesController` com as guardas da base, a trava da rede por pessoa (`royaltiesDaRede-<id>`, o 429) e a
lista do filtro com as desativadas; a `RegraDosRoyalties` estendida (o ciclo, a faixa, o vencimento, a geração, a categoria, as
constantes do desenho), a `ContasDosRoyalties` (pura) e o `RoyaltiesJson`; o `RecorteDoRelatorio::ciclo` e o ramo dele no
`periodo`; cinco consultas só de leitura (o crédito do programa, o retrato, as faturas do vencimento, as unidades e as
desativadas com movimento). Os itens da rede são lidos em duas metades e somados por unidade e dia, e as leituras leves vêm antes
deles (o D646). **As respostas do dono:** a 134 (b) -- a alíquota do ciclo é a da vigência, e o cadastro só sem linha; a 98 (a) --
a rotina das faturas não muda aqui, e as repetidas de um vencimento são contadas. A 133 (a), que a fila também cita (o
faturamento da unidade editável, com o aviso de que o boleto continua com os dados da primeira fatura), não tem o que aplicar
aqui: é da tela de Unidades (o D366), e o relatório não edita o faturamento; o link da cobrança só mostrado vem da 1.7 do desenho
(a primeira versão deste parágrafo atribuía isso à 133 (a), o que a letra dela não diz).

**O provado.** A fatia é par, mas **o ciclo inteiro é do orquestrador**, depois dela; aqui rodaram os testes do que ela toca.
A caracterização nos dois bancos pelo executor `-como-ci` e pelo do PostgreSQL, com o filtro das 22 classes tocadas e vizinhas
(as duas novas, `RegrasDosRoyaltiesTest` sem banco e `RelatorioDeRoyaltiesTest` com o cenário semeado na transação, ids 90006900 a
90006949; e o `RoyaltiesTest`, o `PainelDaUnidadeTest`, o `RelatorioAdmControllerTest`, o `RelatoriosNoMenuTest`, o
`PortoesDaFamiliaDosRelatoriosTest`, o `RegrasDoRecorteDoRelatorioTest`, o `UnidadeApiTest`, o `D367` sem o caso da fatura, o
`D368`, o `FranquiasNoRelatorioTest`, o `SessaoApiTest`, o `ItensDoRelatorioTest`, o `VendasCanceladasTest` e outros): **636 casos
em cada banco, saída 0**. **Um desvio, dito:** a primeira rodada do `D367RoyaltiesDaUnidadeInativaTest` inteiro (nos dois bancos de
teste) incluiu o caso `testAFaturaDoDia16CobraAInativaPeloCicloQueTrabalhou`, que chama a rotina das faturas do dia 16 com o
Asaas dublado (a subclasse troca o `asaas()`, com a configuração vazia: nada sai do processo); o roteiro da fatia proibia chamá-la
mesmo assim, e as rodadas seguintes o excluíram pelo filtro. Os ensaios da família nos dois bancos de contrato, com o banco
remontado e as travas de `/tmp` criadas como root apagadas antes (16, o D499): no MySQL, `ensaiar-permissao-adm-api` e
`ensaiar-fluxo-adm-api` saída 0, e o `ensaiar-recusas-adm-api` saída 0 num banco remontado; no PostgreSQL, permissão, fluxo e o
`medir-adm-api-no-postgres` (a parte do administrador: 200 nas duas folhas, com as `diferencas` do ciclo `2026-01` das fixtures)
saída 0, e o recusas saída 0 num banco novo (356 s). O medidor dos relatórios no PostgreSQL (o `p48-medir-relatorios-pg.sh`):
saída 0, com marca em 20 de 25 e a tela antiga dos Royalties no novo `SO_DO_ADMINISTRADOR` (o 404 ao gerente conferido). O front
tocado e vizinho: **579 casos em 25 arquivos** com `--maxWorkers=2`, e os três novos com `AMPLIFICAR_EFEITOS_MS=50` (31 casos), saída
0; o tsc (com as provas e a prévia), o lint (sem aviso novo) e o build, **324,66 KB** comprimidos (+4,68 KB sobre a 53). Os
portões: `conferir-recorte-por-unidade` (328 métodos; 5 relatórios na família), `conferir-cobertura-por-par` (de 392 para 397 pares,
o ISRelatorio de 126 para 135 chamadas, com a data), `conferir-acl-da-api` (6 ações delegadas à mãe), `conferir-links-mortos`,
`conferir-injecao-dql`, `auditar-group-by`, `conferir-booleanos-em-consulta`, `conferir-booleano-em-coluna-inteira`,
`conferir-relogio-nos-testes`, `conferir-rotas-sem-login`, `conferir-grupos-por-id` e `conferir-rastro-das-rotinas`, saída 0; e o
`conferir-tudo.sh` inteiro no fim: **66 portões ok, saída 0** (a primeira rodada saiu 1, com o recorte e a cobertura vermelhos pela segunda
leitura das unidades, que entrou depois deles: a marca ficou ambígua e o ISRelatorio subiu a 135; consertados os dois, saída 0).

**A medida** (em processo, na cópia, três processos de cada; o D646): a rede do `2026-07` em 1,70 a 1,91 s fria e 0,64 a 0,87 s
quente, 75,9 MB de pico; a unidade 1 em 1,05 a 1,19 s fria e 0,16 a 0,17 s quente, 22,8 a 23,1 MB; o `2025-12` com 89,7 MB e o
`2024-10` com 84,3 MB. Lidos de uma vez, os itens da rede levavam o pico a 122,5, 139,1 e 140,3 MB, e com 128 MB de limite o
cache do Doctrine derrubava o `2025-12` (o `OutOfSpaceException`); com as duas metades e as leituras leves antes, os três passam
com 128 MB.

**O oráculo** (em Python, independente, só SELECT na cópia, sem nome nem texto de erro: o `medir-p54-oraculo.py` do rascunho),
contra o JSON do controller em processo, campo a campo, nos ciclos `2026-07` (a rede e a unidade 1), `2025-12` (os 27 retratos
zerados do 10/01/2026) e `2024-10` (5 das 44 unidades com a alíquota da época): **2.182 conferências, 0 divergências**. O faturado
da rede no `2026-07` é R$ 76.319,36, ao centavo das 26 faturas; os R$ 76.319,34 do desenho eram o `ROUND` do MySQL sobre o
`double`.

**Os mutantes**, cada um sozinho, devolvido byte a byte e conferido pelo sha1, os do servidor nos dois bancos: os do desenho
P54-P1 a P54-P35 (o RM1 a o RM33, o RM39 e o RM40) e os desta fatia P54-X1 a P54-X27 -- 62: 45 do servidor nos dois bancos, 2 de portão (o `conferir-recorte-por-unidade.php`) e 15 da tela no vitest -- todos pela asserção, menos dois que morrem por erro no teste do banco (abaixo). **Os dois de portão, o P54-X18 e o P54-X19, não provavam nada na primeira rodada**: saíram 2 contra um portão que já estava vermelho sem mutante (a marca ambígua da segunda leitura das unidades), e o roteiro contava qualquer saída diferente de 0. Refeitos na verificação, com o portão verde e lendo a saída: os dois saem **1, com o "SEM A GUARDA" do método certo** (`RoyaltiesFaturaRepository::royaltiesDoVencimento` e `RoyaltiesController::unidadesDoFiltro`); e o X18 também fica vermelho pela asserção do `testAsConsultasDosRoyalties`, nos dois bancos. Dois
sobreviveram na primeira rodada e o caso foi reforçado até distingui-los: o P54-P32 (o devido nulo como R$ 0,00: o caso passou a
contar os quatro travessões da linha) e o P54-X17 (as diferenças pela unidade: a desativada que só vendeu ganhou o último dia do
ciclo). O P54-P3 e o P54-X26, que morriam por erro no teste do banco, também morrem pela asserção no teste sem banco.

**No navegador** (o orquestrador). A semente do banco de contrato está no rascunho (`p54-navegador-semente.sql`, ids 90006940 a
90006946): o ciclo `2026-01` com a 90001000 em `canceladaDepois` no 14/02 (o retrato de R$ 280,00 contra R$ 200,00 de hoje, a
desfeita de R$ 80,00) e a fatura Quitada com link de exemplo; o `2026-02` com a 90001000 sem retrato no 10/03, a 90001100 com o
retrato zerado no 10/03 e as faturas Vencida (com link) e Aguardando emissão com erro de exemplo. Sem a semente, o banco de
contrato não tem retrato nem fatura: os dias com venda das fixtures saem `semRetrato` (o `2026-01`, o `2026-02` e o `2026-03`), e
o ciclo de hoje (`2026-10`, em andamento) não tem venda.

**O que o navegador mostrou** (o orquestrador, contra o banco de contrato remontado e semeado, remontado de novo no fim). O
administrador: o item Royalties abre a tela nova no ciclo que vence neste mês (16/09 a 15/10/2026, vence em 29/10), na rede, com
as setas dizendo o ciclo anterior e o seguinte. No **`2026-02`**: a base de R$ 479,70 (descontos de R$ 10,00 fora), o devido de R$
44,97, o faturado de R$ 15,99 e R$ 28,98 por faturar em 2 dias; "2 faturas, R$ 15,99: 1 vencida, R$ 8,99; 1 aguardando emissão, R$
7,00", com a nota da que tem erro no Asaas; por unidade, a Unidade Teste Centro com a fatura Vencida e "abrir a cobrança de
Unidade Teste Centro (abre em outra aba)", a Unidade Sessao Teste com "Erro ao emitir no Asaas" e o texto de exemplo; a linha
"Todas as unidades"; e as diferenças dia a dia, "10/03 · Unidade Fiscal Teste -- o retrato deste dia ficou zerado" e "10/03 ·
Unidade Teste Centro -- o retrato deste dia não existe", cada uma com o faturado e o devido. Tudo igual à API e à semente. A 375 px
nada passa da largura fora das tabelas que rolam (o cartão da diferença termina em 360 px; a captura do painel corta a direita,
como na 51), os totais dos cartões à esquerda (o defeito da 53 não voltou), o tema escuro. O gerente de teste: sem o item no menu,
403 `sem_permissao` na API e 404 na tela antiga (`/mod-relatorios/rel-franquias/royalties`), que dá 200 ao administrador. Nenhum
defeito.

**O ciclo inteiro** (a fatia é par; o orquestrador, depois de apagar as dez travas de `/tmp` criadas como root pela suíte, o
D499): caracterização **5.401 casos em cada banco** (11 pulados de propósito; 40.938 asserções no MySQL e 40.934 no PostgreSQL),
contrato 74 e 74, os 22 ensaios em cada banco de contrato com saída 0, os 66 portões ("Nenhuma falha inesperada") e o front com
3.554 casos em 135 arquivos; todas as etapas com saída 0. A suíte inteira inclui, como desde 13/10 (688922c), o caso do D367 que
chama o `relatoriosRoyaltiesFaturasGerar` com o `asaas()` trocado por um dublê e a configuração vazia: conferido antes de rodar,
toda chamada ao Asaas passa por esse método, e sem a chave o `Asaas.php` lança antes de abrir conexão. Fora do ciclo, o medidor
dos relatórios no PostgreSQL (o roteiro do rascunho, só as duas rotinas do cache), saída 0.

**Débitos:** D640 (o `quitada`), D641 e D642 (PARCIAIS: a tela antiga sai na R5), D643 (CORRIGIDO: a política nos dois ERPs),
D644 (o "Sem alíquota", o P-R1), D645 (as situações das Faturas, a R2) e D646 (a medida da rede) registrados. De pé 161 para 167.
Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; nada falou com o Asaas -- as rotinas do retrato e da fatura só rodaram nos casos que já existiam, no banco de teste (o desvio acima).

**A verificação da fatia** (em três lentes, 28/10/2026) confirmou 13 achados (11 distintos), todos baixos menos o dos mutantes de
portão (médio), e todos tratados:

- **O dia 15, o último do ciclo, saía fechado** (`RegraDosRoyalties::ciclo`): com o `hoje + 1 dia < fim` do desenho, no dia 15 o
  ciclo de hoje vinha sem o "Em andamento", com o › ligado, e o › levava ao 422 do ciclo que ainda não começou. Agora é `<=`; os
  números não mudam (o `fim` é o mesmo). O caso `testNoDia15OCicloAindaEstaEmAndamento`.
- **Na rede, parte e todo lado a lado sem aviso**: o devido e a diferença da rede somam só as unidades conferidas, e o faturado
  todas. A linha "Todas as unidades" diz "devido e diferença de N conferidas de M" e o cartão da diferença "só das N unidades
  conferidas de M"; sem nenhuma conferida, a linha da rede mostra o travessão no bruto, na fidelidade, no devido e na diferença,
  como a da unidade, e nunca R$ 0,00 nem "sem diferença" (dois casos novos no `Royalties.teste.tsx`).
- **A fatura com erro do Asaas**: a linha diz "Erro ao emitir no Asaas" e o resumo a contava como "aguardando emissão", sem
  ligação; o resumo diz agora "1 delas com erro ao emitir no Asaas, contada acima pela situação gravada" (só as da moeda da rede,
  as mesmas do resumo).
- **Os links repetidos**: o "abrir a cobrança" e o "N dias" levam o nome da unidade para o leitor de tela, como o "ver os itens"
  dos Descontos.
- **Os testes e o serviço da tela antiga**: o `RelatorioDeRoyaltiesTest` importa o `FranquiaService` para o caso que o desenho
  manda ("o faturado é o da tela antiga", o `royaltiesCalcularCache`, que só lê repositórios), e o "código não alcança a
  cobrança" não lia os testes. Entrou o `testOsTestesDosRoyaltiesSoLeemOServicoDaTelaAntiga`: dos métodos do serviço (lidos do
  arquivo como texto, sem carregá-lo), o teste novo chama só o `royaltiesCalcularCache`, pelo `->`, pelo `::` ou pelo nome numa
  string, e as regras nenhum.
- **Os números da 1.8 que mudaram** ficaram escritos (a R1 FEITA e a seção 6): o devido da rede é R$ 76.574,10 (e a diferença
  R$ 254,74), e não 76.574,02 e 254,68 -- a mesma causa do faturado; a unidade 1 tem 18 dias com diferença (6 `canceladaDepois` e
  12 `credito`: o retrato dela tem crédito em 16 dias e a cópia nenhum pagamento na forma 6, o D181), e não 6.
- **A 133 (a)**, que este parágrafo atribuía ao link da cobrança (acima, corrigido); **os mutantes de portão** (acima, refeitos);
  **a tabela dos débitos** do `docs/f3-relatorios/README.md` (as linhas do D635 ao D646, que a 53 e a 54 não puseram) e a linha R1
  da tabela das fatias do desenho; **os comentários** com o pico antigo (o `filaDaRede.ts`, o `RoyaltiesController` e o
  `RoyaltiesLigado`: 76 a 90 MB, e não "mais de cem" nem "~122") e as "oito consultas", que na rede podem ser nove.
- **O desvio da nota 0** (o D367 inteiro, acima) já estava dito; nada mais a fazer.

Rodaram de novo, em série: as 22 classes da fatia, **638 casos em cada banco, saída 0** (as 636 e os dois casos novos); o front
tocado e vizinho, **433 casos em 21 arquivos**, e os três dos Royalties com `AMPLIFICAR_EFEITOS_MS=50`, 34 casos, saída 0; o tsc da
prévia, o lint (nada nos arquivos dos Royalties) e o build, **324,82 KB** comprimidos (+4,84 KB sobre a 53); o
`conferir-recorte-por-unidade.php` (328 métodos) saída 0, e o `conferir-tudo.sh` inteiro no fim, **66 portões ok, saída 0**;
os débitos continuam 167 de pé (nenhum novo) e o `conferir-debitos-repetidos` sem par novo. Os mutantes novos, um por vez e devolvidos pelo sha1: o P54-X28 (o `<`
do desenho; os dois bancos), o P54-X29 a X36 (a tela: as duas frases da parte, os dois travessões da rede, o erro fora do total
e o resumo sem o erro, e os dois nomes de link) e o P54-X37 e X38 (o teste chamando outro método do serviço, e citando a rotina
numa string; os dois bancos) -- **11, todos vermelhos pela asserção**. Os ensaios HTTP não rodaram de novo: a única mudança do
servidor é o `<=` do dia 15, que hoje (28/10) nenhum ensaio alcança.

## 28/10/2026 -- FATURAS-DO-GERENTE (a fatia 55 da fila das telas que faltam)

**O que mudou para quem usa.** O item "Faturas" do menu do usuário, só do gerente, abre a tela nova (era a tela não migrada): as
faturas de royalties da unidade da sessão, da mais nova para a mais velha, de 20 em 20 ("‹ anterior · página 1 de 2 · próxima ›",
as pontas desligadas sem sumir debaixo do cursor), cada uma com o vencimento, o valor, a situação -- com as palavras dos
Royalties: "Quitada em dinheiro" e "Em negativação", que a tela antiga deixava em branco (o D645), "Erro ao emitir no Asaas" com o
erro gravado, e o código como veio para o que ninguém conhece --, a descrição e "abrir a cobrança" em outra aba (`noopener
noreferrer`, e o nome diz que abre fora). Em cada fatura, **"a base desta fatura"**: o ciclo que ela cobra (o mês ANTERIOR ao do
vencimento -- a que vence em 29/03 cobra de 16/02 a 15/03), a tabela por categoria com a base, a alíquota efetiva e o royalty,
o faturamento, e o bruto, o crédito do Prog. de Fidelidade com a taxa, o abatimento, o royalty do ciclo e o valor da fatura. A
base é o retrato do ciclo, o número que gerou a fatura, e não o recálculo de hoje: o recálculo e a diferença dia a dia ficam no
Royalties, só do administrador (a decisão 13 do dono, "sem abrir os números da rede"). A tela antiga fica como está até o corte.
Na cópia, de 366 ativos, 118 têm a marca de gerente (os 10 administradores entre eles) e veem as duas -- ninguém perde a porta.
Nada escreve, e nada fala com o Asaas.

**O servidor.** `GET /adm/api/faturas` (`Adm\FaturasDaUnidadeController`, no ISRelatorio, fora da família dos relatórios, com a
`RelatorioAdmControllerFactory`): a sessão (`apenasSessao()`, que o `conferir-acl-da-api.php` passou a contar 2, com a ressalva
impressa), a marca de gerente (403 `papel`, antes da página), a página pela regra (`RegraDosRoyalties::paginaDasFaturas`, de 1 a
10000, senão 422), a sessão solta, e a unidade SEMPRE a da sessão; a página é a `listagemIndex` da tela antiga (com o
`asaasErro` no SELECT), e UMA consulta ao retrato cobre os ciclos da página (`royaltiesDoCiclo`), cortado por fatura na faixa de
16 a 16 do `RegraDosRoyalties::cicloDoVencimento` e somado pelo `ContasDosRoyalties::faturado`; o `FaturasDaUnidadeJson` formata
(a alíquota efetiva pela `taxa` do painel, nula sem base). A linha `adm-api-faturas` em `rotas-publicas.php`, a rota no bloco
congelado do `conferir-rotas-sem-login.php`, quatro vigias no `conferir-recorte-por-unidade.php` (328 para 332 métodos), a
`listagemIndex` fora dos falsos positivos do `conferir-cobertura-por-par.php` (o ISRelatorio de 135 para 137 chamadas) e a
tentativa `/adm/api/faturas` (403 `papel`) no `ensaiar-permissao-adm-api.php`.

**A tela.** `api/faturas.ts`, `Faturas.tsx`, `FaturasLigado.tsx` e a regra pura `baseDaFatura.ts`; o `ehFaturas` do Console (a
Faturas não é relatório e não entra na `TELA_DO_RELATORIO`), o `CAMINHO_DAS_FATURAS` e o `PAPEL_DAS_FATURAS` no
`menu-do-erp.ts`; as peças comuns (o cabeçalho, a região viva, a `TabelaQueRola`) e as palavras do `textosDosRoyalties.ts`; o
App.css com os tokens dos dois temas e o valor à esquerda, sem o `.num` (o defeito da 53). A prévia `Faturas` (cinco células:
a lista com a base aberta e o erro do Asaas, relendo, vazia, a falha e a recusa; valores e links `exemplo.invalid`), o cenário em
`_cadastros.ts`, o `index.ts` e o `componentSrcMap`. No Console.teste, as partidas "que não buscam nada" passaram de Faturas para
Assistentes VIP, e as Faturas ficam fora da fila do dublê; o caso dos não migrados desceu de 11 para 10.

**O que rodou** (a fatia é ímpar: só os testes do que ela toca; **o ciclo inteiro fica para a 56**). A caracterização: o
`FaturasDaUnidadeTest` novo (11 casos) e o `RegrasDosRoyaltiesTest` (2 casos novos), e as 14 classes tocadas e vizinhas (os
Royalties, a sessão, o soltar, o menu, os portões da família, a guarda de login, as rotas do aplicativo, a barreira de origem),
**459 casos em cada banco, saída 0**; sem o `D367RoyaltiesDaUnidadeInativaTest`, o `FaturasDeRoyaltiesTravaTest`, o `RoyaltiesTest`
e o `D368VigenciaDaTaxaDeRoyaltyTest`, que chamam o serviço da cobrança ou do retrato (a nota 0). Os ensaios: o
`ensaiar-permissao-adm-api` no MySQL de contrato (152 de 152 recusadas) e no PostgreSQL, e o `ensaiar-fluxo-adm-api` e o
`medir-adm-api-no-postgres` no PostgreSQL (a rota nova responde 200 ao gerente das fixtures, a lista vazia), todos com saída 0,
depois de apagar as duas travas de `/tmp` criadas como root pela suíte (o D499). O `ensaiar-recusas-adm-api` e o
`ensaiar-rotas-publicas-do-erp` não rodaram: a rota não entra neles. O front tocado e vizinho, **227 casos em 5 arquivos**, e os
dois arquivos novos com `AMPLIFICAR_EFEITOS_MS=50` (13 casos), saída 0; tsc, lint (nada nos arquivos novos) e build, **326,06 KB**
comprimidos (+1,40 KB sobre os 324,66 KB da 54). Os portões da fatia, saída 0: o `conferir-acl-da-api`, o `conferir-rotas-sem-login`,
o `conferir-recorte-por-unidade`, o `conferir-cobertura-por-par`, o `conferir-links-mortos` e o `conferir-segredos`; o
`conferir-tudo.sh` inteiro no fim, **os 66 portões, "Nenhuma falha inesperada", saída 0** (o vitest inteiro dentro dele).

**A medida e o oráculo.** Em processo, na cópia: a primeira página em **0,19 a 0,79 s no primeiro pedido do processo e 5 a 17 ms
no segundo**, 14,7 a 16,1 MB, quatro consultas, todas `SELECT` -- acima dos 0,5 s do desenho no frio, pelo custo fixo do primeiro
despacho: o D647. O oráculo em Python (só SELECT na cópia, sem nome nem texto de erro: a descrição, o link e o erro pelo sha1): as
**890 faturas das 37 unidades, todas as páginas, contra o JSON do controller, 24.269 conferências, 0 divergências** -- a ordem, a
página de 20, o ciclo, a base das seis colunas, o royalty e a alíquota de cada categoria, o bruto, o crédito e a taxa, o
abatimento e o líquido; e o líquido do retrato é o valor da fatura nas 890 (168 a exatamente meio centavo da soma crua, nenhuma
acima).

**Os mutantes**, um por vez, devolvidos e conferidos pelo sha1, os do servidor nos dois bancos, todos vermelhos pela asserção:
os do desenho -- o P55-P1 (RM34, a unidade do `?unidade=`; também o portão do recorte), o P55-P2 (RM35, sem a marca; também o
portão), o P55-P3 (RM36, a base pelo recálculo de hoje), o P55-P4 (RM37, uma consulta ao retrato por fatura), o P55-P5 (RM38, a
alíquota do cadastro no lugar da efetiva) e o P55-P6 (RM41, o ciclo pelo mês do vencimento) -- e os meus: o ciclo pelo
`cicloDoDia` do vencimento (X1), a página acima de 10000 sem 422 (X2), a página ruim virando 1 (X3), o link sem o `rel` (X4), o
`ehFaturas` arrancado (X5), a sessão solta antes da marca (X6), a faixa fechada no fim (X7), a situação desconhecida em branco
(X8), o item sem o papel (X9), o valor com o `.num` (X10), a `listagemIndex` sem o erro (X11), a página pedida ignorada (X12), a
rota fora de `rotas-publicas.php` (X13, o `conferir-rotas-sem-login`), a paginação que some na releitura (X14), o "tentar de novo"
sem o foco no título (X15: sobreviveu à primeira versão do caso, porque o `useFocoNoTitulo` resgata o foco depois; o caso passou a
olhar o foco no "carregando") e a "Sem alíquota" arrancada (X16).

**Débitos:** D645 PARCIAL (certo na tela nova; a antiga até o corte); D647 registrado (a medida). De pé 167 para 168. Nada do
esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; nenhuma rotina rodou, e nada falou com o Asaas.

**O que não se provou aqui:** a tela no navegador (do orquestrador: a semente `p55-navegador-semente.sql` do rascunho, para o
banco de contrato, com o DELETE no fim), a 375 px e os dois temas.

**A correção depois da verificação** (três lentes, 7 achados confirmados, todos verdadeiros e todos tratados). (1) A tabela das
fatias do `spec-royalties.md` dava a R2 como por fazer: a linha diz agora "feita em 28/10/2026 (a fatia 55)". (2) O docblock do
`apenasSessao()` dizia "um caso só" e "de 1 para 12": diz os dois casos e "de 2 para 12", e o `conferir-acl-da-api.php` imprime
"salvo a exceção declarada abaixo" antes da ressalva das Faturas. (3) A tranca da resposta atrasada não tinha caso: o pedido foi
para um gancho, o `usePedidoDasFaturas` (`pedidoDasFaturas.ts`), que o `pedidoDasFaturas.teste.ts` prova com a resposta velha
DEPOIS da nova (2 casos); e a conferência das pontas no clique ganhou dois casos no `FaturasLigado.teste.tsx` (a próxima
apertada na última página, e as duas apertadas com a releitura no ar, não pedem nada). (4) **A fatura sem vencimento derrubava
a página inteira do gerente** (o `->modify()` sobre o `false` do `createFromFormat`: erro fatal, 500): a coluna aceita nulo nos
dois bancos, e nenhuma das 890 da cópia o tem. O `cicloDoVencimento` devolve nulo para o nulo, o vazio e o texto que não é data,
e a fatura vem com o vencimento, o ciclo e os números da base nulos -- e não zero, que diria "o retrato não tem dia" --, e a tela
diz "Sem vencimento gravado: não dá para saber o ciclo que esta fatura cobra, nem a base dele"; o caso
`testAFaturaSemVencimentoVemSemCicloESemBase` (a 90006982, só nele) e o tipo `FaturaSemCiclo` no front. (5) Os comentários do
cenário diziam que o recálculo de hoje daria "R$ 80,00 a menos": daria a base inteira em zero (a única venda do período está
cancelada hoje), e quem distingue o RM36 é o retrato sem venda por trás -- o docblock da classe, o do caso e o `spec-royalties.md`
dizem isso. (6) **O texto escondido do "abrir a cobrança" ficava fora de qualquer caixa posicionada** e podia esticar a página
inteira (a lição da `.clientes-lista`): o `.faturas-fatura` é `position: relative` -- o vitest lê o App.css vazio, e a prova é do
navegador. (7) Os vinte `<summary>` tinham o mesmo nome: cada um diz, escondido, de qual vencimento é ("a base desta fatura, que
vence em 29/03/2026"), e a legenda da tabela e o link dizem "sem vencimento" na fatura sem ele. A prévia ganhou a célula
`SemVencimento` (seis células). **O que rodou de novo**, em série: a caracterização das 14 classes da fatia e vizinhas, **460
casos em cada banco, saída 0** (um caso novo, e dois `assert` novos no `RegrasDosRoyaltiesTest`); o front tocado e vizinho
(`FaturasLigado`, `baseDaFatura`, `pedidoDasFaturas`, `Console`, `menu-do-erp`, `Royalties`), **223 casos em 6 arquivos, saída
0**, e os três das Faturas com `AMPLIFICAR_EFEITOS_MS=50`, 17 casos, saída 0; tsc, lint (nada nos arquivos das Faturas) e build,
**326,25 KB** comprimidos (+1,59 KB sobre os 324,66 KB da 54); o oráculo de novo, contra o JSON do controller regerado em
processo, **24.269 conferências, 0 divergências**; os portões da fatia (o `conferir-acl-da-api`, o `conferir-rotas-sem-login`, o
`conferir-recorte-por-unidade`, o `conferir-cobertura-por-par`, o `conferir-links-mortos`, o `conferir-segredos`, o
`contar-debitos` e o `conferir-debitos-repetidos`, saída 0, de pé ainda 168) e o `conferir-tudo.sh` inteiro no fim, sozinho:
**os 66 portões, "Nenhuma falha inesperada", saída 0**. Os ensaios HTTP não
rodaram de novo: a guarda e a lista vazia das fixtures não mudaram. **Os mutantes novos**, um por vez, devolvidos e conferidos
pelo sha1, os do servidor nos dois bancos: o P55-P6 (RM41) e o X1 refeitos sobre o `cicloDoVencimento` novo (5 falhas pela
asserção em cada banco); o X17, a guarda do nulo arrancada (o defeito do achado 4), vermelho pelo `Error` que ele é -- 2 erros em
cada banco, o caso novo e o do `RegrasDosRoyaltiesTest`, e não pela asserção: é o defeito, e não um mutante de valor; o X18, a
base da fatura sem ciclo em zero, e o X19, o vencimento nulo como texto vazio, vermelhos pela asserção nos dois bancos; e no
front, o X20 (o `<summary>` sem o vencimento escondido), o X21 (a tranca `vigente` arrancada: os 2 casos do gancho), o X22 (o
clique da próxima sem conferir a ponta), o X23 (o clique sem o `ocupado`, com o `aria-disabled` de pé: o caso da releitura) e o
X24 (a fatura sem ciclo sem a frase), todos vermelhos pela asserção. O achado 6 não tem mutante: o CSS chega vazio no vitest.

**No navegador** (o orquestrador, contra o banco de contrato remontado e com as duas sementes do rascunho, remontado de novo no
fim). O `gerente.teste`: o item Faturas do menu do usuário abre a tela nova, com 23 faturas em duas páginas (20 e 3), da mais
nova para a mais velha; as situações com as palavras dos Royalties ("Aguardando pagamento", "Quitada", "Vencida", "Quitada em
dinheiro", "Erro ao emitir no Asaas" com o texto de exemplo e sem o link, "Em negativação", e o `REFUND_REQUESTED` como veio);
o "abrir a cobrança" dizendo de qual fatura e que abre em outra aba. **A base de 29/03/2026** (o ciclo de 16/02 a 15/03): Serviços
R$ 189,90 a 10%, Prod. Barbearia R$ 40,00 a 5%, Prod. Empório R$ 20,00 a 5%, Lava VIP R$ 10,00 a 5%, Sem alíquota R$ 5,00,
faturamento R$ 264,90, bruto R$ 22,49, crédito "R$ 15,00 · 5,7%", abatimento R$ 1,16 e R$ 21,33 -- igual ao contado à mão na
semente; a de 28/02/2026 com 3,5% no serviço e R$ 11,15; a de 29/01/2026 sem retrato, com a base em zero e a frase. Na página 2, a
fatura sem vencimento com "Sem vencimento gravado: não dá para saber o ciclo que esta fatura cobra, nem a base dele." e sem a
base, e o foco no botão apertado. A 375 px nada passa da largura (a tabela da base rola dentro do cartão: o achado 6 não voltou),
os valores à esquerda, o escuro e o claro. Pela API: a `?unidade=90001100` ignorada (a lista é a da sessão), `?pagina=0` 422, e o
administrador de teste, que não tem a marca de gerente, 403 `papel` e sem o item no menu. Nenhum defeito.

## 28/10/2026 -- REL-NPS-CONTAS (a fatia 56 da fila das telas que faltam)

**O que mudou para quem usa: quase nada na tela, ainda.** O NPS ganhou a API nova (`GET /adm/api/relatorios/nps`, `.../meses`,
`.../clientes`, `.../exportacao?formato=txt` e `.../unidades`; a N1 de `docs/f3-relatorios/spec-nps.md`, com a decisão 8 do dono --
três médias de 0 a 10, com respostas e faixas, e os clientes com alguma nota até 6, nenhum número de -100 a 100). A tela é a 57 (a
N2). Até lá, o item NPS do menu da tela nova continua abrindo a tela não migrada (o caminho para a versão antiga), mas passou a
aparecer só para o gerente e o administrador (o `quando` da guarda `relatorioNps`), como o item do menu do ERP antigo (pelo
`RecorteDoRelatorio::veOMenu`): o colaborador com o privilégio perde o item -- a tela antiga ainda abre pelo endereço, e a guarda
dela é da 57.

**O que a N1 prova.** O mês (a recepção e a experiência uma vez por ENVIO, pela cadeia de 60 s da decisão E1; os colaboradores
por resposta, a régua do Profissional Parceiro -- o mesmo número, chave a chave, provado contra o controller do Parceiro), a taxa
de resposta com a interseção (a E2: dos ATENDIDOS no mês, quantos responderam), os clientes pela pior nota (a E3), a linha de cada
unidade só na rede do administrador, com o mínimo de 30 envios (a N2), os 12 meses que terminam no mês, cada um com as respostas
do próprio mês e o total como um período só, e a lista dos clientes com os filtros `faixa` e `colaborador`, o contato (nome,
telefone e e-mail, como a tela antiga mostra) e o TXT dela: `primeiroNome;telefone`, mais `;email` só para o administrador, tirado
do PEDIDO, pelo mesmo recorte e pelo mesmo filtro da lista -- e não da sessão, como a tela antiga (o D652). Quem vê é a política
`nps`: o gerente a unidade dele; o administrador a rede ou uma unidade; o colaborador e o caixa com o privilégio, 403 `papel`
antes do 422 do mês ruim. O filtro ruim da lista e do TXT é 422 antes de soltar a sessão (o `guardasDoRelatorio` da base, que tomou
o lugar do `pedidoDaExportacao` do desenho). Nada consolida: a rotina `npsContabilizar` não rodou nem mudou, e a lista de vendas
segue consolidando a loja escolhida (as respostas 17 (b) e 44 (a), o D428; o `D428NpsDaListaDeVendasTest` verde). No código, a 17
(b) e a 44 (a) aparecem como o que NÃO está lá: nenhuma linha nova chama a consolidação, e o docblock do `NpsController` e o D650
dizem por quê. **A resposta 16 (a), que a fila manda seguir, não se aplica à N1:** ela decide o XLS de Vendas Realizadas filtrado
pelo colaborador (as abas 0 e 1 pelos itens dele, o D230), e o NPS não tem XLS. O filtro `colaborador` da lista e do TXT do NPS vem
da decisão E3 do desenho (as cinco listas da tela antiga), e não dela: o TXT traz os clientes da mesma lista da tela. **Fica para a 57:** a tela, a fila `nps` do `filaDaRede.ts` para o mês da rede,
o `LinkDaFicha` no nome do cliente, o histórico do Parceiro no "ver histórico" e a tela antiga com a política.

**Consertado na própria fatia: a rede caía com 128 MB.** A primeira versão lia os atendidos (os itens do painel da rede, 31.898 em
julho de 2026) de uma vez e antes das respostas: 122,2 a 123,5 MB de pico, e com `memory_limit = 128M` o mês da rede caía nos dois
bancos (o `OutOfSpaceException` do cache do Doctrine em julho; a memória esgotada em dezembro de 2025) -- o achado do D646. Agora as
respostas e os nomes vêm antes, e os itens em duas metades do mês (`ContasDosRoyalties::partes`), cada uma reduzida ao conjunto
dos clientes: 73,6 MB em julho e 94,5 MB em dezembro de 2025, com 128 MB, nos dois bancos. A rede trava por pessoa no mês (o 429,
`npsDaRede-<id>`); os 12 meses e a lista não.

**A medida** (em processo, nas duas cópias, três processos de cada, julho de 2026, `memory_limit = 128M`; o PostgreSQL pela conexão
do `local.postgres.php.dist` posta só no processo, sem trocar o `local.php`): o mês da rede de 1,60 a 2,27 s frio e 0,48 a 0,52 s
quente no MySQL, 1,69 a 1,84 s e 0,37 a 0,43 s no PostgreSQL, 73,6 MB; a unidade 1 de 0,60 a 1,40 s frio e 0,11 s quente no MySQL,
0,73 a 1,25 s e 0,06 a 0,07 s no PostgreSQL, 23,5 MB; os 12 meses da rede de 0,37 a 1,30 s frio, a lista de 0,54 a 1,24 s e o TXT
de 0,93 a 1,59 s, todos de 0,01 a 0,05 s quentes e de 17 a 23 MB. **O mês da rede (teto 3 s e 128 MB) e a unidade (1,5 s) ficaram
nos tetos do desenho; os 12 meses e a lista da rede PASSARAM do teto de 1 s no primeiro pedido** (1,30 s e 1,24 s no MySQL, 1,04 s
na lista do PostgreSQL; o TXT, sem teto próprio, 1,59 s). Tudo no **D655**, com as duas causas medidas. A do mês da rede: a
consulta dos itens leva 0,19 s no MySQL e 0,48 s no PostgreSQL, e o resto é a hidratação e o `normalizar` das linhas, de que só
sobra o conjunto dos clientes. A dos 12 meses, da lista e do TXT, medida na verificação (`medir-p56-fixo.sh`, três processos de
cada, nos dois bancos): o custo fixo do primeiro pedido do processo, e não a rede -- as consultas somam de 0,010 a 0,065 s, e os 12
meses da unidade 1 custam o mesmo frios (0,82 a 1,62 s no MySQL) que os da rede (0,88 a 1,38 s). Por isso só o mês da rede trava:
a trava não tiraria nada de um custo que a unidade paga igual.

**O oráculo** (`medir-p56-oraculo.py`, no rascunho: Python, só SELECT, nas duas cópias, contra o JSON do controller em processo
nos dois bancos, sem imprimir nome, telefone nem e-mail -- o contato e o TXT pelo sha1): **1.828 conferências, 0 divergências**,
antes e depois do conserto da memória. Julho e junho de 2026 na unidade 1 e na rede, os 12 meses da unidade 1 e da rede, a lista
sem filtro, com `faixa=ate6`, com `faixa=de7a8` e com o colaborador de mais respostas, e o TXT do administrador. Os números da
seção 1 do desenho saíram iguais: 296 envios, 317 pesquisas e 290 clientes na rede, 42/42/40 na unidade 1; as taxas 2,0% (290 de
14.532) e 1,8% (40 de 2.235); recepção 9,87 · 296, experiência 9,90 · 296 e colaboradores 9,93 · 387 na rede, 9,60, 9,90 e 10,00 ·
44 na unidade 1; 36 unidades, 9 com resposta, 4 com 30 envios ou mais (107, 63, 42 e 32); o bloco da unidade 1 com 288 envios, 245
clientes, 9,48 · 288, 9,73 · 288, 9,87 · 315 e 18 com alguma nota até 6; as listas de 40 e 290 linhas com 1/3/36 e 4/10/276, 0 sem
telefone.

**O que rodou (a fatia é par: o ciclo inteiro é do orquestrador, depois desta).** Os testes do que a fatia toca, pelos executores
de sempre, nos dois bancos: as classes novas (`RegrasDoNpsTest`, sem banco, 20 casos; `NpsDoRelatorioTest`, 28 casos com os
provedores), as tocadas e as vizinhas -- 828 casos em cada banco, verdes, com 1 pulado que já era (o
`DescontosNoRelatorioTest::testOsTotaisNaoSomamOsDeOutraFranquia`, sem a cópia). Os ensaios da família nos dois bancos de
contrato, com as travas de /tmp criadas como root apagadas antes (15 na primeira vez): o `ensaiar-permissao-adm-api` (157 de 157
recusadas, as cinco folhas do NPS entre elas, e o 403 `papel` do colaborador e do caixa na parte 3) e o `ensaiar-fluxo-adm-api`,
o banco remontado, e o `ensaiar-recusas-adm-api` (333 s no PostgreSQL), todos com saída 0; o `medir-adm-api-no-postgres` com saída 0,
a semente do NPS (2 pesquisas e 3 itens em fevereiro de 2026) e as cinco folhas em 200, o TXT com 23 bytes; o medidor dos
relatórios no PostgreSQL (`p48-medir-relatorios-pg.sh`) com saída 0. O front tocado: 245 casos em 10 arquivos (o menu e o
Console), o tsc, o lint e o build com 326,27 KB (+0,02 KB). Os portões: o recorte por unidade (347 métodos, 6 relatórios na família,
9 ações com a guarda da base), a ACL (9 ações delegadas à base, eram 6), as rotas sem login (16 de relatório guardadas pela base,
eram 11), a cobertura por par (397 para 399 pares; o ISRelatorio de 137 para 144 chamadas; tirando os dois pares, o sha1 volta ao
de 397), a injeção de DQL (22, igual: o detector aprendeu a constante da própria classe, `self::NOME`, que o desenho manda não
contar), o `auditar-group-by`, os links mortos, os booleanos, a ordenação de nulos, o buffer de saída e os grupos por id, todos com
saída 0; e o `conferir-tudo.sh` inteiro no fim (66 portões ok, saída 0; a primeira rodada saiu 1 no "relogio nos testes": o `date` e o `strtotime` do ajudante `mais` do `RegrasDoNpsTest`, sobre um instante escrito, que o portão lê como relógio -- trocados por `DateTimeImmutable::createFromFormat`, o portão e os 20 casos verdes de novo nos dois bancos, e a segunda rodada saiu 0; os mutantes da cadeia, P56-P3 a P6 e X1, rodaram com o ajudante de antes, que dá os mesmos instantes). **Na verificação** (29/10/2026), depois do caso novo do administrador com o 0, a inativa e a que não existe e do outro administrador da trava trazido para a faixa do NPS (o 90006609, só na sessão; era o 90006120, do `ItensDoRelatorioTest`): o `NpsDoRelatorioTest` e o `RegrasDoNpsTest`, 49 casos em cada banco; as classes do NPS, do Parceiro, da família, do recorte, dos itens, da exportação e do menu, 464 casos em cada banco, verdes; a medida da causa dos 12 meses, da lista e do TXT; os mutantes refeitos acima; o `contar-debitos` (de pé, 177), o `conferir-debitos-repetidos` e o `conferir-segredos.sh` com saída 0; e o `conferir-tudo.sh` inteiro de novo, 66 portões ok, saída 0.

**Os mutantes** (no rascunho, `medir-p56-mutantes.py`), um por vez, devolvidos byte a byte e conferidos pelo sha1, os do servidor
nos dois bancos de teste (o `local.php` trocado só dentro do roteiro e devolvido pelo sha1): os 22 da tabela do desenho, P56-P1 a
P22 (os N-M1 a N-M22), vermelhos pela asserção nos dois bancos, menos dois que o desenho manda pegar pelo portão -- o P56-P20 (os
contatos de todos os clientes, e não só os da lista) e o P56-P19b (a condição da rede arrancada do controller; a da resposta é o
P56-P19, vermelho nos testes) ficam verdes nos testes e vermelhos no `conferir-recorte-por-unidade` (a resposta não muda: o JSON
recorta de novo). Os meus, P56-X1 a X26: a janela em 0 na conta com a constante intacta, a taxa pelo menor dos dois, a faixa pela
média e não pela pior nota, o `contabilizado` fora só do relatório, o fim da faixa um segundo depois, o mínimo de 30 contado em
pesquisas, o e-mail no TXT do gerente, a unidade do pedido ignorada na lista, a sessão solta antes do filtro, a trava arrancada, a
unidade travando, o TXT de outra lista, a última resposta trocada pela primeira, a unidade do colaborador pela da pesquisa, o sem
telefone só pelo nulo, a ordem sem tirar a caixa, a nota ao colaborador com os itens de todos, a faixa em lista aceita, os
atendidos numa leitura só e só da última metade -- todos vermelhos pela asserção nos dois bancos (nenhum por erro de tipo); o X19
(o TXT do gerente com o e-mail aceito pelo medidor) no `PortoesDaFamiliaDosRelatoriosTest`; o X20 (o detector sem a constante da
própria classe) e o X21 (uma variável no mesmo lugar) no `conferir-injecao-dql`, 23 contra 22, com o portão verde antes; o X22 (o
`quando` do menu arrancado) no `menu-do-erp.teste.ts` e no `RelatoriosNoMenuTest`; o X23 (a linha da `TELA_DO_RELATORIO`) e o X24
(o item do layout pelo privilégio só) no `RelatoriosNoMenuTest`. **A maioria dos mutantes do controller rodou, na primeira rodada,
contra uma versão anterior** (o `NpsController` de antes das duas metades, sha1 `12fa61f5b709`, e o `conferir-recorte-por-unidade`
de antes da vigia do `painelItens`), e só o P11, o P19b, o X2, o X25 e o X26 foram refeitos contra a final. **Na verificação
(29/10/2026, `medir-p56-mutantes3.py`) rodaram de novo contra a versão final** (o `NpsController` `0f4d2f64f184`, o portão
`9fdb81abeb10`), com o portão rodado sem mutante na MESMA rodada antes (saída 0, "sem a guarda: 0") e a base verde nos dois bancos
(251 casos): o P12, o P14, o P18, o P19, o P20, o X7, o X8, o X9, o X10, o X11 e o X12 -- todos vermelhos como antes, pela asserção
nos dois bancos e pelo portão onde o desenho manda (o P20 de novo verde nos testes e vermelho só no portão), e todos devolvidos pelo
sha1. E os novos da verificação, pelo caso novo do administrador com o 0, a que não existe e a inativa: o X27 (o filtro somando as
inativas, com uma consulta) e o X27b (somando a B, sem consulta), vermelhos pelo caso novo e também por casos que já existiam (o
X27 pela consulta a mais antes de soltar a sessão, o X27b pelo gerente que passa a ver a B); o X28 (as aceitas com a unidade
pedida) com sete falhas; e o **X29** (só o administrador aceita qualquer unidade numérica, sem consulta), **verde em todos os casos
de antes e vermelho só pelo caso novo** ("index: o 0 não é a rede", 200 contra 422) nos dois bancos.

**O ciclo inteiro** (a fatia é par; o orquestrador, sem trava de `/tmp` criada como root para apagar desta vez): caracterização
**5.464 casos em cada banco** (11 pulados de propósito; 41.791 asserções no MySQL e 41.787 no PostgreSQL), contrato 74 e 74, os 22
ensaios em cada banco de contrato com saída 0, os 66 portões ("Nenhuma falha inesperada") e o front com 3.572 casos em 138
arquivos; todas as etapas com saída 0. Fora do ciclo, o medidor dos relatórios no PostgreSQL (o roteiro do rascunho, só as duas
rotinas do cache), saída 0. Nada para o navegador: a N1 não tem tela (a linha do Console ainda abre a tela não migrada). A
pergunta do D656 foi à página das perguntas como a **272**.

**Débitos:** D648 a D656 registrados -- os D-n1 a D-n7 do desenho (o D652 e o D653 PARCIAIS: certos na API nova, a tela antiga
até sair), o D655 (o primeiro pedido da rede) e o D656 (a taxa por unidade na rede: o desenho a tirou só pelo custo da consulta,
que não existe mais -- a coluna `unidade` está no SELECT comum desde 13/10/2026 --, e a pergunta foi ao lote, "TELAS-RESTANTES
[novas] (28/10/2026) -- A taxa de resposta de cada unidade na rede do NPS (D656)"). O D-n8 e o D-n9 não foram registrados (o
`LinkDaFicha` da C4 e a coluna que já existe). De pé, de 168 para 177. Nada do esquema, do crontab, do OneSignal, da produção nem
do BVIP mudou; nenhuma rotina rodou.

## 29/10/2026 -- REL-NPS-TELA (a fatia 57 da fila das telas que faltam)

**O que mudou para quem usa.** O item **NPS** do menu de Relatórios abre a tela nova (a N2 do `docs/f3-relatorios/spec-nps.md`,
sobre a API da 56): o mês com os clientes que responderam, os envios (com as pesquisas gravadas), a taxa de resposta ("1,8% · 36
de 1.980 clientes atendidos", números inventados) e os clientes com alguma nota até 6, com o "ver quem"; as três notas -- recepção, experiência e
colaboradores --, cada uma com a média de 0 a 10, as respostas e as faixas ("0 a 6: 1 · 7 a 8: 3 · 9 a 10: 34"), e nunca um número
de −100 a 100; os 12 meses até o mês, com o "No período" como um período só; os colaboradores avaliados, com o "ver histórico" que
abre o histórico do Profissional Parceiro dentro da tela (só para quem abre o Parceiro) e volta com o foco no botão; na rede, a
linha de cada unidade, com as médias apagadas abaixo de 30 envios e a rede por último, com a taxa (nenhuma linha de unidade a
mostra: a pergunta 272 segue sem resposta); e a lista dos clientes que responderam, que abre sob pedido, com o filtro da faixa da
pior nota e do colaborador, o nome que abre a ficha do ERP em outra aba, o telefone e o e-mail (como a tela antiga), e o TXT dos
contatos do recorte e do filtro na tela. Com outro mês ou outra unidade no ar, nenhum botão anda com os números de antes. **A tela
antiga** (`PesquisasNpsController`) passou a aplicar a política nas quatro ações (a tela, os dois detalhes e o TXT): medido na
cópia, só contagens e sem as fixtures, de 366 ativos **viam 117 e veem 116** -- só a pessoa do grupo 1 que o desenho previa perde
a porta, que já não tinha o item do menu antigo desde a 56. Nenhum pedido novo ao servidor: a API é a da 56.

**Onde está.** `frontend/src/api/nps.ts`, `telas/Nps.tsx`, `telas/NpsLigado.tsx`, a regra pura `telas/listaDoNps.ts`, a fila `nps`
do `filaDaRede.ts` (só o mês da rede, a única ação que o servidor trava), as doze chaves do glossário (`ITENS_DO_NPS`; a
`nps.mesEmAndamento` é desta fatia, o desenho não a escreve), a linha `[CAMINHO_DO_NPS]` do Console (era a `TelaNaoMigrada`), o
CSS (o cartão da lista posicionado, os dois `<select>` na regra `:is(...)` dos campos), a prévia `Nps` (cinco células, nomes,
telefones e e-mails inventados, com as cargas de `src/testes/nps.ts`), o `index.ts` e o `componentSrcMap`. No servidor, o ajudante
`PesquisasNpsController::escopoDoNps` e as cinco vigias novas do `conferir-recorte-por-unidade.php` (de 347 para 352 métodos).
Onde o tipo do front e o desenho divergem, vale a resposta da N1 (anotado na N2 FEITA).

**O que rodou** (a fatia é ímpar: só os testes do que ela toca; **o ciclo inteiro fica para a 58**). Caracterização nas classes
tocadas e vizinhas (o `PesquisasNpsEscopoTest` novo, as do NPS da 56, a tela antiga, o menu, a família, a base, o recorte e as do
Parceiro), **458 casos em cada banco, verdes** (MySQL pelo `-como-ci`, PostgreSQL pelo `rodar-caracterizacao-postgres.sh`). Os
ensaios, depois de apagar as 7 travas de `/tmp` criadas como root (o D499): no MySQL de contrato, o `ensaiar-permissao-adm-api`
com saída 0; no PostgreSQL de contrato, o `ensaiar-permissao-adm-api` com saída 0 e o `medir-adm-api-no-postgres` com **saída 1 na
primeira rodada** -- "A MEDIÇÃO FICOU CEGA" no `GET /adm/api/vendas`, porque rodou sem o `ensaiar-fluxo-adm-api` antes, que é quem
põe a venda de hoje (o próprio medidor o diz no cabeçalho); com o fluxo antes, os dois com saída 0. O medidor dos relatórios no
PostgreSQL (o roteiro do rascunho), saída 0: a tela antiga do NPS com 200 no `index` e no `txt` para o `gerente.teste`. O front:
os 27 arquivos tocados e vizinhos, **541 casos** com `--maxWorkers=2` -- rodados ANTES da última edição do
`NpsLigado.teste.tsx` (o conserto do caso abaixo), e por isso rodados de novo na correção, abaixo --; os quatro novos com
`AMPLIFICAR_EFEITOS_MS=50`, 37 casos, verdes na segunda rodada (só ela ficou guardada; a primeira acusou um caso que conferia o
pedido dos 12 meses logo depois da tabela das unidades, sem esperar o efeito -- consertado no caso); `tsc -b`, `lint` (sem aviso nos arquivos novos) e `build`, com o JS comprimido de **326,27 KB para
332,76 KB (+6,49 KB)**. Os portões: o `conferir-recorte-por-unidade.php` e o `conferir-links-mortos.php` com saída 0, e o
`conferir-tudo.sh` inteiro no fim (66 portões ok, "Nenhuma falha inesperada", saída 0). O `contar-debitos` (de pé, 177) e o `conferir-debitos-repetidos`
(saída 0, depois de encurtar as marcas novas do D652 e do D653, que abriam quatro pares falsos).

**Os mutantes** (`medir-p57-mutantes.py` e a tabela `p57-mutantes-tabela.py`, no rascunho), um por vez, devolvidos byte a byte e
conferidos pelo sha1, os do servidor nos dois bancos, todos vermelhos pela asserção (nenhum por erro de tipo):

| Mutante | Vermelho |
|---|---|
| P57-P1 (N-T1) a média nula escrita "0,00" | `Nps.teste` |
| P57-P2 (N-T2) a tabela das unidades sem a condição | `Nps.teste` |
| P57-P3 (N-T3) o `suficiente` invertido | `Nps.teste` |
| P57-P4 (N-T4) os 12 meses com o mês do pedido | `NpsLigado.teste` (3) e `ConsoleAbreNps.teste` |
| P57-P5 (N-T5) a lista pedida com os números | `NpsLigado.teste` (6) |
| P57-P6 (N-T6) a faixa fora da busca | `NpsLigado.teste` (2) |
| P57-P7 (N-T7) o TXT sem os filtros | `NpsLigado.teste` |
| P57-P8 (N-T8) o "ver histórico" sem o `podeVerOHistorico` | `ConsoleAbreNps.teste` |
| P57-P9 (N-T9) o foco que não volta do histórico | `NpsLigado.teste` |
| P57-P10 (N-T10) o colaborador mantido no mês novo | `NpsLigado.teste` |
| P57-P11 (N-T11) o `txtAction` antigo sem o escopo | `PesquisasNpsEscopoTest` ("o TXT", 200 contra 404), e o portão do recorte |
| P57-P12 (N-T12) o layout pelo `getAcl` só | `RelatoriosNoMenuTest::testOItemDoLayoutEGuardadoPelaPolitica` |
| P57-P13 (N-T13) o item do menu sem o `quando` | `ConsoleAbreNps.teste` e `menu-do-erp.teste` |
| P57-X1 o mês da rede fora da fila `nps` | `NpsLigado.teste` (a fila) |
| P57-X2 a lista da rede na fila | `NpsLigado.teste` (a lista com outro mês da rede no ar) |
| P57-X3 o e-mail no TXT do gerente (a base) | `NpsDoRelatorioTest` (2) |
| P57-X4 o "ver histórico" sem o `podeAbrir` no Console | `ConsoleAbreNps.teste` |
| P57-X5 a taxa na linha de cada unidade (a 272 sem resposta) | `Nps.teste` |
| P57-X6, X7, X8 a tela, o detalhe da unidade e o do colaborador antigos sem o escopo | `PesquisasNpsEscopoTest`, cada um na asserção dele |
| P57-X6G, P11G, X9 a guarda arrancada da tela e do TXT antigos, e a política de outro relatório | `conferir-recorte-por-unidade` ("sem a guarda: 1"), verde antes |
| P57-X10 o nome do cliente sem o `LinkDaFicha` | `Nps.teste` (3) |
| P57-X11 a X14, X18 o "ver histórico", o "ver quem", o botão da lista, os `<select>` e o TXT ligados com outro mês no ar | `Nps.teste`, cada um na asserção dele |
| P57-X15 a lista de antes fora da tela no "relendo" | `NpsLigado.teste` |
| P57-X16 o TXT ligado sem telefone nenhum | `Nps.teste` |
| P57-X17 o foco do "ver quem" fora do título da lista | `NpsLigado.teste` |

**Débitos:** o D652 e o D653 continuam PARCIAIS, com a marca de hoje (a tela nova no menu; a antiga, agora só do gerente e do
administrador, até sair); nenhum novo; de pé, 177. **Para o navegador** (não aberto por esta fatia): a semente do desenho está
no rascunho (`p57-semente-navegador.sql`, fevereiro de 2026, ids 90006680 a 90006688, com o `DELETE` no fim e o esperado
escrito). Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; nenhuma rotina do NPS rodou.

**A verificação** (três lentes) confirmou 9 achados, um médio e oito baixos; a correção tratou oito e deixou um como está (o
código do servidor não mudou): (1) a coluna "Nota ao colaborador" seguia o filtro escolhido, e não a lista na tela -- no
"relendo", o rótulo novo ficava sobre as notas de antes (o "—" da lista sem filtro, ou as notas dadas a outro colaborador): agora
lê o eco `filtro.colaborador` da resposta, com um caso novo na vista; (2) a `nps.taxa` acrescentava "Taxa de resposta:" à frente
do texto do desenho, e o caso do glossário fora copiado do código: os dois com o texto do desenho, letra a letra (o negrito em
"Dos clientes atendidos no mês"); (3) a N2 FEITA contava "X1 a X18 (mais três de portão)", o que dava 34: são 33 (13 + 18 + o X6G e
o P11G; os três de portão são o X6G, o P11G e o X9) -- corrigido lá; (4, o médio) o caso da recusa "não pede nada depois" não
tinha como ficar vermelho: o dublê manda o pedido sem rota às `chamadas`, e o caso só lia o `foraDaFila` -- agora os 12 meses e a
lista têm rota, e o caso confere também que as `chamadas` ficam vazias; (5) nenhum caso punha os 12 meses da rede atrás de outro
mês da rede no ar: o caso da fila confere agora que os da primeira tela saem com o 2026-06 da segunda no ar; (6) o "limpar o
filtro" não tinha caso: um novo confere o pedido sem filtro, o foco no título da lista e o botão desligado com outro mês no ar;
(7) as cargas dos testes e da prévia (`src/testes/nps.ts`) repetiam o exemplo da forma da resposta do `spec-nps.md`, que são
contagens medidas na cópia (a unidade 1 com 42 envios, 40 de 2.235 atendidos, e os 14.532 da rede): agora são inventadas (38
envios, 36 de 1.980, 12.840 na rede), com as asserções que as liam; (8) os 541 casos rodaram antes da última edição do
`NpsLigado.teste.tsx`, só uma rodada amplificada ficou guardada e nenhuma saída do tsc, do lint e do build: tudo rodado de novo e
guardado no rascunho (`p57c-*.txt`), e o parágrafo de cima diz o que valia; (9) o e-mail na lista do gerente segue como está: é
a decisão 10 do dono ("como hoje") e o que a tela antiga mostra; a nota 3 da lente partia de uma premissa que os dois desmentem,
e nenhuma pergunta nova foi ao lote. **Os mutantes novos** (`p57c-mutantes.py`, no rascunho), cada um sozinho e devolvido pelo
sha1, todos vermelhos pela asserção: **P57-X19** (a coluna pelo filtro da tela) e **X24** a **X26** (o "limpar" ligado com outro
mês no ar, sem o foco no título, e com o filtro de antes) no `Nps.teste`; **X20** (o termo acrescentado à `nps.taxa`) no
`glossario.teste`; **X21** e **X22** (os 12 meses e a lista pedidos depois da recusa) e **X23** (os 12 meses da rede na fila
`nps`) no `NpsLigado.teste` -- os três sobreviviam aos casos de antes, como a verificação mostrou. **O que rodou de novo:** as 15
classes da fatia, **458 casos em cada banco, saída 0** (3.672 asserções; MySQL pelo `-como-ci`, PostgreSQL pelo
`rodar-caracterizacao-postgres.sh`); o front tocado e vizinho, os mesmos 27 arquivos, **543 casos, saída 0** (os 541 e os dois
novos); os quatro novos e o glossário com `AMPLIFICAR_EFEITOS_MS=50`, **52 casos, duas rodadas, saída 0 nas duas**; o tsc, o lint
(sem aviso nos arquivos do NPS) e o build, **332,75 KB** comprimidos (+6,48 KB sobre a 56); os portões que a correção toca
(o `conferir-referencias-docs` e o `conferir-segredos.sh`) e o `conferir-tudo.sh` no fim. Os ensaios e o medidor dos relatórios
não rodaram de novo: nada do servidor nem da API mudou.

**No navegador** (o orquestrador, contra o banco de contrato remontado e com a semente do rascunho `p57-semente-navegador.sql`,
remontado de novo no fim). O administrador: o item NPS abre a tela nova; **fevereiro de 2026** na Unidade Teste Centro com 3
clientes, 3 envios e 4 pesquisas gravadas, a recepção 8,00 (3 respostas; 1 / 0 / 2 nas faixas), a experiência 9,33 (3; 0 / 1 / 2),
os colaboradores 9,00 (4; 0 / 1 / 3), 1 cliente com alguma nota até 6, os 12 meses até fev/2026 com "sem pesquisa" nos onze de
antes, o Colaborador Teste com 9,00 -- igual ao contado à mão na semente e à API. O "ver quem" abre a lista filtrada em "alguma nota
até 6" com o foco no título dela; o "ver o histórico de Colaborador Teste" abre o histórico do Parceiro dentro da tela, e o "voltar à
lista" devolve o foco ao botão. A rede ("todas as unidades"): as unidades das que mais enviaram às que menos, a nota abaixo de 30
envios explicada, e a taxa de resposta só na linha "Toda a rede" (a 272 sem resposta); a lista mantém a faixa na troca de recorte,
como o desenho manda (só o colaborador volta a "Todos" quando não está no mês novo), e ganha a coluna Unidade. A 375 px nada passa
da largura fora das tabelas que rolam, no claro e no escuro. O gerente de teste, sem o privilégio do NPS: 403 na API e 404 na tela
antiga (`/mod-relatorios/rel-pesquisas-nps/index`), que dá 200 ao administrador; com o privilégio concedido só no banco de
contrato (a funcionalidade 90002033): a tela da unidade dele, sem a escolha da unidade, 422 para a rede e para outra, a tela antiga
200, a lista com os 3 clientes e o e-mail (a decisão 10, como a tela antiga), o aviso "1 cliente sem telefone fica fora do TXT", e o
TXT baixado com 2 linhas de 2 campos, sem e-mail -- o do administrador com 3 campos e o e-mail (os arquivos apagados depois de
contados). Nenhum defeito.

## 29/10/2026 -- RESPOSTAS-270-271 (a fatia 57b da fila das telas que faltam)

**O que mudou para quem usa.** Duas respostas do dono de 28/10/2026. **A 270 (b), nos Descontos:** os Itens com desconto, as
Vendas com desconto e os Clientes com desconto passam a mostrar a seta da comparação **sem a cor** de bom ou ruim, como o próprio
"Descontos concedidos" e as fatias -- as três contagens dizem quanto desconto se deu --; o **Faturamento das vendas com desconto**,
dinheiro que entrou, continua **verde ao subir**. Nenhum número muda. **A 271 (c), em Vendas Realizadas:** quem tem a marca de
gerente **não é mais tratado como caixa**, mesmo tendo a marca de caixa: escolhe o período e a situação, nas DUAS telas -- a nova
e a antiga, que tinha a mesma trava. O caixa que não é gerente continua só com as concluídas de hoje. O administrador com a marca
de caixa também deixa de ser travado, e isso **não é da letra da 271 (c)**, que só fala do gerente: vem da regra dos relatórios
que ela manda seguir ("como nos relatórios"), cujo passo 1 trata o administrador antes do caixa -- a mesma regra, chamada e não
copiada, o leva junto. Hoje não atinge ninguém (o único grupo de administrador já tem a marca de gerente, e nenhum tem a de
caixa). Com
isso o "ver as vendas" das Canceladas e dos Descontos leva o gerente com caixa ao mês inteiro que promete. **Quem muda de tela:**
medido na cópia (só contagens, sem as fixtures, o `p57b-quemve.sql` do rascunho), de 366 ativos, **ninguém** -- nenhum grupo real
tem a marca de caixa com a de gerente ou a de administrador; os 61 do grupo de caixa continuam travados. Só o `gerente.teste` das
fixtures tem as duas.

**Como.** A regra é a dos relatórios, e não uma cópia: o passo 4 do `RecorteDoRelatorio::escopo` virou o
`RecorteDoRelatorio::soDoCaixa` (o caixa sem a marca de gerente nem a de administrador), que o `escopo`, o
`Adm\VendasController::indexAction` (antes, `!empty(getUsuario('caixa'))`) e o `VendasController::indexVendasAction` da tela
antiga (antes, o `$usuario['caixa']` cru) chamam. A `RegraDaListagemDeVendas` não mudou: recebe o "só do caixa" de quem chama. No
front, só o `neutra` dos três cartões no `OsNumeros` do `Descontos.tsx` e os comentários de `VendasRealizadas.tsx` e
`api/vendas.ts`; o aviso "Quem é do caixa vê só as vendas concluídas de hoje" continua, e só aparece a quem é só do caixa. A prévia
`Descontos` acompanha (o comentário; a célula renderiza a tela). O glossário não fala da cor. Três roteiros HTTP diziam o
comportamento antigo do `gerente.teste` (travado em hoje) e passaram a dizer o novo: o passo 12b do `ensaiar-fluxo-adm-api.php`
(hoje, as ativas, sem a trava), o caso 9h do `ensaiar-recusas-adm-api.php` (o ano corrente pedido e aplicado -- era 2026 escrito,
e a trava escondia que o ano não mandava) e o comentário do `medir-adm-api-no-postgres.php` (a lista agora traz as vendas de 14/02
que ele pede). A primeira rodada do fluxo no banco de contrato, antes dessa troca, saiu 1 no 12b, como devia.

**Os testes.** Novos: o `D639GerenteComCaixaEmVendasRealizadasTest` (sete casos: a API e a tela antiga em processo; o gerente e
caixa e o administrador com caixa, com e sem a de gerente, recebem o período e a situação pedidos; o caixa sem gerente, hoje e as
ativas, com os três campos da tela antiga desligados; o relógio pelo `agora()`) e o `testSoDoCaixaEOCaixaSemGerenteNemAdministrador`
no `RegrasDoRecorteDoRelatorioTest`. No `Descontos.teste.tsx`, o caso "as outras variações têm a cor, como o desenho" deu lugar a
quatro, um por número. **A fatia conta como 57, ímpar: rodaram só os testes do que ela toca, e o ciclo inteiro fica para a 58.**
As 20 classes da fatia e as vizinhas (a listagem de vendas, o D428, a sessão, o recorte, e as dos relatórios que passam pelo
`escopo`, sem o D367 nem os Royalties que chamam a rotina das faturas), **634 casos em cada banco, saída 0** (5.143 asserções;
MySQL pelo `-como-ci`, PostgreSQL pelo `rodar-caracterizacao-postgres.sh`), rodadas de novo depois da última edição do teste novo.
Os ensaios nos dois bancos de contrato, depois de apagar as 15 travas de `/tmp` criadas como root (o D499): no MySQL o
`ensaiar-recusas-adm-api` e o `ensaiar-permissao-adm-api`, e depois o `ensaiar-fluxo-adm-api` e o `medir-adm-api-no-postgres`,
**saída 0 nos quatro**; no PostgreSQL os mesmos quatro, **saída 0** (455 s, 184 s, 40 s e 249 s). O front tocado e vizinho
(Descontos, Vendas Realizadas, Vendas Canceladas, os dois `ConsoleAbre*`, a lista dos descontos, o Console e o glossário), **305
casos em 10 arquivos, saída 0**, e os seis de efeito com `AMPLIFICAR_EFEITOS_MS=50`, **108 casos, saída 0**; o tsc e o lint sem
aviso nos arquivos tocados; o build com **332,76 KB** comprimidos (+0,01 KB sobre a 57). Os portões: o
`conferir-recorte-por-unidade.php` (352 métodos), o `conferir-links-mortos.php` (413 chamadas), o `conferir-segredos.sh` e o
`conferir-tudo.sh` inteiro no fim. O `conferir-relogio-nos-testes` ficou vermelho na primeira rodada do `conferir-tudo` (o teste novo
lia o relógio onze vezes); o teste passou ao `agora()`, e as duas suítes rodaram de novo.

**Os mutantes** (`p57b-mut-front.py` e `p57b-mut-srv.py`, no rascunho), cada um sozinho, devolvido e conferido pelo sha1, todos
vermelhos pela asserção, os do servidor nos dois bancos: **P57b-X1** a **X3** (os itens, as vendas e os clientes com desconto com
a cor de volta), cada um no seu caso do `Descontos.teste`; **X4** (o faturamento delas neutro) no caso dele; **X5** (a API com o
`getUsuario('caixa')` cru: o gerente com caixa tratado como caixa de novo) nos casos do gerente e caixa e do administrador com
caixa; **X6** (o `soDoCaixa` sem a marca de gerente) nos da API, da tela antiga e da regra; **X7** (sem a de administrador) nos
três do administrador; **X8** (a API com `false`: o caixa sem gerente liberado) no caso do caixa; **X9** (a tela antiga com o
`$usuario['caixa']` cru) nos dois casos dela; **X10** (a tela antiga sem a trava) no do caixa; **X11** (o `soDoCaixa` sem a marca
de caixa) em nove casos da regra. Os do servidor rodaram de novo contra a versão final do teste. Um mutante equivalente, dito: o
passo 4 do `escopo` de volta ao `!empty($usuario['caixa'])` não muda nada, porque o administrador e o gerente já saíram nos
passos 1 e 3.

**A verificação em três lentes confirmou 7 achados, todos baixos, os sete consertados.** Três comentários dos roteiros HTTP
ainda diziam o `gerente.teste` travado em hoje (dois no `medir-adm-api-no-postgres.php`, um no docblock das vendas de hoje do
`ensaiar-recusas-adm-api.php`; e, achado ao procurar, um no `ensaiar-permissao-adm-api.php`) -- só comentário, nenhum resultado
muda. O administrador com a marca de caixa aparecia como parte da resposta 271 (c), que só fala do gerente: o roadmap, o D639, o
`spec-comum.md` e o `f2-fatia-vendas.md` agora dizem que ele vai junto pela regra dos relatórios que a opção manda seguir ("como
nos relatórios", o passo 1), e não pela letra; hoje não atinge ninguém. A data da 270 (b) na prévia e a da 271 (c) no
`VendasRealizadas.tsx` eram a da fatia (29/10), e não a da resposta (28/10). Os quatro casos do `Descontos.teste` só viam a seta
que sobe: um quinto põe as três contagens e o faturamento delas em queda (as contagens neutras com a seta para baixo, o
faturamento com o `painel-desce`). Nada segurava que as duas telas chamam o `soDoCaixa` e não uma cópia: o
`testAAcaoDaListaChamaOSoDoCaixaENaoUmaCopia` do D639 (dois conjuntos, a API e a tela antiga) lê a ação sem os comentários e exige
uma chamada e nenhuma das três marcas lida à mão. O comentário do `D428NpsDaListaDeVendasTest` dizia que a lista lia o
`$usuario['caixa']` cru. E o desenho da Fase 2 (`docs/f2-fatia-vendas.md`, a regra 1 e o resumo) ganhou a linha da mudança. Os
mutantes novos, cada um sozinho e devolvido pelo sha1 (`p57bc-mut-front.py` e `p57bc-mut-srv.py`, no rascunho): **P57b-X12** (o
`Variacao` tirando a cor só na subida, `neutra && direcao !== 'desce'`) vermelho só no caso novo da descida -- os quatro de antes
ficaram verdes, como o achado dizia; **X13** e **X14** (a API e a tela antiga com a cópia da regra no lugar da chamada) e **X15**
(a chamada mais uma marca lida à mão) vermelhos no caso novo, cada um no seu conjunto, nos dois bancos. Depois do conserto, as 20
classes da fatia e vizinhas, **636 casos em cada banco, saída 0**; o front tocado e vizinho, **306 casos em 10 arquivos, saída 0**,
e os dois dos Descontos com `AMPLIFICAR_EFEITOS_MS=50`, **43 casos, saída 0**; o tsc, o lint e o build com **332,76 KB**; o
`conferir-recorte-por-unidade.php` (352 métodos), o `conferir-links-mortos.php` (413 chamadas), o `conferir-segredos.sh` e o
`conferir-tudo.sh` inteiro, **saída 0, os 66 ok**; de pé continua 175, e o `conferir-debitos-repetidos` saiu 0. Os ensaios HTTP
não rodaram de novo: nos roteiros só mudou comentário (o `php -l` dos três saiu limpo).

**Para o navegador** (não abri): os Descontos de março de 2026 com as setas (as três contagens sem cor, o faturamento delas
verde ou vermelho); e Vendas Realizadas como o `gerente.teste` (as duas marcas) pelo "ver as vendas" das Canceladas de fevereiro
de 2026 -- a lista das canceladas do mês inteiro, sem o aviso do caixa, com o período e a situação liberados.

**No navegador** (o orquestrador, contra o banco de contrato remontado antes e depois). Os Descontos de março de 2026, como o
administrador: o "Descontos concedidos" e as fatias sem cor, como antes; os itens com desconto (↑ +200,0%), as vendas com
desconto (↑ +100,0%) e os clientes com desconto (↑ +100,0%) agora sem a cor de bom e ruim; o faturamento das vendas com desconto
(↓ −34,1%) com a cor, vermelho na descida -- a 270 (b). Como o `gerente.teste`, que tem as duas marcas, com o privilégio das
Canceladas concedido só no banco de contrato: o "ver as vendas" das Canceladas de fevereiro de 2026 abre Vendas Realizadas com "só
as vendas da unidade Unidade Teste Centro" e a 90001211 cancelada (R$ 80,00), sem o aviso do caixa -- a 271 (c). Nenhum defeito.

**Débitos:** D633 e D639 CORRIGIDOS, com a resposta escrita no corpo; de pé 177 para 175. Nada do esquema, do crontab, do
OneSignal, da produção nem do BVIP mudou.

## 29/10/2026 -- REL-INDICACAO-CONTAS (a fatia 58 da fila das telas que faltam)

**Para quem usa, nada muda ainda:** é a I0 do `docs/f3-relatorios/spec-indicacao.md`, as regras, as contas, as consultas e a forma
das duas respostas do Programa de Indicação, sem rota, sem menu e sem tela (a política `indicacao` continua com `naTelaNova =>
false`). A I1 (a fatia 59) liga `GET /adm/api/relatorios/indicacao` e `.../clientes` e a tela; a I3 (a fatia 75) grava a porta.

**As três portas como o banco as grava hoje**, com a 53 (c) no código desde 10/10/2026: o **aplicativo** são as linhas de
`indicacoes` -- e, até a I3, também o amigo do formulário de quem indica ativo, que ganhou a mesma linha e a ligação: uma linha só,
contada como "aplicativo" (o "enquanto isso" do item 3 do bloco 41b; o "Como se conta" da 59 o dirá); o **formulário** é o amigo
com a marca "Indicação - N" legível e SEM a ligação (os de antes da 53 (c) e os de quem indica inativo, a 67 (c)); a **origem** é
o cadastro "Indicação", só nos clientes novos. A coluna `indicacoes.porta`, o passado dos 183 e a linha do amigo de quem indica
inativo (a 264 (b) e a 265 (b)) ficaram para a 75. Como as respostas do dono aparecem nas contas: a **53 (c)**, a ligação do
formulário -- o amigo com ela sai do formulário (`linhasDoFormulario`, o F6 do cenário e o IND-M39) e a comissão é das duas
portas; a **63 (a)**, os 10 pontos do envio da etapa 6 -- não são pontos da indicação, e o prefixo os deixa fora (o (c) do
cenário); a **65 (b)** e a **145 (c)**, os 10 pontos por compra só do amigo sem a ligação e só a quem está ativo na hora da venda
-- o relatório conta o que foi lançado (`programaPontosDasVendas`), e os mutantes X3 e X4 no PDV ficam vermelhos no
`D304EtapaSeisDaAdesaoTest`; a **67 (c)**, o amigo de quem indica inativo nasce sem a ligação -- conta no formulário; a **68 (c)**, o
cancelamento guarda a comissão -- a parcela "na venda cancelada" (o X6, o cancelamento zerando a comissão, fica vermelho no D304);
a **92 (b)**, os pontos na unidade da venda -- os pontos são os das vendas da base da unidade.

**Os arquivos:** `ISCadastro\Regra\MarcaDaIndicacao` (a marca, o mesmo corpo do `FidelidadeService::extrairIdClienteIndicadorOrigem`,
que não mudou), `ISRelatorio\Regra\ContasDoProgramaDeIndicacao` (pura), `ISRelatorio\Api\ProgramaDeIndicacaoJson` e, a mais do
desenho, `ISRelatorio\Service\ProgramaDeIndicacao` (a composição que a I1 chama depois das guardas); as consultas
`IndicacaoRepository::programaLinhas`, `ClienteRepository::programaFormulario`, `::programaContatos`, `::programaMarcas` e
`PontuacaoHistoricoRepository::programaPontosDasVendas`; o cenário `CenarioDaIndicacao` e os testes `RegrasDoProgramaDeIndicacaoTest`
(sem banco) e `ConsultasDaIndicacaoTest` (nos dois bancos). O que divergiu do desenho está na nota FEITA da I0 (o serviço, a forma
do JSON pelo recorte, o cenário com o F5 -- a rede tem 7 convites, e não 6 --, o literal no `indicarAmigo`, o sentinela também no
formulário, os portões).

**O oráculo** (`medir-p58-oraculo.py`, no rascunho): em Python, só SELECT na cópia (`franquiabv_dev`, MySQL e PostgreSQL), contra o
JSON do serviço em processo, julho e junho de 2026, a rede, a unidade 1 e as unidades 10, 25, 38 e 90001000: **2.010 conferências,
0 divergências**, nome e telefone comparados pelo sha1. Os números da 1.5 do desenho saem iguais: julho de 2026, 12 convites na
unidade 1 (os 12 do formulário) e 33 na rede (3 do aplicativo, 30 do formulário), todos aguardando; 57 novos na unidade 1, 1 por
indicação (a origem), 1,8%; na rede, 1.403 novos, 218 por indicação (1 do formulário, 217 da origem). **A 53 (c) não mudou os números
da cópia**: ela está congelada desde antes de 10/10/2026 (0 amigos com a marca e a ligação, medido de novo nesta fatia); o que ela
mudou é a regra, provada pelo F6. **A ressalva, achada na verificação:** em junho e julho de 2026 toda comissão, as parcelas e a
situação "estreou" saíam zero dos dois lados -- as linhas com comissão da cópia são de janeiro e fevereiro de 2026, nas
unidades 90001000 e 90001150, que o corte `[:4]` deixava fora. O oráculo rodou também nesses dois meses, a rede e as duas
unidades (`medir-p58c-oraculo.py`): **432 conferências, 0 divergências**, com R$ 15,50 em 1 venda (janeiro, 90001000) e R$ 22,50
em 1 venda (fevereiro, 90001150), as duas na parcela "sem a venda registrada" (nenhuma linha da cópia guarda a primeira venda), e
as situações "estreou" e "aderiu" comparadas. As parcelas "na estreia", "sem estreia" e "na venda cancelada" continuam zero na
cópia e só o cenário contado à mão as prova. Ao todo, 2.442 conferências, 0 divergências.

**A medida** (em processo, julho de 2026, `memory_limit = 128M`, três rodadas frias e três quentes): os números do mês de 0,6 a 2,0 s
frios e até 1,0 s quentes; os clientes da unidade 1 de 0,6 a 3,6 s frios; **os clientes da rede de 19,4 a 20,2 s no MySQL e 1,4 a
2,2 s no PostgreSQL**, pelo primeiro serviço dos candidatos da rede (a peça comum) e pelo buffer pool de 128 MB do MySQL da cópia --
o D660, com a matriz inteira; a I1 põe a trava da rede. **Achado na medida e consertado:** a primeira versão caía com 128 MB no cache
do Doctrine (o D646) na rede; agora os itens vão em quatro partes, reduzidos linha a linha, e o pico é de 45,0 MB em julho e 52,6 MB
em dezembro de 2025.

**Os mutantes** (`p58-mut.py`, no rascunho), cada um sozinho, devolvido pelo sha1, nos dois bancos, todos vermelhos por asserção:
P58-P1 a P58-P15 (os IND-M1 a IND-M14 e o IND-M39, do desenho: a marca sem acento, a porta pela origem antes da linha, a faixa por
`<=`, o convite pela adesão, a comissão pelo convite, as duas consultas sem o filtro da unidade -- estas e o P8, o sentinela, também
no `conferir-recorte-por-unidade.php`, saída 1 contra saída 0 antes --, o sentinela, a situação pela adesão antes da estreia, a
estreia só dentro do mês, os pontos sem a conferência do prefixo, a cancelada como estreia, a comissão nula que anula a soma, a ordem crescente e o
formulário sem o filtro da ligação); e P58-X1 a X18 sem o X12: a consulta do formulário sem a coluna da ligação (o F6 duas vezes), a
linha pela unidade do cadastro de quem convidou, os 10 pontos para o amigo com a ligação (no PDV), o `premiaQuemIndica` sem o
"ativo", a faixa do convite fechada no fim (no banco) e a da comissão (na conta), o cancelamento zerando a comissão (no
`VendaService`), o formulário sem o sentinela, o indicador do aplicativo pela marca, a adesão do formulário sem a data, o fim do
recorte sem o dia a mais, as vendas dos pontos sem distinção, só uma metade do mês, o desempate pelo convidado crescente, o literal da
marca no `indicarAmigo` e o crédito fora da estreia, mais o X17 (a venda da base só dos serviços). **Todos rodaram de novo contra a versão final**, depois dos dois
consertos abaixo. Mais o X18 (o prefixo da consulta dos pontos trocado por `%`), vermelho no caso da consulta crua -- a conta não
muda, porque a regra é a conferência em PHP (o P11), e por isso só esse caso o pega. Equivalente, dito: o X12 (os primeiros de cada
parte sem o recorte dos novos; saiu antes de rodar). São 33 mutantes, 66 rodadas, todas vermelhas.

**Achado no `conferir-tudo.sh` e consertado:** o portão `busca por tags` (a regra do D120) saiu 1 com os dois `LIKE` novos sem
`LOWER()` -- no MySQL o `LIKE` ignora caixa e acento, no PostgreSQL não. Agora os dois levam `LOWER()` dos dois lados com o termo
pelo `BuscaPorTags::normalizar`, e a regra fica em PHP nos dois bancos: a marca pela `indicadorDoFormulario` (como já era) e o
prefixo dos pontos letra por letra na `ContasDoProgramaDeIndicacao::pontos` (a consulta passou a devolver a descrição). Um caso
novo em cada nível prova que a frase em caixa alta ou sem o acento não conta.

**O que rodou (a fatia é par: o ciclo inteiro é do orquestrador, depois desta entrada):** as 20 classes da fatia e vizinhas (as da
Indicação, da Fidelidade, o D284, o D304, o D312, o `NovosDoRecorteTest`, o `RelatorioAdmControllerTest`, o
`PortoesDaFamiliaDosRelatoriosTest`, o `RegrasDoRecorteDoRelatorioTest`, o `ItensDoRelatorioTest`, o `RelatoriosNoMenuTest` e o
`ColunasDoPdvTest`), **406 casos em cada banco, saída 0** (37 deles nas duas classes novas), rodados de novo depois do último conserto; o
`conferir-recorte-por-unidade.php` (355 métodos), o `conferir-injecao-dql.php` (22), o `conferir-cobertura-por-par.php` (404), o
`auditar-group-by.php`, o `conferir-booleanos-em-consulta.php`, o `conferir-ordenacao-nulos.php`, o `conferir-links-mortos.php`, o
`conferir-busca-por-tags.php` e o `conferir-segredos.sh`, saída 0; o `conferir-tudo.sh` inteiro, **saída 0, os 66 ok**; o oráculo
rodado de novo contra a versão final (as mesmas 2.010 conferências, 0 divergências). Sem rota nova,
os ensaios HTTP da família não tinham o que medir de novo, e não rodaram; o medidor dos relatórios no PostgreSQL (`p48-medir-relatorios-pg.sh`) saiu 0; o front não mudou.

**A verificação em três lentes** confirmou 9 achados (8 distintos), todos tratados: a pergunta do D657 (o IND-D3, que o desenho
dá ao dono) não tinha ido ao lote -- foi, "TELAS-RESTANTES [marketing] (29/10/2026) -- Programa de Indicação: quem o caixa premia
pela origem do cliente (D657)", com a recomendação (b); o D659 dizia "sem pergunta" sem apontar a seção 3 da fila, que a prevê; a
nota FEITA e a seção 4 do desenho não davam os números D657 a D660 aos IND-D (dão, e o começo da I1 aponta o D660, o D661 e o
D657); o comentário do `conferir-cobertura-por-par.php` dizia "duas metades" (são quatro partes; a contagem não muda); **a
adesão do mês de um convite de outro mês não tinha caso** -- tirar o `OR` da `dataAdesao` do `programaLinhas` sobrevivia nos dois
bancos, e também o `>=` virado `>` na adesão, na venda e na criação do amigo do formulário, e o `<` virado `<=` nas três (medidos
verdes antes do caso, nos dois bancos): o `testAAdesaoEAVendaDoMesTrazemALinhaDeOutroMesEAsFronteirasNoBanco` os prende; **a
unidade pelo cadastro do convidado** (`cConv.unidade`) sobrevivia porque o caso da linha que mudou de unidade tinha o convidado
na U: agora ele é da F, com quem convidou; o oráculo nunca tinha comparado comissão diferente de zero (a ressalva acima); e a
lista do formulário traz o nome e o telefone de quem a marca cita, de qualquer unidade -- não abre dado novo, e ficou no D661 com
o caso `testOContatoDeQuemIndicouVemDeForaDoRecorte`, que o prende para a I1. **Os mutantes da verificação**, P58-X19 a X26 (o `OR`
da adesão arrancado, o `>` na adesão, na venda e no amigo, o `cConv.unidade`, o `<=` no amigo, na adesão e na venda), cada um
sozinho, devolvido pelo sha1, verdes nos dois bancos antes dos casos e vermelhos por asserção nos dois depois (`p58c-mut.py`, no
rascunho). Depois dos consertos, as 20 classes da fatia e vizinhas (o filtro
`Indicac|Fidelidade|D284|D304|D312|NovosDoRecorteTest|RelatorioAdmControllerTest|PortoesDaFamiliaDosRelatoriosTest|RegrasDoRecorteDoRelatorioTest|ItensDoRelatorioTest|RelatoriosNoMenuTest|ColunasDoPdvTest`)
deram **407 casos em cada banco, saída 0** (o `ConsultasDaIndicacaoTest` de 12 para 14 casos); o `conferir-cobertura-por-par.php`, o `conferir-debitos-repetidos.php` (com
o par D659|D661) e o `conferir-segredos.sh` saíram 0, e o `conferir-tudo.sh` de novo, **saída 0, os 66 ok**.

**O ciclo inteiro** (a fatia é par; rodado por mim depois desta entrada, em série, depois de apagar as travas de `/tmp` criadas como root, o D499): caracterização **5.518 em cada banco** (42.100 e 42.096 asserções, 11 pulados), saída 0; contrato 74 de 74 nos dois; os 22 ensaios em cada banco com saída 0; os 66 portões ok; front 3.616 casos em 142 arquivos, tsc, lint e build. O medidor dos relatórios no PostgreSQL (`p48-medir-relatorios-pg.sh`) de novo, saída 0, com o `local.php` restaurado e conferido.

**Débitos:** D657 (as duas leituras da marca, o IND-D3), D658 (o convite que muda de unidade, o IND-D4), D659 (as regras que o
relatório mostra, o IND-D1, com os itens 2 a 4 já decididos), D660 (o tempo da rede) e D661 (o contato de quem indicou de fora do
recorte, da verificação) REGISTRADOS; o IND-D2 já era o D613 e o D615. De pé 175 para 180. Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; nenhuma rotina rodou.

## 29/10/2026 -- Quanto falta e o ritmo (refeitos no Artifact do roadmap, depois da fatia 57b)

Pela mesma régua de 16, 21 e 26/10 (linhas de código com os testes, sem documentos, dumps, carga, fixtures, lockfiles nem o
inventário gerado, até o fim do Corte): **cerca de 68% feito, entre 65% e 71%** (era 62% em 26/10). Feito, ~554 mil linhas; falta
~225 a 300 mil: a fila, 38 blocos, ~95 a 120 mil (pela razão real sobre o previsto de 1,24, a das 16 fatias desde 26/10, e de
1,63, a de todas desde 14/10); os débitos de pé fora da fila, 169 dos 180, ~120 a 160 mil; o preparo do corte, ~10 a 20 mil. No
menu, 33 dos 51 destinos abrem a tela nova; nos débitos, 479 dos 659 não estão mais de pé.

**O ritmo**, de 26/10 às 21h06 a 29/10 às 05h28 (56,4 h de relógio, com as pausas): 16 fatias (da 43 à 57b), mediana de 3,5 h
entre uma e a seguinte, ~2.800 linhas por fatia e ~790 linhas por hora (eram 4,5 h, ~2.700 e ~555 em 26/10). **Quanto tempo:** de 2
a 3 semanas nesse ritmo (perto de 12 a 19/11), de 4 a 6 com pausas maiores; ~325 a 445 h. Fora da conta, as janelas do corte na
produção e o texto das campanhas (92g), que dependem do dono.

## 29/10/2026 -- RESPOSTAS-272-273 (a fatia 58b da fila das telas que faltam)

**As respostas 272 (b) e 273 (b) do dono, de 29/10/2026** (o D656 e o D657). **Para quem usa:** na rede do NPS (o administrador
com "todas"), a linha de cada unidade mostra a taxa de resposta dela embaixo do nome, no formato da linha "Toda a rede" ("1,8% ·
40 de 2.235 clientes atendidos"); a unidade com menos de 30 envios a mostra apagada, como as médias, com o porquê no `title`; a
unidade com resposta e sem atendido no mês mostra o travessão, como a rede sem atendido. No caixa, pela medida da pergunta
ninguém muda hoje (as 183 marcas "… - …" da cópia eram "Indicação - N"; a recontagem com a regra exata não rodou): os 10 pontos por compra do amigo sem a ligação passam a ler quem
indicou só pela marca "Indicação - N" -- "Promoção - N" e "Indicacao - N" sem o acento deixam de dar pontos, "Indicação-N" sem os
espaços passa a dar.

**A 272 (b), no servidor:** `ContasDoNps::atendidosPorUnidade` (o atendido pela unidade de quem fechou a venda, a coluna `unidade`
do SELECT comum, e não a do cadastro do colaborador) e `ContasDoNps::taxasPorUnidade` (a `taxaDeResposta` da rede, com a
interseção, unidade por unidade; o respondente pela `nps.unidade`); o `NpsController::montarOMes` tira os atendidos de cada
unidade da MESMA leitura dos itens que dá a taxa da rede (as duas metades do D655), e o `NpsJson::unidades` põe a
`taxaDeResposta` em cada linha. A lista das linhas não mudou (as ativas e as com resposta; a ativa sem resposta sai apagada, com "0,0%"
se atendeu alguém no mês e com o travessão se não atendeu ninguém). **Na tela:** o `UnidadeDoNps` com a `taxaDeResposta`, o `TaxaNaLinha` com o `apagada`, a nota da tabela, a `nps.taxa`
(cada unidade com a sua, e qual unidade cada lado usa) e a `nps.minimo` (as médias e a taxa); a prévia Nps lê as cargas dos testes
(`src/testes/nps.ts`), com a taxa inventada de cada unidade.

**A 273 (b):** o `VendaPagamentoService::salvarPontuacao` chama a `MarcaDaIndicacao::indicadorDoFormulario` no lugar do
`explode(' - ')` com `is_numeric`; o resto do parágrafo não mudou (a ligação, o `premiaQuemIndica` na hora da venda, a unidade da
venda, o histórico). O `FidelidadeService::extrairIdClienteIndicadorOrigem` delega à marca (o item da I3, adiantado; o
`RegrasDoProgramaDeIndicacaoTest` que confere as duas leituras continua). Os desenhos: a E4 do `spec-nps.md` e o IND-D3 do
`spec-indicacao.md` com as respostas; a I1 e a I3 apontam o que mudou.

**O DOCKER DESKTOP ESTAVA FORA durante toda a fatia** (o `docker ps` sem o daemon desde o primeiro comando, às ~19h40, e ainda às
20h19; nenhum agente o reinicia). Por isso **NÃO rodaram**: a suíte nos dois bancos (os casos novos `RegrasDoNpsTest` x2,
`NpsDoRelatorioTest::testATaxaDeCadaUnidadeNaRede`, `D304EtapaSeisDaAdesaoTest::testOCaixaLeQuemIndicouSoPelaMarcaDaIndicacao` x4 e
`RegrasDoProgramaDeIndicacaoTest::testOCaixaEOCadastroLeemAMarcaSoPelaMarcaDaIndicacao`, com as duas marcas novas do provedor), os
mutantes do servidor (P58b-X2, X3, X4, X7, X10 a X16, prontos em `medir-p58b-mut-srv.py`), o oráculo da taxa de cada unidade na
cópia (julho e junho de 2026, `medir-p58b-oraculo.py`), a contagem das origens que mudam (`medir-p58b-273.py`) e o medidor da API
do NPS. As partes puras rodaram com o PHP do Windows (`medir-p58b-puro.php`: 14 conferências, as contas por unidade, a resposta,
as marcas e a delegação do cadastro), sem o PHPUnit. Os portões que precisam do contêiner (12 do `conferir-tudo.sh`) saíram
FALHOU pelo Docker; os outros 53 ok, e o `conferir-tudo.sh` foi cortado no "testes do front" pelo meu teto de 590 s.

**O que rodou:** o front tocado e vizinho (o `Nps.teste.tsx`, o `NpsLigado`, o `ConsoleAbreNps`, o `listaDoNps` e o
`glossario.teste.tsx`: 53 casos em 5 arquivos, com `--maxWorkers=2`), `npm run build` (o `tsc -b` e o JS comprimido com 332,86 KB,
+0,10 KB sobre os 332,76 KB da 57b), o lint (saída 0, os avisos de antes) e o `tsc -p tsconfig.ds.json`; o
`conferir-recorte-por-unidade.php` (355 métodos), o `conferir-links-mortos.php` (413 chamadas), o `conferir-segredos.sh`, o
`contar-debitos.php` e o `conferir-debitos-repetidos.php`, todos com saída 0. **Os mutantes da tela** (`medir-p58b-mutantes-front.py`),
cada um sozinho, devolvido e conferido pelo sha1, vermelhos pela asserção: **P58b-X1** (a taxa da unidade = a da rede repetida),
**X5** (abaixo de 30 envios, a taxa acesa), **X6** (a taxa sumida da linha da unidade), **X8** (a linha da rede sem a taxa) e
**X9** (a taxa apagada sem o porquê). A fatia conta como a 58, par: **o ciclo inteiro é do orquestrador, depois desta entrada.**

**Débitos:** D656 e D657 PARCIAIS (o código e os testes escritos; falta a prova nos bancos e na cópia, o corpo diz o quê); o par
D234|D657 saiu dos conhecidos do `conferir-debitos-repetidos.php`, que o pedia com o título novo. De pé: 180. Nada do esquema, do
crontab, do OneSignal, da produção nem do BVIP mudou; nenhuma rotina rodou, e o `salvarPontuacao` não rodou contra banco nenhum.

**A correção depois da verificação (29/10/2026, o Docker ainda fora às 20h37 e às 20h45).** A verificação em três lentes
confirmou 11 achados (7 distintos), todos tratados. **(1)** O `spec-nps.md` (a E4 e o D-n9) e o `spec-indicacao.md` (o IND-D3,
a nota 12 e a I1) davam o D656 e o D657 como CORRIGIDOS ou fechados: dizem PARCIAL, feito no código e sem a prova nos bancos.
**(2)** O cabeçalho do `frontend/src/api/nps.ts` ainda dizia que a linha da unidade não tem taxa. **(3)** Uma linha da rede sem
a taxa calculada saía com o travessão "nenhum cliente atendido no mês", frase falsa para quem atendeu: o `NpsJson::mes` exige
a `taxasPorUnidade` (nula fora da rede) e a linha sem a dela lança `LogicException`; o `NpsController` passa nulo fora da
rede, e o caso novo no `RegrasDoNpsTest` cobra as duas faltas (tudo e uma linha). **(4)** O desenho, o D656 e esta entrada
diziam "0,0%" para toda ativa sem resposta; sem atendido é o travessão. **(5)** A chave `taxaDeResposta` arrancada só
derrubava os dois casos pelo `ValueError` do `array_combine`: os dois contam as linhas com a chave antes. **(6)** O docblock
do `literais` voltou para cima dele no `RegrasDoProgramaDeIndicacaoTest`. **(7)** O comentário do caixa dizia "a troca não muda
ninguém hoje" sem a recontagem com a regra exata do `explode`: diz o que a pergunta mediu e que a recontagem falta. **O que
rodou, sem o contêiner:** o `php -l` dos seis PHP tocados; o `RegrasDoNpsTest` (22 de 22, com `date.timezone=America/Sao_Paulo`,
o do `docker/php/php.ini`) e o `RegrasDoProgramaDeIndicacaoTest` (28 de 28) com o PHP 8.2 do Windows e um dublê mínimo do
PHPUnit (`medir-p58b-dubl-phpunit.php` no rascunho; não substitui a suíte); os mutantes que essas duas classes pegam, um por
vez, devolvidos pelo sha1, vermelhos pela asserção: P58b-X3, X4, X7 (refeito sobre o código novo), X16, os novos **X17** (a
linha sem a taxa de volta ao travessão) e **X18** (a chave arrancada: agora pelo `assertCount`, e não pelo `ValueError`), X10,
X11, X12 e X15; o front tocado (53 de 53 em 5 arquivos, `--maxWorkers=2`), o build (332,86 KB comprimido, o mesmo), o lint
(saída 0), o `conferir-recorte-por-unidade.php` (355 métodos), o `conferir-links-mortos.php` (413 chamadas), o
`conferir-referencias-docs.php`, o `conferir-segredos.sh`, o `contar-debitos.php` (180 de pé) e o
`conferir-debitos-repetidos.php`, todos com saída 0; o `conferir-tudo.sh` inteiro, desta vez até o fim, com saída 1: 54 ok
(os "testes do front" entre eles) e os mesmos 12 FALHOU, todos os que precisam do contêiner ou do banco (conferido sozinho o
`conferir-relacoes-doctrine.php`: a conexão ao MySQL recusada). **Continua sem rodar:** a suíte nos dois bancos, os mutantes integrados
X2, X13 e X14 (e os outros nos dois bancos), o oráculo na cópia, a recontagem das origens e o medidor da API do NPS; o D656 e
o D657 seguem PARCIAIS.

**A prova, com o Docker de volta (29 e 30/10/2026, rodada por mim depois da entrada).** O Windows tinha reiniciado às 17h19 e
o Docker Desktop não voltou sozinho; o dono o abriu, e os contêineres subiram sem que nenhum fosse recriado. **A suíte das
classes tocadas e vizinhas** (o filtro `RegrasDoNps|NpsDoRelatorio|PesquisasNps|D304|D312|RegrasDoProgramaDeIndicacao|ConsultasDaIndicacao|Fidelidade|PortoesDaFamiliaDosRelatoriosTest`),
**256 casos em cada banco, saída 0**. **Os mutantes do servidor** (`medir-p58b-mut-srv.py`), P58b-X2, X3, X4, X7 e X10 a X18 (os
da tela, X1, X5, X6, X8 e X9, já tinham rodado), cada um sozinho, devolvido pelo sha1 e com o `local.php` conferido
depois de cada um: **todos vermelhos pela asserção nos dois bancos** (o X2, a taxa da rede repetida em cada linha, pela A
com 3 de 5; o X12, "Indicação-N" recusado, pelos dois casos que o leem; o X13 e o X14 pelos casos que já existiam). **O
oráculo** (`medir-p58b-oraculo.py`, só SELECT na cópia, contra o JSON do controller em processo): julho e junho de 2026 na
rede, nos dois bancos, **440 conferências, 0 divergências**; julho com a unidade 1 em 1,8% (40 de 2.235), a 29 em 13,7% e a
39 em 13,2%, e as abaixo de 30 envios apagadas; a rede com as mesmas 5 consultas e pico de 74,1 MB (era 73,6 MB). **A
recontagem da 273** (`medir-p58b-273.py`, só contagem, nos dois bancos): das 217.540 origens preenchidas, o caixa lia quem
indicou em 183 e lê agora nas mesmas 183; **nenhuma muda de resultado**. **No navegador**, contra o banco de contrato com a
semente da 57: a rede de fevereiro de 2026 com "taxa de resposta 0,0% · 0 de 2 clientes atendidos" embaixo da Unidade Teste
Centro (os 3 que responderam na semente não estão entre os 2 atendidos do contrato, e a linha da rede diz o mesmo), apagada
com o porquê no `title` e a mesma cor das médias apagadas; as quatro unidades sem atendido com o travessão; a linha "Toda a
rede" acesa; 375 px sem rolagem de lado e os dois temas. Nenhum defeito.

**O ciclo inteiro (a 58b conta como a 58, par) achou um vermelho, igual nos dois bancos, e consertado:** o
`D284PontosNoCancelamentoTest::testQuemIndicouPerdeOsPontosDaVendaCancelada` gravava a origem "Indicacao - N" SEM o acento,
porque o caixa antigo só lia o número depois do " - "; pela 273 (b) ela não dá mais os pontos (o caso esperava 10 e veio 0).
O caso passou a gravar a marca, "Indicação - N", e o comentário diz por quê; os outros testes com a origem sem o acento são os
casos negativos de propósito. O D284 não estava no filtro da fatia: só o ciclo inteiro o pegou. O resto do ciclo: contrato 74
de 74 nos dois; os 22 ensaios em cada banco com saída 0; os 66 portões ok; front 3.617 casos em 142 arquivos, tsc, lint e build.
Depois do conserto: o D284 e as classes da fatia, 138 casos em cada banco, saída 0, e **a caracterização inteira de novo, 5.528
em cada banco, saída 0** (42.140 e 42.136 asserções; 12 pulados em cada um -- no MySQL um a mais que no ciclo, numa rodada que
começou depois da meia-noite, e não no D284, que agora vai até a última asserção). O medidor dos relatórios no PostgreSQL
(`p48-medir-relatorios-pg.sh`) com saída 0 e o `local.php` restaurado.

**Débitos:** D656 e D657 CORRIGIDOS, com a prova escrita no corpo; de pé 180 para 178.

## 30/10/2026 -- REL-INDICACAO-TELA (a fatia 59 da fila das telas que faltam)

**O que mudou para quem usa.** O item "Programa de Indicação" do menu abre a tela nova (era a tela não migrada): o mês do painel,
com os cinco números -- os convites (do aplicativo e do formulário), as adesões pelo aplicativo, os clientes novos por indicação
("X de N" dos novos do painel, por porta), a comissão creditada com as parcelas fora da estreia (sem serviço, na venda hoje
cancelada, sem a venda registrada) e os pontos da indicação --, a lista dos clientes novos por indicação (a estreia, o cliente
com o link da ficha, a porta e quem indicou), e a lista dos convites do mês com a situação de hoje de cada um, o nome e o
telefone dos dois (a pergunta 10, "como hoje"), o link da ficha e da venda, e os filtros de porta, de situação e uma busca por
nome ou telefone. O administrador escolhe a unidade ou a rede. **A unidade de cada convite passou a ser a da última compra de
quem convidou antes do convite** (ou a do cadastro dele, se nunca tinha comprado) -- o ponto I2 do dono --, e não mais a gravada
na linha, que muda na primeira compra do convidado; a comissão e a adesão continuam na unidade gravada, que é a da venda. Nada
escreve: a tela só lê. A porta do convite gravada no banco (`indicacoes.porta`) continua da fatia 75: até lá, o amigo do
formulário com a ligação conta como "aplicativo", e o "Como os números são contados" o diz.

**Quem vê.** O gerente e o caixa (a resposta 3 do dono: o caixa da Indicação "fica como está") a unidade da sessão; o
administrador a rede ou uma unidade ativa; o colaborador com o privilégio, 403 `papel`; o 0, a inativa e a inexistente, 422. A
tela antiga (`IndicacoesController`) e o item do menu antigo (`layout.phtml`, pelo `veOMenu`) aplicam a mesma política. **Medido
na cópia, só contagem e sem as fixtures: de 366 ativos, viam 109 e veem 109** -- ninguém com o privilégio é colaborador sem a
marca de gerente, caixa ou administrador (1 caixa que não é gerente continua vendo).

**No servidor.** O `Adm\ProgramaDeIndicacaoController` (`GET /adm/api/relatorios/indicacao`, `.../clientes` e `.../unidades`)
chama o `Service\ProgramaDeIndicacao` da 58 depois das guardas da família, com UMA trava por pessoa (`indicacaoDaRede-<id>`, o
429; o D660) nos números de qualquer recorte e nos clientes da rede -- os números da unidade também leem a rede desde a I2 (a
correção depois da verificação, abaixo); o filho `indicacao` da família, a fábrica, o `naTelaNova`. A I2: o
`VendaRepository::programaUltimaUnidadeAntes` (a unidade de quem fechou a última venda finalizada antes do instante, o empate pelo id
maior), o `ClienteRepository::programaContatos` com a unidade do cadastro, e as peças puras `unidadeDoConvite`,
`comAUnidadeDoConvite`, `paresDosConvites`, `indicadoresDasLinhas` e `daUnidade`; o serviço lê os convites da rede inteira e
recorta pela unidade do convite. **Na tela:** `api/indicacao.ts`, `ProgramaDeIndicacao.tsx`, `ProgramaDeIndicacaoLigado.tsx` e a regra
pura `listaDaIndicacao.ts`; a linha `[CAMINHO_DA_INDICACAO]` do Console, o `quando` do item do menu, a fila `indicacao` no
`filaDaRede.ts` (os números de qualquer recorte e os clientes da rede, que dividem a trava), as nove chaves do glossário (a `indicacao.unidade` da I2, a
`indicacao.pontos` com a 92 (b), a 65 (b) e a 145 (c), e um mês em andamento desta tela), o texto do `AindaNaoGravado` da Indicação
em dia com a 264 (b), a prévia `ProgramaDeIndicacao` (seis células, nomes e telefones inventados), o `index.ts`, o
`componentSrcMap` e os campos na regra `:is(...)`.

**O oráculo** (`medir-p59-oraculo.py` e `medir-p59.php`, no rascunho; só SELECT na cópia, contra o JSON do CONTROLLER despachado em
processo como administrador, campo a campo, nome e telefone pelo sha1): julho, junho, janeiro e fevereiro de 2026, a rede e as
unidades ativas com convite, nos dois bancos -- **2.550 conferências, 0 divergências**. Julho de 2026: a rede com 33 convites (3 e
30), 218 de 1.403 novos (0, 1 e 217) e 10 pontos em 1 venda, como o desenho; a unidade 1 com 11 convites (3 e 8) e 1 de 57 novos.
**A I2 na cópia, igual ao "Medido na cópia" do desenho:** dos 30 do formulário, 8 da unidade 1 contra 12 gravados; 5 mudam de
unidade na rede, e os 3 do aplicativo também; 9 convites de quem nunca tinha comprado ficam no cadastro.

**A medida** (o D660, PARCIAL; julho de 2026, `memory_limit = 128M`, pelo controller): os clientes da rede de 29,2 a 33,0 s frios e
28,4 a 35,0 s quentes no MySQL, de 3,6 a 4,0 s e 2,0 a 2,1 s no PostgreSQL, 45,7 a 46,4 MB; os números da rede de 3,5 a 7,1 s
frios e 2,0 a 2,1 s quentes no MySQL; **os números da unidade 1 subiram para 4,2 a 7,8 s frios e 3,8 a 4,6 s quentes no MySQL**
(eram 0,7 s quentes na 58), porque a I2 lê os amigos do formulário da rede inteira, a consulta sem índice que a I3 tira; no
PostgreSQL, 0,09 a 0,13 s quentes. (A verificação mostrou que a diferença entre a unidade e a rede nesta tabela é do host, e não da
I2: medidas alternadas no mesmo processo, as duas ficam juntas -- a correção, abaixo.) Dezembro de 2025 na rede: 53,3 MB de pico. Nenhum passa de 10 s na unidade com o cache quente.

**O que rodou** (a fatia é ímpar: rodaram só os testes do que ela toca; **o ciclo inteiro fica para a 60**). A caracterização das
16 classes da fatia e vizinhas (as duas novas, `ProgramaDeIndicacaoTest` e `UnidadeDoConviteTest`, e as da Indicação, do recorte,
do menu, da sessão, da família, do NPS e do D304/D312), **401 casos em cada banco, saída 0**, depois do último conserto. Os ensaios
nos bancos de contrato, depois de aquecer a aplicação e de apagar as 2 travas de `/tmp` do contêiner criadas como root (o D499):
no MySQL, o de permissão, o de recusas e o de fluxo, saída 0; no PostgreSQL, os três e o `medir-adm-api-no-postgres` depois do
fluxo, saída 0 (767, 1.324, 120 e 891 s). O medidor dos relatórios no PostgreSQL (`p48-medir-relatorios-pg.sh`), saída 0 (a tela
antiga da Indicação em 200). O front tocado e o achado no grep (24 arquivos, 514 casos, `--maxWorkers=2`), os novos com
`AMPLIFICAR_EFEITOS_MS=50` (29 casos), tsc, lint (saída 0) e o build (336,12 KB comprimido, eram 332,86). Os portões:
`conferir-recorte-por-unidade.php` (de 355 para 362 vigias; `relatorios na familia: 7`), `conferir-cobertura-por-par.php` (de 404
para 405 pares; o ISRelatorio de 153 para 155 chamadas), `conferir-injecao-dql.php`, `auditar-group-by.php`,
`conferir-links-mortos.php`, `conferir-acl-da-api.php`, `conferir-rotas-sem-login.php` (19 rotas guardadas pela base),
`conferir-busca-por-tags.php`, `conferir-booleanos-em-consulta.php` e `conferir-ordenacao-nulos.php`, todos com saída 0;
`conferir-debitos-repetidos.php` (três pares novos declarados distintos, e cinco antigos que a marca nova dos títulos tirou)
e `contar-debitos.php`, saída 0; o `conferir-tudo.sh` inteiro: os 66 portões ok ("Nenhuma falha inesperada."), saída 0, os testes do front entre eles.

**Os mutantes**, cada um sozinho, devolvido byte a byte e conferido pelo sha1; os do servidor nos dois bancos, todos vermelhos
pela asserção salvo onde a tabela diz (os de portão contra o portão verde antes):

| Mutante | Mudança | Vermelho |
|---|---|---|
| P59-P1 (IND-M15) | o `clientesAction` sem o `recorteDoRelatorio`, com um pedido inventado | só por erro nos testes (o pedido sem o período inteiro) e pelo portão do recorte -- não conta; refeito como P1b |
| P59-P1b (IND-M15) | o mesmo, com um pedido de verdade da rede, sem as guardas | o 401, o 403 e o 422 do `ProgramaDeIndicacaoTest` (5 falhas), e o portão do recorte |
| P59-P2 (IND-M16) | o `soltarASessao()` antes do `recorteDoRelatorio` | a contagem de soltas do recusado |
| P59-P3 (IND-M17) | a política com `caixa => 'outros'` | o caixa com 403 (5 falhas) |
| P59-P4 (IND-M18) | o total dos novos pelos candidatos | a comparação com o painel |
| P59-P5 (IND-M19) | os pontos pelas vendas da rede com unidade | 20 na U |
| P59-P6 (IND-M20) | o `gravadoDesde` com data fixa | o caso do `ColunasDoPdv` |
| P59-P7a / P7b (IND-M21) | o telefone fora da linha, no servidor / na tela | o telefone dos dois (3 falhas) / a lista e a busca |
| P59-P8 (IND-M22) | os clientes pedidos com o mês do pedido | o "primeiro sem mês, o segundo com o do recorte" |
| P59-P9 (IND-M23) | o `doMesmoRecorte` dos clientes arrancado | **vivo, equivalente**: o `vigente` do gancho comum é a outra barreira |
| P59-P9b (IND-M23) | o `vigente` arrancado do `usePedidoDeDepois` | a resposta velha dos clientes |
| P59-P10 (IND-M24) | o filtro de porta ignorado | os filtros (2 falhas) |
| P59-P11 (IND-M25) | o `AindaNaoGravado` fora do cartão | o caso do texto |
| P59-P12 (IND-M26) | a coluna Unidade na unidade | o caso da coluna |
| P59-P13 (IND-M27) | a tela antiga sem a guarda do escopo | o colaborador na tela antiga, e o portão do recorte |
| P59-P14 (IND-M28) | o `layout.phtml` com a condição antiga | o `RelatoriosNoMenuTest` |
| P59-P15 (IND-M29) | `<=` no instante | o K4 (a compra no mesmo segundo) |
| P59-P16 (IND-M30) | sem a ordem pelo id | o K3 nos dois bancos -- na primeira rodada só no MySQL; o empate passou a entrar com o id maior primeiro, e o PostgreSQL ficou vermelho também |
| P59-P16b | a ordem pelo id crescente | o K3 |
| P59-P17 (IND-M31) | o `daUnidade` sem efeito | a U com os da F (4 falhas), e o portão do recorte |
| P59-P18 (IND-M32) | a comissão pela unidade nova | a comissão na F |
| P59-X1 / X2 | a trava da rede arrancada dos clientes / dos números | o 429 das duas ações (refeitos contra a versão final na correção) |
| P59-X3 | a política com `outros => 'proprio'` (a guarda do papel arrancada) | o colaborador com 200 (7 falhas) |
| P59-X4 | o gerente vendo a rede (a unidade do pedido trocada por nula) | os números da U (3 falhas), e o portão do recorte |
| P59-X5 | a tela antiga com a política do NPS | o caixa recusado na tela antiga |
| P59-X6 | a unidade do convite pela gravada de novo | a I2 (3 falhas, o D661 entre elas) |
| P59-X7 | a compra DEPOIS do convite aceita | o K1 e o K4 (4 falhas) |
| P59-X8 | a fila da rede também na unidade | "a unidade não espera" (a correção mudou a regra: os números da unidade entram na fila, e o X8 virou o código; ver o X17 e o X18) |
| P59-X9 | o "relendo" sem a lista de antes | a vista e o invólucro (2 falhas) |
| P59-X10 | a venda cancelada conta | o K5 |
| P59-X11 | o cadastro antes da última compra | a I2 (4 falhas) |
| P59-X12 | a busca no telefone com a máscara | a busca pelos dígitos |
| P59-X13 | os clientes da rede fora da fila | a fila dos dois pedidos |
| P59-X14 | o `quando` do item do menu arrancado | o `RelatoriosNoMenuTest` e o `menu-do-erp.teste.ts` |
| P59-X15 | a linha do Console arrancada | o `RelatoriosNoMenuTest`, o `ConsoleAbreIndicacao` e o `Console.teste` |
| P59-X16 | a comissão e a adesão pela rede com unidade | a comissão da F |

**O que ficou de fora.** A porta do convite e o passado (a I3, a fatia 75); a aposentadoria da tela antiga (a I4); a frase da recusa
pelo papel, que omite o caixa nas três políticas em que ele vê -- é da parte comum, e foi registrada (o D662); a base da comissão (o
item 1 do D659). Nenhuma escolha nova do dono: nada foi ao lote. **O que não provei:** a resposta velha dos NÚMEROS no invólucro, que a tela não consegue produzir (a navegação fica desligada com outro mês no
ar), fica provada só no gancho comum.

**A correção depois da verificação** (três lentes, nove achados confirmados, todos baixos; oito consertados, um sem conserto no
código). **(1)** O "Como se conta" dizia que a unidade do convite "não muda depois", e ela é conferida a cada consulta: a compra
que a decidia, cancelada depois, deixa de contar, e o cadastro que vale é o de hoje. A frase passou a dizê-lo ("Ela é conferida a
cada consulta: se aquela compra for cancelada depois, vale a compra anterior"), com o caso
`UnidadeDoConviteTest::testACompraCanceladaDepoisEOCadastroDeHojeMudamAUnidade`; congelar pediria uma coluna, e o D658 o registra.
**(2)** Os tempos nos comentários do controller e do `filaDaRede.ts` passaram a ser os medidos. **(3)** Nenhum caso pedia os
clientes da unidade com a fila ocupada: dois casos novos no `ProgramaDeIndicacaoLigado.teste.tsx` (o P59-X18 vermelho). **(4)** O
desempate pelo id só morria pela ordem física de um empate: o K7, o mesmo empate inserido na ordem oposta, numa asserção só com o
K3 -- sem o id, no MySQL os dois voltam pelo id crescente e no PostgreSQL o K3 erra e o K7 acerta; o P59-P16 fica vermelho nos dois
bancos sem depender do plano. **(5)** A causa publicada da piora dos números da unidade não separava o host: medidos alternados no
mesmo processo (`medir-p59-partes.php`), a unidade 1 e a rede ficam juntas -- 5,97 a 6,06 s contra 5,53 a 6,02 s no MySQL e 0,12 a
0,55 s contra 0,12 a 0,22 s no PostgreSQL --, quase tudo na mesma consulta dos amigos do formulário da rede (5,1 a 6,9 s no MySQL);
o que a I2 pôs a mais na unidade fica abaixo de 0,8 s, e a diferença de 2 s da medida de antes é do host (o D660 tem a tabela).
**(6)** O teste do invólucro foi editado depois da rodada do front: o front e os casos com o amplificador rodaram de novo. **(7)** O
controller mudou depois dos mutantes dele: os P59-P1b, P2, P6, X1, X2 e X4 rodaram de novo contra a versão final, nos dois bancos,
vermelhos pela asserção (o P1b e o X4 também pelo portão do recorte). **(8)** Os mutantes do menu (P59-P14, X14 e X15) rodaram
também no PostgreSQL, vermelhos. **(9)** Os números da unidade, que desde a I2 leem a mesma rede que os da rede, ficavam sem trava:
agora o `indexAction` trava SEMPRE, na mesma trava por pessoa (o 429 com a frase nova `CONSULTA_EM_ANDAMENTO`, "Já há uma
consulta deste relatório em andamento. Espere ela terminar e tente de novo.", que não fala da rede; na rede, a do Parceiro), e o
`clientesAction` só na rede; a tela põe os números de qualquer recorte na fila `indicacao`, e os clientes só com a rede. O preço: o
administrador que troca para uma unidade com os clientes da rede no ar espera a leitura que está lá.

**Os mutantes novos da correção**, cada um sozinho, devolvido byte a byte e conferido pelo sha1, os do servidor nos dois bancos:
P59-X17 (os números da unidade fora da fila, a regra antiga: 2 casos vermelhos), P59-X18 (os clientes da unidade na fila: 3), P59-X19
(os números da unidade sem a trava), P59-X20 (os clientes da unidade com a trava), P59-X21 (a frase da rede no 429 da unidade),
P59-X22 (a frase antiga do "Como se conta": o `glossario.teste.tsx`), e o P59-X10 (a venda cancelada conta) de novo, agora vermelho
também no caso novo da compra cancelada depois -- todos vermelhos pela asserção. **O que rodou na correção:** a caracterização das
16 classes, **402 casos em cada banco, saída 0** (um caso novo), depois da última edição; o ensaio de permissão no MySQL (saída 0;
o meu `timeout` de fora deu 124 depois do fim dele, com o `local.php` já devolvido e conferido pelo sha1) e, no PostgreSQL, o de
permissão, o de fluxo e o `medir-adm-api-no-postgres` depois dele, saída 0 (608, 90 e 636 s), depois de apagar as travas de `/tmp`
criadas como root (7 antes do MySQL, 0 antes do PostgreSQL); o front tocado e o achado no grep (28 arquivos, 545 casos,
`--maxWorkers=2`), os quatro da Indicação com `AMPLIFICAR_EFEITOS_MS=50` (30 casos), tsc, lint e o build (336,16 KB comprimido,
eram 336,12); os portões do recorte (362 vigias), da injeção de DQL, da cobertura por par, do `GROUP BY`, dos links mortos, das
rotas sem login e da ACL, saída 0, e o `conferir-tudo.sh` inteiro no fim. O de recusas não rodou de novo: ele não pede a Indicação.

**No navegador** (a minha conferência, 31/10/2026, contra o banco de contrato com o cenário de setembro de 2025 da I0 semeado pelo
próprio `CenarioDaIndicacao::semear`, ids 90006400 a 90006421; remontado no fim). O administrador abre a tela pelo item "Programa de
Indicação" do menu Relatórios; outubro de 2026 vazio. Setembro de 2025 na unidade da sessão: 5 convites (aplicativo 3, formulário 2),
1 adesão pelo aplicativo, 3 de 4 clientes novos (75,0%), R$ 60,00 em 4 vendas com as três ressalvas (a primeira venda sem serviço, a
cancelada, a sem venda), 10 pontos em 1 venda; a lista com os telefones inventados e "quem indicou não está mais no cadastro" no id
que não existe. Na rede: o "…" enquanto relê, depois 7 convites (4 e 3) com a coluna da unidade e 20 pontos em 2 vendas (a cancelada
fora). A busca "99000-6402", com a máscara, deixa os 2 convites de quem indicou. A 375 px a página não rola de lado (a tabela rola na
própria caixa), nos dois temas. Pela API, com sessões próprias: o gerente.teste e o colab.teste sem o privilégio recebem 403
`sem_permissao` nos números e nos clientes e 404 na tela antiga; o gerente com o privilégio (concedido só no banco de contrato, em
`privilegios_usuarios`) recebe 200 na unidade dele, 422 na rede e em outra unidade, e 200 na tela antiga. Nenhum defeito. Não olhei: o
429 com duas abas e a espera do administrador na fila (os casos da trava e da fila os cobrem), o caixa só caixa (as fixtures não têm
um) e o item do menu escondido do colaborador na tela (o `RelatoriosNoMenuTest` o cobre).

**Débitos:** D658 PARCIAL (o relatório calcula; a escrita e o aplicativo, não), D660 PARCIAL (a trava e a fila; o custo continua, e a
I2 o levou à unidade), D661 CORRIGIDO (pela I2), D662 registrado; de pé 178 para 178. Nada do esquema, do crontab, do OneSignal, da
produção nem do BVIP mudou; nenhuma rotina de comissão ou de pontos rodou fora dos testes.

## 31/10/2026 -- REL-CAIXA-CONTAS (a fatia 60 da fila das telas que faltam)

**Para quem usa, nada muda:** sem rota, sem menu e sem tela; a política `caixa` segue com `naTelaNova => false`, e o item
"Caixa" do menu continua levando ao ERP antigo. A CX2 (a fatia 61) liga a API, e a CX3 (a 62) a tela.

**O que entrou** (a CX0 e a CX1 de `docs/f3-relatorios/spec-caixa.md`): as contas puras do relatório de caixa em
`ISVenda\Regra\ContasDoCaixa` -- o saldo (todas as formas: inicial + recebido − retiradas ativas), a gaveta esperada (só o
Dinheiro, a forma 1, `FormaPagamento::DINHEIRO`, nova: inicial + dinheiro − retiradas ativas − troco; a gorjeta fica na gaveta),
o fora de comanda, os descontos, a gorjeta das finalizadas do caixa, as vendas de outro dia pelo DIA e não pelo instante, as
canceladas depois de finalizadas, os sinais (aberto de outro dia, fechado em outro dia, venda de outro dia, cancelada), a soma, o
resumo por unidade, os operadores e o filtro; as guardas puras em `ISRelatorio\Regra\RegraDoRelatorioDeCaixa` (os filtros e o
`caixaNoRecorte` do detalhe); o `RegraDoCaixaDoDia` com o `diaAnterior` e o `mesmoDia` (o `deOutroDia` delega, a regra do D28 num
lugar só); o `CaixaJson` com o saldo de `ContasDoCaixa::saldo` (nenhum valor muda). E as consultas por conjunto, em lotes de mil:
os caixas do período e os esquecidos abertos de antes de hoje pela unidade do operador, o caixa pelo id, o recebido por forma, o
fora de comanda (a cláusula da venda sem item numa constante que o painel também usa), as vendas, os descontos e as retiradas.
**A devolução de crédito é uma retirada (a resposta 89 (b)):** entra no saldo e na gaveta, que podem sair negativos (a 232 (c)); a
desfeita é uma retirada cancelada (a 231 (a)); e as devoluções se contam à parte pelo registro (`RegistroDaDevolucao::dasRetiradas`,
nova), nunca pelo motivo, e sem consulta quando a tabela não existe no banco.

**A retomada.** O computador reiniciou no meio da fatia. Antes da queda, o implementador anterior escreveu o código e os testes,
rodou as 39 classes da fatia e vizinhas nos dois bancos (728 casos; um vermelho no MySQL, a expectativa dos descontos do K2, que tem
item sem desconto, corrigida no teste; depois saída 0 nos dois), os portões, e os mutantes até o P60-P20; o P60-P21 estava aplicado
na queda, e o orquestrador o devolveu. Depois da queda: as duas classes de repositório do caixa, CRLF no repositório, tinham as
linhas novas em LF (o arquivo misto) e voltaram a CRLF inteiro, só com as linhas novas no diff; as travas de `/tmp` do contêiner
criadas como root (5) apagadas; **as 39 classes rodaram de novo, 728 casos em cada banco, saída 0** (5.196 asserções no MySQL e
5.195 no PostgreSQL); os mutantes P60-P21 em diante, o oráculo, a medida, os portões e os documentos.

**Os mutantes**, cada um sozinho, devolvido byte a byte e conferido pelo sha1, todos vermelhos pela asserção (os de consulta nos
dois bancos): da CX0, P60-P1 a P9 e P11 a P15 (os MC1 a MC9 e MC11 a MC15 do desenho; o MC10, os operadores depois do filtro, é
uma ordem do controller e vai à 61); da CX1, P60-P16 a P24 (os MC16 a MC24); os da fatia -- X1 a devolução fora do saldo, X2 a
desfeita contada como ativa, X3 a devolução pelo motivo em vez do registro, X4 a tabela ausente consultada assim mesmo, X5 o lote
sem o corte de mil, X6 e X7 o saldo e a gaveta negativos truncados em zero, X8 o filtro da unidade arrancado dos abertos, X9 a gorjeta
da cancelada contada, X10 a última retirada só das ativas, X11 o aberto do PostgreSQL (`t`) lido como fechado, X12 toda retirada como
devolução, X13 o painel sem a cláusula da venda sem item --; e os de portão G1 a G3 (a unidade arrancada do `caixaNoRecorte`, do
`relatorioCaixas` e do `relatorioAbertosAntes`), vermelhos no `conferir-recorte-por-unidade.php`, verde antes.

**O oráculo** (`medir-p60-oraculo.py` contra `medir-p60.php`, no rascunho; só SELECT na cópia, sem nome nem motivo): julho e junho
de 2026, a unidade 1 e a rede, nos dois bancos, caixa a caixa e no resumo: **148.724 conferências, 0 divergências.** Uma primeira
rodada deu 2.632, todas da ordem da lista dos abertos no meu roteiro (o aberto que também é do período vinha antes), e não do
código. Julho bate com o medido em 02/10/2026 número a número, menos os abertos de dias anteriores da rede, 139 e não 137: os dois
caixas de fixture do `franquiabv_dev`. A cópia não tem devolução de crédito (0 nos dois bancos): o oráculo dela é o cenário.

**A medida** (julho de 2026, `memory_limit = 128M`; duas medidas, cinco amostras frias por célula: duas voltas na fatia e três na
verificação, a nota 7 pedia três): a rede fria (o primeiro pedido do processo) de 2,24 a 2,89 s no MySQL -- e 4,02 s na primeira
leitura depois das suítes de mutantes, com as vendas e os descontos lendo do disco (o buffer pool de 128 MB é dividido com os bancos
do CI) -- e de 2,15 a 2,76 s no PostgreSQL; a rede quente de 0,73 a 1,16 s no MySQL e de 0,54 a 0,92 s no PostgreSQL (1,11 s na
medida alternada); a unidade 1 fria de 1,58 a 2,37 s e quente de 0,09 a 0,25 s; o pico de 52,3 MB. **Passa de 1 s também quente na
rede do MySQL** (quatro das nove rodadas da 2ª medida), e **a soma fria da rede passa de 2 s no MySQL em todas as amostras** (o
desenho manda registrar e dizer). Os primeiros pedidos dos processos do oráculo, que rodaram logo depois de rodadas de mutantes de
banco, foram até 4,56 s no MySQL e 7,22 s no PostgreSQL (a rede de junho): host carregado, escrito no D665 com a causa provável.
Medida alternada no mesmo processo: o primeiro pedido custa cerca de 1,5 s a mais com qualquer volume -- o processo, e não as
consultas (o D646). Registrado no D665; a trava da rede é da 61.

**Os portões:** `conferir-recorte-por-unidade.php` (365 vigias, eram 362), `conferir-injecao-dql.php` (22 sítios),
`conferir-cobertura-por-par.php` (405 pares, sem mudança, como o desenho previa), `auditar-group-by.php`,
`conferir-booleanos-em-consulta.php`, `conferir-ordenacao-empates.php`, `conferir-ordenacao-nulos.php` e
`conferir-relogio-nos-testes.php`, saída 0. **Três rodadas do `conferir-tudo.sh` na fatia, na ordem:** a primeira (09:09) foi
cortada pelo `timeout 590` do meu comando (saída 124, sem a linha de fim, parada no portão dos bytes de controle) -- tempo, e não
portão; a segunda (09:32) saiu 1: o `conferir-booleano-em-coluna-inteira.php`
subiu de 13 para 15 não resolvidas -- as duas `usu.unidade = :unidade` do caixa, porque as DQL eram constantes feitas de outra
constante (`self::SELECT_DO_RELATORIO . "..."`), e o portão só lê a constante de texto. O método passou a juntar
`self::SELECT_DO_RELATORIO . self::ONDE_*` (o onde de cada consulta numa constante só de texto): o portão voltou a 13, achando o
`FROM` de `usu`. Depois do conserto rodaram de novo as 39 classes (**728 casos em cada banco, saída 0**), os mutantes do
`CaixaRepository` (P60-P16, P17, P22, P23, X8, G2 e G3, vermelhos de novo, os de consulta nos dois bancos), o oráculo (148.724 e
0) e a terceira rodada do **`conferir-tudo.sh` inteiro (10:12): os 66 portões, saída 0**. O front não mudou. **A fatia é par: o ciclo inteiro roda depois
dela, no commit.**

**Débitos:** D663 (o saldo não desconta o troco, a gaveta desconta; o D-cx-c), D664 (os caixas esquecidos abertos no relatório; o
D-cx-b) e D665 (a medida) registrados; o D-cx-a não precisa mais de pergunta (a 88 (a), feita em 15/10/2026). De pé 178 para 181.
Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; nenhuma retirada, devolução ou fechamento de caixa fora
dos testes.

**A verificação em três lentes** confirmou 9 achados, todos baixos, todos tratados:
- **A DQL do fora de comanda por caixa era uma constante feita de outra** (`self::SEM_ITEM` dentro) e sumia inteira do
  `conferir-booleano-em-coluna-inteira.php`, sem contar nem como não resolvida: um `'status' => true` nela passava verde (o P60-X14a,
  verde, confirmou). O método junta `self::DQL_DO_FORA_DE_COMANDA_POR_CAIXA . self::SEM_ITEM . self::AGRUPAR_POR_CAIXA`; o portão
  passou de 638 para 639 ligações conferidas, as não resolvidas seguem 13, e o mesmo `status => true` (P60-X14) ficou vermelho.
- **O item 23 do "Em dia" do `spec-comum.md` dizia que um relatório novo "chama" o `SEM_ITEM`**, que é `private` e depende do
  apelido `ve`: agora diz que o fora de comanda novo entra como método do `VendaPagamentoRepository`, ou muda a visibilidade.
- **As fronteiras no instante exato sem caso:** `relatorioAbertosAntes` com `<= :hoje` e `relatorioCaixas` com `> :inicio`
  sobreviviam (nenhum caixa do cenário abria a 00:00:00). Dois casos novos no `ConsultasDoRelatorioDeCaixaTest`, com os caixas
  90006320 (2014-03-01 00:00:00, de março e não de fevereiro) e 90006321 (aberto a 00:00:00 de hoje, não esquecido, nem na rede): os
  mutantes P60-X15 e X16, vermelhos nos dois bancos.
- **A cancelada só tinha o caso em que total = recebido, troco zero e o mesmo dia:** contar o total, somar o troco dela ou pô-la em
  "outro dia" sobrevivia. Caso novo no `RegrasDoRelatorioDeCaixaTest` (a V4 do dia seguinte, recebido 40,00, troco 5,00): os
  mutantes P60-X17 a X19, vermelhos.
- **A forma 1 inserida fora da transação não deixava caso vermelho:** o `CenarioDoRelatorioDeCaixa` afirma que há transação aberta
  antes de qualquer escrita (no `semear` e no `formaDinheiro`). O mutante P60-X20 (o `rollBack` antes do `semear`) ficou vermelho
  pela asserção nos dois bancos, antes de qualquer escrita, e o `franquiabv_cilocal` e o `franquiabv_pgtestes` ficaram sem a forma 1
  e sem caixa 900063xx; o mesmo mutante sem a asserção não rodou, porque gravaria o cenário no banco do CI.
- **A nota FEITA da CX1 não contava as devoluções do K6** (R4 a R6, os registros 90006330 e 90006331) nem o `valor_final` -53,00
  (o desenho diz 0,00): a nota e a linha do K6 na tabela do `spec-caixa.md` dizem agora.
- **A medida com duas amostras frias por célula, e não três; as amostras frias maiores do oráculo sem causa; e "só o primeiro
  pedido passa de 1 s":** medida de novo com três voltas e a carga anotada (`medir-p60c-tempo.sh`), e o D665, o parágrafo da medida
  acima e o motivo dos pares `D655|D665` e `D660|D665` no `conferir-debitos-repetidos.php` corrigidos.
- **A saída 124 do primeiro `conferir-tudo.sh` fora do roadmap, e a ordem trocada:** dita no parágrafo dos portões.

Depois dos consertos rodaram, em série: as 39 classes da fatia e vizinhas, **731 casos em cada banco, saída 0** (5.252 asserções no
MySQL e 5.251 no PostgreSQL; eram 728, mais os três casos novos); os mutantes novos P60-X14a (verde antes, o achado), X14 a X20 e o
P24 de novo, cada um sozinho e devolvido pelo sha1, todos vermelhos (os de consulta nos dois bancos, pela asserção); os portões da
fatia (o recorte com 365 vigias, a injeção com 22 sítios, a cobertura com 405 pares, o booleano com 13 não resolvidas, o
`GROUP BY`, os booleanos em consulta, as ordenações e o relógio, saída 0); e o `conferir-tudo.sh` inteiro: os 66 portões, saída 0.
Débitos: nenhum novo (o D665 reescrito com a medida nova); de pé 181. O front não mudou.

**O ciclo inteiro (a fatia é par), em série, depois da verificação:** a caracterização inteira, **5.595 casos em cada banco**
(42.834 asserções no MySQL e 42.830 no PostgreSQL, 12 pulados), saída 0 nos dois; o contrato, 74 e 74 (126 asserções), saída 0;
os **22 ensaios em cada banco de contrato com saída 0**; os **66 portões**, saída 0; o front inteiro, **3.648 casos em 146
arquivos**, saída 0. E o medidor dos relatórios no PostgreSQL (`medir-relatorios-no-postgres.php`, fora do ciclo), saída 0, com o
`local.php` de volta ao `franquiabv_dev`, conferido pelo sha1. Nenhum vermelho; nada a consertar.

## 31/10/2026 -- REL-CAIXA-API (a fatia 61 da fila das telas que faltam)

**A CX2 do `docs/f3-relatorios/spec-caixa.md`: a API do relatório de caixa, a política ligada, o menu antigo e a tela antiga com
ela -- sem a tela nova, que é a CX3 (a 62).**

**O que muda para quem usa.** A tela nova ainda não tem o Caixa: o item "Caixa" do menu continua levando à tela não migrada, com o
caminho para a versão antiga. O que muda é quem entra e o que o caixa vê:

- **O menu antigo e a tela antiga seguem a política do Caixa** (gerente, caixa e administrador). Medido na cópia, só contagem e
  sem as fixtures: de 366 ativos, viam o item do menu 177 e veem 175 -- perdem a porta uma pessoa do grupo 1 (Colaborador) e uma
  do grupo 8 (Líder), que tinham o privilégio sem o papel (as duas da tabela 1.2 do desenho); pela URL, as duas recebem o 404.
  Ficam 10 administradores, 105 gerentes e 60 caixas que não são gerentes.
- **O caixa que não é gerente vê só o dia de hoje, inclusive pela URL** (a resposta 4 do dono): a tela antiga escondia o filtro
  dele mas aceitava `?data_inicio=` e `?data_fim=`; agora o servidor força hoje, e o detalhe das retiradas recusa o caixa de outro
  dia ("Erro ao localizar caixa.", a mesma frase do de outra unidade). O gerente que também é caixa deixou de perder o filtro (a
  marca de gerente vence, a resposta 271 (c)).
- **Para quem não muda:** o gerente e o administrador veem a tela antiga como antes; nenhum número da tela antiga mudou.

**A API.** `GET /adm/api/relatorios/caixa?de=&ate=&unidade=&operador=&estado=&pendencia=` (até 31 dias; hoje para o caixa) devolve
o `recorte`, os `filtros`, os `operadores` (os de antes do filtro), o `resumo` das linhas filtradas (os caixas, os abertos, os sem
venda, o valor inicial, as formas com moeda real e interna e o fora de comanda, as retiradas -- com as devoluções de crédito
contadas à parte, dentro da soma --, o saldo, a gaveta, os descontos, a gorjeta, as vendas, as de outro dia, as canceladas, o
troco, os fechados em outro dia e a quebra nula), os `caixas` linha a linha (na rede, `null`, e no lugar as `unidades` pelo nome do
cadastro), os `abertosAntes` (os esquecidos, com a unidade; `null` para o caixa) e o `gravadoDesde`. `GET .../caixa/:id` devolve o
caixa do PDV (`CaixaJson::completo`, com os totais da mesma linha), a `conferencia` (a linha da lista, a mesma função), as vendas de
outro dia e as canceladas; o caixa de fora do recorte é o MESMO 404 de quem não existe ("Caixa não encontrado."), antes de soltar a
sessão, e o id acima do teto da coluna nem chega ao banco. `GET .../caixa/unidades` é o da base. **A rede trava por pessoa** (o
D665: a rede fria leva cerca de 3 s ou mais): o segundo pedido da rede da mesma pessoa é o 429, depois de soltar a sessão; o detalhe e as
unidades não travam.

**Arquivos.** Novos: `module/ISRelatorio/src/Controller/Adm/RelatorioDeCaixaController.php`,
`module/ISRelatorio/src/Api/RelatorioDeCaixaJson.php`, `tests/Caracterizacao/RelatorioDeCaixaTest.php` (15 casos: os 14 da fatia e o da trava presa durante as consultas, da correção) e
`RelatorioDeCaixaNoErpTest.php` (5). Mudados: a política `caixa` (`naTelaNova`), a rota `adm-api-relatorios/caixa` (`ler`,
`unidades`, `ver`), a fábrica no `Module.php`, o item do `layout.phtml` pelo `veOMenu`, o `ISRelatorio\Controller\CaixasController`
(as duas ações com o escopo), o `quando` do item do menu e a linha da `TELA_DO_RELATORIO` do Console (a tela não migrada, como na 50
e na 56; o `RelatoriosNoMenuTest` cobra os três juntos), o `menu-do-erp.teste.ts` (doze destinos com `quando`), o
`RelatorioEmProcesso` (o 7º argumento, os parâmetros da rota), o `D432DataNaFormaTest` (a sessão com as marcas, o D626), o
`NA_TELA_NOVA`, as folhas do `PortoesDaFamiliaDosRelatoriosTest`, o `conferir-recorte-por-unidade.php` (de 365 para 377 vigias), o
`conferir-cobertura-por-par.php` (de 405 para 413 pares; o ISRelatorio de 155 para 166 chamadas) e o
`medir-adm-api-no-postgres.php` (o `CAIXA_DA_FIXTURE` e o detalhe pelo caminho com ponto). O inventário de ações regerado não mudou.
Os ids de hoje do teste não são os do desenho (90006340 colidia com a 60): 90006350 a 90006353.

**O oráculo** (`medir-p61-oraculo.py` contra `medir-p61.php`, no rascunho; só SELECT): as contas da seção 1.3 refeitas em Python
sobre as linhas cruas, contra o JSON do controller em processo com a sessão de administrador, caixa a caixa pelo id, os esquecidos,
o resumo, a linha por unidade da rede e o detalhe de uma amostra de 15 caixas por recorte (a conferência, os totais e as formas do
caixa do PDV). **Na cópia**, julho e junho de 2026, unidade 1 e rede, nos dois bancos: **15.442 conferências, 0 divergências**;
julho bate com a seção 1.4 (64 e 1.068 caixas, 3.106 e 20.221 finalizadas, 7 e 180 canceladas, R$ 21.198,60 e R$ 57.053,19 de
descontos, 47 e 208 retiradas, R$ 421,00 e R$ 2.330,85 de gorjeta, 12 e 139 esquecidos). O recebido, a gaveta e o fora de comanda
dão zero na cópia (o D181: `vendas_pagamentos` vazia), e por isso a gaveta sai negativa ali. **No banco de contrato semeado** (o SQL
no rascunho: três dias de março de 2014 da unidade 90001150, com Dinheiro, cartão e crédito do cliente, uma retirada ativa e uma
cancelada, uma gorjeta, uma cancelada, uma de outro dia e um esquecido), nos dois bancos, unidade e rede: **770 conferências, 0
divergências**, com o recebido (R$ 300,00) e a gaveta (R$ 338,00) de verdade. Os dois bancos de contrato foram remontados depois.

**A medida** (o D665, reescrito com ela): pelo controller, 128 MB, três voltas por célula. Fria, a unidade num dia de 2,12 a 3,16 s,
a unidade no mês de 2,23 a 3,18 s e **a rede no mês de 3,16 a 3,54 s no MySQL e de 2,93 a 3,54 s no PostgreSQL**; quente, 0,05 a
0,12 s, 0,11 a 0,27 s e **0,57 a 1,03 s**; o pico, o maior de cada célula, de 16,0 a 17,3 MB, de 19,7 a 21,8 MB e de 50,1 a 51,2 MB
(51,7 MB na medida alternada, num processo só: o PHP 8.1 não zera o pico entre os pedidos). Na unidade, a medida alternada no mesmo
processo mostra que o frio é o custo do primeiro pedido (de 2,41 a 2,74 s, um dia ou o mês), e não das consultas. **Na rede, não**:
a alternada da fatia sempre começava pela unidade, e a da correção, com as duas ordens lado a lado (`p61c-medida-par.txt`, o host mais
carregado), deu de 4,05 a 4,54 s começando pela rede contra 3,23 a 3,68 s começando pela unidade -- o frio da rede é o do processo
mais o das consultas da rede. **A rede fria passa de 3 s em todas as amostras do MySQL e em duas das três do PostgreSQL (2,93, 3,10 e
3,54 s), e de 4 s em todas as da correção; a unidade passa de 1 s fria**: o desenho manda registrar e dizer -- registrado no D665 e
dito no relatório da fatia; a trava resolve o empilhamento, não o tempo.

**O que rodou (a fatia é ímpar: o ciclo inteiro fica para a 62).** Em série:
- as **40 classes** da fatia, as que o desenho manda ficarem verdes e as achadas no grep (o `CaixasController`, o `rel-caixas`, a
  `PoliticaDoRelatorio`, o `naTelaNova`, o `layout.phtml`, o `CaixaJson`, as consultas da CX1 e os usuários do `RelatorioEmProcesso`;
  o `D367RoyaltiesDaUnidadeInativaTest`, que também usa o trait, ficou de fora por chamar a rotina das faturas, ainda que pelo
  dublê, e o parâmetro novo é opcional): **845 casos em cada banco** (6.685 asserções, 1 pulado, o de sempre do
  `VendasCanceladasTest`), saída 0 -- a rodada final, depois do último ajuste de teste; antes, 845 e 845 com 6.680;
- os ensaios de permissão, recusas e fluxo e o medidor da API, **nos dois bancos de contrato, saída 0** (o permissão recusou 163
  de 163 -- eram 160, mais as três folhas do Caixa --, e a parte 3 mediu o caixa com o recorte de hoje e o colaborador com o 403
  `papel`; o medidor com 234 endpoints, eram 231, o `caixas` e o `caixa.totais.pagamentosPorForma` cheios); o recusas no
  PostgreSQL levou 1.099 s (host carregado), sem vermelho;
- o `medir-relatorios-no-postgres.php` (fora do ciclo), saída 0, o `rel-caixas/index` em 200 com o "Dinheiro (teste)";
- o front tocado (onze arquivos, **243 casos**, e quatro com `AMPLIFICAR_EFEITOS_MS=50`: 1 vermelho, o "o valor unitário só é campo
  para o gerente" do `Console.teste.tsx`, que cai também com o `Console.tsx` da HEAD -- 1 de 3 --, a variação do D471, relido); o
  build com 336,17 KB e o lint, saída 0;
- os portões que a fatia toca, cada um sozinho, saída 0: o recorte (377 vigias, 8 relatórios, 13 ações com a guarda da base), a
  ACL da API (13 delegadas à base), as rotas sem login (22 de relatório guardadas pela base), a guarda do ERP, a ACL como booleano,
  a guarda do caixa, os grupos por id, as referências dos docs, a injeção (22 sítios), o booleano em coluna inteira, os links
  mortos, a remoção por POST e a cobertura (413 pares); e o `conferir-tudo.sh` inteiro: os **66 portões, saída 0** ("Nenhuma falha inesperada"), sem `MSYS_NO_PATHCONV`, rodado depois dos mutantes e das suítes.

**Os mutantes**, cada um sozinho, devolvido byte a byte e conferido pelo sha1; os de servidor nos dois bancos, vermelhos pela
asserção:

| # | Mudança | Caso vermelho |
|---|---|---|
| P61-P1 (MC25) | o `index` sem o `soltarASessao` | a sessão, a trava, o gerente e o caixa (as soltas); o portão do recorte |
| P61-P2 (MC26) | os abertos de antes também no `dia` | o caixa vê só hoje; o portão |
| P61-P3 (MC27) | o `ver` sem o `caixaNoRecorte` | o 404 de fora do recorte; o portão |
| P61-P4 (MC28) | o `ver` com a unidade da SESSÃO (a da query não chega: a base recusa) | o administrador na rede e na B vê o K4 |
| P61-P5 (MC29) | os filtros antes do recorte | as guardas na ordem (o 403, e não o 422); o portão |
| P61-P6 (MC30) | o filtro esquecido (o resumo e a lista sem filtrar) | os filtros |
| P61-P7 (MC31 e o MC10) | os operadores depois do filtro | os filtros |
| P61-P8 (MC32) | a tela antiga sem forçar a data no `dia` | o caixa vê só hoje pela URL; o portão |
| P61-P9 (MC33) | o `retiradasDetalhes` sem o dia | as retiradas de um caixa; o portão |
| P61-P10 (MC34) | o `layout.phtml` só com o `getAcl` | o menu antigo; o `RelatoriosNoMenuTest` |
| P61-P11 (MC35) | o `naTelaNova` de volta a `false` | o `RelatorioAdmControllerTest` e as invariantes; o portão do recorte com a saída 2 |
| P61-X1 | a trava da rede arrancada | a rede, um pedido de cada vez |
| P61-X2 | a trava antes do `soltarASessao` | a rede (zero soltas no 429); o portão |
| P61-X3 | o 404 de fora do recorte com outra frase | o 404 de fora do recorte |
| P61-X4 | o `ver` aceitando o id acima do `MAIOR_ID` | **equivalente no MySQL** (o id não existe, o mesmo 404); no PostgreSQL, o erro do banco (`value "2147483648" is out of range for type integer`), que é o que o teto evita |
| P61-X5 | a vista da tela antiga com a marca de caixa | o gerente que é caixa perde o filtro |
| P61-X6 | o `retiradasDetalhes` sem o escopo | o colaborador recebe o detalhe; o portão |
| P61-X7 | a devolução fora das retiradas no detalhe | a devolução de crédito no detalhe (o K6) |
| P61-X8 | o caixa do relatório sem uma forma | o detalhe do K1 e o caixa do relatório contra o do PDV |
| P61-X9 | as unidades da rede pelo id, e não pelo nome | o administrador na rede |
| P61-X10 | os esquecidos da rede para o gerente | o gerente (o esquecido da B); o portão |
| P61-X11 | os caixas da rede para o gerente | o gerente, os filtros, o caixa e o administrador; o portão |
| P61-X12 | o `ver` sem o `soltarASessao` | o detalhe e a sessão; o portão |
| P61-X13 | a rede pelo escopo, e não pela unidade nula | o administrador na B (morria só por erro de tipo; o caso ganhou a asserção da tabela, e ficou vermelho pela asserção) |
| P61-X14 | o `quando` do menu com a guarda de outro relatório | o `menu-do-erp.teste.ts` e o `RelatoriosNoMenuTest` (a 1ª versão, uma vírgula arrancada, só quebrava a compilação, e foi trocada) |
| P61-X15 (a correção) | a `liberar` da trava da rede antes da lista | a trava presa durante as consultas (o caso novo); o caso de antes da correção ficava verde |
| P61-X16 (a correção) | o `try`/`finally` arrancado | **equivalente** (verde nos dois casos da trava): o handle é variável local, e o PHP fecha o arquivo -- e o `flock` -- quando a ação volta |

**A correção depois da verificação** (cinco achados confirmados, todos baixos, todos tratados): **(1)** o D665 e este parágrafo
diziam que a rede passa de 3 s fria em todas as amostras, e uma do PostgreSQL deu 2,93 s -- agora dizem o MySQL e o PostgreSQL à
parte; **(2)** a nota FEITA da CX2 não dizia que o corpo do `index` passou ao `lista()` privado, onde o portão procura as vigias,
nem que elas são 12 e não as seis novas do desenho -- o item (12) da nota; **(3)** nenhum caso via a trava da rede PRESA durante as
consultas (uma `liberar` antes da lista passava) -- o caso novo `testATravaDaRedeFicaPresaDuranteAsConsultas`, por um registro de
SQL que tenta a trava a cada consulta, o P61-X15 vermelho por ele nos dois bancos, e o P61-X16 ("sem o `finally`") equivalente e
dito; os sete irmãos com trava, sem o caso, viraram o D666; **(4)** a conclusão "o frio custa o mesmo com qualquer volume" vinha de
uma alternada que sempre começava pela unidade -- medida de novo com as duas ordens lado a lado (`medir-p61c-par.sh`), começar pela
rede custa de 0,6 a 1,2 s a mais, o que as consultas da rede custam: a frase vale para a unidade, e não para a rede; **(5)** o pico
da unidade no mês dizia 19,7 MB e chegou a 21,8 MB nas quentes -- o pico agora é o maior de cada célula. Rodaram de novo, em série:
as 40 classes da fatia, **846 casos em cada banco** (6.708 asserções, 1 pulado, o de sempre), saída 0; os portões que a correção toca
(referências dos docs, links mortos, recorte, segredos, débitos repetidos), saída 0; e o `conferir-tudo.sh` inteiro, **66 portões,
saída 0** -- a primeira tentativa saiu **124 às 17h55, o `timeout 590` do meu roteiro**, e não um portão (parou no "clones do
ensaio de corte"; o processo que ficou no Windows foi encerrado, e a segunda, sem teto, levou 26 min). O front e o servidor não
mudaram; os ensaios HTTP não rodaram de novo.

**Pela API, depois da correção** (a conferência do orquestrador, no banco de contrato MySQL, por HTTP real, com o administrador
temporário 90009902 e as fixtures; o banco remontado depois): o administrador recebe 200 na lista de hoje da unidade dele, no mês de
janeiro, na outra unidade (90001100, um caixa) e na rede (`caixas` nulo, 2 unidades), 200 no detalhe do 90001000, o 404 "Caixa não
encontrado." no 99999999, o 404 da rota no `/caixa/abc` e 200 na tela antiga; o gerente de fixture (gerente e caixa) e o
colaborador SEM o privilégio `Relatórios de caixa` recebem o 403 em todos os endereços da API e o 404 na tela antiga; o gerente COM
o privilégio (concedido só no banco de contrato, em `privilegios_usuarios`) recebe 200 na unidade dele (hoje e janeiro: a marca de
gerente vence a de caixa, a 271 (c)), o 422 na rede e na outra unidade, 200 no detalhe do caixa dele, o MESMO 404 no caixa da outra
unidade (90001100, que existe) e no inexistente, uma unidade só em `/unidades` e 200 na tela antiga. Nenhum defeito.

**Débitos:** o D665 reescrito com a medida da API e o que a trava resolve, e corrigido na verificação; o D471 relido; o D666
registrado na correção; de pé 181 para 182.
Nenhuma pergunta nova ao dono. Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; nenhuma retirada,
devolução, sangria ou fechamento de caixa rodou fora dos testes e da semente do banco de contrato, que foi remontado.

## 31/10/2026 -- REL-CAIXA-TELA (a fatia 62 da fila das telas que faltam)

**A CX3 e a CX5 do `docs/f3-relatorios/spec-caixa.md`: a tela nova do relatório de caixa, o detalhe de um caixa, o "ver os caixas"
do painel e a documentação, sobre a API da 61.** Retomada: um implementador anterior desta fatia foi interrompido pelo reinício do
computador e deixou 16 arquivos do front mudados e 13 novos, sem commit; o trabalho foi lido arquivo a arquivo contra o desenho, a
árvore conferida contra a cópia que ficou no rascunho (o diff idêntico, nenhum mutante aplicado, os fins de linha como na HEAD), e
tudo testado de novo do começo.

**O que muda para quem usa.**

- **O item "Caixa" do menu abre a tela nova**, e não mais a tela não migrada: o resumo do período (caixas e abertos, valor inicial,
  retiradas com as canceladas fora da soma e as devoluções de crédito contadas à parte dentro dela, saldo de todas as formas,
  gaveta esperada, troco quando houver, descontos, gorjeta, canceladas depois de finalizadas, vendas de outro dia e a quebra "—"
  com o aviso de que o fechamento ainda não pede o dinheiro contado); o recebido por forma, o mesmo cartão do painel; a tabela por
  caixa com a linha de total e as pendências; e o bloco "Caixas abertos de dias anteriores". O saldo e a gaveta negativos (a
  devolução de crédito além do saldo, a resposta 232 (c)) saem com o sinal e em vermelho.
- **O gerente** escolhe o período (um dia, uma semana, uma quinzena, um mês, ou datas livres até 31 dias), o operador, o estado e
  "só com pendência". **O administrador** escolhe também a unidade ou "todas" -- e, na rede, vê uma linha por unidade, que abre a
  unidade. **O caixa que não é gerente** vê só o dia de hoje da unidade dele, sem filtro e sem os esquecidos, com o "atualizar".
- **O detalhe de um caixa** abre no lugar da lista, com os mesmos blocos da tela do caixa (o resumo, o recebido por forma e as
  retiradas), a conferência (a gaveta, quando fechou ou há quanto está aberto, a última retirada, as devoluções, a gorjeta, o fora
  de comanda e a quebra), as vendas de outro dia e as canceladas, cada uma com "ver a venda" (Vendas Realizadas filtrada, só para
  quem a abre, e nenhuma para o caixa que não é gerente -- a correção, abaixo). Voltar não pede a lista de novo e devolve o foco ao botão do caixa. O caixa de fora
  do recorte diz "Este caixa não está neste recorte.".
- **O painel** ganhou o "ver os caixas" no recebido por forma (só para quem abre o Caixa), que abre o Caixa no período do painel (e,
  para o administrador, na unidade escolhida), e uma frase no rodapé: "Os caixas contam pelo dia de abertura; a venda lançada num
  caixa de outro dia aparece lá marcada.". O resto do cartão não mudou uma letra (conferido contra o painel da HEAD).
- **Para quem não muda:** a tela do caixa (abrir, retirar, fechar) é a mesma -- os blocos só foram exportados; a tela antiga do
  relatório continua no ERP como a 61 a deixou; nenhum número do servidor mudou.
- **A rede** anda na fila `caixa` do `filaDaRede.ts` (a trava por pessoa da 61): uma leitura de cada vez; a unidade e o detalhe não
  esperam.

**O que rodou.** A fatia é PAR: rodaram aqui só os testes do que ela toca; **o ciclo inteiro roda depois, no commit, pelo
orquestrador**.

- **Front:** os 34 arquivos da fatia e os achados no grep (o `/relatorios/caixa`, o `/painel`, o `TELA_DO_RELATORIO`, o
  `relatorioCaixa`, o `aoVerCaixas`, o `filaDaRede`, o `RecebidoPorForma` e quem importa os módulos tocados), `--maxWorkers=2`:
  **673 casos, saída 0**. Com `AMPLIFICAR_EFEITOS_MS=50`, os seis arquivos de efeito (290 casos): **saída 1, 2 vermelhos do
  `Console.teste.tsx`** (o "o valor unitário só é campo para o gerente" e o "sem vendas/comandasNova/escrever...", da comanda, que a
  fatia não toca) -- o D471, relido, e contado inteiro: com a fatia, os dois caíram no lote dos seis arquivos e em 1 de 2 rodadas do
  `Console.teste.tsx` sozinho; o `medir-p62-d471.py`, que troca SÓ o `Console.tsx` (o resto da árvore segue com a fatia, então o
  braço "HEAD" dele não é a HEAD), deu 0 de 8 com o da fatia e 2 de 8 com o da HEAD, numa saída que não foi gravada; e a HEAD de
  verdade, numa cópia do `git archive`, 0 de 7. **Nesta fatia não há prova de que o vermelho cai com a HEAD inteira**: a evidência é
  estatística e mistura árvores; o que sustenta que é o D471 é a comanda fora da fatia e o próprio débito, em que o "valor unitário"
  já caía antes desta tela existir (numa releitura anterior à 61, com o amplificador, sem nada do Caixa na árvore). O `tsc` e o build, saída 0; o lint, saída 0 (8 avisos, nenhum nos arquivos da fatia).
- **O pacote:** o JS de 1.344,65 KB (336,17 KB comprimido) na HEAD, construída à parte de um `git archive`, para 1.375,96 KB
  (**343,52 KB comprimido, +7,35 KB**, abaixo dos 15 KB do desenho); o CSS de 95,35 KB (17,00) para 96,35 KB (17,14).
- **Servidor** (nada mudou nele): o `RelatoriosNoMenuTest`, o `RelatorioDeCaixaTest` e o `RelatorioDeCaixaNoErpTest`, **119 casos em
  cada banco** (960 asserções), saída 0 nos dois.
- **Portões:** `conferir-acl-como-booleano.php` e `conferir-grupos-por-id.php`, saída 0; `conferir-segredos.sh`, saída 0;
  `contar-debitos.php` (de pé 182) e `conferir-debitos-repetidos.php`, saída 0; o `conferir-tudo.sh` inteiro, **66 portões, saída 0** (das 20h53 às
  21h17); como os docs da fatia foram escritos durante ele, as referências dos docs e os links mortos rodaram de novo sozinhos
  depois, saída 0.
- **O oráculo pela tela** (o da CX3): a semente da 61 nos dois bancos de contrato (três dias de março de 2014 na unidade 90001150,
  com Dinheiro, cartão e crédito do cliente, uma retirada ativa e uma cancelada, gorjeta, cancelada, venda de outro dia, troco e um
  esquecido aberto); o JSON do controller em processo (a lista da unidade, a rede e o detalhe de cada caixa) desenhado pela vista
  pura no vitest, e o que a tela mostra -- o resumo, as barras e os totais das formas, cada linha e a de total, a linha por unidade,
  os esquecidos e o detalhe de cada caixa -- contra as contas da seção 1.3 refeitas em Python sobre as linhas cruas: **332
  conferências, 0 divergências** (MySQL e PostgreSQL). Falsificado: com a coluna Gaveta da tabela trocada pelo saldo, 3
  divergências. Os dois bancos de contrato foram remontados depois. O saldo negativo e a devolução não estão na semente: são dos
  casos da vista. (A correção, abaixo, achou o extrator apagado e a saída da falsificação não gravada, e os refez.)
- **O `RecebidoPorForma`:** o `textContent` e o `outerHTML` do cartão do painel (com e sem fora de comanda, e vazio) conferidos
  contra o `Painel.tsx` da HEAD desenhado no mesmo vitest: iguais.

**Os mutantes**, um por vez, devolvidos pelo sha1, vermelhos pela asserção (nenhum por compilação), salvo o equivalente dito:

| # | Mutante | Quem ficou vermelho |
|---|---|---|
| P62-P1 (MC36) | sem o `vigente` | a resposta velha |
| P62-P2 (MC37) | a `EscolhaDoIntervalo` também no `dia` | os dois casos do `dia` |
| P62-P3 (MC38) | o detalhe sem a unidade do recorte | o pedido do detalhe; a regra |
| P62-P4 (MC39) | o detalhe com os filtros da lista, no invólucro | **equivalente sozinho**: a api manda só o recorte |
| P62-P4b (MC39) | a api do detalhe com a busca inteira | o caso novo do `api/relatorioDeCaixa.teste.ts` |
| P62-P4c (MC39) | as duas camadas juntas | o pedido do detalhe |
| P62-P5 (MC40) | voltar pede a lista de novo | a volta; o 404 |
| P62-P6 (MC41) | o foco não volta ao botão | a volta da lista e a dos esquecidos |
| P62-P7b (MC42) | "ver a venda" sem o `podeAbrir` | o caso do Console (a 1ª versão casava três linhas da tabela e não foi aplicada) |
| P62-P8 (MC43) | a `pendencia` fora do pedido | os filtros; o pedido do detalhe; a regra |
| P62-P9 e P9b (MC44) | sem o `AindaNaoGravado` na quebra, no resumo e no detalhe | a quebra e as canceladas; o resumo |
| P62-P10 (MC45) | "ver os caixas" sem `de` e `ate` | o gerente e o administrador pelo painel |
| P62-P11 (MC46) | a extração sem o aviso de outro dia | o `CaixaLigado.teste.tsx` |
| P62-X1 | a rede na fila de outro relatório | a fila é a do caixa |
| P62-X2 | a rede fora da fila | a rede espera a do ar; a fila é da aba |
| P62-X3 | o saldo e a gaveta negativos sem o sinal | o negativo |
| P62-X4 | o negativo sem a marca | o negativo; a regra; o detalhe |
| P62-X5 e X6 | a devolução de crédito sem a contagem à parte, na lista e no detalhe | o resumo, a linha, o total; a conferência |
| P62-X7 | o escopo `dia` com os filtros | os dois casos do `dia` |
| P62-X8 | "ver os caixas" sem o privilégio | sem o privilégio; sem a guarda |
| P62-X9 | uma chave do glossário com uma palavra a menos | o texto do desenho |
| P62-X10 | o `RecebidoPorForma` com uma palavra trocada | os três casos do texto de antes |
| P62-X11 | o 404 do detalhe sem a frase | o 404 |
| P62-X12 | o "tentar de novo" do 429 da rede com outro pedido | o 429 |
| P62-X13 e X14 | a escolha do período nasce com o guardado (no invólucro; na `EscolhaDoIntervalo`) | o atalho dos números |
| P62-X15 | a cancelada com "ver a venda" no `dia` | o `dia` |
| P62-X16 | "ver os caixas" sem a unidade dos números | o caso novo do administrador no `PainelLigado.teste.tsx` |
| P62-X17 | o item Caixa de volta à tela não migrada | os sete destinos; quatro do Console |
| P62-X18 | o rodapé sem a frase do caixa | o painel e o cartão |
| P62-X19 | a unidade da abertura também para o gerente | o gerente pelo painel |
| P62-X20 | as retiradas canceladas sem a contagem à parte | quatro casos da vista |
| P62-X21 | o estado sem a hora do fechamento | a linha; a regra |
| P62-X22 | a linha de total pelo primeiro caixa | o total |

Dois casos novos nesta retomada: o `api/relatorioDeCaixa.teste.ts` (o P4 sobreviveu e mostrou as duas camadas) e o do administrador
no `PainelLigado.teste.tsx` (a unidade dos números no "ver os caixas", que nenhum caso via).

**Documentação:** a CX3 e a CX5 marcadas FEITAS no `spec-caixa.md`, com o que divergiu; o item 25 do bloco "Em dia" do
`spec-comum.md`; a subseção "O Caixa" da seção 6 do `docs/f3-relatorios.md`. **Débitos:** os da seção 4 do desenho já estavam
registrados (D663, D664, D665); o D471 relido; nenhum novo; de pé 182. O Artifact do roadmap e o `/design-sync` (a prévia nova
`RelatorioDeCaixa`, seis cenários com nomes inventados) ficam para o orquestrador e o dono; a conferência no navegador (gerente,
caixa, 375 px, os dois temas) também. Nenhuma pergunta nova ao dono. Nada do esquema, do crontab, do OneSignal, da produção nem do
BVIP mudou; nenhuma retirada, devolução, sangria ou fechamento de caixa rodou fora dos testes e da semente do contrato, que foi
remontado.

**A correção depois da verificação (31/10/2026).** A verificação em três lentes confirmou 10 achados, todos baixos; os 10 foram
tratados:

1. **No escopo `dia`, o "ver a venda" das vendas de outro dia abria Vendas Realizadas vazia**: a Regra 1 do
   `RegraDaListagemDeVendas` força ao caixa as ativas E o dia de hoje, e a venda de outro dia de um caixa de hoje é sempre de um dia
   anterior. O desenho só tirara o botão da cancelada (pelo estado); agora, no `dia`, nenhuma venda o tem
   (`DetalheDoCaixaNoRelatorio.tsx`); o caso do `dia` no `RelatorioDeCaixaLigado.teste.tsx` passou a exigir as duas sem o botão.
2. **A frase "Nenhum caixa com estes filtros."** não estava no desenho nem nas divergências: fica (o molde da Indicação,
   `spec-indicacao.md:781`) e está declarada na nota FEITA da CX3, item (10).
3. **"Aberto há / hoje"** na conferência do caixa aberto no mesmo dia: agora "Aberto / hoje", como a tabela ("aberto hoje").
4. **O saldo negativo do detalhe saía sem a cor de perigo** que a nota FEITA (1) afirmava: o `ResumoDoCaixa` ganhou a prop
   `marcarNegativo`, que só o detalhe passa; sem ela, o DOM da tela do caixa do PDV é o de antes (um caso confere o `innerHTML`).
5. **A nota FEITA (3) citava o spec-ordem como se ele mandasse a unidade do caixa**: ele diz "a unidade do recorte". A nota agora
   declara a divergência (na rede, a do recorte seria 0, e Vendas Realizadas abriria na rede inteira).
6. **O caso do `dia` dizia que o bloco dos abertos some, mas a carga já vinha sem ele**: a vista passou a escondê-lo também no `dia`
   (`!doDia`), e um caso novo da vista traz o bloco na resposta de propósito; o caso do invólucro diz, num comentário, que prova a
   carga.
7. **O extrator do oráculo pela tela tinha sido apagado sem cópia**: reconstruído (a cópia-mestra no rascunho,
   `p62-oraculo-extrator.teste.tsx`; o `medir-p62-oraculo.py` o copia para `src/telas` só durante a rodada e o tira no fim). Com o
   mesmo JSON do controller gravado em 31/10 (`p62-oraculo-entrada-*.json`), o `medir-p62-oraculo-refeito.py` deu o texto da tela do
   PostgreSQL **byte a byte igual** ao gravado; o do MySQL difere em 3 folhas, as 3 células da Gaveta, porque o
   `p62-oraculo-tela-mysql.json` guardado era o da FALSIFICAÇÃO (que roda só no MySQL e sobrescreveu o arquivo) -- o reconstruído dá
   ali a gaveta certa (R$ 235,00, R$ 50,00, R$ 53,00, a mesma da conferência de cada detalhe). A falsificação refeita, gravada em
   `p62-oraculo-falso.txt`: com a Gaveta trocada pelo saldo, **3 folhas diferentes em cada banco**, as 3 células. O oráculo inteiro
   (contra o Python, com o contrato semeado) não rodou de novo: a tela do contrato não mudou com a correção (os caixas de 2014 têm
   milhares de dias, e nenhum é do `dia`).
8. e 9. **O D471 contado só pelo lado favorável, e o braço "HEAD" que não era a HEAD**: o parágrafo acima foi reescrito com todas as
   contagens. Rodado de novo com a saída gravada (`p62c-d471.txt`), quatro voltas: 1 de 4 com o
   `Console.tsx` da fatia e 1 de 4 com o da HEAD, na mesma volta. O débito D471 ganhou o mesmo.
10. **A nota FEITA da CX3 se contradizia sobre o MC39**: agora diz P4 (o invólucro sozinho) equivalente, P4b (a api sozinha)
    vermelho pelo caso novo, e P4c (as duas) vermelho pelo pedido do detalhe.

**Os mutantes da correção**, um por vez, devolvidos pelo sha1, todos vermelhos pela asserção (`p62c-mutantes.txt`):

| # | Mutante | Quem ficou vermelho |
|---|---|---|
| P62-C1 | a venda de outro dia com "ver a venda" no `dia` | o caso do `dia` do invólucro |
| P62-C2 | as duas listas com "ver a venda" no `dia` | o caso do `dia` do invólucro |
| P62-C3 | "Aberto há / hoje" de volta | o caso novo do aberto hoje |
| P62-C4 | o saldo do detalhe sem a marca | o caso do detalhe com a gaveta negativa |
| P62-C5 | a marca também no PDV (o padrão ligado) | o caso novo do `ResumoDoCaixa` sem a prop |
| P62-C6 | o bloco dos abertos no `dia` | o caso novo da vista com o bloco na resposta |

**Os testes da correção:** o front da fatia, os mesmos 34 arquivos, `--maxWorkers=2`: **676 casos (os 673 e 3 novos),
saída 0**; com `AMPLIFICAR_EFEITOS_MS=50`, o `RelatorioDeCaixaLigado`, o `RelatorioDeCaixa`, o `PainelLigado`, o `CaixaLigado`, o
`ConsoleAbreCaixa` e o `Console.teste.tsx`: **299 casos, saída 0** (o D471 não caiu desta vez); o build (com o `tsc -b`) e o lint,
saída 0 (os mesmos 8 avisos, nenhum nos arquivos da fatia); o pacote, **343,59 KB comprimido** (1.376,23 KB), +7,42 KB sobre a
HEAD. O servidor não mudou, e as três classes rodaram de novo: **119 casos em cada banco** (960 asserções), saída 0 no MySQL e no
PostgreSQL. **Portões:** `conferir-acl-como-booleano.php`, `conferir-grupos-por-id.php`, `conferir-segredos.sh`, `contar-debitos.php`
(de pé 182, como antes) e `conferir-debitos-repetidos.php`, saída 0; o `conferir-tudo.sh` inteiro, **66 portões, saída 0** (das 22h14
às 22h39), que deixou 2 travas `caixaDaRede-*.lock` de root em `/tmp` do contêiner (o D499), apagadas; depois dele, com este
parágrafo escrito, as referências dos docs e os links mortos de novo, saída 0. O `local.php` ficou no `franquiabv_dev` com o sha1 de
referência. Nenhum débito novo; nenhuma pergunta nova ao dono.

**O ciclo inteiro, depois da correção (a fatia é par; das 22h43 de 31/10 à 01h35 de 01/11/2026).** A caracterização inteira, **5.615
casos em cada banco**, saída 0 (12 pulados, como antes); contrato **74 e 74**; os 22 ensaios no PostgreSQL com saída 0; os **66
portões**; o front, **3.706 casos em 152 arquivos**. **Uma saída diferente de 0:** no MySQL, o `ensaiar-fluxo-adm-api` saiu **1**
-- a etapa rodou das 23h17 às 00h09, com o banco de contrato montado antes da meia-noite, o vermelho falso já conhecido do ensaio que
atravessa o dia; a etapa repetida sozinha, com o banco montado de novo (das 01h37 às 02h23), deu **22 de 22 com saída 0**. Depois, o
medidor dos relatórios no PostgreSQL (`p48`), saída 0. O comentário da `PoliticaDoRelatorio` que ainda dizia que o item abria a tela
não migrada foi corrigido (só o comentário); depois dele, o `conferir-recorte-por-unidade.php` (377 vigias) e o
`PortoesDaFamiliaDosRelatoriosTest` com o `RelatoriosNoMenuTest` (71 casos), saída 0.

**No navegador, depois do ciclo, contra o banco de contrato** (a semente da 61; um grupo temporário só de caixa para o `fiscal.teste`,
o privilégio "Relatórios de caixa" concedido ao gerente e a ele, e o administrador temporário -- tudo apagado com o banco remontado
depois): **o gerente** abriu o Caixa pelo "ver os caixas" do painel (o `de` e o `ate` do mês do painel) e pelo item do menu, que
abre a tela nova; o dia de hoje bate com o detalhe (saldo R$ 521,80, gaveta R$ 50,00, as 5 vendas de outro dia somando os R$ 531,80
do resumo, "Aberto / hoje"); o "ver a venda" abriu Vendas Realizadas com a venda certa e a unidade do caixa; o mês anterior vazio com
"Nenhum caixa aberto neste período."; voltar do detalhe não pediu a lista de novo e devolveu o foco ao botão do caixa. **O caixa que
não é gerente** viu só hoje, sem filtros nem esquecidos, com o "atualizar", e no detalhe as vendas de outro dia sem o "ver a venda" (a
correção 1). **O administrador**, em todas as unidades: uma linha por unidade, que soma o resumo (R$ 421,80 + R$ 239,90 = R$ 661,70
recebidos), e os dois esquecidos de 2014 da semente; o de 05/03/2014 com a gaveta de R$ 53,00 e o saldo de R$ 83,00 do oráculo, e a
volta com o foco no botão dele. A 375 px, sem rolagem lateral (a tabela rola dentro do cartão), e nos dois temas. Nenhum defeito.

## 01/11/2026 -- Quanto falta e o ritmo (refeitos no Artifact do roadmap, versão 210, depois da fatia 62)

Pela mesma régua de 16, 21, 26 e 29/10 (linhas de código com os testes, sem documentos, dumps, carga, fixtures, lockfiles nem o
inventário gerado, até o fim do Corte): **cerca de 70% feito, entre 66% e 73%** (era 68% em 29/10). Feito, ~568 mil linhas (as seis
fatias desde 29/10 somam 13,7 mil); falta ~205 a 290 mil: a fila, 32 blocos, ~75 a 105 mil (a previsão deles, ~63 mil, pela razão
real sobre o previsto de 1,17, a das cinco fatias da fila desde 29/10, e de 1,63, a de todas desde 14/10, não recontada); os débitos
de pé fora da fila, cerca de 170 dos 182 (não recontei quais a fila leva), ~120 a 165 mil; o preparo do corte, ~10 a 20 mil. No menu,
35 dos 51 destinos abrem a tela nova; nos débitos, 482 dos 664 não estão mais de pé (72,6%).

**O ritmo**, de 29/10 às 05h28 a 01/11 às 02h53 (69,4 h de relógio, com as pausas: os dois reinícios da máquina, na 60 e na 62, o
Docker fora na 58b e as pausas do dono): 6 fatias (58, 58b, 59, 60, 61 e 62), mediana de 10,7 h entre uma e a seguinte, média de
11,6 h, ~2.300 linhas por fatia e ~200 linhas por hora de relógio (eram 3,5 h, ~2.800 e ~790 em 29/10). Sem pausa conhecida, a 58 e a
61 levaram 5,5 e 5,2 h (~330 a 390 linhas por hora). **Quanto tempo:** de 2 a 3 semanas sem parar (perto de 15 a 21/11), de 5 a 8 com
pausas como as destes dias (começo a fim de dezembro); ~335 a 470 h. Fora da conta, as janelas do corte na produção e o texto das
campanhas (92g), que dependem do dono.


## 01/11/2026 -- REL-ATENDIMENTOS-CONTAS (a fatia 63 da fila das telas que faltam)

**Para quem usa, nada muda:** sem rota, sem menu e sem tela; a política `atendimentos` segue com `naTelaNova => false`, e o
item "Atendimentos" continua levando ao ERP antigo. A A2 (a fatia 64) liga a API e a tela.

**O que entrou** (a A0 e a A1 de `docs/f3-relatorios/spec-atendimentos.md`, com o bloco "Em dia" do `spec-comum.md`, item 26, por
cima): o período `ano` na base -- `RecorteDoRelatorio::ano` (vazio é o ano de hoje; recusa a lista, o que não é `AAAA`, o ano
que não começou e o antes de 2000; doze meses, cada um que já chegou com a faixa e a comparação do `interpretar` do painel daquele
mês, os outros `futuro`; a busca desde o 1º de dezembro do ano anterior) e `::anoDaUnidade` (o piso da unidade, para a guarda
própria da A2); o despachante chama o `ano`. **O gancho das guardas próprias já existia** desde a fatia 42 (a C1, que o criou;
a fatia 43 pôs a `exportacaoAction` a chamá-lo), e o controller não mudou; o ramo do ano no nome do arquivo fica para a A3 (o item 8 do `spec-comum.md`). As contas puras do ano:
`ContasDoPainelDaUnidade::porCategoria` (pela categoria da lista fechada, "sem" para o resto),
`ContasDoProfissionalParceiro::dosColaboradores` (a linha de vários colaboradores numa passada; o `doColaborador` delega) e o
`foraDaLista` com a lista dada, e o `ISRelatorio\Regra\ContasDosAtendimentos` novo (os itens por mês, a lista do ano, as linhas
de cada mês e do ano, o fora da lista pelo ano, a linha da unidade com o numerador das médias e o anterior, os candidatos e os
clientes novos). Nenhuma consulta nova.

**O oráculo** (`medir-p63-oraculo.py` contra `medir-p63.php`, no rascunho; só SELECT na cópia, sem nome): a unidade 1, 2026 (em
andamento, hoje 01/11) e 2025 (fechado), nos dois bancos, os meses contados em Python sem o painel e a estreia pelo MIN da história
inteira: **21.382 conferências, 0 divergências** (4.859 em cada banco em 2026, 5.832 em 2025). 2026 dá o da seção 1.5 do desenho:
R$ 1.964.346,85, 4.660 clientes, 18 linhas, fora 3 com R$ 3.926,50, janeiro R$ 250.687,90 contra R$ 315.337,28. Falsificado (o fora
do ano com um a mais e o anterior de janeiro trocado), **2 divergências**, gravadas em `p63-oraculo-my-2026-falsificado.txt`. A
primeira forma da estreia no oráculo (o EXISTS por cliente, a do repositório) passou de 12 min no PostgreSQL e foi cancelada
(`pg_cancel_backend` da minha consulta); a de duas junções mediu em segundos.

**A medida** (unidade 1, a maior; MySQL com buffer pool de 128 MB; `memory_limit` 512M, o do contêiner; duas voltas nas duas
ordens, três rodadas por processo; o frio é a primeira rodada de cada processo, e não o buffer pool frio, que o processo anterior
já aquecera): o ano lido de uma vez **passa de 128 MB** -- 146 MB em 2026, 214,5 MB em 2025; com 128M os dois anos caem em erro
fatal no `normalizar`, com 256M no cache de metadados do Doctrine (o D646). O `painelPrimeiroServicoOuCredito` dos ~4.700
candidatos do ano: **no MySQL, frio de 11,9 a 26,7 s e quente de 11,4 a 20,8 s**; no PostgreSQL, frio de 0,94 a 1,35 s e quente de
0,51 a 0,83 s; os itens, frio de 3,5 a 11,4 s e quente de 1,6 a 11,2 s no MySQL, frio de 2,1 a 2,85 s e quente de 0,96 a 1,7 s no
PostgreSQL; as contas puras, frio de 0,97 a 2,50 s e quente de 0,79 a 1,65 s no MySQL, de 0,79 a 1,53 s no PostgreSQL; os novos,
0,31 a 1,05 s. **A bancada sintética do desenho (53 mil itens) passa dos 2 s pedidos:** de 1,83 a 2,65 s em oito rodadas (o
"1,495 s" da primeira escrita não estava em saída guardada nem se repetiu; a verificação, abaixo). **Registrado no D667**, para a
A2 e a A3 decidirem a leitura do ano (mês a mês, como os Royalties).

**O que rodou (a fatia é ímpar: só os testes do que ela toca):** as 36 classes da fatia e das vizinhas (as das contas e das
peças comuns tocadas, e as dos oito relatórios que usam a base), **912 casos em cada banco, saída 0** (7.767 asserções no MySQL e
7.766 no PostgreSQL; um pulado, a bancada). Uma primeira rodada, antes dos últimos ajustes de teste, deu 911 e 911, saída 0. Os
portões `conferir-recorte-por-unidade.php` (377 métodos), `conferir-cobertura-por-par.php` (sem par novo, como o desenho previa),
`conferir-relogio-nos-testes.php` e `conferir-segredos.sh`, saída 0; o `conferir-tudo.sh` inteiro, sozinho, depois de apagar 22
travas de `/tmp` do contêiner criadas como root pelas suítes: **66 portões, saída 0**.

**Os mutantes**, cada um sozinho, devolvido byte a byte e conferido pelo sha1, todos vermelhos pela asserção: P63-P1 a P8 (os AM1 a
AM8; o P7 e o P8, o gancho arrancado e o gancho depois de soltar, nos dois bancos e na vigia; o P7 só depois da verificação no
caso do ano, abaixo), P63-P9 a P19 (os AM9 a AM19), e os da
fatia: X1 o mês errado, X2 o anterior lido do próprio mês, X4 o desempate pelo id invertido, X5 a lista sem o próprio, X6 os
candidatos do próprio pela regra da unidade, X7 o `porCategoria` sem o filtro do período, X8 a célula sem as gavetas, X9 a recusa
da guarda própria ignorada (nos dois bancos e na vigia), X10 o `naLista` sempre verdadeiro, X11 o ano nunca em andamento, X12 o
`ate` sem o dia a menos, X13 sem o `trim` e X14 o ano dos novos da linha pelo último mês. O P1, o P2 e o X9 primeiro só quebravam
por erro (o ano aceito quebrava adiante); os casos passaram a converter o erro em valor, e ficaram vermelhos pela asserção. **Não
há divisão nas contas da A1** (as médias são da A2, pelo `porCada`): o mutante "a divisão por zero sem a guarda" não se aplica. O
fora do ANO sem a lista dada é equivalente na unidade (a lista do ano é, por construção, quem fez serviço no ano).

**Débitos:** o D667 registrado; de pé 182 para 183. Os D-at1 a D-at3 da seção 4 do desenho ficam para a A2 (a tela antiga ao lado
da nova). Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; na cópia, só SELECT.

**A verificação da fatia 63** confirmou 5 achados, todos baixos e tratados, nenhum ao dono: (1) a origem do gancho das guardas
próprias estava como "fatia 43" no `spec-atendimentos.md`, no item 26 do `spec-comum.md` e aqui: ele nasceu na 42 (b1d972ec, a
C1), e a 43 (a8693eb7, a C3) pôs a `exportacaoAction` a chamá-lo; (2) com o AM7 (o gancho arrancado), o caso do ano caía por ERRO --
o pdf de 2001 seguia até o `LogicException` do nome do arquivo do ano -- e a falha vinha só do caso antigo da Cota-parte: o caso
converte agora a `LogicException` no 500, e o P63-P7, o P8 e o X9, rodados de novo um por vez com o veredito POR CASO
(`medir-p63c-mutantes.py`), ficaram vermelhos pela asserção nos dois casos e nos dois bancos, com o controle sem mutante verde
(19 casos, 197 asserções); (3) a bancada, os 128M e os 256M não estavam em saída guardada: rodados de novo e guardados
(`p63c-bancada.txt`, `p63c-bancada-pedacos.txt`, `p63c-limite.txt`), **o "1,495 s" da bancada não se repetiu** -- oito rodadas de
1,83 a 2,65 s, seis acima dos 2 s do "Medir" da A1, e 193 MB de pico (e não 261,5) --, e os dois anos caem com 128M no
`normalizar` e com 256M no cache de metadados; o D667 diz onde vai o tempo (a `unidade` refaz o `dosColaboradores` e o
`porCategoria` que as `linhas` já fizeram) e o "menos de 2 s" fica para a A2; (4) o relatório da implementação dava as contas puras
de 0,79 a 2,50 s também no PostgreSQL, onde foram de 0,79 a 1,53 s (o texto daqui já estava certo); (5) a medida misturava o frio
e o quente: separados aqui e no D667, com o aviso de que o frio é o do processo, e não o buffer pool frio. Depois: as 36 classes,
**912 casos em cada banco, saída 0** (7.767 asserções no MySQL e 7.766 no PostgreSQL, um pulado, a bancada); os portões
`conferir-recorte-por-unidade.php` (377 métodos), `conferir-relogio-nos-testes.php`, `conferir-referencias-docs.php`,
`conferir-links-mortos.php`, `contar-debitos.php`, `conferir-debitos-repetidos.php` e `conferir-segredos.sh`, saída 0. A bancada
com `MEDIR_ATENDIMENTOS=1` sai 1 em seis de oito rodadas (a asserção dos 2 s), de propósito fora da suíte. De pé, 183.

## 01/11/2026 -- REL-ATENDIMENTOS-API (a fatia 64 da fila das telas que faltam)

**Para quem usa:** o item "Atendimentos" do menu abre a tela nova. O ano de uma unidade, mês a mês: o cartão da unidade (a linha do
antigo Dash. Faturamento -- faturamento, descontos, atendimentos, ticket, clientes, médias por cliente, serviços e produtos por
categoria, cortesias, dias trabalhados e o crescimento em porcentagem e em reais) e a tabela por colaborador, com o seletor
"Número", o "fora da lista" e a linha da unidade; o mês em andamento vai até hoje, os que não chegaram ficam com "—", o mês com
item e sem serviço leva "sem serviço". O administrador escolhe a unidade (sem "todas as unidades": o ano da rede espera o "mês
fechado"); o gerente vê a unidade da sessão; quem não é gerente nem administrador, o caixa inclusive, vê só a própria linha. "ver o
histórico" abre o histórico do Profissional Parceiro no lugar, para quem o abre. No ERP antigo, o item do menu passa pela mesma
política, e o arquivo de quem vê só a si mesmo é só o PDF do bloco dele. Os clientes novos e a exportação nova são a A3 (a 65).

**O servidor:** `Adm\AtendimentosController` (`GET /adm/api/relatorios/atendimentos` e `.../unidades`), não final, com as guardas da
base, as próprias (a rede, 422; o ano antes da criação da unidade, 422) e só então a sessão solta; a trava por pessoa
(`atendimentos-<id>`, o 429, no `finally`); o ano lido MÊS A MÊS (`ContasDosAtendimentos::faixasDaBusca`) e a grade NUMA PASSADA
(`ContasDosAtendimentos::grade`); o `AtendimentosJson`. A política com o `naTelaNova`, a rota, a fábrica, o `veOMenu` do
`layout.phtml`, e a tela antiga com o `pedidoDaExportacao`. **A tela:** `api/atendimentos.ts`, `Atendimentos.tsx`,
`AtendimentosLigado.tsx`, `NavegacaoDoAno.tsx`, `numerosDosAtendimentos.ts`, a linha da `TELA_DO_RELATORIO`, o `quando` do menu, a
fila `atendimentos`, o glossário (`atendimentos.*`) e a prévia (`.design-sync/previews/Atendimentos.tsx`, seis cenários).

**O oráculo** (`medir-p64-oraculo.py` contra `medir-p64.php`, o controller em processo; só SELECT, sem nome): na cópia, a unidade 1
em 2026 e 2025, nos dois bancos, **18.614 conferências, 0 divergências** -- 2026 dá a tabela 1.5 do desenho (R$ 1.964.346,85, 4.660
clientes, 18 linhas, fora 3 com R$ 3.926,50, crescimento de janeiro −20,5 e de julho 11,3, consumo por cliente de julho 116,11 e do
ano 385,76); no contrato, a unidade das fixtures em 2026 e o cenário do `AtendimentosTest` semeado (2023, 2024 e 2026) e depois
apagado, **4.890 conferências, 0 divergências** nos dois bancos; falsificado (o fora com um a mais e o anterior de janeiro trocado),
**2 divergências**, gravadas (`p64-oraculo-falsificado.txt`). Para abrir no navegador: `docker cp` do
`p64-semear-contrato.php` do rascunho para `/tmp` e `php /tmp/p64-semear-contrato.php franquiabv_contrato` no contêiner (com
`apagar` no fim, o tira); o gerente.teste não é da unidade U: entre como administrador e escolha "Unidade Atendimentos U (teste)",
ano 2024 (A e B nas linhas, C no fora, e o crescimento de janeiro +120,0%).

**A medida** (o D667, PARCIAL; unidade 1, `memory_limit` 512M, três rodadas por processo nas duas ordens, o frio é a primeira):
o pico do PHP de 88 a 90 MB em 2026 (era 146) e de 131 a 133 MB em 2025 (era 214,5) -- **com 128M, 2025 ainda cai**; com 256M,
passam os dois. MySQL com **128 MB de buffer pool**: 2026 de 4,6 a 6,6 s frio e de 2,6 a 3,8 s quente; 2025 de 6,5 a 10,2 s frio e
de 4,3 a 7,2 s quente; com **2 GB** (e de volta a 128 MB, conferido pelo `SELECT @@innodb_buffer_pool_size`), 2026 de 4,9 a 5,2 s
frio e 2,6 a 2,8 s quente, 2025 de 6,6 a 6,8 s frio e 4,0 a 4,9 s quente; no PostgreSQL, 2026 de 3,9 a 4,5 s frio e 1,6 a 1,8 s
quente, 2025 de 5,1 a 5,4 s frio e 2,7 a 3,1 s quente. Nenhum acima dos 15 s do desenho, nenhum pico acima de 256 MB. A bancada
sintética: a grade numa passada de 0,64 a 0,83 s em seis rodadas (as quatro funções da A1, de 1,61 a 2,14 s). Saídas em
`p64-medida.txt`, `p64-limite.txt` e `p64-bancada.txt` do rascunho. O pacote do front: 343,59 KB (a HEAD, construída à parte) para
346,76 KB comprimido (+3,17 KB; o desenho: até 15).

**O que rodou (a fatia é par: o ciclo inteiro roda depois, pelo orquestrador; aqui, só o que a fatia toca):** o filtro de 38 classes
da fatia, das vizinhas e dos relatórios que usam a base (por expressão: casa também as de nome parecido), **1.208 casos em cada
banco, saída 0** (9.907 asserções no MySQL e 9.906 no PostgreSQL; dois pulados, a bancada fora da suíte e um de antes desta fatia). A primeira rodada no MySQL saiu 1 pelo `D105CacheVazioDosRelatoriosTest`
(a tela antiga lê a política pelo usuário inteiro, que a sessão dupla não tinha -- o D626; o caso passou a usar a sessão do
`RelatorioEmProcesso`). Os ensaios: o `ensaiar-permissao-adm-api` no contrato MySQL (montado antes) e no PostgreSQL, **165 de 165
recusas** e o colaborador e o caixa com 200 `proprio` no Atendimentos, saída 0 nos dois -- o meu roteiro em volta do MySQL saiu 124
(o `timeout 590` dele, às 07:25; o ensaio terminou com saída 0 e o `local.php` voltou, conferido pelo sha1); o
`medir-adm-api-no-postgres` com as duas folhas novas, **saída 0**. O front tocado, 12 arquivos, **300 casos, saída 0**, e de novo
com `AMPLIFICAR_EFEITOS_MS=50`, 300 de 300 (o vermelho conhecido do `Console.teste`, o D471, não caiu desta vez); tsc, lint (só os
avisos de antes) e build. **Os portões:** o `conferir-recorte-por-unidade.php` com 388 métodos (eram 377: as onze vigias
novas do Atendimentos, lidas sem comentários), o `conferir-cobertura-por-par.php` (413 pares, o sha1 igual; o ISRelatorio de 166
para 168 chamadas, de propósito), o `contar-debitos.php` e o `conferir-debitos-repetidos.php`, saída 0. O `conferir-tudo.sh` inteiro,
sozinho, depois de apagar as travas de `/tmp` criadas como root: a primeira vez **saiu 1** (terminou às 09:36), com 65 de 66 portões e o
`conferir-relogio-nos-testes.php` vermelho -- o caso novo das faixas lia o relógio do PHP (`date` e `strtotime` só para tirar um
dia de uma data); trocado por `DateTimeImmutable` sobre a data (gravado às 09:34:58), e o `conferir-tudo.sh` de novo: **66 portões,
saída 0**. A rodada da classe nos dois bancos depois da troca NÃO ficou em saída guardada (a verificação o achou): foi refeita na
correção, abaixo.

**Os mutantes**, cada um sozinho, devolvido byte a byte e conferido pelo sha1, os do servidor nos DOIS bancos e na vigia, com o
veredito POR CASO (`medir-p64-mutantes.py` e `medir-p64-mutantes-front.py`): P64-P1 a P8 (os AM20 a AM27) e P64-X1 a X15 no
servidor; P64-AT1 a AT8 e P64-C1 a C13 na tela -- todos vermelhos pela asserção, menos o **X6 (a trava sem o `finally`),
equivalente**: o handle sai de escopo ao fim da ação e o PHP solta o `flock` (o X16 da 61). Na primeira volta, o P3 (a guarda da
rede arrancada) e o X1 (o index sem as guardas próprias) caíam por ERRO (o `find(null)` adiante), o X15 (o numerador das médias da
unidade só de quem está na lista), o AT5 (o seletor que pede), o C9 (a navegação ligada sem os números) e o C12 (os números fora da
fila) ficavam verdes: o caso passou a converter a exceção no 500, o teste das contas ganhou um colaborador fora da lista que vende a
cliente com cadastro, e os do front a espera dos efeitos antes de contar os pedidos e a navegação desligada no 422; rodados de novo,
vermelhos pela asserção (o AT5 primeiro chamava uma função fora de escopo, o que não era mutante: refeito passando a função ao cartão).
O AT8 ("o próprio com o cartão da unidade") não existe na vista, que desenha o que o servidor manda: o mutante da vista tirou a frase
do próprio, e a unidade nula é o P1.

**Débitos:** D306 e D307 atualizados (PARCIAL), D667 PARCIAL, D668 e D669 registrados, D670 registrado e PARCIAL (o formulário antigo
ainda lista os nomes dos colegas ao próprio); os pares novos do `conferir-debitos-repetidos.php` (D668|D670, D651|D668 e
D668|D669) acrescentados aos conhecidos, com o motivo; **de pé, 183 para 186**. **Ao dono, no lote:** a P-a (a régua das médias da linha da unidade; a tela segue o
desenho, e o caminho de volta é uma linha nas contas), a P-b e a P-c (da A4; nada muda agora). Nada do esquema, do crontab, do
OneSignal, da produção nem do BVIP mudou; na cópia, só SELECT; no contrato, a semente do cenário foi posta e tirada pela faixa dele.

**A verificação da fatia 64** (três lentes) confirmou 8 achados, um médio e sete baixos, todos tratados na correção:
- **(médio)** a subseção "### O Atendimentos" do `f3-relatorios.md` tinha entrado na seção 7, entre o Radar e o Fluxo: movida para
  a seção 6, depois do Caixa, antes do `---` (a 2.10 do desenho manda a 6);
- a tabela por colaborador ignorava o `linha` do `NUMEROS`: com "Crescimento (%)" ou "(R$)" no seletor, cada colaborador virava uma
  fileira de "—", e no próprio a tabela inteira. Agora o número sem linha de colaborador mostra só a linha da unidade, e o seletor
  só oferece o número que tem alguma linha na tabela (o próprio não vê o crescimento). Caso novo na vista; mutantes **P64-C14** (as
  linhas de volta) e **P64-C15** (o seletor sem o filtro), vermelhos pela asserção;
- o comentário do topo de `api/relatorios.ts` ainda dizia que o Atendimentos poria o `PeriodoDoAno` na união: agora aponta para
  `api/atendimentos.ts` e diz por quê; o `spec-ordem.md` (1.8) ganhou a nota de que a A2 não acrescentou (só texto, sem caso);
- a pergunta P-c dizia "113 o do Atendimentos", que é a sobreposição: contado de novo hoje na cópia (só SELECT, só contagem,
  `p64c-publico.sql` e `p64c-publico-saida.txt` do rascunho), 115 veem o Dash. Faturamento, 113 deles também o Atendimentos, e 118
  veem o Atendimentos -- iguais ao desenho; a frase foi corrigida no lote;
- **a estreia rápida que as notas pediam (duas junções no lugar do `EXISTS` por cliente) não foi tentada**: a A2 não chama a
  estreia, mas a de 11 a 27 s no MySQL segue intocada para a A3 (a 65), que começa por ela -- dito no D667 (segue PARCIAL) e na
  nota FEITA da A2;
- o glossário criava `atendimentos.mesEmAndamento`, fora da convenção: o Atendimentos compara, e passou a usar a
  `relatorios.mesEmAndamento`, mais a `atendimentos.futuro` ("Os meses que ainda não chegaram ficam com “—”."). Mutante **P64-C17**
  (sem a chave comum), vermelho pela asserção do `glossario.teste.tsx`;
- nenhum caso provava o foco depois de trocar o ano: o caso do "‹" do `AtendimentosLigado.teste.tsx` confere que o foco fica no
  mesmo botão, no ar e depois da resposta. Mutante **P64-C16** (a `NavegacaoDoAno` com `key={recorte?.ano}`), vermelho pela
  asserção;
- a rodada final da `RegrasDosAtendimentosTest` não tinha saída guardada: refeita, **17 casos, saída 0, nos dois bancos**
  (`p64c-regras-mysql.txt` e `p64c-regras-pg.txt`; um pulado, a bancada).

Depois da correção, em série: as 38 classes da fatia e das vizinhas, **1.208 casos em cada banco, saída 0** (9.907 asserções no
MySQL e 9.906 no PostgreSQL; `p64-classes-mysqlcorr.txt` e `p64-classes-pgcorr.txt`); o front que a fatia toca e os que leem o
glossário, o menu e os tipos dos relatórios, 36 arquivos, **680 casos, saída 0**, e de novo com `AMPLIFICAR_EFEITOS_MS=50`, 680 de
680; tsc, lint (8 avisos, nenhum dos arquivos da fatia) e build com **346,79 KB** comprimido (era 346,76 na fatia; +3,20 KB sobre a
62). Os mutantes novos, cada um sozinho e devolvido pelo sha1 (`medir-p64-mutantes-front.py`). Os portões que a correção toca,
depois dos documentos: o `contar-debitos.php` (de pé, 186, sem mudar), o `conferir-debitos-repetidos.php`, o
`conferir-referencias-docs.php`, o `conferir-links-mortos.php` e o `conferir-segredos.sh`, saída 0 (`p64c-portoes.txt`); o
`conferir-tudo.sh` inteiro não rodou de novo (fica no ciclo). Nada do servidor mudou na correção; o `local.php` ficou no `franquiabv_dev`, conferido pelo sha1.

**O ciclo inteiro da fatia 64** (a fatia é par; rodou depois da correção, em série, com o `local.php` conferido pelo sha1 e as
travas de `/tmp` apagadas antes): caracterização **5.660 em cada banco, saída 0** (12 pulados); contrato **74 e 74**; os **22
ensaios em cada banco com saída 0**; os **66 portões**; front **3.734 em 156 arquivos**; e o medidor dos relatórios no PostgreSQL
(`p48-medir-relatorios-pg.sh`) com saída 0. Nenhum vermelho.

**No navegador, contra o banco de contrato** com o cenário da fatia (`CenarioDosAtendimentos`, semeado pelo
`p64-semear-contrato.php`; a Ana feita gerente da unidade U e o Bruno colaborador, os dois com o privilégio, só no contrato): o
administrador pelo menu, a unidade U em 2026 (outubro R$ 60, novembro "até hoje" R$ 100, dezembro "—") e, pelo "‹" com o foco
no botão, 2025, 2024 (o dezembro com o serviço da Ana pelo pacote, R$ 0 no faturamento, e o produto do Bruno "sem serviço") e
2023, onde o "‹" fica `aria-disabled` ("antes do começo da unidade") sem perder o foco; 375 px sem rolagem lateral da página e os
dois temas. O gerente pelo menu, sem o seletor de unidade, com os mesmos números; pela API, a rede e a unidade V com 422. O
colaborador só com a própria linha, sem a tabela da unidade e sem o Crescimento no seletor; o JSON dele sem colega e sem os totais
da unidade. Pela API como administrador: a rede, 2022 (antes da criação), 2027 e a lista de anos com 422. **Um defeito de texto**:
a frase do piso saía "começou em 2023: O Atendimentos não mostra..." -- o controller passa "o Atendimentos" ao
`RecorteDoRelatorio::anoDaUnidade` (a exportação antiga já passava "Atendimentos"); o caso do `AtendimentosTest` e os três textos
do front acompanharam. Depois: o `AtendimentosTest` e o `AtendimentosAntigoTest` (o filtro pegou as vizinhas: 37 casos) com saída 0
nos dois bancos, e os dois arquivos do front, 21 casos, saída 0. A limpeza: os cookies expirados, as sessões do contêiner
apagadas, o `local.php` de volta ao `franquiabv_dev` (sha1 OK) e o banco de contrato remontado -- o "apagar" da semente caiu na
chave dos `usuarios_acessos` gravados pelos logins e a transação desfez; a remontagem tirou tudo (0 usuários e 0 vendas do cenário).

## 01/11/2026 -- REL-ATENDIMENTOS-TELA (a fatia 65 da fila das telas que faltam)

**Para quem usa:** o Atendimentos da tela nova ganhou os **clientes novos** e o **PDF e o XLS**. O "Clientes novos" aparece no
seletor do número e na tabela da unidade, mês a mês e no ano, pela estreia na rede (o primeiro serviço ou a primeira compra de
crédito, em qualquer unidade -- a régua do painel), e conta para quem fez o serviço da estreia. Ele chega depois dos números:
as células mostram "…" enquanto contam e "—" na falha, com uma linha "Os clientes novos não vieram: ..." e o "tentar os
clientes de novo", que pede só eles. Debaixo da tabela da unidade, "N clientes novos em AAAA, dos quais M sem colaborador
(estrearam comprando crédito ou com serviço em outra unidade)". Os botões "abrir o PDF" e "baixar a planilha (XLS)" ficam no
cabeçalho, com o ano e (do administrador) a unidade dos números na tela, e desligados sem os números ou relendo. O arquivo é
`atendimentos-2026-unidade-1.pdf` (ou `.xls`), um bloco por colaborador como o arquivo de hoje (os números nas linhas, os meses
e o ano nas colunas), mais o bloco do fora da lista e o da unidade -- a forma veio da verificação da fatia, abaixo --, o mês que não
chegou com "—" no PDF e vazio no XLS, e as notas do "Como os números são contados". O colaborador que vê só a si mesmo recebe só a
linha dele nos clientes, e só o bloco dele no arquivo. O "Como os números são contados" ganhou a regra dos clientes novos e, nas médias da unidade, a
diferença para o Dash. Faturamento antigo (a resposta 274 (a) do dono).

**A estreia primeiro (o D667).** A forma agrupada do oráculo da 63 (o `MIN` do crédito e o `MIN` do serviço pela junção nos
itens) foi medida lado a lado com o `VendaRepository::painelPrimeiroServicoOuCredito` de hoje, na cópia, unidade 1, sobre os
candidatos do ano (`medir-p65-estreia.php`, `p65-estreia.txt`): **o mesmo resultado onde foi comparada** -- no PostgreSQL,
513 clientes em 2026 e 697 em 2025, o mesmo sha1, nas duas ordens; no MySQL, só 2026 (513, o mesmo sha1), numa ordem só -- e
**mais lenta nos dois**: no MySQL com 128 MB de buffer pool, 302 e 305 s contra 22,8 e 33,8 s (2026; o 2025 foi cortado às 14:45:
o laço do roteiro parado, o PHP morto por `/proc` e a consulta, só de leitura, por `KILL QUERY`); no PostgreSQL, de 6,3 a 13,5 s
contra 0,7 a 2,5 s. O corte dos 120 s das notas veio tarde: o roteiro não tinha corte por tempo, e a forma nova rodou DUAS vezes
acima de 300 s antes de o 2025 começar (a verificação da fatia; o `medir-p65-estreia.php` do rascunho ganhou o corte depois). Não trocou: a consulta, o
painel, o histórico, os novos do recorte e a Indicação não mudaram.

**O servidor.** `GET /adm/api/relatorios/atendimentos/clientes` (`clientesAction` e `montarClientes`): as guardas da base, as
próprias (a rede, o ano antes da unidade) e só então soltar a sessão; os itens do ANO mês a mês (no próprio, só os dele), as compras
de crédito só fora do próprio, a lista do ano (os mesmos ids na ordem dos números), os candidatos, a estreia perguntada uma vez desde
o início do ano e `ContasDosAtendimentos::novos`; uma trava por pessoa própria (`atendimentosClientes-<id>`, o 429), para a troca
de ano não esperar os clientes do ano velho. `.../exportacao` é a da base, com o `documento()` (`AtendimentosJson::documento`, das
MESMAS respostas, sem conta nova) e o `AtendimentosJson::clientes`; o `NUMEROS` ganhou `novos`. Na base, o ramo do ano no
`nomeDoArquivo`; no `PdfDoRelatorio`, o nulo como "—". As rotas `clientes` e `exportacao` na família. A frase de uma recusa é a
mesma no `index`, nos `clientes` e na exportação (a guarda é uma só; o caso confere o corpo inteiro). **A tela:**
`api/atendimentos.ts` (`ClientesDosAtendimentos`, `lerClientesDosAtendimentos`), `AtendimentosLigado.tsx` (o
`usePedidoDeDepois` com o ano e a unidade do recorte que os números aplicaram, zerado a cada troca, na fila
`atendimentosClientes`; o endereço da exportação), `Atendimentos.tsx` (as células pelo `ValorDeDepois`, a linha da falha, a frase
dos sem colaborador, os `BotoesDeExportacao`), `numerosDosAtendimentos.ts`, o glossário (`atendimentos.novos`, a diferença das
médias) e a prévia (os clientes chegados, os que não vieram, e os botões). **O que divergiu do desenho** está na A3 do
`spec-atendimentos.md` (FEITA) e no item 28 do bloco "Em dia" do `spec-comum.md`.

**As respostas 274 a 276 do dono (todas (a)):** a régua nova das médias da unidade fica, com a diferença escrita na tela e no
arquivo; uma unidade e um ano por vez, e o Dash. Faturamento sai na A4 sem substituto para a tabela de várias unidades; na A4,
quem via o Dash. Faturamento e não via o Atendimentos o recebe. Escritas na A4 do desenho, no D306 e no D307. Nenhuma pergunta
nova ao dono.

**O oráculo** (`medir-p65-oraculo.py`, montado do da 63 sem mudar as contas cruas: a estreia pelo `MIN` de SQL próprio, e não
pelo código; só SELECT; nenhum nome), contra o `/clientes` do controller em processo (`medir-p65.php`): na cópia, unidade 1, 2026
e 2025, nos dois bancos, **1.038 conferências, 0 divergências**, e julho de 2026 dá **57**, o do painel (`p65-oraculo-copia.txt`);
no banco de contrato com o `CenarioDosAtendimentos` semeado (2024, 2026 e 2023), o `/clientes` e o XLS baixado e lido no contêiner
pelo PhpSpreadsheet (os novos, o faturamento, os clientes, os atendimentos, os descontos, o fora e a ordem das linhas), **1.090
conferências nos dois bancos, 0 divergências**; falsificado (um novo a mais em julho e o ano de um colaborador), **2**, gravadas em
`p65-oraculo-contrato-falsificado.txt`. A primeira rodada do oráculo da cópia saiu 1 nos quatro recortes (o padrão `memoria=128M`
do oráculo da 63 derrubou o cache do Doctrine; refeita com 512M), e a primeira do contrato no MySQL saiu 1 nos quatro (um
`KeyError` do medidor: o `$formato` zerado junto com o `$GLOBALS['formato']`; corrigido, o contrato remontado e o oráculo refeito).

**A medida** (`medir-p65-tempo.sh`, `p65-medida.txt`; unidade 1, `memory_limit` 512M, o do contêiner; o frio é a primeira de três
rodadas do processo, sem esvaziar o buffer pool): o `/clientes` no **MySQL com 128 MB de buffer pool**, 2026 de 20,5 a 27,7 s e
2025 de 33,3 a 40,1 s -- e, na rodada do oráculo da cópia (o mesmo caminho, uma rodada, às 15:20 e 15:25, com os mutantes do
front rodando no host), **50,4 s em 2026 e 58,5 s em 2025** (`p65-oraculo-copia.txt`; a carga do host é a causa provável, não
provada); no PostgreSQL, 2026 de 2,6 a 6,6 s e 2025 de 3,7 a 7,7 s; pico de 78 MB (2026) e 118,5 MB (2025). Nenhum
recorte passou dos 120 s; o de 2025 no MySQL passa dos 32 s do desenho (o D311, a estreia gravada). A exportação de 2025: o XLS em
46,0 s (MySQL) e 11,9 s (PostgreSQL), o PDF em 41,6 s e 10,9 s, pico de 133 a 139 MB (com a leitura de mais um `index` no mesmo
processo, para trocar os nomes por ids); com 512M e 256M os dois saem, **com 128M os dois caem** no `ContasDoPainelDaUnidade.php:836`,
como o `index` de 2025. O limite não subiu. Tudo no D667.

**O que rodou (a fatia é ímpar: só os testes do que ela toca).** As 32 classes da fatia, das achadas no grep (`atendimentos`,
`nomeDoArquivo`, `painelPrimeiroServicoOuCredito`, `painelComprasDeCredito`, a exportação comum) e dos relatórios da família:
**936 casos em cada banco**, saída 0 no PostgreSQL de primeira; no MySQL a primeira rodada saiu **1** (o
`PortoesDaFamiliaDosRelatoriosTest::testAListaDerivadaDoEnsaio`, a lista das folhas sem as duas novas -- esperado, atualizado) e a
final, depois dos casos novos dos mutantes, 936 em cada banco, saída 0. O front tocado (29 arquivos, **587 casos**, saída 0, com `--maxWorkers=2`;
os três do Atendimentos com `AMPLIFICAR_EFEITOS_MS=50`, 34 casos, saída 0); tsc, lint (os 8 avisos de antes, nenhum dos
arquivos da fatia) e build com **347,71 KB** comprimido (+0,92 KB sobre os 346,79 da 64). Os portões: o
`conferir-recorte-por-unidade.php` (de 388 para **392** métodos, quatro vigias novas; 15 ações na família), o
`conferir-acl-da-api.php` (227 ações conferem privilégio), o `conferir-rotas-sem-login.php` (26 rotas de relatório guardadas pela
base), o `conferir-cobertura-por-par.php` (o ISRelatorio de 168 para **172** chamadas, os pares 413), o `conferir-rotas-orfas.php`
e o `conferir-segredos.sh`, saída 0; e o `conferir-tudo.sh` inteiro, sozinho, com **66 portões ok**, saída 0 ("Nenhuma falha inesperada"; `p65-conferir-tudo.txt`). Os ensaios nos dois bancos de contrato
(antes, `montar-banco-contrato.sh`): no MySQL, o de permissão (com o `clientes` e a `exportacao` na lista derivada, 403 sem o
privilégio, e o `clientes` do `colab.teste` com uma linha e a unidade nula), o de recusas, o de fluxo e o
`medir-adm-api-no-postgres.php`, **saída 0**; no PostgreSQL, os mesmos, saída 0 (706 s, 1.219 s, 117 s e 860 s). Os bancos de
contrato foram remontados depois da semente.

**Os mutantes**, cada um sozinho, devolvido e conferido pelo sha1, o veredito por caso (`medir-p65-mutantes.py`,
`p65-mutantes.txt`, `p65-mutantes-2.txt`; `medir-p65-mutantes-front.py`, `p65-mutantes-front.txt`): **P65-P1 a P6** (os AM28 a
AM33: sem as compras de crédito, o primeiro desde o fim do ano, o próprio com o total da unidade, o documento sem os novos, o
documento pelo nome, o `txt` na política) e **P65-X1 a X18** (as guardas dos clientes arrancadas ou depois de soltar; sem a trava,
ou na dos números; os itens do próprio sem o colaborador; as compras lidas no próprio; o nome sem a unidade; a frase do piso
diferente na exportação; o nulo do PDF em branco e o do XLS como zero; o ramo do ano arrancado; o subtítulo sem o "em andamento";
os clientes fora da ordem; o mês futuro com zero; as notas fora do glossário; e a estreia sem o crédito, com a junção trocada e com
o "antes" virado, estes no `PainelDaUnidadeTest`, no `NovosDoRecorteTest` e no `ConsultasDaIndicacaoTest` também), todos
**vermelhos pela asserção** no MySQL, e os de consulta (P1, P2, X5, X16, X17, X18) também no PostgreSQL. **Dois ficaram verdes na
primeira rodada e mostraram caso que faltava:** o X5 (as contas filtram pelo colaborador, e o resultado saía igual) -- o caso do
próprio passou a exigir o id dele em cada leitura de itens --, e o P5 (no cenário a ordem do faturamento e a do nome coincidem) --
o caso da exportação passou a montar o documento com o JSON invertido; refeitos, vermelhos. Na tela, **P65-AT9 a AT12** (o "0"
contando, o ano do pedido, o PDF sem a unidade, a falha sem o "tentar") e **P65-C1 a C8** (a troca de ano sem zerar, os botões
ligados relendo, os clientes na fila dos números, a frase dos sem colaborador, a linha da falha, o "tentar" pedindo os números, os
clientes sem a unidade do administrador -- verde na primeira rodada, e o caso do PDF passou a conferir o endereço dos clientes),
vermelhos pela asserção. **Um equivalente, o P65-C2** (sem o `doMesmoRecorte` dos clientes): a troca de recorte já zera os
clientes (`aoTrocar`) e descarta a resposta velha (o `vigente` do gancho); o filtro fica como defesa.

**Débitos:** o D667 PARCIAL (a estreia agrupada medida e recusada, os clientes e a exportação medidos; a exportação na trava dos
números e com os itens lidos uma vez, pela verificação abaixo);
o D306 PARCIAL com as respostas 274 a 276; o D307 com a 275; nenhum novo. De pé, 186, sem mudar (o `contar-debitos.php`); o `conferir-debitos-repetidos.php`, nenhum par novo. Nada do esquema, do crontab, do OneSignal, da
produção nem do BVIP mudou; na cópia, só SELECT (e um `KILL QUERY` da medida cortada, só de leitura); a semente só nos bancos de
contrato, que foram remontados; o `local.php` no `franquiabv_dev`, conferido pelo sha1.

**A verificação da fatia 65 (01/11/2026), e a correção.** Três lentes confirmaram 14 achados -- três médios e onze baixos --, e
todos foram tratados (os roteiros e as saídas com o prefixo `v65-` no rascunho):

- **O arquivo era um bloco por NÚMERO, e a resposta 10 do dono ("como hoje") pede um por COLABORADOR (médio).** O `spec-ordem.md`
  (seção 2, item 1) a resolveu assim, e o bloco 65 da fila também; a nota 2 das notas da fatia citava o corpo do desenho. Seguida a
  resposta do dono: `AtendimentosJson::documento` monta um bloco por colaborador (o nome no título, os números nas linhas, cada uma
  com o `_tipo` da C3), mais o bloco "Fora da lista (N)" e o "Unidade"; o próprio recebe só o dele. O `testAExportacao` confere a
  planilha bloco a bloco contra o JSON, os tipos de cada linha e o texto do PDF; o oráculo do contrato foi refeito com o XLS na forma
  nova (`medir-v65.php` e `medir-v65-oraculo.py`, o extrator transposto): **545 conferências no MySQL e 545 no PostgreSQL, 0
  divergências**; falsificado, **2** (`v65-oraculo-contrato-falsificado.txt`).
- **A exportação fora de trava podia esgotar os cinco workers do PHP-FPM, e lia os itens do ano duas vezes (médio).** A base ganhou
  o gancho `travaDoArquivo` (depois de soltar; o 429; solto no `finally`) -- a `exportacaoAction` não é sobrescrita, porque o portão
  derivado do recorte exige a guarda da base como primeira instrução de toda ação --, e o Atendimentos devolve nele a trava dos
  números da pessoa; o `documento` lê os itens uma vez (`itensPorMes`). O arquivo de 2025 da unidade 1 caiu de 41,3 a 46,0 s para
  28,1 a 33,5 s no MySQL e de 10,9 a 11,9 s para 7,2 a 8,3 s no PostgreSQL, com o mesmo pico de 133 a 139 MB e a mesma queda com
  128M (`v65-medida.txt`).
- **O caso do ano em andamento ficava vazio em dezembro (médio):** o `testOAnoEmAndamentoNoArquivo` usa agora 15 de junho do ano
  do teste como o `hoje()` do controller, e exige os seis meses futuros; o P65-X14 (o mês futuro com zero), que passava verde em
  qualquer dezembro, rodou de novo, vermelho por ele.
- **Os documentos afirmavam mais do que as saídas guardadas** (baixos): a estreia agrupada no MySQL foi comparada só em 2026 e numa
  ordem só (o D667, este parágrafo da fatia e a A3 diziam "697 em 2025, nos dois bancos, nas duas ordens"); o corte dos 120 s veio
  tarde (o `medir-p65-estreia.php` ganhou o corte); os 50,4 s e 58,5 s do `/clientes` no MySQL guardados no `p65-oraculo-copia.txt`
  não estavam em documento nenhum. Corrigidos no D667, aqui em cima e na A3 do `spec-atendimentos.md`.
- **Baixos, de texto:** a nota 274 (a) no glossário e nas notas do arquivo citava só o consumo e os serviços por cliente, e os
  produtos por cliente também caem (o texto, o caso do `glossario.teste.tsx` e o `AtendimentosJson::NOTAS`); a nota FEITA dizia que
  o fora da lista mostra "—" nos clientes novos, e a linha nem aparece; os comentários do `nomeDoArquivo`, da política
  `atendimentos`, do `ITENS_DOS_ATENDIMENTOS` e a seção 4 do desenho ainda tratavam a A3 e as perguntas P-a a P-c como pendentes.
- **O dublê do `ConsoleAbreAtendimentos.teste.tsx` não respondia ao `/clientes`** (baixo): a tela do Console abria com os clientes em
  falha sem caso nenhum ver; o dublê responde, e o caso exige o pedido e a frase "3 clientes novos em 2024".
- **O X6 e os outros mutantes do servidor só no MySQL** (baixo): o X6 e o P3 rodaram no PostgreSQL, vermelhos pelo
  `testOProprioSoComOsClientesDele`; os demais (P4 a P6, X1 a X4, X7 a X15) mexem no controller, na regra pura ou no arquivo, sem
  consulta, e ficaram só no MySQL.
- **O `conferir-tudo.sh` da fatia rodou antes da última edição do roadmap e do desenho** (baixo): rodou de novo no fim desta
  correção, abaixo.

**Os mutantes da verificação** (`medir-v65-mutantes.py`, `v65-mutantes.txt`; `medir-v65-mutantes-front.py`,
`v65-mutantes-front.txt`), cada um sozinho, devolvido pelo sha1, o veredito por caso: **P65-V1** (o documento sem o `_tipo`),
**V2** (os blocos pelo nome), **V3** (a unidade sem os clientes novos), **V4** (o fora com todos os números) e **V9** (o arquivo
lendo os itens duas vezes), vermelhos pelo `testAExportacao`; **V5** (a base sem a trava do arquivo), **V7** (o Atendimentos sem ela)
e **V8** (na trava dos clientes), vermelhos pelo `testAExportacaoAndaNaTravaDosNumeros` (e o V5, o V7 e o V8 também pela vigia
nova); **X14** de novo, vermelho pelo `testOAnoEmAndamentoNoArquivo`; **X6** e **P3** no PostgreSQL, vermelhos; no front, **V10** (o
texto das médias sem os produtos) e **V11** (o dublê sem os clientes), vermelhos pela asserção. **Um equivalente, o V6** (a base sem
soltar a trava): o handle é local, e o PHP o fecha ao sair da `exportacaoAction`, o que solta o `flock` -- o mesmo do X16 da fatia
61.

**O que rodou depois da correção (a fatia é ímpar).** As mesmas 32 classes: **937 casos em cada banco** (o caso novo da trava),
saída 0 (`v65-mysql.txt`, `v65-pg.txt`). O front tocado (`glossario`, `ConsoleAbreAtendimentos`, `Atendimentos` e
`AtendimentosLigado`, 50 casos, saída 0; os três do Atendimentos com `AMPLIFICAR_EFEITOS_MS=50`, 34 casos, saída 0); lint (os
avisos de antes) e build com **347,71 KB** comprimido, sem mudar. O `conferir-recorte-por-unidade.php` com **394** métodos (a
vigia da trava no Atendimentos e na base; a do `painelItens` passou para o `itensPorMes`), o `conferir-cobertura-por-par.php` com o
ISRelatorio em 172, o `contar-debitos.php` (de pé, 186) e o `conferir-debitos-repetidos.php`, saída 0. Os ensaios nos dois bancos
de contrato (`medir-v65-contrato.sh`, `v65-contrato.txt`): permissão, recusas, fluxo e o `medir-adm-api-no-postgres.php`, saída 0
no MySQL e no PostgreSQL (698 s, 1.185 s, 132 s e 834 s); os bancos de contrato remontados; o `local.php` no `franquiabv_dev`,
conferido pelo sha1. Por último, sozinho, o `conferir-tudo.sh` inteiro, depois da última edição dos documentos: **66 portões ok,
saída 0** ("Nenhuma falha inesperada", das 21:40 às 22:07; `v65-conferir-tudo.txt`). A primeira tentativa saiu **124** às 21:39, no
portão "clones do ensaio de corte": o `timeout 590` do MEU roteiro, e não o portão (`v65-conferir-tudo-124.txt`); sem órfão nem
`local.php` trocado depois dela. Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; na cópia, só SELECT.

**No navegador, contra o banco de contrato** com o cenário da fatia (`CenarioDosAtendimentos`, semeado pelo
`p64-semear-contrato.php`; o administrador de teste e o colaborador Bruno com o privilégio, só no contrato), depois da correção: o
administrador abre o Atendimentos pelo menu, e o "Clientes novos" mostra "…" enquanto conta e chega depois dos números; na unidade
U, 2024, os novos são 1 e 1 em janeiro e fevereiro e 3 no ano (o ano passa da soma dos meses, como o desenho e o glossário dizem:
quem estreou em outra unidade entra no ano), e a frase "3 clientes novos em 2024, dos quais 2 sem colaborador (...)"; com o
"Clientes novos" no seletor, a tabela por colaborador não tem a linha do fora. O PDF e o XLS saem com 200 e o nome
`atendimentos-2024-unidade-90006200.pdf`/`.xls`; os dois com um bloco por colaborador, o "Fora da lista (1)" (o faturamento e as
cortesias) e a "Unidade", as notas com o texto dos produtos por cliente, o mês futuro "—" no PDF e vazio no XLS (lido pelo
PhpSpreadsheet). O colaborador recebe só a própria linha nos clientes (`unidade: null`) e só o próprio bloco no arquivo; a outra
unidade dá 422, o ano antes da criação dá 422 com a MESMA frase nos clientes e na exportação ("...começou em 2023: o Atendimentos
não mostra ano antes disso."), e o TXT dá 422. A 375 px, no tema claro, a tabela rola dentro do quadro e a página não. Visto e não
tratado como defeito: as quantidades (serviços, produtos, cortesias) saem no arquivo com duas casas ("3,00") e na tela como na
resposta ("3", ou "1,5" quando fracionária) -- é o `_tipo` decimal que o desenho dá às quantidades, e o valor é o mesmo. Depois:
os cookies expirados, as sessões apagadas, o `local.php` de volta ao `franquiabv_dev` (o sha1 confere) e o banco de contrato
remontado.

## 02/11/2026 -- REL-COTA-PARTE-SERVIDOR (a fatia 66 da fila das telas que faltam)

**Para quem usa, só uma frase muda:** o item "Cota-parte" do menu continua abrindo a tela antiga. Esta fatia é a CP1 do
`docs/f3-relatorios/spec-cota-parte.md`: o servidor da Cota-parte e da Cota-parte v2, sem tela (a CP2, a fatia 67), sem
exportação (a CP3, a 68) e sem mexer no ERP antigo (a CP4). A única mudança visível é a frase de "desde quando vale" da aba
Cota-parte de Usuários (nas duas telas e na nota da aba), que agora descreve o relatório novo: "A Cota-parte refaz a conta com o
percentual de hoje e a cota-parte extra de hoje, também nos meses passados; as sessões de pacote saem com a cota-parte gravada."
Até a CP2 e a CP4, ela não descreve os arquivos que o menu abre: o PDF V1 refaz sem a extra, e o XLS V1 refaz também a sessão. Na
cópia (só SELECT), itens concluídos com a extra do par: 10 em 2026 (de janeiro a junho, nenhum em julho) e 39 em 2025 (o D671).

**O servidor das duas:** `GET /adm/api/relatorios/cota-parte` (a lista por colaborador, o fora da lista e o total), `.../extrato`
(um colaborador, por bloco e produto) e `.../unidades`, e o mesmo em `cota-parte-v2`, só do administrador. Por item e em centavos:
a recalculada (o percentual de hoje mais a extra de hoje; a 134 (b) do dono, sem vigência) na v1, a gravada com o aviso na v2. **A
sessão de pacote fica com a gravada na v1** (`A_SESSAO_RECALCULA = false`, o `spec-ordem.md`, seção 2, item 3: o número do PDF que
o colaborador vê hoje); o aviso da v2 compara com a recalculada pura, a sessão inclusive. A gorjeta fica fora da cota e entra só no
"a repassar". O caixa vê só hoje, sem cota; o colaborador só a própria linha, sem a produção nem o faturamento (o D158 -- o P-CP1 foi
para a página do lote como a pergunta 277, 180 pessoas no escopo próprio na cópia, e o dono respondeu (a): fica como o D158). O intervalo não tem teto e é lido mês a mês; a lista e
o extrato andam cada um numa trava por pessoa (o 429), em todo pedido. O `naTelaNova` das duas foi ligado, mas o menu de ninguém
muda: o `RelatoriosNoMenuTest` ganhou o `SO_A_API`, que a CP2 e a CP4 tiram. `RecorteDoRelatorio::colaborador` saiu do Parceiro
sem mudar a regra. Os ids do teste são 90008000 a 90008099: a faixa 900083xx do desenho já era usada pelo
`UsuarioAgendaECotaParteApiTest`.

**O oráculo** (Python, só SELECT, contra o JSON do controller em processo): na cópia, nos dois bancos, 7.872 conferências em cada
(unidade 1 e rede em julho de 2026, unidade 1 em junho; as duas réguas, linha a linha, a ordem, o fora, o total e o extrato do
maior colaborador), 0 divergências. A gravada dá a 1.7 do desenho (R$ 102.170,12 na unidade 1, R$ 654.911,28 na rede); a
recalculada da rede em julho dá **R$ 656.926,57** -- R$ 3,76 abaixo dos R$ 656.930,33 do desenho, a diferença da sessão (R$
6.549,38 gravados contra R$ 6.553,14 recalculados); 2.802 itens com aviso de 15 colaboradores; junho da unidade 1 com o colaborador
de 9 itens e "-880.00". No contrato semeado (`CenarioDaCotaParte`, pelo `p66-semear-contrato.php` do rascunho), 1.000 conferências
em cada banco, 0 divergências; falsificado, 2 (gravado em `p66-oraculo-contrato-falsificado.txt`).

**A medida** (`p66-medida.txt` do rascunho; memory_limit 512M, três rodadas por processo, a primeira fria do processo e as duas
seguintes quentes; o MySQL com 128 MB de buffer pool antes e depois). Cada número abaixo é "frio; quente", MySQL e depois
PostgreSQL entre parênteses: a rede em julho, 4,21 s; 1,37 a 1,64 s (4,20 s; 2,18 a 2,53 s), pico de 134 MB -- o Profissional
Parceiro da rede em julho, lado a lado, duas vezes, 2,83 e 3,09 s; 1,11 a 1,43 s (5,69 e 3,48 s; 1,86 a 3,00 s), pico de 134 MB.
**A meta do desenho não foi cumprida no MySQL** (a Cota-parte até 20% acima do Parceiro): no frio, de 36% a 49% acima; no quente,
a média 17% acima, mas a pior rodada da Cota-parte (1,64 s) 48% acima da melhor do Parceiro (1,11 s); no PostgreSQL, abaixo dele
nas médias. Registrado no D675, para a CP2 medir por consulta. A unidade 1 em julho, 2,53 s; 0,23 s (3,75 s; 0,40 s), pico de 34
MB; o extrato do maior colaborador da unidade 1, 1,96 s; 0,11 a 0,12 s (2,03 s; 0,04 s), pico de 20 MB; 12 meses da unidade 1,
6,37 s; 3,87 a 3,96 s (4,90 s; 2,00 a 2,02 s), pico de 36 MB; 3 meses da rede, 6,53 s; 3,84 a 4,24 s (6,81 s; 4,58 a 4,68 s), pico
de 140 MB; **12 meses da rede, 19,8 s; 16,4 a 17,4 s no MySQL e 25,7 s; 23,5 a 23,6 s no PostgreSQL, pico de 174,5 MB** -- 1,3 vez
o de um mês (o desenho pedia abaixo de 2 vezes), e nenhuma regra de parada tocada; a v2 da rede em julho, 3,80 s; 1,32 a 1,48 s
(3,98 s; 1,94 a 2,40 s), como a v1. Base de comparação do desenho (3,0 s e 88 MB, de outubro): o Parceiro medido hoje
pelo mesmo caminho dá 134 MB de pico, com o limite de 512M.

**O que rodou** (a fatia é par: o ciclo inteiro é do orquestrador, depois): as classes da fatia e as vizinhas -- 34 classes, 1.267
casos em cada banco, saída 0 (um pulado, o de sempre, o da cópia); depois da correção dos testes, `CotaParteTest` e
`RegrasDaCotaParteTest`, 51 casos em cada banco, saída 0. O front tocado (o `formularioDaCotaParte`, as duas telas de Usuários e o
`cotaParteDaTelaAntiga`), 167 casos com `--maxWorkers=2`, saída 0; tsc e lint, saída 0. Os ensaios de permissão, recusas e fluxo e
o `medir-adm-api-no-postgres` nos dois bancos de contrato: no PostgreSQL, os quatro com saída 0; no MySQL, permissão e recusas 0, e
na primeira rodada **o fluxo saiu 1** (a finalização da venda 409: o banco montado às 23:35 e o ensaio depois da meia-noite) e **o
medidor saiu 2** ("MAIS DE 10 FUNCIONALIDADES A CONCEDER": a Cota-parte é a décima funcionalidade de relatório mais o Parceiro);
o medidor ganhou a lista `CONCESSOES` (90006170 a 90006179 e 90006192 a 90006199), e os dois, num banco montado de novo, saíram 0.
A v2 é medida pela parte do administrador do medidor (200 na lista e no extrato), e dá 403 `papel` ao gerente. Os portões: o
`conferir-recorte-por-unidade` com 404 métodos vigiados (eram 394), a cobertura por par com 414 pares (o
`percentuaisExtras`; o ISRelatorio de 172 para 177 chamadas), a injeção de DQL, o group by, os booleanos, as rotas sem login e a
ACL da API, todos saída 0; o `conferir-tudo.sh` no fim, sozinho, com 66 portões, saída 0.

**Os mutantes**, um por vez, devolvidos pelo sha1, o veredito por caso: os 18 do desenho (P66-P1 a P18, o CP-M1 a CP-M18),
vermelhos pela asserção, o CP-M15 só pela vigia do portão (o JSON filtra), como o desenho previa; os de consulta (o P7, o P16, o X7
e o X25) também no PostgreSQL. Os meus, P66-X1 a X34: o `A_SESSAO_RECALCULA = true`, o aviso contra a régua da v1, cada guarda do
extrato arrancada e a solta antes delas, a solta antes das guardas do índice, o `MAIOR_ID` sem o 404 sem consulta, a outra frase
para o id que não existe, o JSON sem o filtro do próprio, cada uma das catorze chaves do `ESCONDIDOS`, a fatia com um dia a mais e
a menos, a gorjeta dentro da cota, a gorjeta do colaborador só das vendas com item dele, a trava solta antes das consultas (a da
lista e a do extrato), a v2 aceita para o gerente, a v2 com a política da v1, a frase de "desde quando vale" sem a mudança, o recorte
do colaborador sem o motivo próprio e o Parceiro delegando com a frase errada -- todos vermelhos pela asserção. **Equivalentes,
ditos:** a trava sem o `finally` (o X26: o handle é variável local, e o PHP fecha o arquivo ao voltar, como o D666 já dizia); e o X23
como primeiro escrito (a virada um dia antes) não conta -- dá laço infinito e estoura a memória, não a asserção --, e foi trocado
pelo X23b (a fatia termina na véspera da virada), vermelho pela asserção. **A verificação dos mutantes achou um defeito de teste,
consertado:** o caso que anda na árvore lia a própria constante `ESCONDIDOS`, e as chaves `percentualDeHoje`, `percentualGravado` e
`aviso` tiradas dela passavam verdes (seis mutantes); o caso passou a ter a tabela 1.4 escrita à mão (`NAO_VE`), e os seis ficaram
vermelhos pela asserção. E o caso do `MAIOR_ID` no PostgreSQL caía por erro do banco: passou a converter a exceção, e o X7 ficou
vermelho pela asserção nos dois bancos.

**Débitos:** D671 (as quatro contas da cota-parte, PARCIAL: o servidor novo tem uma só), D672 (o portão de grupos por id), D673 (o
meio centavo do `round`), D674 (a gorjeta dos arquivos antigos) e, na verificação, D675 (o frio da rede acima da meta) registrados;
o D-cp3 já era o D376. De pé de 186 para 191.

**A verificação** confirmou 11 achados, um médio e dez baixos, todos tratados. O médio: a guarda do formato do
colaborador no extrato podia ir para depois do `soltarASessao` sem nenhum caso vermelho (o 422 é o mesmo; ninguém olhava a solta
no caminho do 422) -- os três pedidos de 422 do `CotaParteTest` (o "abc", o "1.5" e o sem `?colaborador=`) e o 422 do período
passaram a exigir a sessão presa, e o mutante novo P66-X35 (a guarda depois da solta) ficou vermelho pela asserção nos dois bancos.
Os baixos: a tabela de faixas de ids do `spec-comum.md` ainda dava à Cota-parte a 90008300 a 90008399 (agora 90008000 a 90008099,
e a 900083xx com o dono de fato); o "para quem usa, nada muda" escrito aqui, no `spec-cota-parte.md`, no `spec-comum.md` e no
`f3-relatorios.md`, quando a frase de "desde quando vale" muda e não descreve o PDF V1 nem o XLS V1 que o menu abre (dito em cada
um, com a contagem, e no D671); o docblock da frase, que só ressalvava julho; o comentário do `Usuarios.tsx` com a data da nota
antiga trocada; o `SO_A_API` (o desvio da nota, mantido: a CP2 ganhou a nota "Em dia" para tirar a lista, e o caso do menu e o do
Console ficam vermelhos se ela ficar); a meta dos 20% sem registro (o D675); o frio e o quente misturados nos intervalos longos
(o parágrafo da medida, refeito); o ensaio de permissão que nunca via a linha do colaborador na Cota-parte (o pedido era de hoje,
sem item: o predicado aceitava a lista vazia) -- agora pede também fevereiro de 2026, o mês da venda concluída dele nas fixtures,
e exige a linha, sem a produção nem o faturamento; e o `conferir-tudo.sh` rodado antes da última edição deste parágrafo (rodado de
novo depois, abaixo).

**O que rodou depois da correção:** as mesmas 34 classes, 1.267 casos em cada banco, saída 0 (um pulado, o da cópia; a primeira
rodada do PostgreSQL foi cortada pelo `timeout` do MEU roteiro, deixou um phpunit órfão no contêiner, morto por `/proc`, e a
seguinte saiu 1 só por montar o banco com ele ainda conectado; a repetida saiu 0). O mutante P66-X35 (a guarda do formato do
colaborador depois do `soltarASessao`), vermelho pela asserção da solta no `testOExtratoDeQuemNaoEstaNaLista`, nos dois bancos,
devolvido pelo sha1; o P66-X36 (o `ESCONDIDOS['proprio']` sem a `producao`, o troco do P66-P9) contra o ensaio de permissão no
contrato do MySQL: saída 1, o pedido de hoje ainda verde e o de fevereiro com o "ESPERAVA" -- o vermelho que o ensaio não dava
antes. O ensaio de permissão sem mutante, nos dois bancos de contrato, saída 0. O front: o `UsuariosLigado.teste.tsx` (109 casos)
com `--maxWorkers=2`, tsc e lint, saída 0 (só um comentário mudou). O `conferir-tudo.sh` sozinho, depois da última edição: na
primeira rodada **saiu 1**, só nos testes do front (65 de 66 portões ok); o front inteiro, sozinho, com o mesmo `--maxWorkers=4`,
deu 3.744 de 3.744 em 156 arquivos, saída 0; repetido, o `conferir-tudo.sh` **saiu 1 de novo**, de novo só no front, e a saída
guardada diz o caso: o `UsuariosLigado.cadastro.teste.tsx` ("o nascimento e o endereço que faltam..."), estourado no teto de 5 s
-- o mesmo caso lento que a verificação de 24/10/2026 já registrou aqui, num arquivo que a fatia não toca; sozinho, 3,36 s, e o
arquivo, duas vezes, 24 de 24. Os outros 65 portões, ok nas duas rodadas. Os portões que leem documento rodaram de novo, um a um,
depois desta última edição: referências da documentação, débitos repetidos, links mortos, ACL da API, cobertura por par, grupos
por id, injeção de DQL, rotas sem login, recorte por unidade, booleano em coluna inteira, provedores no contêiner, segredos e o
contar-débitos, todos saída 0. Os bancos de contrato foram remontados; o `local.php` voltou ao `franquiabv_dev` (o sha1 confere).

**A fatia é par: o ciclo inteiro**, depois da correção. Caracterização 5.708 casos em cada banco, saída 0; contrato 74 e 74; os
ensaios nos dois bancos, todos com saída 0; front 3.744 em 156 arquivos, saída 0; os portões 65 de 66, e o vermelho de novo só nos
testes do front, agora com três casos do `UsuariosLigado.cadastro.teste.tsx` estourados (dois no teto de 5 s, um no `findBy` de
"Login já cadastrado"). Esse arquivo testa o `Usuarios.tsx`, de que a fatia só trocou a frase; sozinho, duas vezes, 24 de 24 (35,5 s e
31,4 s), e a etapa do front do mesmo ciclo os passou. O medidor dos relatórios no PostgreSQL, saída 0. **Pela API**, por HTTP real no
banco de contrato do MySQL semeado com o `CenarioDaCotaParte` (setembro de 2023, unidade 90001000), com o privilégio "Relatórios de
vendas" concedido só ali ao gerente, ao colaborador e ao fiscal.teste (num grupo só de caixa): o administrador com 200 na lista
(recalculada, 73,80 na unidade), na v2 (gravada, 80,30, o aviso de 3 itens e 10,50 no 90001003 -- gravada menos a recalculada pura,
sem a extra) e no extrato, 404 no colaborador sem linha, 422 no "abc" e 404 na exportação, que ainda não tem rota; o gerente com a
mesma lista no escopo da unidade, 403 `papel` na v2, 422 na outra unidade e o 404 do recorte no colaborador de fora; o colaborador
só com a própria linha, sem produção, faturamento, percentual, fora e total, o extrato próprio com o valor de cada item nulo e 403
`proprio` no alheio; o caixa só com hoje e sem a cota no total, mesmo pedindo setembro. Nenhum defeito. A pergunta 277 foi
respondida (a): fica como o D158.

Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; na cópia, só SELECT; a semente só nos bancos de contrato,
que foram remontados; o `local.php` voltou ao `franquiabv_dev` (o sha1 confere).

## 02/11/2026 -- REL-COTA-PARTE-TELA (a fatia 67 da fila das telas que faltam)

**Para quem usa:** o item "Cota-parte" do menu da tela nova abre a **tela nova da Cota-parte** -- a tabela por colaborador
(produção, faturamento, descontos, serviços, produtos, cota-parte, % da cota, gorjetas e "a repassar"), o total, a nota de quem
ficou de fora e o "ver o extrato" de cada um, que abre no lugar da lista (um bloco por tipo de item, com o total, a gorjeta e o "a
repassar"; o voltar não pede a lista de novo e devolve o foco ao botão). O período é livre, com os atalhos (o mês é o padrão, e a
tela lembra o último por relatório e por unidade); o administrador escolhe a unidade ou a rede. O caixa vê o dia de hoje, sem a
cota; o colaborador, só a própria linha, sem a produção, o faturamento, os descontos nem o percentual (a pergunta 277, (a), como o
D158). Era um item em duas formas (a tela antiga e, para o "Colaborador caixa", o PDF em aba nova, pela guarda `vendasExportadas`,
que saiu da sessão); agora é um só. **A "Cota-parte v2" entrou no menu, só para o administrador** (pelo papel): a gravada na
venda, a coluna "Aviso" ("3 itens · R$ 10,50 a mais gravados"), uma nota no topo com os itens, os colaboradores e a diferença que o
servidor somou -- dita como a gravada contra o percentual de hoje aplicado a cada item, e não como a diferença entre as duas telas
(a Cota-parte do menu deixa a sessão de pacote com a gravada) -- e o link para a Cota-parte no mesmo período e unidade; a
Cota-parte tem o link para a v2, só para o administrador. **Contado na cópia** (só SELECT, usuários ativos sem as fixtures): 356
têm o privilégio "Relatórios de vendas" e viam um "Cota-parte" antes; os mesmos 356 veem um depois -- 10 administradores (a rede),
108 no escopo da unidade, 58 do grupo 2 no dia (do PDF para a tela do dia) e 180 só a si --; ninguém ganhou nem perdeu o item, e os
10 administradores ganharam a v2. No ERP antigo, o item do `layout.phtml` passou ao `veOMenu` da política (o mesmo conjunto), e o
caixa continua com o PDF do dia ali até a CP4. A frase de "desde quando vale" de Usuários agora descreve o que o menu da tela nova
abre; o PDF V1 e o XLS V1 seguem pela URL antiga até a CP4 (o D671 anda, PARCIAL). Sem exportação ainda (a CP3, a fatia 68).

**A fatia foi retomada:** um implementador anterior caiu sem rede depois de 33 minutos, sem relatório, e deixou trabalho parcial
na árvore. Cada arquivo foi lido com o `git diff` contra o desenho e as notas, e provado do começo (nenhum teste dele conta).
Aproveitados: a API do front, a vista, o extrato, os textos, o glossário, o `SessaoJson` sem a guarda e os dois testes do servidor;
consertados: o `layout.phtml` (ele trocara o `if`/`else` do grupo 2 para o ajudante do teste achar a política como o `if` mais
perto -- voltou à forma de antes, só com o `if` de fora no `veOMenu`, e o caso do layout passou a esperar o do grupo como o mais
perto e a política entre os que o envolvem), o texto do `cota.gravada` e o "voltar" do extrato da v2. Escritos agora: o
`CotaParteLigado.tsx`, o menu, a sessão do front, o Console, as filas, o `index.ts`, as prévias e todos os testes do front. A
medida que ele deixou (`p67-medida.txt`, a primeira sessão) foi lida com o roteiro que a gerou e usada, e medida de novo.

**Novos:** `frontend/src/api/cotaParte.ts`; `telas/CotaParte.tsx`, `CotaParteLigado.tsx`, `ExtratoDaCotaParte.tsx`,
`ExtratoDaCotaParteLigado.tsx` e `textosDaCotaParte.ts` (o `ESCONDIDOS_NA_TELA` e o `colunasDoEscopo`); `testes/cotaParte.ts`; os
testes `CotaParte`, `CotaParteLigado`, `ExtratoDaCotaParteLigado`, `textosDaCotaParte` e `ConsoleAbreCotaParte`; as prévias
`CotaParte` (o gerente, o caixa no dia, o colaborador e a v2 com aviso) e `ExtratoDaCotaParte` (a Cota-parte e a v2 com aviso e a
venda de pacote). **Mudados:** `menu-do-erp.ts` (um "Cota-parte" com o `quando` da `relatorioCotaParte`, a "Cota-parte v2" com o
`exige`, a v2 no `SO_NA_TELA_NOVA`, a `vendasExportadas` fora; 15 grupos e 51 destinos), `api/sessao.ts`, `Console.tsx` (as duas
linhas da `TELA_DO_RELATORIO`, o link para a outra só com o `podeAbrir`), `Moldura.tsx` (o filtro `eAFormaQueSobra`, sem par, saiu:
o D322), `filaDaRede.ts` (`cotaParte` e `cotaParteExtrato`), o glossário (as sete chaves `cota.*`; o `cota.recalculada` diz que a
sessão de pacote fica com a gravada, e o `cota.gravada` o que o aviso soma), `SessaoJson.php`, `layout.phtml`, os comentários da
`PoliticaDoRelatorio` e do `CotaParteController`; nos testes, o `RelatoriosNoMenuTest` (o `SO_A_API` e o `SEM_ITEM_NO_MENU` saíram,
com o caso que lia o primeiro), o `SessaoApiTest`, o `RegrasDaCotaParteTest` (o caso novo lê o `ESCONDIDOS_NA_TELA` da tela e o
compara com o `CotaParteJson::ESCONDIDOS`), o `menu-do-erp.teste.ts`, o `Console.teste.tsx` (5 destinos na tela não migrada, 45
que o Console abre) e os dublês dos `ConsoleAbre*`. **Divergências do desenho**, no `spec-cota-parte.md` (a CP2 FEITA): o primeiro
pedido sem período e o ciclo pelo hoje do servidor (o molde do Caixa); o `AberturaDoRelatorio` já tinha a unidade; o 422 do
período, que a tela deixou de mandar (o piso de 2000 na escolha, na correção abaixo).

**A medida antes de abrir (o D675)**, por consulta, duas sessões em cada banco, o buffer pool de 128 MB antes e depois: o frio da
Cota-parte da rede em julho ficou de −5% a +23% do Parceiro (7 de 8 pares dentro dos 20% da meta), e o quente de +8% a +29% nas
médias (o +29% no MySQL com a máquina carregada; +14% na outra sessão). O SQL é o do Parceiro; a diferença é o PHP das duas réguas
e das linhas por produto. Nenhum ganho aplicado: o candidato (as linhas por produto só no extrato) mexe na CP1; o D675 fica
PARCIAL. **O pacote:** 351,69 KB comprimido na entrega e 351,81 KB depois da correção; o antes, medido na correção com o `vite build` da
HEAD (53af9301, a fatia 66) numa cópia do `frontend/` tirada pelo `git archive` (`p67c-build-antes.txt`), é 347,69 KB: +4,12 KB,
abaixo dos 15 KB do desenho.

**O oráculo pela tela:** o JSON do controller em processo nos dois bancos de contrato semeados pelo cenário da 66, desenhado pela
vista (o `CotaParte` e o `ExtratoDaCotaParte` renderizados no vitest com a resposta gravada em arquivo), contra as contas em Python
sobre as linhas cruas: cada célula da tabela da v1 e da v2, a ordem, o total, a nota do fora, a nota do aviso da v2 e os extratos
do maior colaborador, bloco a bloco -- na unidade 90001000 e na rede em setembro de 2023 e na unidade de 01/01/2026 até hoje: 717
conferências em cada banco, 0 divergências; falsificado (o total da v1 e uma linha da v2), 2, gravado.

**O que rodou (a fatia é ímpar: só o que ela toca).** No servidor, nos dois bancos, em série, as 17 classes que a fatia toca ou
que o grep achou (a sessão, o menu, a Cota-parte, as regras, os portões da família, os que leem o `layout.phtml` e os vizinhos da
Cota-parte de Usuários): 580 casos em cada banco, saída 0 (um pulado, o mesmo nos dois). No front: os 22 arquivos tocados ou
achados pelo grep, 369 casos com `--maxWorkers=4`, saída 0; com `AMPLIFICAR_EFEITOS_MS=50`, 367 de 369, os dois vermelhos em casos
da comanda dentro do Console (`Console.teste.tsx`, "o valor unitário só é campo para o gerente" e "cancelar a comanda pede
vendas/cancelar/escrever"), que a fatia não toca; a seção da comanda sozinha com o amplificador, duas vezes: 5 de 5 e 4 de 5, num
terceiro caso -- a família do D471. Todos os arquivos da Cota-parte, com o amplificador, verdes (um caso do extrato da v2 caiu na
primeira rodada por esperar o título, que nasce com o nome do botão; passou a esperar o primeiro bloco). tsc, lint (nenhum aviso
nos arquivos da fatia) e build, saída 0. Nos dois bancos de contrato: os ensaios de permissão, recusas e fluxo e o
`medir-adm-api-no-postgres`, todos saída 0. Os portões, no fim desta entrada.

**Os mutantes**, cada um sozinho, devolvido pelo sha1, vermelhos pela asserção: P67-P1 (CP-T1, o `colunasDoEscopo` com a
"Cota-parte" no dia) e P67-P1b (o "cota" fora do `ESCONDIDOS_NA_TELA` do dia, também no teste do servidor); P67-P2 (CP-T2, a chave
do atalho sem a rota); P67-P3 (CP-T3, o extrato com o pedido); P67-P4 (CP-T4, o voltar pede a lista); P67-P5 (CP-T5, a nota da v2
só com aviso); P67-P6 (CP-T6, o link sem a unidade, no Console); P67-P7 (CP-T7, os dois "Cota-parte" de volta); e P67-X2 a X7 (cada
coluna escondida do dia e do próprio, no vitest e no teste do servidor), X8 (o "Aviso" na v1), X9 (o link da v2 sem o `podeAbrir`),
X10 (a v2 sem o `exige`, também no `RelatoriosNoMenuTest`), X11 (a `vendasExportadas` de volta, no `SessaoApiTest`), X12 (a frase do
aviso com outro número), X13 (o vazio do próprio com a frase de todos), X14 (o 403 pelo papel com o "tentar de novo"), X15 (o 404
do extrato com o "tentar de novo"), X16 e X17 (o "ainda não gravado" sem a condição do pacote, na lista e no extrato), X18 (o
`layout.phtml` de volta ao `getAcl`), X19 (a linha da Cota-parte fora da `TELA_DO_RELATORIO`), X20 (o item sem o `quando`) e X21 (o
extrato do gerente com a unidade). Nenhum equivalente.

**Débitos:** D675 PARCIAL (a medida), D671 PARCIAL (a tela abre pelo menu), D672 PARCIAL (o sítio do `SessaoJson` saiu) e D322
PARCIAL (a Cota-parte em dobro saiu na raiz). Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou; na cópia, só
SELECT; a semente só nos bancos de contrato, que foram remontados; o `local.php` voltou ao `franquiabv_dev` (o sha1 confere). O
navegador veio depois da correção (o último parágrafo).

**Os portões:** os que a fatia toca, um a um, saída 0 -- o contar-débitos (de pé 191, como antes: os quatro débitos já estavam de
pé), os débitos repetidos, as referências da documentação, os links mortos, os grupos por id, o recorte por unidade, as rotas sem
login, a ACL da API, a cobertura por par e os segredos; e o `conferir-tudo.sh` inteiro, sozinho, sem suíte junto: 66 de 66, saída
0, com o front inteiro dentro dele (3.786 casos em 161 arquivos). Os que leem documento rodaram de novo depois deste parágrafo.

**A correção depois da verificação (três lentes, 9 achados confirmados: um médio e oito baixos, todos tratados).** **(1)** O 422 do
dia antes de 2000 prendia a tela: o livre aceitava 1999 (sem `min` e sem o piso na conta do livre), o 422 tirava os números e a
escolha do período, e o "tentar de novo" repetia o mesmo pedido -- a única saída era o menu; e a divergência 7 do desenho dizia
que nenhuma tela da família trata o 422, o que o Atendimentos desmente. A `EscolhaDoIntervalo` (comum: o Caixa também a usa)
ganhou o piso do servidor -- o `min`, a frase ao lado do campo, o ‹ desligado no piso e o ciclo que o atravessa cortado nele --,
e o pedido não sai; a divergência 7 foi reescrita e o item 30 do `spec-comum.md` ganhou o piso. **(2)** A legenda da linha do
colaborador no extrato da v2 dizia "na Cota-parte": agora diz a régua da rota. **(3, o médio)** Nenhum caso prendia o "Como se
conta" das duas: um caso novo abre a nota da v1 e da v2 e compara o primeiro parágrafo com o texto escrito por extenso (a sessão
de pacote na gravada, na v1; o que o aviso soma, a sessão inclusive, na v2) e o resto entre as duas. **(4)** O extrato aberto na
rede não tinha caso: o novo prova o `unidade=todas`. **(5)** A memória do atalho por rota só era provada num sentido: dois casos
novos, a v2 que não lê nem grava a da v1, e o dia 2 do mês na v2 com o atalho lembrado dela (o caso que faltava, dito na
entrega). **(6)** A condição da data do "ainda não gravado" não tinha caso: um na lista e um no extrato, com a coluna já gravada.
**(7)** O pacote de antes foi medido (acima). **(8)** As faixas por consulta do D675 não batiam com a saída: refeitas pelo
`medir-p67c-faixas.py` (os itens de 0,534 a 1,184 s no MySQL e de 1,223 a 2,543 s no PostgreSQL), sem mudar as porcentagens.
**(9)** O registro do X10, do X19 e do X20 não nomeava o caso do PHPUnit (o roteiro lia o caso sem o "with data set"): rodados de
novo, cada um sozinho, devolvidos pelo sha1, vermelhos pela asserção no `RelatoriosNoMenuTest` -- o X10 e o X20 no
`testOItemDoMenuUsaAsConstantesEOQuandoSegueATelaNova` (a v2 e a Cota-parte), o X19 no
`testATabelaDoConsoleTemATelaDeCadaRelatorioLigado` (`p67c-mutantes-php.txt`). **Os mutantes novos**, cada um sozinho, devolvidos
pelo sha1, vermelhos pela asserção (`p67c-mutantes-front.txt`): P67-C1a (o piso fora do livre: o caso da escolha e o da
Cota-parte), C1b (o ‹ anda no piso), C1c (o ciclo não é cortado no piso: dois casos), C2 (a legenda fixa), C3a (as duas listas do
glossário trocadas), C3b (a recalculada dizendo que a sessão é refeita), C3c (a gravada sem a sessão), C4 (o extrato da rede sem
a unidade), C5a (o `aoChegar` com a memória da v1), C5b (o atalho inicial com a memória da v1), C5c (a escolha gravando na chave
da v1), C6a e C6b (o "ainda não gravado" sem a condição da data, na lista e no extrato). Nenhum equivalente. **O que rodou de
novo:** as 17 classes no servidor, em série, 580 casos em cada banco, saída 0 (um pulado, o mesmo); no front, os arquivos da fatia
mais os da escolha do período e do Caixa (26 arquivos), 432 casos, saída 0, e de novo com `AMPLIFICAR_EFEITOS_MS=50`, 432 de 432;
o build (tsc e vite) e o lint (os mesmos 8 avisos de antes, nenhum nos arquivos tocados), saída 0. Depois deste parágrafo: o contar-débitos (de pé 191, como antes), os débitos repetidos, as referências da
documentação, os links mortos e os segredos, saída 0; e o `conferir-tudo.sh` inteiro, sozinho, sem suíte junto: 66 de 66, saída
0, com o front inteiro dentro dele (3.797 casos em 161 arquivos).

**No navegador, depois da correção**, contra o banco de contrato semeado pelo cenário da 66 (o `local.php` trocado e devolvido, o
sha1 confere; o banco remontado depois), cada pessoa no seu endereço (`localhost`, `colab.localhost` e `adm.localhost`: o cookie
de lembrar recriava a sessão do gerente). **O gerente:** Relatórios com dois itens, um "Cota-parte" só e sem a v2; a tela com o
mês, a lista de setembro de 2023 da unidade com o total de R$ 73,80 (o mesmo da API da 66), o fora da lista e a nota do pacote; o
"de um dia a outro" com 01/12/1999 mostrou "Os relatórios não mostram dia antes de 2000." ao lado do campo, sem pedido e com os
números de antes na tela (a correção (1)); o extrato do Colaborador Teste (o corte 20,30 e a pomada 4,50, a 24,80 da linha, o
pacote sem cota e a nota do D310), e o "voltar" sem pedir a lista e com o foco no "Ver o extrato" da linha; a 375 px a tabela rola
no cartão (993 px em 344) e a página não; a quinzena escolhida ficou em `relatorio:cota-parte:ciclo:<unidade>` e voltou depois de
recarregar. **O colaborador:** um "Cota-parte"; só a própria linha, sem produção, faturamento, descontos nem percentual (a 277
(a)), e o extrato sem valor nem percentual dos itens. **O administrador:** a "Cota-parte" e a "Cota-parte v2"; a v2 da unidade em
setembro de 2023 com R$ 80,30, a coluna Aviso e a frase "3 itens de 1 colaborador [...] R$ 10,50 acima do percentual de hoje
aplicado a cada um" (o mesmo número da API); o "Abrir a Cota-parte no mesmo período" pediu `de`, `ate` e `unidade` e mostrou os
R$ 73,80; o tema claro. Nenhum defeito. A carga leva de 10 a 20 s no banco de contrato com a aplicação fria, como nas fatias de
antes.

## 02/11/2026 -- REL-COTA-PARTE-ARQUIVO (a fatia 68 da fila das telas que faltam)

**Para quem usa:** na tela nova, a **Cota-parte e a Cota-parte v2 ganharam o PDF e a planilha** (`.xlsx`, a de hoje; a pergunta
10, "como hoje") no cabeçalho da lista e do extrato, no escopo de quem pede. O arquivo é a tela: um bloco por colaborador (o
extrato dele por tipo e item, a gorjeta, o "A repassar" e o "Total do colaborador") e, para o gerente, o caixa e o administrador, o
"Total da unidade" (ou da rede) com a linha de cada um, a de quem ficou de fora e o total; no extrato, o arquivo é o bloco daquele
colaborador. O que o escopo esconde na tela não existe no arquivo, nem o nome da coluna: o colaborador não vê "Valor", produção nem
faturamento (a pergunta 277 (a), o D158) e a gorjeta dele vem na coluna da cota, dita "fora da cota-parte"; o caixa não vê
"Cota-parte", "% da cota" nem "A repassar" (a pergunta 4). As notas são os textos do "Como se conta"; a v2 leva também o que ela é
e a frase do aviso com a diferença que o servidor somou. **No ERP antigo,** o PDF e a planilha antigos ficam até a aposentadoria
(o D306), mas passam a obedecer à mesma política (o D376, CORRIGIDO): **o caixa deixa de escolher colaborador e período pela URL**
(recebe o dia, de todos), **o colaborador recebe só a si**, mesmo pedindo outro, e **o `?versao=2` só vale para o administrador**
(o gerente que a pede sai na versão 1). O PDF do dia do caixa no menu antigo passou do "grupo 2" ao escopo `dia` da política (o
D672, CORRIGIDO). **Contado na cópia** (só SELECT, só contagem, usuários ativos sem as fixtures, 03/11/2026): as 356 pessoas que
veem o item "Vendas" do ERP antigo continuam vendo o mesmo item -- 10 na rede e 108 na unidade com o formulário, 58 no dia com o
PDF do dia (as 58 do grupo 2, e só elas), 180 só a si com o formulário --; ninguém perdeu o item nem o próprio PDF. O grupo 9 tem 0
ativos (1 inativo); pelas marcas, ele cai no `proprio`: antes escolheria qualquer colaborador pela URL, agora só a si -- mas só no
pedido: as colunas do PDF antigo seguem pelo id do grupo, e ele ainda recebe nele a própria produção ("Valor Total") e não a cota,
o contrário do `proprio` da tela nova; a planilha antiga, pelas marcas, não. Fica com o PDF antigo, até a aposentadoria (o D671 o
diz).

**Novos:** `ISRelatorio\Regra\PedidoDaCotaParteAntiga` (a regra do pedido antigo, sem banco, `final`); o
`tests/Caracterizacao/PedidoDaCotaParteAntigaTest.php` (a matriz dos sete usuários, o caixa esperto, o colaborador que pede outro, a
versão 2 só do administrador, os cantos, e o `indexAction` despachado em processo). **Mudados:** `CotaParteJson::documento` (o
documento das respostas já escondidas: a coluna sai quando o JSON a esconde; a quantidade inteira quando todas são); o
`CotaParteController` (`guardasDoRelatorio`, o `?colaborador=` do arquivo pela regra do extrato, antes de soltar a sessão;
`travaDoArquivo`, a trava da lista ou do extrato da pessoa; `documento`, `array|Response`, com o 404 do colaborador fora do recorte);
o `RelatorioAdmController` (o `documento()` devolve `array|Response`, e a `exportacaoAction` devolve a resposta como veio); as rotas
`exportacao` nos dois filhos do `module.config.php`; o `VendasController` antigo (as duas ações pela regra); o `layout.phtml` (o escopo
`dia`); o `scripts/conferir-grupos-por-id.php` (as agulhas com `===` e aspas duplas, a entrada do grupo 9 fora, e as formas
conhecidas que as agulhas têm de achar); o `scripts/conferir-recorte-por-unidade.php` (cinco vigias: de 404 para 409); na tela, o
`FORMATOS_DA_COTA_PARTE`, os `BotoesDeExportacao` na `CotaParte.tsx` e na `ExtratoDaCotaParte.tsx`, o recorte DA RESPOSTA no endereço
(`CotaParteLigado.tsx`, e no extrato também o colaborador), as duas prévias; o comentário do `SessaoJson`. **Nada mudou** na
`PlanilhaDoRelatorio` nem no `DocumentoDoRelatorio`: o `.xlsx` estava pronto desde a fatia 43. Os testes: `CotaParteTest` (sete casos
do arquivo e o arquivo no caso da sessão e do roteador), `ExportacaoDoRelatorioTest` (o documento que recusa), `RelatoriosNoMenuTest`
(o `if` do escopo `dia`), `PortoesDaFamiliaDosRelatoriosTest` (as duas folhas novas), `CotaParteLigado.teste.tsx` (quatro casos).

**O oráculo** (a planilha aberta pelo PhpSpreadsheet, cada célula contra a resposta JSON do mesmo pedido -- a lista e o extrato de
cada colaborador --, pelas colunas da tabela do desenho, e os rótulos escondidos procurados; o controller em processo, só leitura,
`p68/medir-p68.php`): na cópia, a unidade 1 e a rede em julho de 2026 e a v2 da unidade 1 em julho e em junho, **21.662
conferências em cada banco, 0 divergências**; falsificado (um centavo a mais na cota de um colaborador), **2**, gravado em
`p68/oraculo-falsificado.txt`. No banco de contrato semeado pelo cenário da 66 (setembro de 2023), o gerente, o colaborador, o
caixa e o administrador (na v2 e na rede): **695 conferências em cada banco, 0 divergências**, e nenhum rótulo escondido no arquivo
do colaborador nem no do caixa.

**A medida** (a máquina mudou: é a primeira fatia no Mac Mini M4, e os números não se comparam com os do Windows): o controller em
processo na cópia, três rodadas por processo (a primeira, a fria do processo), `memory_limit` lido 512M, buffer pool do MySQL 128 MB
antes e depois. A planilha da rede em julho de 2026: no MySQL 0,805 s fria e 0,73 e 0,75 s, no PostgreSQL 0,787 s e 0,70 e 0,719 s,
pico de 160 MB, 91 KB de arquivo; o PDF da rede: 0,446 / 0,396 / 0,387 s e 0,415 / 0,365 / 0,352 s, 140 MB, 250 KB; a planilha da
v2 da rede: de 1,07 a 1,15 s, 166 a 168 MB; a da unidade 1: de 0,08 a 0,17 s, 56 MB. Tudo abaixo de 60 s e de 512M; acima de 128M
na rede, como a lista (o D667 e o D646 dizem o mesmo das outras). **O pacote:** 352,03 KB comprimido, +0,22 KB contra a HEAD medida
no mesmo Mac (351,81 KB).

**O que rodou** (a fatia é par: o ciclo inteiro roda depois, pelo orquestrador): as 37 classes da fatia, das vizinhas e dos
relatórios da base (o filtro em `p68/classes.txt`), **1.012 casos em cada banco, saída 0** (2 pulados, declarados: a bancada do
Atendimentos e o desconto fora da unidade sem a cópia); o front inteiro, **3.801 de 3.801 em 161 arquivos**, tsc, lint (saída 0,
os 8 avisos de antes) e build; com `AMPLIFICAR_EFEITOS_MS=50`, os 33 arquivos do front que falam da Cota-parte ou da exportação, 600
de 606 -- **os 6 vermelhos são do `Console.teste.tsx` e caem igual na HEAD** (a suíte inteira amplificada deu 27 vermelhos em 11
arquivos, os mesmos 27 nomes numa cópia da HEAD no mesmo Mac: nenhum é da fatia, e fica dito para a próxima); os ensaios de
permissão, recusas e fluxo e o `medir-adm-api-no-postgres` nos dois bancos de contrato, saída 0 (o medidor mediu o PDF e o `.xlsx`
da Cota-parte ao gerente e os da v2 ao administrador); os portões tocados, saída 0.

**Os mutantes** (um por vez, devolvidos pelo sha1, vermelhos pela asserção ou pelo portão; `p68/mutantes.txt`): os do desenho,
P68-P1 a P9 (CP-E1 o documento sem esconder; CP-E2 a planilha em `.xls`; CP-E3 sem devolver a `Response`, o 404 vira 500; CP-E4 a
régua fixa; CP-A1 o caixa com o colaborador da query; CP-A2 a versão 2 ao gerente; CP-A3 o `!= 2` de volta no layout e CP-A4 o
`$grupo === 2` de volta na sessão, os dois vermelhos no portão de grupos; CP-A5 o `exportarAction` sem a regra, na vigia nova), e os
meus, P68-X1 a X16 (o arquivo fora da trava; a sessão solta antes das guardas; sem o "Total da unidade"; o "Total da unidade" no
`proprio`; o nulo como 0; o botão com o recorte do pedido; o extrato sem o colaborador; as datas da query no caixa; outro colaborador
no `proprio`; o `?versao=2` do gerente no `indexAction`; as duas agulhas sem o `===`; o 403 do arquivo alheio; a lista sem o `so`,
vermelha só na vigia -- o JSON filtra; a gorjeta do colaborador fora do arquivo; a quantidade sempre decimal). Nenhum equivalente.

**Débitos:** D376 CORRIGIDO; D672 CORRIGIDO; D671 PARCIAL (os arquivos antigos seguem no ERP até a aposentadoria, o D306); D306
PARCIAL (a "Vendas Ver. 2" respondida virou a v2); D310 conferido (a resposta 16 já estava; a cota do pacote espera a fatia 74). O
D322 já tinha a "Cota-parte em dobro". De pé de 191 para 189. Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP
mudou; na cópia, só SELECT; a semente só no banco de contrato, que foi remontado nos dois bancos.

**A verificação** (três lentes) confirmou 11 achados, um médio e dez baixos, todos tratados. **O médio:** os estados dos botões de
exportação não tinham caso -- relendo, carregando, na falha, no 404 e na recusa do extrato, e o endereço do extrato do gerente,
sem a unidade --; ganharam sete casos (`CotaParte.teste.tsx` e `ExtratoDaCotaParteLigado.teste.tsx`), e o caso do extrato agora
separa o recorte DA RESPOSTA do da busca (a resposta com outro período e outra unidade). **Os baixos:** as cortesias do fora, que a
tela mostra e o arquivo perdia, vão nas notas com a frase da tela (`fraseDoFora`, que saiu da `NotaDoFora` para o
`textosDaCotaParte.ts`); a frase do aviso da v2 e a do fora têm os mesmos casos nos dois lados (`textosDaCotaParte.casos.json`, o
singular, a diferença abaixo, a zerada, o "-0.00" e o milhar), e o servidor escreve o dinheiro e o número como o `formato.ts`, sem
`float`; o arquivo de um colaborador leva `-colaborador-<id>` no nome (o do A, o do B e o da lista saíam com o mesmo); o arquivo
da rede ganhou caso ("Todas as unidades", "<nome> · <unidade>" e o "Total da rede"); as notas do pacote no `proprio` e no arquivo
de um, e a falta dela sem venda de pacote; o colaborador sem nada no período; a paisagem; o teto do id (4294967295) no arquivo; a
quantidade do "Total do colaborador" dita como é (a soma dos blocos, como o `$colTotQua` do PDF de hoje; o docblock dizia "nenhuma
conta nova"); o `exportarAction` antigo provado em processo (a geração saiu para o `VendasController::gerarArquivo`, e o
`PedidoDaCotaParteAntigaTest` guarda o pedido que chegaria aos geradores: a releitura da URL depois da regra, que a vigia deixava
verde, fica vermelha); os três comentários que diziam "até a CP4"; e o grupo 9, que cai no `proprio` só no pedido -- as colunas do
PDF antigo seguem pelo id do grupo, e ele ainda recebe nele a própria produção e não a cota, escrito no D376, no D671 e acima. **Os
mutantes da correção** (um por vez, devolvidos pelo sha1, `p68/correcao/php-mutantes-my.txt`, `-pg.txt` e `front-mutantes.txt`):
C68-P1 a P21 no servidor, nos dois bancos -- vermelhos pela asserção 19 de 21 no MySQL e 20 de 21 no PostgreSQL: o P14 (sem o
teto do id) é vermelho só no PostgreSQL (no MySQL o id acima da coluna só não acha linha), e o P18 (o `exportarAction` sem o `if ($request === null)`),
equivalente: o nulo só sai sem usuário na sessão, e sem usuário o `getAcesso('ler')` já recusa antes; C68-F1 a F9 na tela -- 7
vermelhos e 2 equivalentes (o `!recusado` da lista e o do extrato: o `CabecalhoDoRelatorio` já esconde a vaga da exportação na
recusa). **O que rodou de novo:** as 37 classes e o `UsuarioCotaPartePelaApiTest` (um comentário), **1.044 casos em cada banco,
saída 0** (2 pulados, os mesmos); o front inteiro, **3.818 de 3.818 em 161 arquivos**, e os seis arquivos da Cota-parte e da
exportação com `AMPLIFICAR_EFEITOS_MS=50`, 78 de 78; tsc, lint (os 8 avisos de antes) e build, **352,03 KB** comprimido (o mesmo:
a frase mudou de arquivo); os portões tocados, saída 0 (o `conferir-recorte-por-unidade` com 409 vigias; de pé 189). O
`conferir-tudo.sh` sozinho saiu 1 na primeira rodada, no "inventário atualizado": com a geração no `gerarArquivo`, o
`scripts/inventariar-acoes.php` lia o `exportar` como "indefinido"; ele passou a reconhecer o `->gerarArquivo(` como entrega de
arquivo (como já reconhecia o `->gerarXls(` e o `->entregar(`), o inventário commitado voltou a bater, e a segunda rodada deu **66 de
66, saída 0**. Os ensaios HTTP não rodaram de novo: nenhum pede o arquivo de um colaborador nem lê as notas. Nenhuma pergunta nova
ao dono.

**O ciclo inteiro** (a fatia é par; rodado por mim depois da correção, no Mac, em 9 min 28 s): caracterização **5.727 em cada
banco, saída 0** (12 pulados, os de antes; eram 5.708 na 66: a 67 e a 68 somaram 19); contrato 74 e 74; os ensaios nos dois bancos
de contrato, saída 0; os portões, **66 de 66**; o front, **3.818 de 3.818**. **No navegador**, contra o banco de contrato semeado
pelo cenário da 66 (setembro de 2023): o gerente com os botões "abrir o PDF" e "baixar a planilha (XLSX)" no cabeçalho, o endereço
do arquivo com o período da resposta e sem a unidade; no extrato, com o `colaborador`, o nome do arquivo com `-colaborador-<id>`, e
os botões desligados e sem endereço quando a sessão cai; a 375 px, sem rolagem lateral. As planilhas, pedidas por HTTP como cada um,
lidas célula a célula: a do gerente com os R$ 73,80 da tela, o "Total da unidade", o fora da lista com a cortesia e a nota do
pacote; a do colaborador sem "Valor", sem produção e sem o "Total da unidade"; a do caixa só com hoje, sem a cota, igual ao JSON
dele; a v2 do administrador com os R$ 80,30, as colunas do aviso e a frase dos R$ 10,50; a da rede com o "Todas as unidades", o
"<nome> · <unidade>" e o "Total da rede" iguais ao JSON da rede; e a v2 ao gerente, 403. No ERP antigo: o caixa do grupo 2 recebe o
PDF do dia, o mesmo arquivo com ou sem colaborador e datas na URL; o administrador tem o item da v2 no menu e o gerente não. **Um
achado, que não nasce na 68:** um grupo só com a marca de caixa e id diferente de 2 recebe o PDF antigo quebrado (o cabeçalho do
`PdfVendasVersao1` escolhe pelo id 1, 2 ou 9 ou pela marca de gerente; sem ramo, a tabela fica sem largura e sai `text/html` com
avisos do PHP); na cópia ninguém cai nele (os 58 do escopo `dia` são do grupo 2), e está escrito no D671, que sai com o PDF antigo.
Os usuários do navegador, as sessões deles e a semente ficaram só no banco de contrato, que foi remontado; o `local.php` voltou ao
`franquiabv_dev`, conferido pelo sha1.

## 03/11/2026 -- REL-PRODUTOS-REDE (a fatia 69 da fila das telas que faltam)

**O que mudou para quem usa.** Três coisas, a F2, a F3 e a F4 do `docs/f3-relatorios/spec-produtos.md`, sobre a tela da fatia 47:

- **O administrador passa a ver o Produtos / Serviços da rede** ("todas as unidades" na escolha da unidade). O serviço com família
  (a etiqueta do cadastro, que só o administrador aplica) é uma linha só, em todas as unidades, com o nome da família (as inativas
  também) e o selo "família"; o resto vai pelo nome, com os espaços juntados e sem caixa -- o acento conta. A linha da rede não tem
  categoria (a coluna "Unidades" diz em quantas unidades de quem fechou ela vendeu no mês), o subtotal por categoria continua, e o
  cabeçalho diz "Todas as unidades". O gerente que pedisse a rede recebe a recusa de sempre. Na cópia (só SELECT, só contagem): os
  **10 administradores ativos** passam a ver a rede; **ninguém ganha nem perde o item do menu** (10 na rede, 106 na unidade, 1
  recusado, os mesmos de antes: o menu não mudou).
- **Na unidade, cada linha ganha o "quem fez (N)"**: o colaborador de cada item (e não quem fechou a venda), com a quantidade e o
  faturamento do mês, numa linha logo abaixo; nas Cortesias, só a quantidade. A comissão não entra (é da Cota-parte).
- **O Catálogo de produtos da tela nova** mostra o "Vendidos" desde sempre **sem a cortesia** (a regra do painel), com a coluna
  "Cortesias" ao lado, a frase "Vendidos e cortesias, desde sempre. O mês está em Relatórios › Produtos / Serviços." e o "ver no
  relatório" (só a quem abre o relatório). **A tela antiga de Configurações continua contando a cortesia** (o D676, registrado), e
  o operador do PDV que lê a lista só pela porta do D168 recebe a linha de antes, sem as cortesias. Na
  cópia: na unidade 39, **39 dos 125 produtos ativos mudam de "Vendidos" e 12.144 unidades vão para "Cortesias"** (e 3 inativos, 16
  unidades); na unidade 1, nenhum.

O "ver todos" do painel não mudou, e o relatório continua sem exportação (a pergunta 10).

**A medida** (a primeira deste relatório no Mac Mini M4: não se compara com a da fatia 47, no Windows; `p69-medida.txt` do
rascunho, em processo, buffer pool de 128 MB antes e depois, três rodadas por processo, a primeira a fria do processo, o banco
nunca reiniciado; o `memory_limit` do contêiner é 512M, lido): **a rede de julho de 2026 com junho de 0,57 a 0,65 s no MySQL e de
0,51 a 0,78 s no PostgreSQL, 132 MB de pico nos dois** -- abaixo da parada do desenho (256 MB e 10 s), e a rede entrou. Com
`memory_limit=128M` ela cai nos dois bancos (no MySQL no `normalizar`, no PostgreSQL no cache do Doctrine), como a Retirada e os
Descontos: escrito no D631. A resposta da unidade 1 em julho foi de 29.187 para 51.450 bytes (o "quem fez"), abaixo dos 200 KB, e a
unidade segue em 0,07 a 0,17 s. A listagem de produtos da unidade 39 (página 1) de 0,028 para 0,019 a 0,020 s quente no MySQL e de
0,007 a 0,016 para 0,013 a 0,014 s no PostgreSQL. **O pacote**: 352,03 KB comprimido (a HEAD, construída à parte) para 352,65 KB
(+0,62 KB).

**O oráculo** (`medir-p69-oraculo.py`, Python sobre os SELECTs crus da cópia contra o JSON dos controllers em processo; a chave e o
nome em `sha1`, sem texto da cópia na saída): a rede de julho com junho, 360 conferências; os 212 pares produto × colaborador da
unidade 1 em julho, 84; o catálogo de produtos das unidades 39 e 1, página a página, 406 -- **em cada banco, 0 divergências**; a
rede de julho dá R$ 1.871.383,01 contra R$ 1.638.856,65, os números da seção 1.8 do desenho. Falsificado (um centavo no total da
rede; uma unidade a mais no "Vendidos" do catálogo): 1 e 404 divergências, saída 1, gravado.

**O que rodou** (a fatia é ímpar: só os testes do que ela toca, e o ciclo inteiro não): as 82 classes da caracterização que tocam
o relatório, o painel, o catálogo e os itens (a lista do grep da nota 5), **1.683 casos em cada banco, saída 0** (5 pulados, os de
antes); o front tocado com `--maxWorkers=4` (404 de 404) e com `AMPLIFICAR_EFEITOS_MS=50` (8 vermelhos, todos na lista dos 27 da
HEAD no amplificado, nenhum da fatia); o front inteiro, **3.826 de 3.826**; tsc, lint e build com saída 0; as prévias com o tipo
conferido (3 erros, em arquivos que a fatia não toca); os ensaios de permissão, recusas, fluxo e o `medir-adm-api-no-postgres` nos
dois bancos de contrato, **saída 0**; e o `conferir-tudo.sh` sozinho, **66 de 66, saída 0**. Os portões: o recorte de 409 para 413
vigias, a cobertura por par de 414 para 416 pares (`ProdutoTagRepository::nomesDasFamilias` e
`VendaProdutoRepository::catalogoVendidosPorPreco`, com a linha de data), a injeção de DQL sem subir.

**Os mutantes**, um por vez, devolvidos pelo sha1, todos vermelhos pela asserção ou pelo portão: os do desenho, P69-P1 a P15 (R1 a
R7, Q1 a Q4, V1 a V4), e os meus, P69-X1 a X14 (a rede liberada ao gerente; o detalhe com a comissão; o nome da família vindo da
linha; a tela sem o selo; o botão "quem fez" sem devolver o foco; o `ehCortesia` sem o arredondamento; o `vendidosSemCortesia` com
os ids do pedido, vermelho no portão do recorte; o "ver no relatório" sem o `podeAbrir`; a rede sem a trava; a rede fora da fila da
tela; a ordem da rede sem a chave; os ids e as unidades da linha só da comparação vindos de junho; a coluna "Categoria" na rede; a
sessão solta antes das guardas da base, vermelho no portão). Nenhum equivalente.

**A verificação** em três lentes confirmou 7 achados, todos baixos, todos tratados: (1) a frase do glossário `produtos.linha`
dizia, também na rede, que dois itens com o mesmo nome são duas linhas e que a soma é o faturamento do painel -- agora a rede
mostra a `produtos.linhaNaRede` no lugar dela, e a unidade a `produtos.quemFez` logo depois (a lista `ITENS_DOS_PRODUTOS_NA_REDE`);
(2) o nome da família que só vendeu na comparação não tinha caso (o `testARede` ganhou a Barba com uma família só de junho); (3) o
"outro mês nasce fechado" e o `aria-controls` do "quem fez" não tinham caso (dois casos novos no `ProdutosVendidosLigado`); (4) o
arredondamento do `vendidosSemCortesia` não tinha caso (um produto com 1,5 e 1,4 vendidos e 1,6 de cortesia, 3 e 2); (5) os
mutantes de consulta tinham rodado só no MySQL (P69-P5, P9, P14 e X3 rodaram de novo nos dois bancos, vermelhos nos dois); (6) o
comentário do cabeçalho prometia "a rede pedida" antes do recorte, e o código mostra a unidade da sessão, como os irmãos (o
comentário foi corrigido); (7) as cortesias saíam também pela porta do PDV do D168, para quem não tem o privilégio de configuração
-- agora só pela primeira porta, como o filtro de etiqueta, e a do PDV recebe a linha de antes da fatia, sem a consulta a mais
(escrito no D676). Os mutantes da correção, um por vez, devolvidos pelo sha1, vermelhos pela asserção: C69-A1 (os nomes das
famílias só do mês), A4 e A5 (as somas truncadas) e A6 (as cortesias pela porta do PDV) nos dois bancos; A2 (a chave da gaveta sem
o recorte), A3 (sem o `aria-controls`), A7 (a rede com o glossário da unidade), A8 (a unidade sem quem fez no glossário) e A9 (a
rede com quem fez) no vitest. Depois da correção: as 82 classes, **1.683 casos em cada banco, saída 0** (5 pulados); o front dos
17 arquivos tocados e vizinhos, 473 de 473, e com `AMPLIFICAR_EFEITOS_MS=50` 8 vermelhos, todos na lista da HEAD; o front inteiro,
**3.828 de 3.828**; tsc, lint e build com saída 0, o pacote 352,69 KB (+0,66 KB contra a HEAD); as prévias com os mesmos 3 erros de
antes; os ensaios de permissão, recusas, fluxo e o `medir-adm-api-no-postgres` nos dois bancos de contrato, remontados, saída 0;
e o `conferir-tudo.sh` sozinho, **66 de 66, saída 0**.

**Débitos:** D676 registrado (a tela antiga de Configurações conta a cortesia); o D631 ganhou a medida do Produtos; o D624, o D625
e o D626 conferidos, sem mudança. De pé de 189 para 190. Nada do esquema, do crontab, do OneSignal, da produção nem do BVIP mudou;
na cópia, só SELECT; o banco de contrato foi remontado antes dos ensaios. Nenhuma pergunta nova ao dono.

**No navegador** (por mim, contra o banco de contrato com as vendas de hoje e de ontem do cenário da Cota-parte): o administrador na
unidade com o "quem fez (2)" no Corte, o `aria-expanded` e o `aria-controls`, a linha do detalhe logo abaixo com os dois
colaboradores e o foco ficando no botão; na rede, o "Todas as unidades", a coluna "Unidades", nenhum "quem fez", o selo "família"
numa família posta só no contrato e a frase da rede no "Como os números são contados"; a 375 px, sem rolagem lateral; o Catálogo
com a coluna "Cortesias" (o café do cenário com 1), a frase e o "ver no relatório", que abre o relatório no mês de hoje com o foco
no título; e, por HTTP, o gerente com 200 na unidade, com o "quem fez", e 422 na rede. Nenhum defeito da fatia. **Um defeito anterior,
consertado aqui:** o `esquecerAsFilasDaRede` (o que os testes chamam depois de cada caso) esquecia a fila `atendimentosClientes`,
desde a fatia 65; a lista escrita à mão virou um laço sobre o próprio `noAr`, e o front inteiro deu de novo **3.828 de 3.828**, tsc e
lint com saída 0.

## 03/11/2026 -- PDV-CANCELAMENTO (a fatia 70 da fila das telas que faltam)

**O que mudou para quem usa.** A P0 e a P1 do `docs/f3-relatorios/spec-ordem.md` (seção 4), a seção 3 do `spec-canceladas.md`,
o "antes ou depois do fechamento" da seção 3 do `spec-caixa.md` e a R4 do `spec-royalties.md`, nas duas cópias locais:

- **Todo cancelamento pelas duas telas grava quando e qual gerente autorizou** (o gerente da senha que a tela já pedia, e não quem
  está no caixa), e a rotina das comandas abandonadas grava só quando. Nenhum fluxo pede nada novo: o cancelamento já pedia a senha
  do gerente.
- **As Vendas Canceladas ganham o cartão "Cancelamentos no período"**, pelo dia do cancelamento e não pelo da venda: desfeitas e
  comandas, as que mudaram um mês já fechado, das desfeitas quantas no dia do fechamento e quantas depois, e "Por quem autorizou",
  com a rotina numa linha. Só a partir de hoje (o "Gravado desde 03/11/2026" no topo); um mês inteiro antes disso diz "sem registro".
  As taxas continuam pelo mês da venda.
- **O Caixa separa as canceladas antes e depois do fechamento** (e "sem registro" as de antes de hoje), no resumo e na conferência,
  e cada cancelada do detalhe diz quando e por quem -- ou "pela rotina". O saldo de agora não muda. A quebra continua com o "ainda
  não gravado": é da fatia 72.
- **Os Royalties apontam qual venda foi cancelada depois do retrato**, com o valor, o instante e quem autorizou, nos dias a partir
  de hoje; a venda cancelada depois de faturada continua sem virar crédito (a RY1).
- Na cópia (só SELECT, só contagem, sem nome): **62.509 vendas canceladas, todas "sem registro"** (21.538 desfeitas, as outras
  comandas); canceladas por mês da venda em 2026: jan 533, fev 463, mar 531, abr 499, mai 437, jun 465, jul 543, ago 295, set 34 (a
  cópia vai até 29/09/2026).

**O esquema.** Quatro migrações, uma por pergunta, só acrescentam (a 83 (a) do dono): `scripts/migracao-venda-cancelamento.sql`
(`vendas.cancelado_em`, `cancelado_por` com `FK_VENDAS_cancelado_por`, `vendas_produtos.desconto_lancado_por` com a chave),
`-caixa-valor-contado.sql`, `-produtos-estoque-motivo.sql` e `-cota-do-pacote.sql` (`produtos.cota_parte_pacote` 0 por padrão,
`vendas_produtos.vendedor_do_pacote` com a chave e `cota_do_vendedor`); o `mysql.sql`, o `postgres/schema.sql` regerado, os irmãos
em `postgres/`, as entidades, o laço do passo 0 do procedimento, o do ensaio geral, a tabela "O que fica no banco" da etapa 1 e
o `scripts/migrar-dados.sh` regerado pelo `gerar-migracao-dados.php` (só as cinco tabelas mudam; gerado, e não rodado).
**Aplicadas nas duas cópias, duas vezes** (a segunda sem efeito, saída 0), as contagens das cinco tabelas iguais antes e depois.
**O `ALTER` com chave estrangeira, medido** (MySQL 8.4, 128 MB de buffer pool): o do cancelamento, `vendas` (2,27 milhões) e
`vendas_produtos` (3,26 milhões), 44,2 s; o da cota do pacote, 19,8 s; os dois sem chave, 0,1 a 0,2 s (`INSTANT`). No PostgreSQL,
2,0 s e 0,6 s. **A data "gravado desde" não é das migrações** (a nota 4 da fatia): só o `cancelamento` tem linha em
`colunas_gravadas`, escrita por um passo próprio depois do código (`scripts/colunas-gravadas-cancelamento.sql`), e na produção no
"7d" da janela; as outras quatro esperam o escritor (71 a 74). **A leitura por `cancelado_em`, medida:** a tabela inteira, de 0,34
a 0,42 s no MySQL e de 0,13 a 0,40 s no PostgreSQL, quente -- abaixo de 1 s, sem índice (o D677; o frio não se mede sem reiniciar).

**O oráculo.** As migrações em dois bancos descartáveis (o `mysql.sql` da HEAD mais as quatro, duas vezes, contra um montado do
`mysql.sql` novo, apagados no fim): **128 conferências do `information_schema`, 0 divergências**; falsificado (a migração sem a
conferência do `information_schema`), a segunda rodada para em "Duplicate column" e sai 1. No banco de contrato, **dois
cancelamentos de verdade pela API** com a senha do gerente de teste (a venda 90001010, de março, e a comanda 90001500, de setembro),
e o JSON das Vendas Canceladas (o cartão, março com o bloco nulo e a venda nas taxas) e do detalhe do Caixa conferido contra um
SELECT das colunas: **22 conferências, 0 divergências**; falsificado (o autor esperado trocado), 2 divergências e saída 1. O dos
Royalties não se prova por HTTP (o dia de hoje está sempre em apuração): fica no teste em processo.

**O que rodou** (a fatia é par: o ciclo inteiro roda depois). As classes da fatia e as vizinhas (o que o grep da nota 5 achou e os
relatórios que leem `relatorioItens`), **1.397 casos em cada banco, saída 0** (1 pulado, de antes). O front inteiro **3.833 de
3.833**; os tocados com `AMPLIFICAR_EFEITOS_MS=50`, 118 de 118; tsc, lint (os 8 avisos de antes) e build com **353,61 KB** (+0,92
KB). Os ensaios de permissão, recusas e fluxo e o `medir-adm-api-no-postgres` nos dois bancos de contrato, saída 0 (o de recusas no
MySQL saiu 2 na primeira vez pelo caixa que o de fluxo deixa aberto -- o resíduo conhecido; remontado, 0); o medidor dos relatórios
no PostgreSQL, saída 0. Os portões: o do passo 0, o schema-tool (igual à linha de base), o `verificar-schema` e o
`-postgres`, a unicidade do schema, o recorte por unidade, a injeção de DQL, o booleano em coluna inteira, a cobertura por par (de
416 para 418 pares, ISRelatorio de 178 para 180, o "sem menção" em 118), a lacuna do CI e o novo
`conferir-escritores-do-cancelamento.php`, saída 0; o `conferir-tudo.sh` inteiro, sozinho, no fim: **67 portões, saída 0**. A
primeira rodada saiu 1 por um portão, o relógio nos testes: os dois casos novos liam `date()` (o D87); passaram ao `agora()`, e os
dois arquivos rodaram de novo nos dois bancos (15 de 15) antes da segunda rodada.

**Os mutantes**, um por vez, devolvidos pelo sha1, todos vermelhos pela asserção ou pelo portão: P70-P1 e P2 (cada porta sem a
gravação), P3 e P4 (o operador no lugar do gerente, no ERP e na API), P5 (o bloco pelo dia da venda; nos dois bancos), P6 (a rotina
com autor), P7 (a cancelada antes do fechamento contada como depois), P8 (o "sem registro" com data), P9 (o instante comparado com
a data da venda, e não com o `escrito`; puro e em processo), P10 (a soma maior que a diferença aceita); e os meus: X1 (a linha do
`valorContado` gravada agora), X2 (a migração sem o `information_schema`, pelo oráculo), X3 (a FK sem nome, pelo portão do passo
0), X4 (a rotina gravando `cancelado_por`, pelo portão novo), X5 (o portão sem a porta do `VendaService`), X6 (o passo próprio
gravando também o valor contado), X7 (o `normalizar` sem o instante), X8 (o detalhe do caixa sem quem), X9 (o bloco sem o corte na
data), X10 a X12 (o cartão sem a data, os Royalties sem a lista, a rotina dita com autor, no front) e X13 (a lista antes da data).
Nenhum equivalente.

**Débitos:** D677 (a leitura por `cancelado_em` lê a tabela inteira) e D678 (a base do teste do BVIP sem as oito colunas
mapeadas; o deploy pede o `ALTER` à mão) registrados; o D310 PARCIAL (as colunas da cota do pacote existem); de pé de 190 para 192.
Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`, só as quatro migrações e a linha
`cancelamento` de `colunas_gravadas`, e SELECT; o banco de contrato foi remontado.

**A correção depois da verificação.** As três lentes confirmaram 10 achados, três médios e sete baixos, todos tratados:
- **Royalties (médio):** a partir da data, a frase do `canceladaDepois` contava e somava também a venda cancelada ANTES do retrato
  ("2 vendas, R$ 100,00" sobre uma lista de uma venda de R$ 80,00). Agora `canceladas.vendas` e `valor` são os da lista a partir da
  data, e os do dia inteiro antes dela; e o dia igual à data ganhou caso.
- **A etapa 1 sem o passo da linha (médio):** a linha `cancelamento` de `colunas_gravadas` passou a ser escrita no passo 7 do
  `procedimento-de-corte-etapa-1.md`, logo depois da troca da árvore e antes de liberar (no MySQL, pelo
  `scripts/colunas-gravadas-cancelamento.sql`). A carga da etapa 2 a leva ao PostgreSQL, e o 7d de lá, idempotente, só a escreve no
  corte de uma vez só. Sem isso, entre as duas etapas, os três relatórios diriam "ainda não gravado" com o dado gravado, e os
  cancelamentos desse meio ficariam de fora calados. O procedimento, o ensaio geral, o `spec-ordem`, o `spec-comum` e os dois
  arquivos SQL dizem o mesmo.
- **O Caixa no front (médio):** as partes "antes do fechamento" e "sem registro" e o "quando e quem: sem registro" ganharam caso
  (puro e na tela).
- **Baixos:**
  - o glossário `mesDaVenda` voltou ao texto da V1 sem a data, e com a data a tela usa a chave nova `mesDaVendaGravado`;
  - o cartão sem nenhum cancelamento e o "Usuário N" ganharam caso;
  - o cancelamento que regravasse o fechamento do caixa agora fica vermelho (`RelatorioDeCaixaTest::testOCancelamentoNaoRegravaOFechamento`, pelo `VendaService`);
  - o caso dos escritores de `status = 0` vê qualquer variável, a constante, o `UPDATE` de SQL e de DQL e o construtor de consultas, e o desenho diz o que ele não vê;
  - o desempate do "Por quem autorizou" ganhou caso;
  - o `spec-caixa` diz a frase que a tela mostra;
  - a coluna `produtos.cota_parte_pacote` não vem mais do POST cru do cadastro antigo: o construtor de `Produto` tira a chave em qualquer grafia. O resto do formulário segue hidratado inteiro: o **D679**, registrado.

**O que rodou depois da correção:**
- **Servidor:** as classes da fatia, as vizinhas e as do cadastro do catálogo: 1.615 casos em cada banco, saída 0, 4 pulados. Com o
  filtro só da fatia, 1.401 casos e 1 pulado, o de antes; os outros 3 pulados vêm das classes do catálogo acrescentadas.
- **Front:** o inteiro, 3.838 de 3.838. Os tocados com `AMPLIFICAR_EFEITOS_MS=50`, 87 de 87. tsc, lint (os 8 avisos de antes) e
  build com 353,62 KB.
- **Portões:** o `conferir-tudo.sh` sozinho, 67 portões, saída 0. O `conferir-debitos-repetidos` saiu 1 com os 6 pares novos do
  D679; lidos os dois débitos de cada par, nenhum era o mesmo defeito, e os 6 entraram com o motivo; saída 0.
- **Ensaios HTTP:** não rodaram de novo.

**Os mutantes da correção**, um por vez, devolvidos pelo sha1, todos vermelhos pela asserção, nenhum equivalente: C70-1 (a contagem
do dia inteiro; puro e em processo), C70-2 (`>=` para `>` no dia da data), C70-3 e 4 (o empate pelo id invertido; a rotina
primeiro), C70-5 (o cancelamento regravando o fechamento do caixa), C70-6 e 7 (o `$vendaTemp->setStatus(0)` e o `UPDATE` do DQL com
status 0, que a regex de antes deixava passar), C70-8 (o construtor sem o filtro da cota), e no front C70-F1 a F7 (sem a parte
antes; sem a sem registro; a sem instante dita como da rotina; a tabela vazia no lugar da frase; o "Usuário N" apagado; a lista com a
data sempre; o texto da V1 sem o "que ainda não é gravado").

**Débitos:** D679 registrado; de pé de 192 para 193.

**O ciclo inteiro, depois da correção** (por mim; a fatia é par), com o ajuste da Cota-parte do dono já commitado (acf19106):
caracterização **5.765 em cada banco**, saída 0 (12 pulados); contrato 74 e 74; os ensaios nos dois bancos, saída 0; os portões,
saída 0; o front **3.839 de 3.839**; e o medidor dos relatórios no PostgreSQL, saída 0.

**No navegador** (por mim, contra o banco de contrato semeado, com a linha `cancelamento` escrita pelo
`scripts/colunas-gravadas-cancelamento.sql`, como o passo 7 da etapa 1 a escreve): o gerente cancelou a comanda 90001500 (de
08/09/2026, R$ 129,90, com um pagamento de R$ 100,00) pela tela de Comandas abertas, com a própria senha e um motivo; o banco gravou
`cancelado_em` no instante e `cancelado_por` = o gerente (90001001), e não o operador da comanda (90001000). O administrador viu:
- **nas Vendas Canceladas** de novembro, o "Gravado desde 03/11/2026", o cartão "Cancelamentos no período" com a venda (R$ 129,90,
  "0 no dia do fechamento, 1 depois", "mudaram um mês já fechado") e o "Gerente Teste" em "Por quem autorizou"; a 375 px, sem
  rolagem lateral (a página com 375 de largura, o cartão com 343). O gerente não vê o relatório (403), como antes;
- **no Caixa** do dia, "1 antes do fechamento · 2 sem registro" (o caixa segue aberto), e no detalhe a comanda com "cancelada em
  03/11/2026 às 08:41 por Gerente Teste", as duas antigas da semente com "quando e quem: sem registro";
- **nos Royalties**, com um retrato de 08/09 posto à mão só no contrato (escrito antes do cancelamento, a comanda dentro), o dia
  08/09 com "o dia tem 1 venda finalizada e hoje cancelada, somando R$ 129,90. O cancelamento veio depois do retrato." -- a frase
  da regra de soma da R1, porque a VENDA é de antes da data, como o desenho manda ("para os dias a partir de `DESDE`"). **Visto
  aqui, e não é defeito:** a venda de um dia antigo cancelada depois da data tem o instante e o autor gravados, e mesmo assim a tela
  não a aponta; o caso comum dos primeiros meses (a venda do mês passado desfeita hoje) fica na frase da soma até as vendas de
  depois da data começarem a ser canceladas. Apontar também essas pede outra regra para o dia que mistura canceladas com e sem
  registro -- fica escrito aqui para o dono decidir se vale. **(O dono decidiu em 03/11/2026: a resposta 279 (b), "deixar como
  está"; registrada na fatia 74c.)**

**Um resíduo da semente, não da fatia:** a comanda 90001500 do `fixtures.sql` está ABERTA e já com `caixa`, e por isso as Vendas
Canceladas a contam como desfeita, e o Caixa como "cancelada depois de finalizada" (a regra da V1: com caixa, foi fechada). Na
cópia, a única comanda aberta não tem caixa (o fechamento é que o grava), e as 2.452 canceladas sem caixa são as comandas. Nenhum
defeito da fatia no navegador; o banco de contrato foi remontado no fim.

## 03/11/2026 -- PDV-DESCONTO-LANCADO-POR (a fatia 71 da fila das telas que faltam)

**O que muda para quem usa (nas cópias locais).** Todo desconto digitado no PDV -- no novo (a comanda) e no antigo, e o cupom
que o PDV antigo põe no campo do desconto -- passa a gravar **quem o lançou**: quem entrou no sistema, também quando está usando a
conta de outra pessoa (a personificação, o D315). Nenhum fluxo pede nada novo a quem usa. O desconto do convênio, que o sistema
dá sozinho, não tem quem lançou; o convênio com um desconto do balcão por cima grava quem digitou o do balcão; o cupom que vence
o digitado tira o autor; e quem só aperta o "+" ou o "−" de um item com desconto não vira o autor dele (o mesmo desconto, o
mesmo autor). **Os Descontos** mostram, a partir de 03/11/2026 (a data gravada), o grupo "Por quem lançou (só o manual)", o
filtro "Lançado por" na lista e a coluna "Lançado por" em cada item; o desconto manual de antes da data diz **"não registrado"**,
e o mês todo antes da data não tem o grupo. **Contado na cópia** (`franquiabv_dev`, só SELECT, só contagem, a rede, vendas
finalizadas): em 2026, 28.990 itens com desconto, dos quais 9.500 manuais, que ficam "não registrado" -- por mês, de janeiro a
agosto: 1.316 de 3.726, 1.198 de 3.710, 1.288 de 3.725, 1.372 de 4.083, 1.257 de 3.880, 1.158 de 3.752, 1.263 de 4.206 e 648 de
1.908 (a cópia vai até 23/08/2026); nenhum item com autor.

**A linha da data**, no molde da do cancelamento: `scripts/colunas-gravadas-desconto-manual.sql` e o irmão em `postgres/`,
idempotentes, depois do código. Aplicada nas duas cópias (MySQL e PostgreSQL) às 09:16 de 03/11/2026, duas vezes, a segunda sem
efeito: `cancelamento` e `descontoManual` têm data, `valorContado`, `motivoDaRetirada` e `cotaDoPacote` seguem nulos. **No
corte:** no passo 7 da etapa 1, logo depois da do cancelamento (a aplicação em manutenção: ninguém lança desconto entre a troca e
a linha), no 7d do `procedimento-de-corte.md` (idempotente), no ensaio geral e na tabela "O que fica no banco". Nada na
produção.

**O que entrou no código.** O escritor é um só, `VendaProdutoService::calcularLinha` (o ramo do desconto digitado, só quando ele
tira algo; o `alterar` zera o autor com o desconto e o devolve quando o desconto que fica é o mesmo); as SETE chamadas das duas
telas (o desenho falava em três) passam `autorDaEscrita()['gravadoPor']`, fora do pedido cru, e a chave é obrigatória no
`insert` e no `alterar`. A consulta dos relatórios ganhou `lancadoPor` (a linha passa de 27 a 28 chaves; a do painel continua
com 17). O Descontos: `ContasDosDescontos::porQuemLancou` e `lancadoPor`, o filtro, a resposta e a tela (a D4). O portão novo
`scripts/conferir-escritores-do-desconto.php` (no `conferir-tudo.sh` e no CI), irmão do do cancelamento. A regra do ajuste e o que
divergiu do desenho estão nos blocos FEITA do `spec-ordem.md` (a P2) e do `spec-descontos.md` (a D4).

**O oráculo.** No banco de contrato (MySQL), uma comanda de verdade pela API com a senha do gerente de teste -- o corte do
cliente de convênio com R$ 7,50 digitados por cima (a linha dividida), outro corte só com o convênio e a lavagem com R$ 4,00 de
desconto manual --, paga e finalizada; o JSON do Descontos (o índice, a lista inteira, o filtro do gerente e o mês anterior)
contra um SELECT das colunas, com a conta da origem refeita à parte: **18 conferências, 0 divergências**; falsificado (o autor
esperado trocado), 1 divergência, saída 1. A primeira rodada acusou 1 divergência que era do oráculo (o `??` do PHP lê o nulo
como ausente); consertado o oráculo, o banco remontado e rodado de novo. Na cópia, só SELECT, pelo código: julho de 2026 na rede
(31.898 itens) dá o grupo **nulo** -- o mês todo antes da data --, e o SELECT, nenhum item com autor; novembro, o mês da data, a
lista vazia (a cópia não tem venda nele): **4 conferências, 0 divergências**; falsificado, 1.

**O que rodou (a fatia é ímpar: só os testes do que ela toca; o ciclo inteiro fica para a 72).** O PHPUnit das 100 classes que o
grep da nota 5 acha (as portas do PDV, os relatórios que leem a consulta comum, o Descontos, o `ColunasDoPdvTest`, o
`ItensDoRelatorioTest`, o `RelatoriosNoMenuTest`, o `PortoesDaFamiliaDosRelatoriosTest`): **2.036 casos em cada banco, saída 0**
(um pulado em cada, o da cópia de produção, declarado). Depois disso entraram o caso do cupom que vence e os itens de convênio e
cupom de depois da data, rodados com o `DescontoGravaQuemLancouTest`, o `DescontosTest` e o `RegrasDosDescontosTest`, saída 0. O
front inteiro, **3.848 de 3.848** (161 arquivos); os seis arquivos tocados de novo com `AMPLIFICAR_EFEITOS_MS=50`, 87 de 87; tsc,
lint (oxlint, nenhum aviso nos arquivos tocados) e build, **354,18 KB** comprimido (+0,56 KB). Os ensaios de permissão, recusas
e fluxo e o `medir-adm-api-no-postgres` nos dois bancos de contrato, **saída 0** cada (as 35 travas `/tmp/*.lock` do contêiner
apagadas antes). O `conferir-tudo.sh` inteiro, sozinho: **68 portões, saída 0** -- a primeira rodada saiu 1 no "cobertura por par": o
`Adm\DescontosController` chama o `UsuarioRepository::nomesDoCadastro` duas vezes, e a contagem do módulo `ISRelatorio` passou de
180 a 182 no portão, com o porquê (o par já existia; o total de pares e o sha1 não mudam). O `conferir-segredos.sh`, saída 0.

**Os mutantes**, um por vez, devolvidos byte a byte e conferidos pelo sha1, todos vermelhos pela asserção (ou pelo portão):
P71-P1a, b e c (a porta sem a gravação: a API ao lançar, o ERP, o ajuste) e P71-P2a e b (o autor como `usuarioId`, no ERP e na
API) -- vermelhos no `DescontoGravaQuemLancouTest` E no portão; P71-P3 (o convênio gravando); P71-P4ab (o `alterar` sem zerar,
arrancando os dois nulos); P71-P5 (o convênio e o cupom no grupo), P71-P6 (o `null` virando lista vazia) e P71-P7 (a data com
`>`), nos dois bancos; P71-X1 (a linha `valorContado` gravada junto no passo novo) e P71-X2 (o passo sem o `NOT EXISTS`, rodado
duas vezes), cada um no arquivo do seu banco; P71-X3 (o portão sem a porta do ajuste: com a porta passando nulo, o portão de
volta ao verde -- é aquele trecho que a segura); P71-X4 (a tela com o colaborador do item no lugar de quem lançou); P71-X5 (o
ajuste do "+" sem manter o autor); P71-X6 (o cupom que vence sem tirar o autor); P71-X7b (o filtro ignorado); P71-X8 (o eco sem o
filtro); P71-X9 (o lançamento sem a chave aceito); P71-X10 (a coluna fora da consulta, nos dois bancos); P71-X11 (o `normalizar`
sem a chave); P71-X12 (o autor no item de convênio e de cupom); P71-X13 (o grupo mostrado com a data e sem o registro do mês).
**Equivalentes, ditos:** o P71-P4a (só o nulo do `alterar`) e o P71-P4b (só o do `calcularLinha`) -- cada um é coberto pelo
outro, porque o `alterar` chama o `calcularLinha`; o P4a fica vermelho no portão, que exige o nulo no `alterar`. O P71-X7 (o
parâmetro fora da lista) só quebrava por erro de tipo, e não conta: o X7b o substituiu. A primeira rodada dos três mutantes do
front saiu inválida (o `vitest` rodou na raiz, sem arquivos de teste, e criou uma pasta de cache que foi apagada); refeitos na
pasta do front, vermelhos.

**Débitos:** nenhum aberto nem fechado; o D678 (a base do teste do BVIP sem as colunas) ganhou a nota de que esta fatia lê e
escreve mais uma sem mudar o esquema. De pé, 193. **Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na
`franquiabv_dev`, só SELECT e a linha `descontoManual` de `colunas_gravadas` (e o mesmo na cópia do PostgreSQL); o banco de
contrato foi remontado. O navegador não foi aberto por mim (é do dono).**

**A correção depois da verificação** (cinco achados baixos, confirmados; quatro consertados com caso, um ao dono). (1) **A
personificação no ajuste** só era segurada pelo portão textual: o `DescontoGravaQuemLancouTest` ganhou o caso do ajuste com a
sessão de uma pessoa e quem entrou de outra (`testOAjusteNaPersonificacaoGravaQuemEntrou`). (2) **As duas portas do produto do
cupom com mais de uma unidade** -- a linha a parte quando o cupom vence e o `alterar` quando o digitado vence -- gravavam o autor
sem caso de comportamento: dois casos pela API, com 2 cortes, o cupom de 20% e a personificação. (3) **Na tela**, o grupo "Por
quem lançou" e o filtro "Lançado por" não tinham caso do estado ocupado nem do nome ausente: o nome dos três lugares saiu para uma
regra só (`nomeDeQuemLancou`, em `listaDosDescontos.ts`) e entraram os casos do ocupado e do "Usuário nº N". (4) **O "CUPOM VIP:"
escrito à mão no motivo do PDV antigo** faz o desconto digitado contar como cupom, fora do grupo, embora o banco tenha quem o
lançou: é regra do dono (o desenho manda o cupom fora do grupo), e foi ao lote ("Descontos: o item com a marca do cupom mostra
quem o lançou?"), com o **D680** registrado -- na cópia, 21 itens de 2026 com a marca, 17 sem convênio (só contagem). (5) **O "−"
que encolhe a linha** mantinha o autor de antes com o desconto maior em proporção (os R$ 50,00 de 5 cortes viravam 83% de um): a
regra do ajuste ganhou a condição de o preço cheio da linha não diminuir, e o "−" grava quem o apertou
(`testOMenosQueAumentaAProporcaoGravaQuemAjustou`); escrito no bloco FEITA da P2 do `spec-ordem.md`. **Os mutantes novos**, um
por vez, devolvidos pelo sha1, todos vermelhos por um caso só: C71-1 (o ajuste com o `usuarioId`), C71-2a (a linha a parte com o
autor nulo), C71-2b (o `alterar` do cupom com o autor nulo), C71-5 (a condição do preço cheio arrancada), no MySQL; F71-1 e F71-2
(o grupo e o filtro com o ocupado desligado), F71-3 (a regra sem o número), F71-4 e F71-5 (o filtro e o grupo de volta à
expressão em linha, sem o número), no front. **Rodou de novo:** as 100 classes da fatia, **2.041 casos em cada banco, saída 0**
(um pulado em cada, o declarado; 5 casos novos); o front inteiro **3.851 de 3.851**, e os 5 arquivos do Descontos com
`AMPLIFICAR_EFEITOS_MS=50`, 84 de 84; lint e build saída 0 (**354,20 KB**); os dois portões de escritores, o
`conferir-segredos.sh`, e o `conferir-tudo.sh` sozinho, **68 portões, saída 0**; o `contar-debitos.php` e o
`conferir-debitos-repetidos.php`, saída 0 (o par D477|D680 registrado como assuntos distintos). Os ensaios HTTP não rodaram de
novo: a correção não mexe em rota nem em resposta. De pé, 193 para **194** (o D680).

**No navegador** (por mim, contra o banco de contrato semeado, com as duas linhas de `colunas_gravadas` escritas pelos passos
próprios, como o passo 7 da etapa 1 as escreve): o gerente lançou pela tela de Comandas abertas um corte de R$ 45,10 com 10% de
desconto digitado e o motivo obrigatório na comanda 90001001, do Colaborador Teste; o banco gravou `desconto_lancado_por` =
o gerente (90001001), e não o colaborador do item (90001000). Recebido em dinheiro e finalizado pela tela. O administrador viu no
Descontos de novembro o "Manual" com o "Gravado desde 03/11/2026", o grupo "Por quem lançou (só o manual)" com o Gerente Teste, o
filtro "Lançado por" e a coluna "Lançado por" com o Gerente Teste ao lado do Colaborador Teste; outubro, todo antes da data, sem o
grupo e sem a coluna; a 375 px, as tabelas rolando no cartão e a página sem rolagem lateral. O gerente não vê o relatório (403),
como antes. Nenhum defeito; o banco de contrato foi remontado no fim.

## 03/11/2026 -- PDV-GAVETA-CONTADA (a fatia 72 da fila das telas que faltam)

**O que mudou para quem usa** (a pergunta 15 (c) do dono: "o fechamento passa a pedir o dinheiro contado; o relatório mostra a
quebra"). Nas cópias, **todo fechamento de caixa pede o dinheiro contado na gaveta, e não fecha sem ele**: no ERP antigo, o campo
"*Dinheiro contado na gaveta" no formulário de fechar, obrigatório, na máscara da casa, com a recusa ao lado do campo e a ajuda
"o dinheiro que está na gaveta agora, contado na mão"; na tela nova do caixa, o campo na confirmação do fechamento, com o foco
nele e a recusa ao lado. O zero vale (a gaveta vazia é uma contagem). **O relatório de Caixa mostra a quebra** -- o contado menos
a gaveta esperada na hora do fechamento --, a partir da data, em palavras ("sobrou R$ X", "faltou R$ X", "bateu"), na coluna
Quebra da tabela e da de unidades, no resumo (a soma só dos caixas que têm, e de quantos) e no detalhe (o contado e a gaveta ao
fechar); o caixa aberto diz "caixa aberto", e o fechado antes da data (ou sem contagem) "sem registro" -- nunca R$ 0,00. O "ainda não gravado" virou
"Gravado desde 03/11/2026; antes disso, sem registro.", e o "Como os números são contados" ganhou a quebra. **Contado na cópia**
(só SELECT, só contagem, sem nome; os dois bancos dão o mesmo): em 2026, **7.475 caixas fechados**, todos sem contagem, que ficam
"sem registro" -- 995 em janeiro, 863 em fevereiro, 1.042 em março, 1.010 em abril, 1.014 em maio, 979 em junho, 1.067 em julho
e 505 em agosto (pelo mês da abertura; a cópia vai até meados de agosto); **101 pessoas** fecharam caixa; numa unidade, nos dias
em que há fechamento, **1,50 por dia em média** (de 1 a 6; 2.893 unidade-dias com um, 1.747 com dois, 345 com três ou mais): é
quantas vezes por dia o campo novo aparece. 139 caixas seguem abertos na cópia (os esquecidos do D664): nada os fecha.

**O que entrou.** O escritor é um só, o `CaixaService::fecharComSaldoRecalculado($caixa, int $contado)`, em centavos, gravado
pelo `fechar` -- agora PRIVADO, a única porta -- dentro da mesma trava e da mesma transação do status. As duas portas leem o
número pela regra pura nova `RegraDoValorContado` (a máscara no ERP, o decimal do protocolo na API, os dois com o teto do caixa);
a API devolve 422 com `campos.contado` sem ele, negativo, texto, com vírgula, com a terceira casa (também no número JSON) ou
acima do teto, depois do 404 e do 409. O envio do fechamento do ERP para um caixa que já não está aberto -- a corrida, que dizia
"Caixa fechado com sucesso"; a aba velha com o caixa já fechado, que ABRIA um caixa novo; e a aba velha depois de outro caixa
aberto, que o fechava com a contagem do velho -- passou a dizer "Este caixa já estava fechado: a contagem digitada não foi
gravada." e não grava nada (o D681, registrado e corrigido; os dois últimos caminhos, achados na verificação). Nenhuma rotina fecha caixa (lido no código). O Caixa: o `contado` na consulta, a consulta nova
`VendaPagamentoRepository::relatorioDinheiroDasCanceladas`, o `saldoAoFechar`, a `gavetaAoFechar` (a de agora com o dinheiro,
menos o troco, das canceladas DEPOIS do fechamento) e a quebra, só no caixa fechado com a contagem e a partir das DUAS datas, a
do valor contado e a do cancelamento -- o invariante: com as duas datas, o caixa fechou com o escritor do cancelamento no ar,
e por isso toda cancelada de depois do fechamento de um caixa com quebra tem o instante; vale em qualquer ordem em que as linhas
tenham sido escritas. **A linha da data**,
`scripts/colunas-gravadas-valor-contado.sql` e o irmão em `postgres/`, idempotente, no passo 7 da etapa 1 logo depois da do
desconto manual, no 7d do corte, no ensaio geral e na tabela "O que fica no banco"; **aplicada nas duas cópias** (o único INSERT na
cópia), duas vezes, a segunda sem efeito: `cancelamento`, `descontoManual` e `valorContado` com data, `motivoDaRetirada` e
`cotaDoPacote` nulos. Portão novo: `scripts/conferir-escritores-do-contado.php` (no `conferir-tudo.sh` e no CI). Nada do esquema
mudou (a coluna é da fatia 70).

**O oráculo, no banco de contrato** (MySQL), um fechamento de verdade pela API com o gerente de teste: o caixa da fixture fechado
com R$ 50,00 contados; um caixa novo com R$ 100,00; três vendas em dinheiro (uma paga com R$ 100,00, o troco virando gorjeta);
uma cancelada antes do fechamento; a recusa sem o contado (422, o caixa aberto e sem contagem); o fechamento com R$ 280,00
contados; e a outra cancelada depois dele. A conta a mão (gaveta ao fechar 289,90, quebra −9,90) bateu com o SELECT e com o
JSON da lista, do resumo e do detalhe: **23 conferências, 0 divergências**; falsificado (a quebra esperada um centavo a mais),
**1 divergência, saída 1**.

**O que rodou** (a fatia é par: o ciclo inteiro é do coordenador, depois). As classes que a fatia toca e as vizinhas, 122:
**2.383 casos em cada banco, saída 0** (um pulado em cada, sem nome nesta rodada; a correção o identificou, abaixo). O front inteiro **3.868 de 3.868**; os oito arquivos do
caixa e do relatório com `AMPLIFICAR_EFEITOS_MS=50`, 140 de 140; tsc, lint (os 8 avisos de antes) e build saída 0 (**355,05 KB**,
+0,85 KB). Os ensaios de permissão, recusas (com o 9d2 e o 9d3, o fechamento sem o contado e com ele negativo: 422, e o caixa
aberto), fluxo (o passo 17 manda o contado e confere a coluna) e o `medir-adm-api-no-postgres`, **nos dois bancos de contrato,
saída 0**. O `conferir-tudo.sh` sozinho, **69 portões, saída 0** (a primeira rodada saiu 1 pela cobertura por par: o par novo
`relatorioDinheiroDasCanceladas`, declarado na base, 418 para 419). `contar-debitos` e `conferir-debitos-repetidos`, saída 0.

**Os mutantes**, um por vez, devolvidos pelo sha1, todos vermelhos pela asserção ou pelo portão: P72-P1 (a porta sem a
gravação), P2a (a API aceitando sem o contado), P2b (o ERP com o campo opcional), P3 (a quebra com a gaveta de agora), P4 (a
cancelada de antes no "ao fechar"), P5 (a quebra zero no caixa aberto), P6 (o resumo somando o nulo como zero), P7 (o caixa de
antes da data com quebra) -- o P3 nos dois bancos --; X1 e X1b (o passo gravando o `motivoDaRetirada` junto, nos dois), X2 e X2b
(o passo sem o `NOT EXISTS`, nos dois), X3a (a API passando 0 no lugar do contado), X3b (um segundo escritor, no abrir: só o
portão), X3c (o `fechar` público), X4 (a tela sem a recusa ao lado do campo), X5 (o "sobrou/faltou" trocado), X6 (o ERP sem a
mensagem do campo), X7 (o hidratador do caixa sem tirar a chave: o caso; o portão textual não o vê), X8 e X9 (a consulta do
dinheiro das canceladas com a finalizada e com outra forma, nos dois), X10 (o controller sem a data do contado, nos dois), X11 (a
coluna sem a data), X12 (o vazio aceito como zero na tela) e X13 (o resumo sem quebra virando "0.00").

**A verificação e a correção** (três lentes; 7 achados confirmados, um médio e seis baixos, um deles em dobro -- 6 distintos --,
todos tratados). (1, o médio) **A aba velha do fechamento abria um caixa novo:** com o caixa já fechado noutro lugar, o POST do
fechar caía no `abrir()`, cujo filtro só pede o `valor_inicial` (o fechar o traz preenchido) e cujo token é o mesmo (a sessão do
`Csrf` é a do nome do elemento); abria um caixa com o valor inicial do velho, dizia "Caixa aberto com sucesso" e a contagem sumia
-- e o texto do D681 o explicava errado. Lendo, achei o terceiro caminho: com outro caixa já aberto, o `fechar()` o fechava com a
contagem do velho. O conserto: o formulário de fechar leva o id do caixa num campo escondido; o `abrir()` recusa o POST com o
`caixa` ou o `valor_contado`, o `fechar()` recusa o id que não é o do aberto, e a frase é uma só
(`CaixasController::JA_ESTAVA_FECHADO`). (2) **O D681 sem caso:** o `testOErpDizQueJaEstavaFechadoQuandoOServicoRecusa`, com o
serviço de verdade e o caixa fechado por SQL logo antes da trava. (3) **O D664 sem a nota da 72:** fechar os esquecidos pelo
`UPDATE` os deixa "sem registro", e pela tela pediria uma contagem que ninguém fez (o zero daria um "faltou" falso); a nota no
D664 e no `medir-caixas-abertos.sql`, e o glossário `caixa.quebra` passou a dizer "o aberto, o de antes e o fechado sem
contagem". (4) **O caso pulado "o de antes" sem prova:** com `--display-skipped`, é o
`RegrasDosAtendimentosTest::testBancadaDoAno` ("a bancada roda so com MEDIR_ATENDIMENTOS=1", desde a a2dffd59, 01/11/2026), sem
mudança na fatia, nos dois bancos. (5) **O invariante do `ColunasDoPdvTest` nunca falhava** (os três `INSERT` na transação do
caso saem com o mesmo instante) e os dois specs o citavam como prova: a asserção saiu, e o `spec-caixa.md` e o `spec-comum.md`
dizem que quem segura a regra é o `temQuebra`, que pede as duas datas em qualquer ordem -- os mutantes C72-8 e C72-8b (o
`temQuebra` sem a data do cancelamento, e sem o dia dela) ficam vermelhos no `testSemQuebraENulaNuncaZero`. (6, dito duas vezes)
**O número JSON com três casas era arredondado e aceito** (o `number_format`: 1.001 gravava R$ 1,00, 150.555 gravava R$ 150,56,
sem o 422 que o texto leva): agora passa pelo mesmo padrão como o PHP o escreve (`(string)`, o do `Monetario::deDecimal`), com os
casos de três casas, do meio centavo, do teto e do 1.0E20 na regra e dois 422 novos na API. **Os mutantes da correção**, um por
vez, no MySQL, devolvidos pelo sha1, todos vermelhos pela asserção: C72-1 (o `abrir` sem a guarda: os três casos da aba velha),
C72-2 (o `fechar` sem conferir o id), C72-3 (o `false` do serviço ignorado), C72-4 e C72-5 (a guarda do `abrir` só com um dos
dois campos), C72-6 (a tela sem o campo escondido), C72-7 (o `number_format` de volta: cinco casos) e C72-8/8b. **Depois da
correção:** as 122 classes, **2.397 casos em cada banco, saída 0**, um pulado em cada (a bancada, acima); o front inteiro **3.868
de 3.868**; os cinco arquivos do caixa, do relatório e do glossário com `AMPLIFICAR_EFEITOS_MS=50`, 99 de 99; lint (os 8 avisos de
antes) e build saída 0 (**355,06 KB**); os ensaios de permissão, recusas, fluxo e o `medir-adm-api-no-postgres` nos dois bancos de
contrato, saída 0; o `conferir-tudo.sh` sozinho, **69 portões, saída 0**; o `conferir-escritores-do-contado.php`, o
`conferir-segredos.sh`, o `contar-debitos` e o `conferir-debitos-repetidos`, saída 0 (os dois pares D568|D681 e D573|D681 saíram
da lista: o título novo do D681 não os forma mais). O banco de contrato foi remontado.

**Dívidas:** o D681 (registrado e corrigido, ampliado na verificação); o D678 e o D664 com a nota da 72. De pé, **194** (sem
mudança). Nada da produção, do BVIP,
do OneSignal nem do crontab mudou; na `franquiabv_dev`, só a linha `valorContado` de `colunas_gravadas` e SELECT; o banco de
contrato foi remontado. O navegador é do coordenador.

**O ciclo inteiro, depois da correção** (por mim; a fatia é par), no Mac, em 9 min 27 s: caracterização **5.883 em cada banco**,
saída 0 (os mesmos 12 pulados da 68 e da 70); contrato 74 e 74; os ensaios nos dois bancos, saída 0; os portões, saída 0; o front
**3.868 de 3.868**; e o medidor dos relatórios no PostgreSQL, saída 0.

**No navegador** (por mim, contra o banco de contrato semeado, com as linhas `cancelamento`, `descontoManual` e `valorContado`
escritas pelos `scripts/colunas-gravadas-*.sql`, como o passo 7 da etapa 1 as escreve):
- **no ERP antigo**, o gerente viu o campo obrigatório "Dinheiro contado na gaveta", vazio (o "0,00" é só a máscara), com a ajuda
  "contado na mão" e o id do caixa num campo escondido; enviado vazio, a recusa voltou ao lado do campo e o caixa seguiu aberto;
- **na tela nova** (Abrir/Fechar Caixa), o "fechar caixa…" abriu a confirmação com o campo e o foco nele; confirmado vazio, a recusa
  ao lado; com 450,00, o caixa 90001000 fechou ("fechado às 11:15, com saldo de R$ 521,80") e o banco gravou `valor_contado` = 450 e
  `status` = 0;
- **no relatório de Caixa**, o administrador viu a coluna Quebra, o "Gravado desde 03/11/2026" e o resumo "em 1 caixa fechado com a
  contagem". A primeira leitura deu gaveta R$ 50,00 e "sobrou R$ 400,00": a forma "Dinheiro (teste)" das fixtures tem o id 90001000,
  e a gaveta conta só a forma 1 (`FormaPagamento::DINHEIRO`, o "Dinheiro" da cópia) -- o resíduo conhecido do contrato desde a
  fatia 61, não defeito da fatia. Com a forma 1 posta à mão só no contrato e os seis pagamentos em dinheiro desse caixa passados
  para ela, a conta a mão (100,00 + 371,80 − 50,00 de troco = 421,80; 450,00 − 421,80) bateu com a tela: gaveta **R$ 421,80** e
  **"sobrou R$ 28,20"** na lista, no total e no resumo; no detalhe, "Contado na gaveta R$ 450,00", a gaveta ao fechar R$ 421,80 e a
  quebra. Em janeiro (antes da data), o caixa 90001150 com "sem registro" na Quebra e o resumo com "—, nenhum caixa fechado com a
  contagem neste período". A 375 px, sem rolagem lateral (a página com 375 de largura);
- **a aba velha** (o médio da verificação): com o caixa já fechado, o POST com os campos do formulário de fechar (o id do caixa, o
  valor inicial, 450,00 e o token da sessão) voltou ao gerenciar com "Este caixa já estava fechado: a contagem digitada não foi
  gravada." e nenhum caixa novo no banco.

O banco de contrato foi remontado, o `local.php` devolvido (conferido pelo sha1) e a sessão do navegador encerrada; na
`franquiabv_dev`, 0 caixas com contagem.

## 03/11/2026 -- PDV-MOTIVO-DA-RETIRADA (a fatia 73 da fila das telas que faltam)

**O que mudou para quem usa** (a pergunta 9 (a) do dono: "uso interno na comanda nova; saída do Estoque com motivo (perda,
vencimento, acerto de inventário); acerto fora do total"). Nas cópias, **toda saída de produto pela tela de estoque pede o
motivo -- perda, vencimento ou acerto de inventário --, e não sai sem ele**: no ERP antigo, o campo "Motivo da saída" na aba
Estoque, obrigatório na saída (a entrada o ignora), com a recusa ao lado do campo; na tela nova do estoque, o grupo "Motivo da
saída" que aparece com a saída, com a recusa ao lado e o foco nele; na API, o 422 em `motivo`. As duas listas de movimentações
ganharam a coluna Motivo. **A retirada de uso interno** (o modal do PDV antigo e a comanda nova) **não muda para quem a lança**, e
passa a ficar registrada como `uso`. **O relatório de Retirada de Produtos**, a partir da data, separa as colunas Perda,
Vencimento e Acerto de inventário -- este "(fora do total)": conta nas saídas do estoque e nas pessoas, e fica fora do "saiu sem
receita" --; a saída de antes, sem motivo, continua na coluna dela, que passa a se chamar "Saída sem motivo (gravada antes de
dd/mm/aaaa)" quando a data cai no período; o mês todo antes da data fica como estava ("sem registro" nas três). O aviso virou
"Gravado desde 03/11/2026; antes disso, sem registro.", e o "Como os números são contados" ganhou os motivos. **Contado na cópia**
(só SELECT, só contagem, sem nome; o MySQL): em 2026, **455 saídas pela tela de estoque** (452 ativas) -- 71 em janeiro, 51 em
fevereiro, 89 em março, 44 em abril, 44 em maio, 72 em junho, 60 em julho e 24 em agosto (a cópia vai até meados de agosto) --, que
ficam "sem motivo"; **543 retiradas de uso** (541 ativas) -- 54, 59, 60, 59, 61, 70, 126 e 54 --, que ficam "uso interno" por quem
pediu; **23 pessoas** fizeram saída pela tela de estoque em 2026; e `SELECT motivo, COUNT(*) FROM produtos_estoque GROUP BY
motivo` dá **33.576 nulas, e nada mais**, nas duas cópias, antes e depois da linha da data.

**O que entrou.** Nenhum esquema: a coluna `produtos_estoque.motivo` é da P0 (a fatia 70). Os escritores são dois, no
`ProdutoService`: o `estoqueAtualizar` grava o motivo só na saída, um de `ProdutoEstoque::MOTIVOS_DA_TELA_DE_ESTOQUE`, e NÃO grava
a saída sem ele (o `false` do pedido que não é movimentação; a entrada grava nulo); o `retirada` grava `uso`. As três portas leem o
motivo pela regra pura nova `ISConfiguracao\Regra\RegraDoMotivoDaSaida` (o `paraGravar`, o `aceitoNoErp`, o `recusaDaApi`), que
lê a lista da entidade; o construtor da entidade não hidrata o motivo. Na API, a saída sem motivo, com `uso` ou com texto livre, e
a entrada com motivo, são 422 em `campos.motivo` com as frases do desenho ("Obrigatório na saída: perda, vencimento ou acerto." e
"A entrada não tem motivo."), junto das outras recusas de campo e antes do 409; o `ProdutoJson::movimentacao` ganhou `motivo`. No
ERP antigo, o `Select` sem o `InArray` próprio (ele recusaria o vazio da entrada, em inglês) e o POST sem o campo levado ao filtro
como vazio. Na Retirada, o `retiradasDoPeriodo` lê `est.motivo`; a porta, o total e as três colunas já estavam prontos desde a F0.
Nenhuma outra porta faz linha em `produtos_estoque` (lido: a venda baixa o estoque sem linha, e não há `INSERT` cru). **A linha da
data**, `scripts/colunas-gravadas-motivo-da-retirada.sql` e o irmão em `postgres/`, idempotente, no passo 7 da etapa 1 logo depois
da do valor contado, no 7d do corte, no ensaio geral e na tabela "O que fica no banco" da etapa 1; **aplicada nas duas cópias
locais**, duas vezes (a segunda sem efeito; o único INSERT na cópia): `cancelamento`, `descontoManual`, `valorContado` e
`motivoDaRetirada` têm data, `cotaDoPacote` segue nulo. **Portão novo:** `scripts/conferir-escritores-do-motivo.php`, no
`conferir-tudo.sh` e no `ci.yml` (a movimentação só nasce no serviço, em dois métodos, cada um com a sua gravação do motivo;
ninguém mais grava o motivo; as duas telas do estoque são as únicas que chamam o `estoqueAtualizar`, e pedem o motivo pela regra).
A prévia ganhou o `RetiradaDeProdutos` → `ComOMotivo`, e as do estoque, o motivo nas movimentações (o upload é do dono).

**O oráculo**, no banco de contrato, pela API com o gerente de teste: as três recusas (a saída sem motivo, a com `uso`, a entrada
com motivo) com o 422 em `motivo` e nenhuma linha; uma perda de 2, um vencimento de 1, um acerto de 5 e uma entrada de 3 pelo
estoque, e uma retirada de uso de 1 pela comanda nova; o banco com as cinco linhas e o motivo de cada uma (a entrada nula, a
retirada `uso`) e o saldo 20 − 2 − 1 − 5 + 3 − 1 = 14; e a Retirada do mês conferida contra um SELECT (cada porta pela regra) e
contra a conta a mão (a perda 2, o vencimento 1, o acerto 5, o "saiu sem receita" sem o acerto, as saídas do estoque com ele, a
porta de cada saída no detalhe, a entrada fora): **26 conferências, 0 divergências**; falsificado (a perda esperada 3), **1**.

**O que rodou.** A fatia é ímpar: só os testes do que ela toca -- as **118 classes** que o grep de `estoque`, `retirada`,
`ProdutoEstoque`, `ProdutoService`, `ProdutosController`, `ColunasDoPdv`, `colunas_gravadas`, `perda`, `vencimento`, `acerto` e
dos portões acha (com as do grep da nota 5), **2.518 casos em cada banco, saída 0**, os mesmos 5 pulados nos dois, pelo nome:
`ComboboxDeAnosNoRelatorioTest::testOComboDoPainelDeduplicaOsAnos`, `D199AtivarRemoverPorPostTest::testOPostSemEstadoNaoMudaNada`
("vipclass"), `D199AtivarRemoverPorPostTest::testOAtivarDaListaEFormularioComOEstado` ("mensagens" e "vipclass") e
`VendasNoRelatorioTest::testODescontoNaoSomaODeOutraUnidade` (o `RegrasDosAtendimentosTest::testBancadaDoAno` não está no
recorte); o front inteiro **3.877 de 3.877**; os arquivos tocados com `AMPLIFICAR_EFEITOS_MS=50`, 201 casos, 2 vermelhos -- os dois
já vermelhos na HEAD (`p68/amp-nomes-head.txt`: "a página que falha ao virar leva o foco ao título, e não ao vazio" e "o aviso da
gravação não passa ao próximo item aberto"), nenhum novo; tsc e lint limpos (os 8 avisos de antes); build 355,88 KB (+0,82 KB); a
prévia com o tsc de rascunho, saída 0; os ensaios de permissão, recusas (os casos 197 a 200 novos, e o 196 conferindo o `uso`) e
fluxo e o `medir-adm-api-no-postgres`, nos dois bancos de contrato, saída 0; o `conferir-tudo.sh` sozinho, **70 portões, saída
0**; `conferir-segredos.sh`, saída 0. **Os mutantes**, um por vez, devolvidos pelo sha1, os de consulta e de servidor nos dois
bancos, todos vermelhos (nenhum equivalente): **P73-P1** (RP1, a retirada sem `uso`: o `RetiradaDeEstoqueNaComandaTest` e o
portão), **P2** (RP2, a saída sem motivo aceita na API: o 422), **P2b** (o serviço sem a recusa: o caso do serviço e o portão),
**P3** (RP3, a data esquecida -- o controller passando nulo: as três colunas nulas), **P3b** (a consulta sem `est.motivo`), **P4**
(RP4, o motivo gravado também na entrada: o caso da entrada), **P5** (a 4.3, a porta sem a gravação: os casos e o portão); e os da
fatia: **X1a/X1b** (a linha `cotaDoPacote` gravada junto no passo novo, cada banco), **X2a/X2b** (o passo sem o `NOT EXISTS`), **X3**
(um segundo escritor do motivo no ERP: o portão), **X4** (o `uso` aceito pela tela de estoque), **X5** (o acerto dentro do total:
o `RegrasDaRetiradaTest` e o `RetiradaDeProdutosTest`), **X6a/X6b** (a tela nova e a antiga sem a recusa ao lado do campo), **X7**
(o filtro do ERP sem a conferência), **X8** (o POST sem o campo direto ao filtro), **X9** (o construtor hidratando o motivo), **X10**
(a entrada mandando o motivo), **X11** (a tela nova sem a recusa antes de pedir), **X12** (o rótulo "gravada antes de" esquecido),
**X13** (o "Como os números são contados" sem os motivos), **X14** (a entrada com motivo aceita na API) e **X15** (a API sem passar o
motivo ao serviço).

**Débitos:** D682 registrado (a tela antiga da Retirada de Produtos soma o acerto, a perda e o vencimento como retirada; a nova os
separa); D678 com a nota da 73. De pé 194 para 195. Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na
`franquiabv_dev`, só a linha `motivoDaRetirada` de `colunas_gravadas` e SELECT (a cópia do PostgreSQL também); o banco de contrato
foi remontado. O navegador não foi aberto pelo implementador (é do coordenador, abaixo).

**A verificação em três lentes (03/11/2026) confirmou 5 achados, todos baixos, tratados.** (1) A tela antiga da Retirada conta
o acerto como retirada também no histórico por produto (`historicoRetiradasPorProduto`), e não só na soma: o D682 passou a dizer
as duas leituras, e a escolha -- tirar o acerto da tela antiga ou deixá-la até ser aposentada no corte -- foi ao lote do dono
("Retirada de Produtos antiga: o acerto de inventário sai da soma e do histórico?"); a tela antiga não mudou. (2) As faixas por
mês do `f3-relatorios.md` misturavam o total e as ativas: as duas passam a ser contadas com todas as linhas (de 24 a 89 e de 54 a
126), como no parágrafo acima, refeitas na cópia só com SELECT. (3) O detalhe da Retirada sem a data ("Saída pelo estoque") não
tinha caso: o caso novo do `RetiradaDeProdutos.teste.tsx` (julho, o Café Especial). (4) Nenhum caso prendia a marca da recusa do
motivo saindo com a troca de operação: o caso novo do `CatalogoLigado.teste.tsx` (a saída recusada, a entrada, a saída de novo,
sem o `aria-invalid` nem a frase). (5) O motivo aparado que a tela antiga leva ao serviço (o `getValue('motivo')` do
`estoqueAction` e o `StringTrim` do filtro) não tinha caso nem portão: o
`MotivoDaSaidaDoEstoqueTest::testOErpGravaOMotivoAparadoPeloFiltro` (o POST com " perda " grava `perda`) e uma afirmação nova no
`conferir-escritores-do-motivo.php`. Os mutantes da correção, um por vez e devolvidos pelo sha1, todos vermelhos pela asserção:
**C73-3** (o detalhe sem o `&& motivoGravado`), **C73-4** (a troca de operação sem o `setErroDoMotivo(null)`), **C73-5a** (o
controller sem o `getValue('motivo')`; o caso e o portão) e **C73-5b** (o filtro sem o `StringTrim`). Depois da correção, em série:
as mesmas 118 classes, **2.519 casos em cada banco, saída 0** (os mesmos 5 pulados); o front inteiro **3.879 de 3.879**; os dois
arquivos tocados com `AMPLIFICAR_EFEITOS_MS=50`, 157 casos e os 2 vermelhos que já estão na lista da HEAD, nenhum novo; tsc limpo;
lint com os mesmos 8 avisos; o `conferir-tudo.sh` sozinho, **70 portões, saída 0**; `contar-debitos` e
`conferir-debitos-repetidos`, saída 0 (de pé 195, sem mudança). A tela e o servidor não mudaram, e por isso o build e os ensaios
HTTP não rodaram de novo.

**No navegador** (por mim, contra o banco de contrato semeado, com as quatro linhas da data escritas pelos
`scripts/colunas-gravadas-*.sql`, como o passo 7 da etapa 1 as escreve), o administrador de teste:
- **na tela nova do estoque** (Produtos, a Pomada Teste): a entrada de 10 lançada sem pedir motivo; com "saída", o grupo "Motivo
  da saída" (Perda, Vencimento, Acerto de inventário) apareceu; lançada sem motivo, a recusa "Obrigatório na saída: perda,
  vencimento ou acerto." ao lado do grupo, com o `aria-invalid` nele e o foco no primeiro rádio; com Perda, a recusa sumiu e a
  saída de 2 entrou; depois um acerto de 3; a lista com a coluna Motivo ("Perda", "Acerto de inventário", o traço na entrada);
- **na tela antiga do estoque** (`/mod-configuracoes/produtos/estoque/90001001`): o campo "Motivo da saída" com a ajuda, a
  coluna Motivo no histórico; a retirada de 1 sem motivo voltou com a recusa ao lado do campo; com Vencimento, "Estoque atualizado
  com sucesso" e a linha no histórico. O banco gravou `perda`, `acerto` e `vencimento`, e nulo na entrada;
- **na Retirada de Produtos** de novembro: Perda 2, Vencimento 1, Acerto de inventário (fora do total) 3, "Saiu sem receita" 3 (a
  perda e o vencimento, sem o acerto) e o custo de R$ 60,00, a coluna "Saída sem motivo (gravada antes de 03/11/2026)" e o
  "Gravado desde 03/11/2026"; as pessoas com as 6 unidades, o acerto inclusive -- a conta a mão bateu. Março (antes da data), como
  antes: "Saída pela tela de estoque" 3 e uso interno 2, sem as três colunas. A 375 px, sem rolagem lateral (a página com 375 de
  largura).

A sessão do navegador foi encerrada antes de o `local.php` voltar (conferido pelo sha1), e o banco de contrato foi remontado; na
`franquiabv_dev`, 0 linhas com motivo.

## 03/11/2026 -- PDV-COTA-DO-PACOTE (a fatia 74 da fila das telas que faltam)

A P5 da seção 4 do `docs/f3-relatorios/spec-ordem.md` (a cota do pacote, de quem vendeu, por sessão: a pergunta 16 (b) do dono,
"quem vendeu, por sessão -- aos poucos, a cada sessão executada, e só pelo que foi usado"; o D310) e a seção 3 do
`spec-cota-parte.md` (o bloco de quem vendeu na Cota-parte), sobre as colunas da fatia 70; os passos 52 e 53 da 3.3. A quinta e
última fatia do PDV.

**O que muda para quem usa.** O cadastro do pacote, no ERP antigo e na tela nova, ganha o campo "Cota-parte de quem vendeu": o
percentual da cota-parte de quem vendeu o pacote, pago a cada sessão usada, sobre o valor da sessão; zero por padrão, e com zero
nada muda. A partir da data, cada sessão de pacote finalizada -- pela comanda ou pela agenda -- grava quem vendeu o pacote e, com
percentual, a cota dele; a comissão de quem executou a sessão não muda. A Cota-parte mostra, para quem vendeu, o bloco "Pacotes que
vendeu, por sessão usada" (as sessões usadas, o valor delas e a cota), na lista (a coluna "Cota das sessões dos pacotes que
vendeu"), no extrato, no PDF (na página do colaborador) e na planilha; o colaborador vê a própria, e nunca a de outro; a v2 mostra
a gravada, com o aviso. **Na cópia** (só SELECT, só contagem): 144 pacotes ativos (239 no total), nenhum com percentual diferente
de zero; as sessões de pacote de 2026, por mês, 349, 334, 334, 385, 379, 339, 369 e 185 (de janeiro a agosto) -- o volume que,
daqui em diante, grava quem vendeu; 2 dos 3.475 pacotes de cliente sem o item `pac` na venda de origem (os que ficariam sem
vendedor; 1 sessão em 2026), e nenhum sem venda; 23 pessoas venderam pacote em 2026.

**O que entrou.** O percentual (`produtos.cota_parte_pacote`) se grava só pelo cadastro do pacote, nas três portas -- a API
(`Adm\PacotesController`, o 422 em `campos.cotaPartePacote` para texto, negativo, acima de 100 ou três casas; zero sem a chave no
`POST`, e o `PUT` sem ela não o muda), o ERP antigo (o campo com a recusa ao lado, a vírgula da casa) e a tela nova (`Pacotes.tsx`,
o campo, a frase e a recusa ao lado) --, pela regra pura `RegraDaCotaDoPacote` (centésimos de ponto inteiros). Quem vendeu e a cota
(`vendas_produtos.vendedor_do_pacote` e `cota_do_vendedor`) se gravam só no `VendaPagamentoService::atualizarValorComissaoPacote`,
pelo `finalizar` (a porta da comanda e a da agenda): o vendedor é o colaborador do item `pac` da venda de origem
(`VendaProdutoRepository::vendedorDoPacote`), gravado também com 0%; a cota é o valor da sessão (o `valor_total_relatorio`, e nunca
o `valor_total`, que é zero) vezes o percentual de hoje, em centavos. A leitura comum ganhou `vendedorDoPacote` e `cotaDoVendedor`
(a linha do relatório de 28 para 30 chaves; a do painel não muda), e o percentual de hoje vem de
`ClientePacoteRepository::percentuaisDaCotaDoPacote`. Na Cota-parte, o `doRecorte` credita a sessão a quem executou e a quem
vendeu (`ContasDaCotaParte::QUEM_VENDEU`), com a recalculada pelo percentual de hoje e a gravada, e o `cota.pacotes` deixa de ser
nulo para o período que termina depois da data. Portão novo: `scripts/conferir-escritores-do-pacote.php`, no `conferir-tudo.sh` e no
`ci.yml`; a cobertura por par passa de 419 para 421.

**A linha da data.** A `cotaDoPacote` de `colunas_gravadas`, num passo próprio e idempotente
(`scripts/colunas-gravadas-cota-do-pacote.sql` e o irmão em `postgres/`), no passo 7 da etapa 1 logo depois da do motivo da
retirada, no 7d do corte, no ensaio geral e na tabela "O que fica no banco". Aplicada nas duas cópias, duas vezes (a segunda sem
efeito: o `INSERT 0 0` do PostgreSQL), o único INSERT na cópia: as cinco linhas têm data.

**A medida** (a Cota-parte da rede em julho de 2026 na cópia, pelo controller em processo, o molde da 68, três rodadas, a quente):
no MySQL, a planilha de 0,80 a 0,87 s e 160 MB de pico, o PDF de 0,43 a 0,49 s e 142 MB, a v2 de 1,16 a 1,21 s e 168 MB; no
PostgreSQL, 0,76 a 1,08 s e 160 MB, 0,40 a 0,46 s e 140 MB, 1,12 a 1,18 s e 166 MB (o `memory_limit` de 512M; o buffer pool de
128 MB antes e depois). Contra a 68, na mesma máquina: de 5% a 10% mais, os mesmos picos; o PDF com 289.898 bytes, e não 250.358,
pela página por colaborador do pedido de 03/11, e não por esta fatia. Em julho nenhuma sessão tem vendedor (são de antes da data):
o custo medido é o das duas colunas a mais na leitura.

**O oráculo** (no banco de contrato, pela API, o gerente de teste e o administrador do contrato): 10% num pacote; o pacote vendido
pela comanda (o V); duas sessões, uma pela comanda e outra pela agenda, pelo B, finalizadas; o banco com o vendedor e a cota de cada
uma (80,00 × 10% = 8,00) e a comissão do B igual à de antes; a Cota-parte do dia (a lista, o extrato do V e o do B, a planilha)
contra um SELECT e contra a conta à mão; o percentual em 12,5% e a recalculada (10,00 por sessão); a v2 com a gravada e o aviso:
**34 conferências, 0 divergências**; falsificado (a cota à mão de 9,00), **5 divergências, saída 1**.

**O que rodou** (a fatia é par: o ciclo inteiro é do coordenador, depois). Os testes do que a fatia toca, em série: as 118 classes
do grep, **2.191 casos em cada banco, saída 0**, 5 pulados, os mesmos nos dois (o `D199AtivarRemoverPorPostTest` com vipclass e
mensagens, três; o `DescontosNoRelatorioTest::testOsTotaisNaoSomamOsDeOutraFranquia`, sem desconto fora da unidade no banco sem a
cópia; o `RegrasDosAtendimentosTest::testBancadaDoAno`, só com `MEDIR_ATENDIMENTOS=1`). O front inteiro, **3.900 de 3.900**; os 37
arquivos tocados e do grep com `AMPLIFICAR_EFEITOS_MS=50`, 823 casos e 8 vermelhos, todos na lista da HEAD (6 do `Console.teste.tsx`
e 2 do `PacotesLigado.teste.tsx`), nenhum novo; tsc limpo; lint com os mesmos 8 avisos; build com 356,65 KB (+0,77 KB sobre a 73).
Os ensaios de permissão, recusas (os novos 201 a 203: 101 e três casas recusados, 12.5 aceito), fluxo e o
`medir-adm-api-no-postgres`, nos dois bancos de contrato, saída 0 (253 de 253 no de recusas). O `conferir-tudo.sh` sozinho, saída 0
(depois da correção abaixo). `contar-debitos` e `conferir-debitos-repetidos`, saída 0.

**Os mutantes**, um por vez, devolvidos pelo sha1, os de consulta nos dois bancos, todos vermelhos pela asserção ou pelo portão
(nenhum equivalente): **P74-P1** (a 4.3, a porta sem a gravação: quatro casos e o portão), **P2** (a 4.3, a cota sobre o
`valor_total`: três casos e o portão), **P3** (o bloco creditado a quem executou no lugar de quem vendeu), **P4** (a sessão
creditada só a quem executou) e **P4b** (só a quem vendeu), **P5** (o `cota.pacotes` não nulo antes da data), **P6** (quem só
vendeu fora da lista), **P7** (a recalculada com o percentual gravado no lugar do de hoje); e os da fatia: **X1a/X1b** (outra linha
gravada junto no passo novo, cada banco), **X2a/X2b** (o passo sem o `NOT EXISTS`), **X3a** (um segundo escritor da cota no
`VendaProdutoService`: o portão) e **X3b** (um segundo escritor do percentual no `ProdutoService`: o portão), **X4** (o vendedor não
gravado com 0%), **X5** (a comissão de quem executou mudada: os casos e o portão), **X6** (o colaborador vendo a cota de quem
vendeu de outro), **X7** (o bloco fora do PDF e da planilha), **X8** (o 101 aceito pela regra: o caso da regra; o serviço ainda o
recusa, por exceção) e **X8b** (o 101 aceito na tela nova); e, da verificação, **X9** a **X13** (abaixo).

**Débitos:** D310 CORRIGIDO (o desenho inteiro entrou; na produção, pelo corte); D683 registrado (a cota de quem vendeu vai a
quem lançou o pacote numa venda depois cancelada -- 232 pacotes de cliente na cópia, 118 ativos, 38 sessões em 2026 -- e a quem
saiu da empresa -- 5 dos 23 vendedores de 2026, 104 sessões --, com as duas perguntas no lote: "Cota de quem vendeu: o pacote de
uma venda cancelada" e "Cota de quem vendeu: quem já saiu da empresa"; ficou o padrão do desenho, que não perde dado). De pé 195,
sem mudança. Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`, só a linha `cotaDoPacote` de
`colunas_gravadas` e SELECT (a cópia do PostgreSQL também); o banco de contrato foi remontado. O navegador é do coordenador, e não
foi aberto aqui.

**A verificação em três lentes (03/11/2026) confirmou 5 achados, dois médios e três baixos (o implementador parou antes de fechar a
fatia), todos tratados.** (1) **Com 0%, a sessão com vendedor mudava a "% da cota" e o "fora da lista"** (médio): a base da linha de
quem vendeu recebia a sessão, que já estava na de quem a executou -- a "% da cota" do vendedor e a do total caíam sem
configuração nenhuma, e a base do total contava a sessão duas vezes. A base e a produção da linha de quem vendeu não recebem mais a
sessão (o bloco tem a base dele): a "% da cota" é a cota-parte inteira de cada um sobre a base da produção dele, e a de quem só
vendeu fica nula. Casos novos: `RegrasDaCotaParteTest::testComZeroPorCentoNadaMudaNaListaNoForaNemNoTotal` e
`CotaParteTest::testComZeroPorCentoNadaMudaNaLista` (as mesmas sessões com e sem vendedor, a 0%: a mesma lista, o mesmo "fora", o
mesmo total, a mesma "% da cota"). (2) **A documentação não tinha sido escrita** (médio): este parágrafo, o D310, a P5 e os passos
52 e 53 do `spec-ordem.md`, a seção 3 do `spec-cota-parte.md` (FEITA, com o que divergiu), o item 37 do bloco "Em dia" do
`spec-comum.md` e a subseção da Cota-parte do `f3-relatorios.md`. A frase "duas vezes" do ensaio geral tem a saída guardada (as duas
rodadas de cada cópia, o PostgreSQL com `INSERT 0 1` e `INSERT 0 0`). (3) **Faltava a prova dos ensaios no PostgreSQL, dos
mutantes, do amplificador, do tsc e do `conferir-tudo.sh`, e a medida** (médio): tudo rodou, acima. E o `conferir-tudo.sh` estava
VERMELHO em dois portões que ninguém tinha rodado: o `conferir-dia-do-cenario-da-agenda.php` (o D437: o
`CotaDoPacoteGravaQuemVendeuTest` usava o `DiaLivreNaAgenda` com o dia escrito -- a validade do pacote em `daquiA(365)` e o
deslocamento escolhido fora do `setUp`; agora o `$this->deslocamento` do `setUp` e as datas a partir dele) e o
`conferir-cobertura-por-par.php` (os dois pares novos, um sem caso e o outro citado sem a classe: o caso novo
`testAsConsultasDeQuemVendeuEDoPercentualDeHoje`, pelos dois repositórios, e a base do portão de 419 para 421, com o "sem menção" em
118 e o "nome sem a própria classe" em 22). (4) **O nome da coluna** "Cota de pacotes vendidos" lia como a cota da venda do pacote,
que segue sem cota (baixo): passou a "Cota das sessões dos pacotes que vendeu", na tela, na planilha e no PDF, e o glossário
`cota.quemVendeu` (e a nota do arquivo) diz que o valor da sessão fica na produção e na "% da cota" de quem a executou. (5) **Quem só
vendeu, com 0%, era contado no "Fora da lista"** (baixo): o `separar` não conta a linha que só existe pelo bloco de quem vendeu
(sem item, gorjeta nem faturamento), depois do `somar`, para as fatias seguirem se somando. Os mutantes da correção, um por vez e
devolvidos pelo sha1, todos vermelhos pela asserção: **X9** (a base da linha de quem vendeu recebendo a sessão de novo, nos dois
bancos), **X10** (o "fora" contando quem só vendeu, nos dois), **X11a/X11b** (o nome antigo da coluna, na tela e no arquivo), **X12**
(o `vendedorDoPacote` sem o filtro do pacote, nos dois) e **X13** (o percentual de hoje trocado por um fixo, nos dois). Depois da
correção, em série, os números acima.

**O implementador e uma das três lentes caíram no meio** (o erro 500 da API, não um defeito da fatia): as duas lentes que
terminaram leram o `git diff` e o rascunho `p74/`, e a correção fez o que faltava -- os cinco achados, os docs e as provas -- e por
isso os números acima são os da correção. Depois, conferi o `local.php` pelo sha1, nenhum processo órfão no contêiner e os
mutantes devolvidos.

**O ciclo inteiro, depois da correção** (por mim; a fatia é par), no Mac, em 9 min 29 s: caracterização **5.971 em cada banco**,
saída 0 (os mesmos 12 pulados); contrato 74 e 74; os ensaios nos dois bancos, saída 0; os portões, saída 0; o front **3.900 de
3.900**; e o medidor dos relatórios no PostgreSQL, saída 0.

**No navegador** (por mim, contra o banco de contrato com as cinco linhas da data, e com o oráculo da fatia rodado nele outra vez --
34 conferências, 0 divergências --: o pacote a 10%, vendido pelo Barbeiro Evasão Teste, as duas sessões de R$ 80,00 executadas pelo
Colaborador Teste, uma pela comanda e outra pela agenda, e o percentual mudado para 12,5% no fim):
- **o banco** com o vendedor e a cota de R$ 8,00 em cada sessão (10% do valor da sessão na hora da finalização, e não do
  `valor_total`, que é zero), e a comissão de quem executou em zero, como antes;
- **a Cota-parte** do gerente (01/11 a 03/11, o "de um dia a outro"): a coluna "Cota das sessões dos pacotes que vendeu" com
  R$ 20,00 para quem vendeu (as duas sessões pelos 12,5% de hoje, a régua desta Cota-parte; a gravada é a da v2) e R$ 0,00 para quem
  executou, e o "Gravado desde 03/11/2026"; o extrato de quem vendeu com o bloco "Venda de pacote" (R$ 400,00, cota zero) e o bloco
  "Pacotes que vendeu, por sessão usada" (Corte Teste, 2, R$ 160,00, R$ 20,00, 12,5%); a 375 px, sem rolagem lateral (as tabelas
  rolam dentro do cartão);
- **o cadastro do pacote** na tela nova, pelo administrador de teste: "Cota-parte de quem vendeu 12,50% por sessão usada" na ficha;
  na edição, o campo com a ajuda; 101 recusado ao lado do campo ("De 0 a 100, com até duas casas, como 10,50"), com o foco nele; 10
  salvo ("Pacote salvo.", "10,00% por sessão usada"); e a tela antiga do pacote com o campo "Cota-parte de quem vendeu" em "10,00".

A sessão do navegador foi encerrada antes de o `local.php` voltar (conferido pelo sha1), e o banco de contrato foi remontado; na
`franquiabv_dev`, nenhum pacote com percentual e nenhuma sessão com vendedor.

## 03/11/2026 -- RESPOSTAS-278-A-280 (a fatia 74c: as respostas do dono de 03/11/2026)

As três respostas do lote de 03/11/2026, seguidas à letra:

- **278 (a), o D680 -- muda o relatório de Descontos.** "Mostrar quem lançou também no item do cupom: a coluna 'Lançado por' diz o
  nome em todo item que tem o registro, o cupom inclusive; o grupo 'só o manual' continua sem o cupom. Nada muda no caixa." **Para
  quem usa:** na lista do Descontos, o item com a marca do cupom que tem quem o lançou gravado passa a mostrar o nome na coluna
  "Lançado por" (o "CUPOM VIP:" escrito à mão no PDV antigo, e o cupom aplicado no PDV antigo sem convênio, que também grava quem o
  aplicou); os totais, o grupo "Por quem lançou (só o manual)" e o filtro "Lançado por" não mudam. O cupom sem registro (o da
  comanda nova e o do convênio, que não gravam autor, e o de antes da data) segue com o traço. No servidor, o
  `DescontosJson::itens` dá o `lancadoPor` de todo item pelo `ContasDosDescontos::lancadoPor`, e não só do com parte manual; a
  `origem`, o `porQuemLancou` e o `filtrar` não mudaram. Na tela, a regra `quemLancouNaLinha` já escrevia o nome sempre que o
  servidor o trazia: mudaram o glossário `descontos.quemLancou` (que é também o "Como os números são contados") e os comentários.
  O D680 CORRIGIDO. **Na cópia** (só SELECT, só contagem, nas duas): 0 itens com `desconto_lancado_por`, o cupom inclusive (a
  linha `descontoManual` é de hoje, e nenhuma venda nova existe na cópia); na do MySQL, 296 itens com a marca e desconto, de
  vendas de qualquer situação, em todo o histórico.
- **279 (b), os Royalties (a R4) -- só docs.** "Deixar como está: a lista aponta só os dias a partir da data; antes dela, a frase
  da soma, mesmo para o cancelamento de hoje." Escrito no bloco da R4 FEITA do `spec-royalties.md`, no `f3-relatorios.md` e na
  observação da fatia 70 acima. Nada no código.
- **280 (b), o D682 -- só docs.** "Deixar a tela antiga como está até ser desligada no corte: quem abrir a tela antiga depois da
  data vê o acerto como retirada; a tela nova é a que o menu novo abre." O D682 ANOTADO (adiado por decisão do dono), e a nota
  do `spec-retirada.md` com a resposta. Nada no código.

**Os testes (a fatia é ímpar: só os do que ela toca).** O `DescontosTest::testQuemLancouODescontoManual` ganhou o cupom de
depois da data com o autor mostrando o nome (90006581), o convênio de depois da data com o registro também, e um cupom novo sem
autor (90006583) nulo; o grupo e o filtro seguem sem eles. As classes `DescontosTest`, `DescontosNoRelatorioTest`,
`RegrasDosDescontosTest`, `DescontoGravaQuemLancouTest`, `PortoesDaFamiliaDosRelatoriosTest`, `ColunasDoPdvTest` e
`RelatorioAdmControllerTest`: 152 casos em cada banco, saída 0 (1 pulado nos dois, o
`DescontosNoRelatorioTest::testOsTotaisNaoSomamOsDeOutraFranquia`, que pede a cópia da produção). No front, o
`listaDosDescontos.teste.ts` (o cupom com o registro dá o nome; sem ele, o traço) e o `Descontos.teste.tsx` (a coluna com o nome
no cupom; o grupo e o filtro, que vêm do servidor, sem quem só aplicou cupom -- da correção, abaixo); os arquivos do Descontos e do glossário, 94 de 94, também com `AMPLIFICAR_EFEITOS_MS=50`; o front
inteiro 3.902 de 3.902; tsc, lint e build com 356,72 KB (+0,07 KB). Os ensaios de permissão e o `medir-adm-api-no-postgres` nos
dois bancos de contrato, saída 0 (175 de 175 recusas; nenhum endpoint quebrou). O `conferir-tudo.sh` sozinho, 71 portões, saída
0; o `contar-debitos.php` e o `conferir-debitos-repetidos.php`, saída 0.

**Os mutantes**, um por vez, devolvidos pelo sha1: o `lancadoPor` do cupom de volta ao traço no servidor (o `DescontosJson::itens`
só com a parte manual) -- vermelho o `testQuemLancouODescontoManual`, nos dois bancos; na tela (`quemLancouNaLinha` com o traço no
cupom) -- vermelhos os dois casos novos do front; o grupo contando o cupom -- vermelhos o `testQuemLancouODescontoManual` e três
casos das regras, nos dois bancos; o filtro pegando o cupom (sem escolher a parte manual) -- vermelhos o
`testQuemLancouODescontoManual` e o `testOFiltroDeQuemLancouCasaOGrupo`, nos dois bancos. Nenhum equivalente.

**O oráculo, no banco de contrato (MySQL):** uma comanda aberta pela API com o gerente de teste; o corte com 5,00 de desconto e o
motivo "CUPOM VIP: teste" pelo PDV ANTIGO (`/mod-vendas/vendasProdutos/adicionar`, a porta onde o caso existe; a API nova recusa
a marca com 422, o D276, conferido); a lavagem com 4,00 de desconto manual pela API; paga e finalizada pela API. Contra um SELECT:
o corte com a marca, o gerente gravado e a origem cupom; o índice do Descontos com o gerente em "Por quem lançou" só com a
lavagem (1 item, R$ 4,00); a lista com o nome do cadastro em "Lançado por" no corte; o filtro do gerente sem o corte e com a
lavagem: 21 conferências, 0 divergências; falsificado (esperando a regra de antes), 2. O banco de contrato foi remontado depois.

**A verificação da 74c (03/11/2026)** confirmou 5 achados (4 distintos), todos baixos, tratados: o corpo do D682 dizia que o
débito "fica de pé por decisão", e a marca ANOTADO o tira do "de pé" (agora "fica adiado por decisão do dono"); o
`spec-descontos.md` citava os ids 90006580-90006582 (agora 90006580-90006584, dentro da faixa da D4); nenhum caso pegava o nome
de quem SÓ aplicou cupom na página -- o nome vem da coleta do `DescontosController::lista`, e o autor do cupom do caso também
lançava manual --: o `DescontosTest::testQuemLancouODescontoManual` ganhou o cupom 90006584, em 24/05, gravado com um usuário
que não lançou manual nenhum, com o nome do cadastro; e o caso do front prometia provar o grupo e o filtro, que vêm do servidor:
agora o autor do cupom é outro, e o caso prova que a tela não o põe no grupo nem nas opções do filtro (o título diz isso). Os
mutantes da correção, um por vez, devolvidos pelo sha1: C74c-1 (a coleta dos nomes só dos itens com parte manual) -- vermelho o
`testQuemLancouODescontoManual`, nos dois bancos; F74c-1 (o filtro acrescentando quem lançou os itens da página) -- vermelho o
caso novo do `Descontos.teste.tsx`. Depois da correção: as sete classes, 152 casos em cada banco, saída 0 (o mesmo 1 pulado);
os arquivos do Descontos e do glossário, 94 de 94, também com `AMPLIFICAR_EFEITOS_MS=50`; o front inteiro 3.902 de 3.902; tsc,
lint e build com 356,72 KB; o `conferir-tudo.sh` sozinho, 71 portões, saída 0; o `contar-debitos.php` (de pé 193, o D682 adiado) e o
`conferir-debitos-repetidos.php`, saída 0. Os ensaios HTTP não rodaram de novo: a correção não mudou o servidor.

**O navegador é do coordenador**, e não rodou nesta fatia. Débitos: D680 CORRIGIDO; D682 ANOTADO; de pé de 195 para 193. Nada
da produção, do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`, só SELECT.

**No navegador** (por mim, contra o banco de contrato com as cinco linhas da data e o oráculo da 74c rodado nele outra vez -- 21
conferências, 0 divergências --: a comanda 90001611, com o corte "CUPOM VIP: teste" pelo PDV antigo e a lavagem com desconto
manual pela API, paga e finalizada), o administrador de teste nos Descontos de novembro: o item do cupom com "Gerente Teste" em
"Lançado por", e o grupo "Por quem lançou (só o manual)" e o filtro só com o item manual (1 item, R$ 4,00); o cupom continua
contado como cupom nos totais (R$ 5,00, 55,6%). A sessão do navegador foi encerrada antes de o `local.php` voltar (conferido pelo
sha1), e o banco de contrato foi remontado.

## 03/11/2026 -- PDV-CUSTO-DO-ITEM (a fatia 74b da fila das telas que faltam)

A resposta 224 (b) do dono, de 15/10/2026 ("Sim, daqui em diante": cada item vendido guarda o custo do cadastro naquele momento,
o Produtos / Serviços mostra a margem e a Retirada o valor em reais a partir desse dia, e o de antes aparece como "sem
registro"), pela seção 3 do `docs/f3-relatorios/spec-produtos.md`, a seção 3 do `spec-comum.md` e a F1 do `spec-retirada.md`;
fecha o D627. A margem do pacote espera o D624.

**O que muda para quem usa.** A partir da data, todo produto de estoque vendido (Barbearia, Empório, Insumo) -- pelo PDV
antigo, pela comanda nova ou pela agenda -- guarda o custo que o cadastro tem naquele momento; o serviço, o pacote vendido e a
sessão de pacote não guardam custo. O **Produtos / Serviços** diz no cartão do mês desde quando o custo é gravado ("Gravado
desde dd/mm/aaaa; antes disso, sem registro.", ou que ainda não é), e com a data mostra nos Produtos vendidos as colunas
"Custo" e "Margem" (o faturamento menos o custo gravado) e, nas Cortesias, o "Custo" -- só dos itens com o custo gravado; o resto
aparece "sem registro", com a quantidade ao lado, e nunca pelo custo de hoje do cadastro. A **Retirada de Produtos** conta a
cortesia vendida desde a data pelo custo gravado na venda, e diz no cartão quantas foram assim; a saída do estoque continua
pelo custo de hoje. **Na cópia** (só SELECT, só contagem): os itens de produto de estoque vendidos em 2026, que ficam "sem
registro", por mês de janeiro a agosto -- 1.660, 1.632, 1.820, 2.149, 2.067, 2.010, 2.233 e 1.113 (dos quais 957, 910, 1.047,
1.334, 1.235, 1.215, 1.304 e 607 cortesias); **441 dos 2.392 produtos de estoque ativos têm o custo zero** no cadastro (nenhum
nulo; 78 venderam com preço em 2026; em julho, 175 itens e R$ 10.345,90) -- a margem deles será o faturamento inteiro (o caso
`CustoDoItemGravadoTest::testOCadastroSemCustoGravaZero`; o D684, com a pergunta no lote); 131 têm o custo maior que o preço (116
deles de preço zero, a cortesia de balcão), e 2 o custo 10 vezes o preço ou mais; nenhum produto com o custo 50 vezes o preço;
nos pacotes, **183 dos 239 com o custo igual ao preço e 30 com o custo 100 vezes o preço** (o número do D624, conferido).

**A migração, só acrescentando** (a 83 (a)). `scripts/migracao-custo-do-item.sql`: `vendas_produtos.custo_unitario`, `double`
anulável, sem chave nem índice, `ALGORITHM=INSTANT`, idempotente pela conferência do `information_schema`, o desfazer no
cabeçalho; o irmão em `postgres/` (`ADD COLUMN IF NOT EXISTS`); o `mysql.sql` com a coluna, e o `postgres/schema.sql` e o
`scripts/migrar-dados.sh` regerados pelos geradores (o segundo gerado, nunca rodado). No laço do passo 0 do
`docs/procedimento-de-corte.md` e do `docs/ensaio-geral-do-corte.md`, com a linha `| 0 |`; o `conferir-passo-zero-do-corte.php`
sai 0 (55 diferenças, todas cobertas). **Aplicada nas duas cópias locais, duas vezes** (a segunda sem efeito); **o `ALTER`
medido**: 0,56 s no MySQL com o `COUNT` dos 3.264.661 itens que a conferência faz (0,40 s só a conferência, na segunda vez: o
`ALTER` em si, cerca de 0,2 s), e 0,09 s no PostgreSQL. O oráculo da migração em dois bancos descartáveis
(`custo74b_descartavel`, do `mysql.sql` do HEAD, e `custo74b_descartavel_pg`, do `schema.sql` do HEAD): rodada duas vezes, saída
0 e 0, a coluna com o tipo, a nulabilidade, o padrão e o comentário iguais aos da cópia; sem a conferência do `information_schema`
(o mutante M10), a segunda vez para em "Duplicate column name" (saída 1); no PostgreSQL, sem o `IF NOT EXISTS`, em "already
exists" (saída 3). Os dois bancos descartáveis foram apagados. **Na produção, só pelo passo 0, pelo dono.**

**O escritor, um só.** `VendaProdutoService::gravarOCustoDoCadastro`: o `valor_compra` do cadastro POR UNIDADE, só no produto de
estoque (`Produto::movimentaEstoque`) e fora do pacote; nulo no resto. O `insert` o chama depois da conta da linha (o PDV antigo
e as quatro portas da API da comanda nova: o lançamento, a linha à parte e o ajuste do produto do cupom, a troca pelo cupom), a
agenda (`VendaService::comandaAbrirAgenda`) também, e o `comoSessao` o zera (o item que vira sessão, inclusive o que herda a
sessão do item tirado). **O ajuste mantém o custo da entrada**, como mantém o preço: o custo do item é o unitário vezes a
quantidade de agora. O cupom, o convênio e o desconto não mudam o custo; a cortesia tem custo; o cadastro com custo zero grava
zero. O construtor de `VendaProduto` tira a chave do arranjo que hidrata (o molde do D679). Portão novo:
`scripts/conferir-escritores-do-custo.php` (o setter só no serviço, a regra no escritor, o `comoSessao` zerando, toda porta que
cria item chamando o escritor -- e a porta nova desconhecida falha --, nenhum `UPDATE` na coluna), no `conferir-tudo.sh` e no
`ci.yml`.

**A linha da data.** A `custoDoItem` de `colunas_gravadas`, a sexta, num passo próprio e idempotente
(`scripts/colunas-gravadas-custo-do-item.sql` e o irmão em `postgres/`), DEPOIS do código: no passo 7 da etapa 1 depois das cinco
de antes, no 7d do corte, no ensaio geral e na tabela "O que fica no banco" da etapa 1. **Aplicada nas duas cópias, duas vezes**
(a segunda sem efeito): as seis linhas com data, só a `indicacaoDoFormulario` (a I3) sem. Os dois únicos escritos na cópia são o
`ALTER` e esta linha. `ColunasDoPdv::CHAVES` tem sete chaves, e o `colunasDoPdv.ts` também.

**A leitura comum.** `venpro.custoUnitario` em `COLUNAS_DOS_RELATORIOS`, e a chave opcional `custoDoItem` no `normalizar` (o
custo do item inteiro em centavos: o unitário arredondado ao centavo vezes a quantidade, arredondado uma vez): a linha do
relatório de 30 para 31 chaves; a do painel continua com 17. A regra "tem o custo gravado" é uma só,
`ContasDosProdutosVendidos::temCustoGravado` (o dia do item na data ou depois, e o custo no item), lida pelos dois relatórios.

**O Produtos / Serviços.** O `gravadoDesde.custoDoItem` na resposta; nas gavetas de produto e de cortesia, `custo`, `margem` e
`semCustoGravado`, por linha e na gaveta, na unidade e na rede (nulos fora delas); o custo e a margem nulos sem nenhum item com o
registro. O "Como os números são contados": o `produtos.semLucro` mudou ("Ainda não há custo nem margem...") e vale sem a data;
com ela, o `produtos.custoEMargem`. **Medido** (o Mac Mini M4, em processo, três rodadas): a rede de julho de 2026 com junho de
0,65 a 0,74 s no MySQL e de 0,56 a 0,87 s no PostgreSQL, 132 MB de pico nos dois, 175.412 bytes; a unidade 1 de 0,13 a 0,18 s e
de 0,08 a 0,14 s, 34 MB -- o da fatia 69 (0,51 a 0,78 s, 132 MB), com a coluna a mais.

**A Retirada.** Os itens vêm da base dos relatórios (`relatorioItens`, as mesmas linhas do `painelItens` com as colunas a mais),
e `cortesias()` soma por produto as cortesias com o custo gravado (maior que zero: o zero não vale zero na Retirada) e o custo
delas; o `custo()` as conta por ele, e só o resto pelo custo de hoje; o produto sem custo hoje, só com cortesias gravadas, tem o
custo delas. A resposta ganhou `gravadoDesde.custoDoItem` e `cortesiasComCustoGravado`; o cartão diz qual é qual; o "Como os
números são contados" troca o `retiradas.custoAtual` pelo `retiradas.custoComOGravado` com a data. **Medido**: a consulta e o
`normalizar` da rede de julho (31.898 itens) de 0,22 a 0,28 s e 126 MB pelo `painelItens` para de 0,30 a 0,35 s e 128 MB pelo
`relatorioItens` no MySQL, e de 0,18 a 0,23 s para de 0,25 a 0,31 s, 124 MB nos dois, no PostgreSQL.

**O oráculo, no contrato, de verdade pela API** (o gerente de teste): uma comanda do cliente do pacote com 2 Pomadas (custo
20,00), a Cerveja em cortesia (0,00; custo 5,00), um Corte pelo preço e um Corte pela sessão do pacote, paga e finalizada; o custo
da Pomada mudado para 22,50 e o da Cerveja para 7,00 pela API do catálogo; outra comanda com 1 Pomada. O banco com o custo de
cada item (20,00, 5,00, nulo no serviço e na sessão; a Pomada nova a 22,50 e a antiga a 20,00); o Produtos do mês (a Pomada com
62,50 de custo e 117,20 de margem, a cortesia da Cerveja com 5,00 e -5,00, os serviços sem custo) e a Retirada do mês (a cortesia
da Cerveja pelo custo gravado, 1 a 5,00, e o resto a 7,00) contra um SELECT e a conta à mão: **20 conferências, 0
divergências; falsificado (a Pomada a 62,51), 1** (saída 1, gravada).

**Os testes** (a fatia é par pelo esquema: o ciclo inteiro é do coordenador). Os do que a fatia toca, as 122 classes do grep
(o escritor por porta, o `ColunasDoPdvTest` com as seis linhas, o Produtos e a Retirada com e sem a data, a unidade e a rede, as
31 chaves, o passo zero, e o que o grep achou): **2.120 casos em cada banco, saída 0**, 15.310 asserções no MySQL e 15.306 no
PostgreSQL, os mesmos 8 pulados nos dois -- `AniversariantesNoRelatorioTest::testORecortePorUnidadeVale`,
`ComboboxDeAnosNoRelatorioTest::testOComboDoPainelDeduplicaOsAnos`, três do `D199AtivarRemoverPorPostTest` (os conjuntos
`vipclass` e `mensagens`), `RegrasDosAtendimentosTest::testBancadaDoAno`, `VendasNoRelatorioTest::testODescontoNaoSomaODeOutraUnidade`
e `VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador` (os que pedem a cópia ou o `MEDIR_ATENDIMENTOS`). Novos: o
`CustoDoItemGravadoTest` (10 casos: o PDV antigo, a API e o ajuste, o serviço e o pacote, a sessão, o item que herda a sessão,
a cortesia, o cupom e o convênio, o custo zero, a agenda, o construtor), e casos no `ColunasDoPdvTest`, no `ItensDoRelatorioTest`,
no `RegrasDosProdutosVendidosTest`, no `ProdutosVendidosTest`, no `RegrasDaRetiradaTest` e no `RetiradaDeProdutosTest`. O front
inteiro **3.907 de 3.907** (161 arquivos); os sete tocados com `AMPLIFICAR_EFEITOS_MS=50`, 103 de 103; tsc, lint (0 erros, os 8 avisos de antes, nenhum nos arquivos tocados) e
build, **357,41 KB** comprimido (+0,69 KB). Os ensaios de permissão, recusas (253 de 253), fluxo e o `medir-adm-api-no-postgres`
nos dois bancos de contrato, **saída 0**. O `conferir-tudo.sh` sozinho, **72 portões, saída 0** (o primeiro, vermelho no
`conferir-recorte-por-unidade.php`: as duas vigias do Produtos e da Retirada liam a chamada de antes -- atualizadas à nova, com a
mesma unidade do recorte); o `conferir-schema-tool.sh`, igual à linha de base; o `conferir-segredos.sh`, saída 0.

**Os mutantes**, um por vez, devolvidos pelo sha1 (o diretório inteiro conferido depois de cada rodada), todos vermelhos pela
asserção ou pelo portão: M1 a porta sem a gravação (8 casos e o portão); M2 o serviço gravando custo (2 e o portão); M3 a sessão
gravando custo (1 e o portão); M3b o `comoSessao` sem zerar (1 e o portão); M3c a agenda sem a gravação (1 e o portão); M4 o
custo de hoje do cadastro no lugar do gravado (nos dois bancos); M5 o custo nulo lido como zero, a margem do item sem registro
igual ao faturamento (3 no MySQL, 1 no PostgreSQL); M5b o custo zero no lugar do nulo; M5c a data ignorada; M5d o faturamento do
item sem registro entrando na margem (nos dois bancos, depois de o caso do banco ganhar o Café vendido sem registro -- antes, só a
regra pura o pegava); M6a a cortesia sem custo no escritor; M6b a cortesia sem o custo gravado na Retirada (nos dois bancos); M7
outra linha gravada junto no passo novo; M8 o passo sem o `NOT EXISTS` (vermelho pela asserção depois de o caso rodar o passo do
custo sozinho primeiro e os outros cinco depois -- antes, quebrava por exceção, e não contava); M9 o portão sem a porta da
agenda; M10 a migração sem a conferência (nos descartáveis, acima). E na tela, F1 a F7 (a margem fora da coluna, o "sem
registro" como R$ 0,00, o glossário sem a troca nos dois relatórios, a nota do cartão, a frase da Retirada, as colunas sem a
data), todos vermelhos. Nenhum equivalente.

**Fica de fora, e por quê.** A margem do pacote (o D624: o custo dele no cadastro é o preço copiado); recalcular o passado; mudar
o custo do cadastro; a tela antiga "Lucro" (o S11, como está); índice novo. **Uma escolha do dono, no lote** ("O produto sem
custo no cadastro: a margem é o faturamento inteiro?", o D684): o padrão que não perde dado ficou -- o zero gravado, e a margem
inteira, dita no "Como os números são contados".

Débitos: **D627 CORRIGIDO**; **D624** com a nota da 74b (segue de pé); **D684** registrado; de pé 193, sem mudança. O navegador é
do coordenador. Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`, só o `ALTER` da migração, a
linha `custoDoItem` de `colunas_gravadas` e SELECT; o banco de contrato foi remontado.

**A correção da 74b, depois da verificação em três lentes** (7 achados confirmados, um médio e seis baixos, todos tratados):
- **O médio: a cortesia gravada com custo ZERO ia pelo custo de HOJE na Retirada** -- corrigido o cadastro depois, o passado
  ganhava valor, o contrário da 224 (b) e da nota da fatia ("o custo gravado onde houver"), e a pergunta do D684 descrevia isso
  errado ao dono. Agora ela é "sem custo", pela regra da Retirada (o produto sem custo não vale zero): a linha fica fora do valor
  e conta em `produtosSemCusto`, tenha o cadastro custo hoje ou não (`cortesias()` soma o `comCustoGravadoZero`; o caso
  `RegrasDaRetiradaTest::testACortesiaGravadaComZeroFicaSemCusto`); o corpo do D684 e a pergunta no lote dizem isso.
- **O texto da Retirada**: o `retiradas.custoComOGravado` diz que o gravado não muda com o cadastro e que a cortesia gravada com
  zero fica fora; e, com a data dentro do período, o rótulo deixa de ser "Custo atual" -- o cartão é "Custo do que saiu sem
  receita", a coluna "Custo", e o produto fora é o "sem custo no cadastro ou na venda"; com a data depois do período, tudo como antes.
- **A letra da 224 (b) na Retirada** ("o que veio antes aparece como 'sem registro'"): a cortesia de antes da data e a saída do
  estoque seguem pelo custo de hoje, pela nota da fatia; escrito no `spec-retirada.md` e levado ao lote (o **D685**, "A Retirada
  antes da data do custo", recomendando ficar como está).
- **O Insumo** (e o `pro`) gravava o custo e caía em Outros, sem custo nem margem: os Outros entraram em `GAVETAS_COM_CUSTO` e
  na tela, com o custo e a margem (5 itens de Insumo em 2026 na cópia).
- **O `AindaNaoGravado`** foi para a gaveta de Produtos vendidos, como o desenho pede (sem ela no mês, para a primeira gaveta com
  custo que se desenha), e as colunas do custo só aparecem com a data dentro do período -- no mês inteiro antes dela eram "sem
  registro" em todas as linhas.
- **Os dois arredondamentos do `custoDoItem`** ganharam caso (`ItensDoRelatorioTest::testOCustoDoItemArredondaOUnitarioAntesDaQuantidade`:
  2 x 1,235 dá 248, e 0,5 x 1,15 dá 58).
- **O `cortesiasComCustoGravado` da linha nulo com o filtro** ganhou asserção no `RetiradaDeProdutosTest::testAsCortesiasPeloCustoGravado`.

Os mutantes da correção, um por vez, devolvidos pelo sha1, todos vermelhos pela asserção: C74b-1 (a gravada com zero de volta ao
custo de hoje em `cortesias()`) e C74b-1b (o `custo()` sem a guarda dela); C74b-2 (sem o arredondamento de dentro) e C74b-3 (o
`(int)` no de fora), nos dois bancos; C74b-4 (a linha da Retirada sem o nulo do filtro), nos dois bancos; C74b-5 (os Outros fora
de `GAVETAS_COM_CUSTO`); e na tela F74b-1 (a nota de volta ao cartão), F74b-1b (a nota só nos Produtos vendidos), F74b-2 (as
colunas sem a régua do período), F74b-3 (os Outros sem as colunas), F74b-4 (a coluna sempre "Custo atual"), F74b-5 (o rótulo sem
a régua do período) e F74b-6 (a frase sem o "ou na venda"). Nenhum equivalente. **Os testes da correção**: as 17 classes que
leem o que ela tocou (a Retirada, o Produtos, o `normalizar` e quem os usa), **570 casos em cada banco, saída 0** (4.918 asserções
no MySQL, 4.917 no PostgreSQL, nenhum pulado); o front inteiro **3.910 de 3.910**; os cinco arquivos tocados com
`AMPLIFICAR_EFEITOS_MS=50`, 89 de 89; tsc, lint (0 erros, os 8 avisos de antes) e build, **357,63 KB** (+0,22 KB); o
`conferir-tudo.sh` sozinho, **72 portões, saída 0**; o `conferir-segredos.sh`, o `contar-debitos.php` e o
`conferir-debitos-repetidos.php`, saída 0. Os ensaios HTTP não rodaram de novo (a correção não muda rota, guarda nem recusa). O
navegador e o ciclo inteiro são do coordenador. Débitos: **D685** registrado; de pé 193 para 194.

**O ciclo inteiro, depois da correção** (por mim; a fatia é par), no Mac, em 9 min 30 s: caracterização **5.993 em cada banco**,
saída 0 (os mesmos 12 pulados); contrato 74 e 74; os ensaios nos dois bancos, saída 0; os portões (72), saída 0; o front **3.910
de 3.910**; e o medidor dos relatórios no PostgreSQL, saída 0.

**No navegador** (por mim, contra o banco de contrato com as seis linhas da data; a Cerveja Teste das fixtures com estoque 10 e o
custo de 5,00 no cadastro, posto à mão só no contrato):
- **a venda pela tela de Comandas abertas**, pelo gerente de teste: duas Cervejas a R$ 15,00, recebidas e finalizadas; o banco
  gravou `custo_unitario` 5 no item; depois, o custo do cadastro mudado para 8,00;
- **a cortesia**, pela mesma tela: uma Cerveja a R$ 0,00 numa comanda com dois serviços, finalizada pelo gerente (o administrador
  de teste não tem caixa aberto, e a finalização o recusou, como deve); o banco gravou 8 na cortesia e nulo nos dois serviços;
  depois, o custo do cadastro mudado para 9,00;
- **o Produtos / Serviços de novembro**, pelo administrador de teste: na gaveta de Produtos vendidos, "Gravado desde 03/11/2026;
  antes disso, sem registro." e "Custo gravado R$ 10,00 · margem R$ 20,00", com as colunas Custo e Margem na linha da Cerveja
  (R$ 10,00 e R$ 20,00: o gravado, e não os R$ 16,00 ou R$ 18,00 do cadastro de depois); nas Cortesias, "Custo gravado R$ 8,00"
  e a coluna Custo com R$ 8,00; a 375 px, sem rolagem lateral;
- **a Retirada de novembro**: "Custo do que saiu sem receita" R$ 8,00 (a cortesia pelo custo gravado, e não os 9,00 de hoje), com
  a frase "a cortesia com o custo gravado na venda pelo custo dela; o resto ao custo de hoje do cadastro", a coluna "Custo" na linha
  da Cerveja e o "Gravado desde 03/11/2026"; outubro, sem nada no contrato, com a frase do mês vazio (o mês antes da data só está
  provado pelos casos do front, F74b-2 e F74b-5).

A sessão do navegador foi encerrada antes de o `local.php` voltar (conferido pelo sha1), e o banco de contrato foi remontado; na
`franquiabv_dev`, nenhum item com o custo gravado.

## 03/11/2026 -- RESPOSTAS-281-E-282 (a fatia 74d: as respostas do dono de 03/11/2026)

As duas respostas do lote de 03/11/2026 sobre o D683, seguidas à letra:

- **281 (a), a venda de origem cancelada -- muda a gravação e a Cota-parte.** "Sem cota quando a venda de origem foi cancelada: a
  sessão grava o vendedor (fica o registro), mas a cota sai zero, e a Cota-parte não a paga. Se a venda foi refeita, o pacote novo
  é o que paga." **Para quem usa:** a sessão de um pacote cuja venda de origem foi cancelada não paga cota a quem vendeu. Na
  Cota-parte e na v2 ela continua no bloco "Pacotes que vendeu, por sessão usada", com a quantidade e o valor, a cota zero e a
  marca em texto ao lado do produto -- "1 sessão com a venda de origem cancelada, sem cota" --, também no PDF e na planilha
  (depois do nome do item); quem só vendeu pacotes assim fica sem cota, e por isso fora da lista e fora do "fora da lista", como
  com 0%. O pacote da venda refeita é outro pacote do cliente e paga normalmente quando a sessão sai dele (o cancelamento deixa o
  antigo ativo, e o que vence antes sai antes: a verificação, abaixo, e o D686); a comissão de quem executou a sessão não muda. O
  "Como os números são contados" (o glossário `cota.quemVendeu`) e a nota do arquivo dizem a regra. **No servidor:** a regra pura
  `RegraDaCotaDoPacote::semCotaPelaOrigem` (o status de cancelada, `RegraDaVendaNoPdv::VENDA_CANCELADA`, o mesmo das duas telas de
  cancelamento e das Canceladas; não o `cancelado_em`, que a venda cancelada antes da fatia 70 não tem); a porta da sessão
  (`VendaPagamentoService::atualizarValorComissaoPacote`, a comanda e a agenda) grava a cota zero quando a origem está cancelada
  na finalização; e a LEITURA zera, nas duas réguas, a cota da sessão cuja origem está cancelada HOJE -- também a gravada antes do
  cancelamento, para a Cota-parte e a v2 dizerem o mesmo -- pela consulta nova `ClientePacoteRepository::comAVendaDeOrigemCancelada`
  (uma por mês lido, só com sessão com vendedor, pela chave primária: com os 251 pacotes das sessões de julho de 2026 da cópia,
  0,9 ms no MySQL e 1,4 ms de execução no PostgreSQL, pelo `EXPLAIN ANALYZE`). O extrato ganhou o `origemCancelada` em cada linha
  (quantas sessões; nulo sem nenhuma, e fora do bloco de quem vendeu). O portão `conferir-escritores-do-pacote.php` ganhou a
  afirmação 4b (a porta passa pela `semCotaPelaOrigem`); o `conferir-cobertura-por-par.php`, o par novo (de 421 para 422; o
  `ISRelatorio` de 184 para 185). **Na cópia** (só SELECT, só contagem, nas duas): 38 sessões de 2026 usaram um pacote de venda
  de origem hoje cancelada (132 em todo o histórico, no MySQL), e nenhuma tem a cota de quem vendeu gravada (nenhuma sessão da
  cópia tem: a linha `cotaDoPacote` é de hoje).
- **282 (a), quem já saiu da empresa -- só docs.** "Como está: a cota continua sendo de quem vendeu, ativo ou não, e aparece na
  Cota-parte para o gerente acertar com ele." Escrito no D683 e no `spec-cota-parte.md` (a seção 3). Nada no código.

O D683 CORRIGIDO (a 281 com o que entrou; a 282 "como está, por decisão do dono").

**Os testes (a fatia é ímpar: só os do que ela toca).** Casos novos: no `CotaDoPacoteGravaQuemVendeuTest`, a origem cancelada
pela comanda (o vendedor gravado, a cota zero, a comissão igual) e a venda refeita pagando 8,00, a mesma pela agenda, e a consulta
nova (ids 90008453 e 90008454, da faixa da 74); no `RegrasDaCotaParteTest`, as duas réguas com a outra sessão do mesmo vendedor
pagando, e quem só vendeu fora da lista e do "fora", o extrato da v2 com a marca e sem aviso, e a soma das fatias; no
`CotaParteTest`, a lista, a v2, o extrato, a planilha e o PDF com a sessão do A de origem cancelada (o pacote do cliente 90008043,
o `CenarioDaCotaParte::semearOrigemCancelada`) e a do W de origem válida, e depois as duas; e a frase da marca nos casos
compartilhados (`textosDaCotaParte.casos.json`). As 63 classes que o grep achou (as da 74, as da finalização e as que leem a
Cota-parte): 1.234 casos em cada banco, saída 0, nenhum pulado. No front, o `CotaParte.teste.tsx` (a marca ao lado do produto, e a
linha sem ela) e o `textosDaCotaParte.teste.ts` (os casos compartilhados); os arquivos da Cota-parte, do extrato e do glossário,
101 de 101, com `--maxWorkers=4` e com `AMPLIFICAR_EFEITOS_MS=50`; o front inteiro 3.914 de 3.914; tsc, lint e build com 357,79 KB
(+0,16 KB). Os ensaios de permissão e o `medir-adm-api-no-postgres` nos dois bancos de contrato, saída 0. O `conferir-tudo.sh`
sozinho, 72 portões, saída 0; o `conferir-segredos.sh`, o `contar-debitos.php` e o `conferir-debitos-repetidos.php`, saída 0.

**Os mutantes**, um por vez, devolvidos pelo sha1, os de servidor nos dois bancos: M1 a porta gravando a cota com a origem
cancelada -- vermelhos os dois casos da origem cancelada do `CotaDoPacoteGravaQuemVendeuTest` (e o portão, afirmação 4b); M2 a
recalculada pagando a origem cancelada e M3 a gravada pagando -- vermelhos o caso da API e os dois das regras; M4 a sessão de
outro pacote, de venda válida, zerada junto -- vermelhos o da API e o das duas réguas; M5 a consulta devolvendo também a origem
concluída -- vermelhos a consulta, cinco casos da API da 74 e o novo; M6 a montagem sem passar as origens canceladas -- vermelho o da API;
M7 a regra pura trocando o status -- vermelhos cinco casos do escritor. No front: a marca arrancada -- vermelho o caso novo; a
frase mudada de um lado -- vermelhos os três casos compartilhados. E o portão sem a gravação da cota continua vermelho (a
afirmação 4 da 74). Nenhum equivalente.

**O oráculo, no banco de contrato (MySQL), pela API:** o percentual do pacote a 10%; o pacote vendido pelo V em duas vendas (uma
para cada cliente); uma sessão do pacote da venda A executada pelo B e finalizada ANTES do cancelamento (gravou 8,00); a venda A
cancelada pela API com a credencial do gerente de teste (o pacote do cliente continua de pé); outra sessão do pacote A depois
(gravou o vendedor e 0,00) e uma do pacote B (8,00), finalizadas. Contra um SELECT e a conta à mão: o banco (o pacote, o vendedor,
a cota, o valor e a comissão de cada sessão), a soma das origens válidas (8,00) e a gravada inteira (16,00), a Cota-parte do dia
(o V com 8,00 de pacote, o total igual, o B sem), o extrato do V (3 sessões, 240,00, cota 8,00, 2 de origem cancelada), o do B (as
três sessões com a comissão gravada), a v2 pelo administrador (8,00, sem aviso) e a planilha (a marca depois do nome): 32
conferências, 0 divergências; falsificado (8,00 esperados como 9,00), 5. O banco de contrato foi remontado depois.

**A verificação em três lentes confirmou 6 achados, dois médios e o resto baixo, todos tratados:**
- **(médio) "o pacote da venda refeita paga" quase nunca vale.** O `VendaService::cancelar` deixa ativo o pacote do cliente da
  venda cancelada, e o `ClientePacoteRepository::selecionarPacote` gasta o que vence antes: com a venda refeita, a sessão seguinte
  sai do pacote antigo, sem cota, e o novo só paga quando o antigo acaba. Na cópia (só SELECT, só contagem, recontado na
  correção): 30 das 38 sessões de 2026 de origem cancelada, de 18 pacotes, saíram do antigo com o cliente já tendo o da venda
  refeita. O código segue a letra da 281 (a cota); qual pacote a sessão gasta mexe nas sessões do cliente e é do dono: o **D686**,
  registrado, e a pergunta no lote ("Cota de quem vendeu: a sessão sai do pacote da venda cancelada": encerrar o pacote no
  cancelamento, gastar primeiro o de venda válida, ou como está; recomendo gastar primeiro o de venda válida). O glossário, a nota
  do arquivo, o spec, o D683 e este parágrafo dizem como é hoje.
- **(médio) o "% gravado" da v2 diluído.** A base do produto no bloco de quem vendeu recebia a sessão de origem cancelada: uma
  sessão cancelada ao lado de uma gravada a 10% dava "gravado a 5,0%", e todas canceladas, 0,0%. Agora a base é só a das sessões
  que pagam (`ContasDaCotaParte::creditarQuemVendeu`), e o "% gravado" sai nulo ("—") quando nenhuma paga.
- **(baixo) o caixa via a marca "sem cota".** O `origemCancelada` entrou no `CotaParteJson::ESCONDIDOS['dia']`, no
  `ESCONDIDOS_NA_TELA`, na tabela da 1.4 do `spec-cota-parte.md` e no portão `conferir-recorte-por-unidade.php`; o item do
  arquivo do caixa sai sem ela.
- **(baixo) a leitura não chamava a regra pura.** A `comAVendaDeOrigemCancelada` repetia a constante no DQL; agora traz o status da
  venda de origem e chama a `RegraDaCotaDoPacote::semCotaPelaOrigem`, a mesma da porta.
- **(baixo) o status nulo sem caso**, e **(baixo) o glossário e a nota sem dizer que quem só vendeu pacotes assim sai da lista e do
  "fora"** -- os dois com caso.

Casos novos da correção: no `RegrasDaCotaParteTest`, a base do produto e o "% gravado" da v2 (10,0% com a sessão cancelada ao
lado; nulo com todas), o caixa sem a marca no extrato e no item do arquivo (as duas réguas) e o do gerente com ela, e a nota
dizendo a regra; no `CotaParteTest`, pela API, o "% gravado" do extrato da v2 (nulo no A, 10,0% no W); no
`CotaDoPacoteGravaQuemVendeuTest`, a regra pura com o 0, o '0', o nulo e os outros status, e a consulta com a origem de status
nulo. As 63 classes que o grep acha (as que citam a conta, a resposta, a montagem, o repositório e a regra do pacote, a
finalização e a Cota-parte): 1.232 casos em cada banco, saída 0, nenhum pulado. No front, os seis arquivos da Cota-parte, do
extrato e do glossário, 99 de 99, com `--maxWorkers=4` e com `AMPLIFICAR_EFEITOS_MS=50`; o front inteiro 3.914 de 3.914; tsc,
lint e build com 357,89 KB (+0,10 KB). Os ensaios de permissão e o `medir-adm-api-no-postgres` nos dois bancos de contrato,
saída 0. O `conferir-tudo.sh` sozinho, 72 portões, saída 0; o `conferir-segredos.sh`, o `conferir-referencias-docs.php`, o
`contar-debitos.php` e o `conferir-debitos-repetidos.php` (com o par D683|D686 registrado), saída 0. **Os mutantes da correção**,
um por vez, devolvidos pelo sha1: N1 a base do produto recebendo a sessão cancelada -- vermelhos 3 casos (o da API e os dois das
regras); N2 o caixa vendo a marca -- vermelhos 3 casos das regras e o portão `conferir-recorte-por-unidade.php`; N3 a regra pura
trocando o status (o M7 de novo) -- agora vermelhos 13 casos, a consulta e a Cota-parte pela API inclusive, nos dois bancos (antes,
só os do escritor); N4 o nulo contado como cancelado -- vermelhos a regra pura e a consulta, nos dois bancos; N5 a nota sem a frase
de quem sai da lista -- vermelhos o caso das regras e o que compara a nota com o glossário; no front, o `ESCONDIDOS_NA_TELA` sem a
chave nova -- vermelhos o caso da tabela 1.4 do front e o `RegrasDaCotaParteTest::testATelaEscondeAsMesmasChavesQueOServidor`.
Nenhum equivalente. **O oráculo de novo**, com a conferência nova do "% gravado" da v2 contra um SELECT (a cota sobre o valor só
das sessões de origem válida, 10,0%): 34 conferências, 0 divergências; falsificado, 5; o banco de contrato remontado depois.

**O navegador é do coordenador**, e não rodou nesta fatia. Débitos: D683 CORRIGIDO; D686 registrado; de pé 194, sem mudança. Nada
da produção, do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`, só SELECT.

**Conferido por mim depois da correção** (a correção conta que, na primeira rodada dos mutantes N1 a N5, a cópia de reserva caiu
num diretório que já existia e os cinco ficaram aplicados juntos, devolvidos à mão): as classes da Cota-parte, do escritor do
pacote e dos portões da família dos relatórios, **110 casos em cada banco, saída 0**; os dois arquivos da Cota-parte no front, 42
de 42; o `conferir-tudo.sh` sozinho, **72 portões, saída 0**. Nenhum mutante ficou na árvore.

**No navegador** (por mim, contra o banco de contrato com as seis linhas da data e o oráculo da fatia rodado nele outra vez -- 32
conferências, 0 divergências --: o pacote a 10%, vendido pelo Barbeiro Evasão Teste em duas vendas, a venda A cancelada depois da
primeira sessão, e três sessões do Corte Teste executadas pelo Colaborador Teste): o banco com a cota de R$ 8,00 na sessão de A
gravada antes do cancelamento, R$ 0,00 na de depois e R$ 8,00 na de B; a Cota-parte do gerente (01/11 a 03/11) com R$ 8,00 na coluna
"Cota das sessões dos pacotes que vendeu" para quem vendeu -- só a de B: a leitura zera também a sessão gravada antes do
cancelamento --; o extrato dele com "Corte Teste (2 sessões com a venda de origem cancelada, sem cota)", 3 sessões, R$ 240,00, R$
8,00, 10,0%; a 375 px, sem rolagem lateral. A sessão do navegador foi encerrada antes de o `local.php` voltar (conferido pelo
sha1), e o banco de contrato foi remontado; na `franquiabv_dev`, nenhuma sessão com vendedor.

## 03/11/2026 -- RESPOSTAS-283-E-284 (a fatia 74e: as respostas do dono de 03/11/2026)

As duas respostas do dono sobre os débitos da fatia 74b. **283 (b), o D684**: "Custo zero conta como 'sem custo', como na
Retirada: esses itens ficam fora do custo e da margem, e o relatório diz quantos ficaram de fora." **284 (a), o D685**: "Como
está: a cortesia nova pelo custo gravado; o resto pelo custo de hoje, com a tela dizendo qual é qual."

**O que muda para quem usa.** No Produtos / Serviços, o produto vendido com o custo zero no cadastro (gravado zero na venda)
deixa de aparecer com a margem igual ao faturamento inteiro: fica fora do custo e da margem, nas três gavetas com custo
(Produtos vendidos, Cortesias e Outros), e a tela diz quantos ficaram de fora, separado do "sem registro" -- na frase da gaveta
("Custo gravado R$ X · margem R$ Y · N sem registro · M sem custo no cadastro na venda"), na célula do custo ("R$ X (M sem custo
no cadastro na venda)"), "sem custo no cadastro na venda" na linha só com zero, e "Nenhum item com custo · M sem custo no cadastro
na venda" na gaveta sem nenhum item com custo. A margem do item com custo maior que zero não muda. O "Como os números são
contados" (`produtos.custoEMargem`) diz a regra. Na Retirada, nada muda (a 284 a).

**Como.** A regra é UMA só para os dois relatórios: a pura `ContasDosProdutosVendidos::situacaoDoCusto` (sem o registro, "sem
registro"; com ele e o custo do item maior que zero, "com custo"; com ele e zero, "sem custo no cadastro"), chamada pelo `contar`
do Produtos / Serviços e pela `ContasDaRetirada::cortesias` -- que já tratava o zero assim e não mudou o que faz. A resposta ganhou
`semCustoNoCadastro` (a quantidade, na gaveta e na linha; nula fora das gavetas com custo), ao lado do `semCustoGravado`, que
continua sendo só o nulo. O zero é o do centavo: o `custoDoItem` arredonda o unitário ao centavo antes da quantidade (a 74b), e um
custo de 0,004 grava 0,004 e vale zero centavos -- "sem custo", também com 3 unidades; o de 0,005 vale um centavo e entra. O
escritor não mudou (o cadastro sem custo grava zero; só a leitura mudou). A 284 (a) só nos docs: o D685 ANOTADO, como o D682 da
74c, e a resposta no `spec-retirada.md`. **Contado na cópia** (só SELECT, só contagem): nenhum item tem `custo_unitario` gravado
(a linha `custoDoItem` é de hoje); pelo cadastro de hoje, julho de 2026 teria 175 itens com preço (R$ 10.345,90) e 25 cortesias
"sem custo", de 2.233 itens de produto de estoque; nenhum produto de estoque tem custo entre zero e um centavo.

**O que rodou (a fatia é ímpar: só os testes do que ela toca).** As classes do Produtos / Serviços, da Retirada, da base dos
relatórios, do escritor do custo e dos portões da família (`ProdutosVendidosTest`, `RegrasDosProdutosVendidosTest`,
`RetiradaDeProdutosTest`, `RegrasDaRetiradaTest`, `ItensDoRelatorioTest`, `PortoesDaFamiliaDosRelatoriosTest`,
`CustoDoItemGravadoTest`, `ColunasDoPdvTest`): **148 casos em cada banco, saída 0, nenhum pulado** -- quatro casos novos
(`testASituacaoDoCustoGravado`, `testOCustoGravadoZeroFicaSemCusto` nas regras e contra o banco, `testNaRedeOCustoZeroFicaSemCusto`)
e o Óleo do `testAMargemSoDosItensComOCustoGravado` passado a "sem custo". No front, os arquivos do Produtos, do glossário, da
Retirada, do Console e do painel que o grep achou, 186 de 186, com `--maxWorkers=4` e com `AMPLIFICAR_EFEITOS_MS=50`; o front
inteiro **3.915 de 3.915**; tsc, lint (0 erros) e build com 358,04 KB (+0,15 KB). O JSON mudou de forma: o ensaio de permissão
(175 de 175 recusados) e o `medir-adm-api-no-postgres` (246 endpoints, nenhum quebrou) nos dois bancos de contrato, saída 0. O
`conferir-tudo.sh` sozinho, **72 portões, saída 0**; o `conferir-segredos.sh`, o `conferir-referencias-docs.php`, o
`contar-debitos.php` e o `conferir-debitos-repetidos.php` (com o par D607|D685 registrado: só a marca de estado coincide), saída 0.

**Os mutantes**, um por vez, cada cópia de reserva num diretório novo e vazio, devolvidos pelo sha1: M1 o zero gravado de volta
a custo zero (a margem inteira) -- vermelhos 4 casos, nos dois bancos; M2 a contagem "sem custo" somando o nulo -- 3 casos; M3 o
nulo virando "sem custo" (o "sem registro" esvaziado) -- 5 casos; M4 a Retirada deixando de tratar o zero -- o
`RegrasDaRetiradaTest::testACortesiaGravadaComZeroFicaSemCusto`, nos dois bancos; M5 a regra comum com `>= 0` -- 6 casos, dos
dois relatórios; M6 o `custoDoItem` sem arredondar o unitário antes da quantidade -- 3 casos (o 0,004 com 3 unidades). No front:
F1 as duas contagens juntas, F2 a linha só com zero dizendo "sem registro", F3 a gaveta sem custo calada -- os três vermelhos no
caso novo do `ProdutosVendidosLigado`; F4 o glossário com a frase antiga -- o caso do "Como os números são contados". Nenhum
equivalente.

**O oráculo**, no banco de contrato (MySQL) pela API, com o gerente de teste: o custo da Pomada posto em 0,00 e o da Cerveja em
5,00 pelo catálogo; uma comanda com 2 Pomadas a 59,90, 1 Cerveja a 15,00 e a Pomada em cortesia, paga e finalizada; o banco com
o zero GRAVADO como zero; o JSON do Produtos (a Pomada sem custo nem margem e 2 sem custo; a Cerveja com 5,00 e a margem de 10,00;
a gaveta; a cortesia com 1 sem custo; os serviços nulos) e o da Retirada (a cortesia gravada com zero fora do valor, nenhuma pelo
gravado, contada em `produtosSemCusto`) contra um SELECT e a conta à mão: **16 conferências, 0 divergências**; falsificado (o
esperado da 74b, a margem inteira), 1. O banco de contrato foi remontado.

**O navegador é do coordenador**, e não rodou nesta fatia. Débitos: D684 CORRIGIDO; D685 ANOTADO (adiado por decisão do dono);
de pé 194 para 192. Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`, só SELECT.

**A correção da 74e** (a verificação em três lentes confirmou 5 achados, um repetido, todos baixos, tratados): (1) a tela
escreve "sem custo no cadastro na venda", e não o "sem custo no cadastro ou na venda" da Retirada, e o comentário dizia "as
palavras da Retirada" -- ficou a frase, de propósito (o "ou" da Retirada existe porque o resto dela sai ao custo de hoje do
cadastro; aqui o custo de hoje nunca entra, e um "ou" diria que corrigir o cadastro hoje muda a margem de ontem), e o comentário
e o `spec-produtos.md` dizem por quê; (2) o glossário dizia que o custo "com menos de um centavo" fica sem custo, e o de 0,005 a
0,0099 entra (o `custoDoItem` arredonda o unitário ao centavo): agora "menos de meio centavo", com a asserção no caso do "Como os
números são contados"; (3) o parágrafo da 74b no `f3-relatorios.md` ("os três campos", a margem "de todo item com o custo
gravado", sem a `situacaoDoCusto`) e o cabeçalho do `ProdutosVendidosJson` ("os tres nulos") atualizados; (4) nenhum caso
prendia a guarda da gaveta sem custo e só com "sem registro": o caso novo do `ProdutosVendidosLigado` (a gaveta de Cortesias com
2 sem registro e nenhum zero fica sem a frase, e a célula diz "sem registro"). **Os mutantes da correção**, um por vez, cada
cópia de reserva num diretório novo e vazio, devolvidos pelo sha1: G1 a guarda trocada por `fora.length > 0` e G2 pelos dois
contadores -- vermelhos no caso novo; G3 o glossário de volta a "menos de um centavo" -- vermelho no caso do "Como os números são
contados". Nenhum equivalente. **Os testes da correção**: as oito classes da fatia, **148 casos em cada banco, saída 0, nenhum
pulado**; os quatro arquivos do front do Produtos, do Console e do glossário, 70 de 70, também com `AMPLIFICAR_EFEITOS_MS=50`;
o front inteiro **3.916 de 3.916**; tsc, lint e build (358,04 KB), saída 0; o `conferir-tudo.sh` sozinho, **72 portões, saída
0**; o `contar-debitos.php`, o `conferir-debitos-repetidos.php`, o `conferir-referencias-docs.php` e o `conferir-segredos.sh`,
saída 0; de pé 192, sem mudança. O JSON não mudou de forma: os ensaios HTTP não rodaram de novo.

**No navegador** (por mim, contra o banco de contrato com as seis linhas da data e o oráculo da fatia rodado nele outra vez -- 16
conferências, 0 divergências --: a Pomada Teste com o custo 0,00 no cadastro e a Cerveja Teste com 5,00, numa comanda paga e
finalizada com 2 Pomadas, 1 Cerveja e uma Pomada em cortesia), o administrador de teste no Produtos / Serviços de novembro: na
gaveta de Produtos vendidos, "Custo gravado R$ 5,00 · margem R$ 10,00 · 2 sem custo no cadastro na venda", a Pomada com "sem custo
no cadastro na venda" no custo e na margem (e não a margem de R$ 119,80, o faturamento inteiro), a Cerveja com R$ 5,00 e R$ 10,00;
nas Cortesias, "Nenhum item com custo · 1 sem custo no cadastro na venda" e a Pomada com a mesma frase; a 375 px, sem rolagem
lateral. A sessão do navegador foi encerrada antes de o `local.php` voltar (conferido pelo sha1), e o banco de contrato foi
remontado; na `franquiabv_dev`, nenhum item com o custo gravado.

## 03/11/2026 -- RESPOSTA-285 (a fatia 74f: a resposta do dono de 03/11/2026)

**A resposta 285, a opção (a)** (e não a (b) que a pergunta recomendava), sobre o D686 da 74d: "Encerrar o pacote no
cancelamento: cancelar a venda desativa o pacote do cliente que ela criou (as sessões que restavam somem); quem refez a venda
gasta o pacote novo, que paga a cota. Muda quantas sessões o cliente tem." **O que muda para quem usa:** cancelar uma venda que
vendeu um pacote encerra o pacote que ela deu ao cliente, com as sessões que restavam -- o cliente não consegue mais lançá-lo, e ele
some da lista de pacotes do PDV e da comanda (a sessão já lançada numa comanda aberta antes ainda sai dele: o D687); com a venda refeita, a sessão seguinte sai do pacote novo, que paga a cota de quem
vendeu desde a primeira. O modal do cancelamento no ERP antigo diz isso ("Se a venda vendeu um pacote, cancelá-la encerra o
pacote do cliente criado por ela, com as sessões que restavam: ele não pode mais ser lançado numa comanda. A sessão dele que já
estiver lançada numa comanda aberta ainda é descontada dele ao finalizar." -- a frase geral, porque a listagem não tem as sessões
de cada pacote; a segunda frase é o D687, abaixo); a tela nova só cancela a comanda aberta, que ainda não deu pacote. **No código:** o
`VendaService::cancelar` (a porta do ERP e da API; a rotina só cancela comanda aberta) encerra, pelo
`ClientePacoteRepository::encerrarOsDaVenda`, todo pacote do cliente ativo com `venda` = a cancelada, o usado em parte também e
os dois da venda que criou dois, com a marca da remoção pela ficha (`status` falso e a `data_alteracao`; sem coluna nova, sem
`DELETE`, o restante gravado como estava); o pacote de outra venda do cliente não é tocado, e a venda sem pacote não muda nada. O
cancelamento passou a correr **numa transação só**: o que falha no meio deixa a venda como estava e o pacote ativo (até aqui cada
`flush` gravava o seu pedaço). Não há porta que desfaça o cancelamento, e o pacote encerrado não volta. O que já foi usado não
muda (as sessões gravadas, a cota zero da 74d, a comissão). O glossário `cota.quemVendeu`, a `NOTA_DE_QUEM_VENDEU`, o
`spec-cota-parte.md` e a nota 5 do "Depois" do `procedimento-de-corte.md` dizem a regra nova. O D686 CORRIGIDO.

**Na cópia (só SELECT, só contagem):** em 2026, 21 das 3.800 vendas canceladas tinham criado 48 pacotes de cliente (16 clientes;
18 dessas vendas, mais de um); hoje 24 deles continuam ativos, com 20 sessões restantes nos 12 com saldo positivo (3 com o saldo negativo, -51 somados; a soma
dos 24 dá -31) (o restante na hora do cancelamento não se
recupera: a conta é de hoje); os outros 24 já foram removidos pela ficha. **Daqui em diante:** os pacotes de venda JÁ cancelada
não foram encerrados -- 118 ativos no histórico, de 66 vendas e 39 clientes, 166 sessões restantes nos 69 com saldo positivo (5 com o saldo negativo,
-56 somados; a soma dos 118 dá 110), nenhum dentro da validade hoje
(o mais novo venceu em 07/09/2026). **Duas perguntas ao dono, ao lote:** o **D688** ("Os pacotes de venda já cancelada: encerrar
uma vez no corte?", recomendando encerrar) e o **D687** ("A sessão já lançada no pacote que o cancelamento encerrou": a sessão
lançada numa comanda aberta ANTES do cancelamento ainda sai do pacote encerrado, sem cobrança e com a cota zero, porque o
finalizar não lê o `status` do pacote; recomendando a recusa no finalizar). A comanda aberta pela agenda depois do cancelamento
não tem o problema: o lançamento já não oferece o pacote encerrado.

**A fatia é ímpar: rodaram só os testes do que ela toca.** O caso novo `PacoteEncerradoNoCancelamentoTest` (as duas portas, a
venda sem pacote, a transação que falha, a sessão depois do cancelamento saindo do pacote da venda refeita e pagando 8,00, a
sessão lançada antes -- o D687 --, e o modal do ERP), 7 casos, e as 59 classes do cancelamento, do pacote do cliente, da sessão,
da Cota-parte e dos portões da família que o grep achou: **1.237 casos em cada banco, saída 0, nenhum pulado**; depois da troca
dos portões, as 4 classes que os leem, 77 casos em cada banco, saída 0. No front: os arquivos da Cota-parte, do glossário e das
Comandas, 211 de 211 com `--maxWorkers=4`; com `AMPLIFICAR_EFEITOS_MS=50`, 210 de 211 -- o vermelho, "as pílulas do catálogo
andam pelas setas" do `ComandasLigada.teste.tsx`, cai igual com o glossário do `HEAD` (o D471, e não desta fatia); o front inteiro
**3.916 de 3.916**; tsc, lint e build (358,07 KB, +0,03 KB), saída 0. **Os mutantes**, um por vez, cada reserva num diretório
novo e vazio, devolvidos pelo sha1, nos dois bancos os de servidor: M1 o cancelamento sem encerrar (as duas portas e as duas
sessões vermelhas), M2 o encerramento fora da transação e M2b o cancelamento sem transação (a transação que falha), M3 encerrando
todo pacote do cliente (as portas, a venda sem pacote e a sessão), M6 só o primeiro pacote da venda, M7 zerando o restante, M5 o
modal sem a frase e M8 o glossário antigo (o `CotaParteTest`, que compara a nota e o glossário) -- todos vermelhos pela asserção;
**M4, encerrando também o pacote sem venda de origem, é equivalente**: `clientes_pacotes.venda` é NOT NULL nos dois esquemas. E o
M9, o `cancelar` sem chamar o corpo, vermelho no `conferir-escritores-do-cancelamento.php`. **Os portões:** o par novo no
`conferir-cobertura-por-par.php` (422 para 423, o ISVenda de 139 para 140); o `conferir-escritores-do-cancelamento.php` lê o
corpo do cancelamento no `cancelarNaTransacao` e confere que o `cancelar` o chama; o `docs/inventario-acoes.csv` regerado (uma
linha); seis pares distintos no `conferir-debitos-repetidos.php`; o `conferir-recorte-por-unidade.php`, saída 0 (o recorte e a
permissão do cancelamento não mudam); o `conferir-tudo.sh` sozinho, **72 portões, saída 0**; o `contar-debitos.php` e o
`conferir-debitos-repetidos.php`, saída 0. **Os ensaios** nos dois bancos de contrato, cada um com o banco remontado antes:
`ensaiar-permissao-adm-api`, `ensaiar-fluxo-adm-api` e `ensaiar-recusas-adm-api` (253 de 253), saída 0 nos dois, e o
`medir-adm-api-no-postgres`, saída 0. **O oráculo no contrato, pela API:** o pacote a 10% vendido pelo vendedor de teste em duas
vendas (A e B) ao mesmo cliente, uma sessão antes do cancelamento (saiu do pacote de A e gravou 8,00), a venda A cancelada pela
API, uma sessão nova (saiu do pacote de B e gravou 8,00), contra um SELECT e a conta à mão -- o pacote de A encerrado com as
sessões que restavam, o de B ativo, os outros pacotes do cliente como estavam, a `data_alteracao` só no de A, as duas sessões, a
Cota-parte do dia (8,00, só a de B) e o extrato: **25 conferências, 0 divergências**; falsificado (a cota à mão de 8,00 para
9,00), 3. Débitos: D686 CORRIGIDO; D687 e D688 registrados; de pé 192 para 193. **O navegador é do coordenador**, e não rodou
nesta fatia. Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`, só SELECT; o banco de contrato
foi remontado.

**A verificação em três lentes confirmou 6 achados, todos baixos, tratados.** (1) O modal do ERP, o glossário `cota.quemVendeu`
e a `NOTA_DE_QUEM_VENDEU` prometiam que o pacote encerrado não se usa mais, e a sessão já lançada numa comanda aberta antes do
cancelamento ainda sai dele (o D687): os três dizem agora a exceção ("A sessão que já estava lançada numa comanda aberta antes do
cancelamento ainda sai do pacote encerrado, sem cota."; no modal, "ele não pode mais ser lançado numa comanda. A sessão dele que
já estiver lançada numa comanda aberta ainda é descontada dele ao finalizar."), e o caso do D687 confere a frase da nota. (2) O
caso da transação falhava antes de qualquer `flush`, e "a venda como estava" não provava nada: o caso novo
`testOCancelamentoQueFalhaDepoisDeUmFlushDesfazTudo` grava o crédito de verdade (o `flush` grava também o status 0 da venda) e
falha no débito -- a venda, o crédito e os pacotes como estavam. (3) O pacote da fixture vence em '2030-12-31', e a partir de 2030
a sessão do caso iria a ele: o caso o empurra para daqui a 730 dias, ativo, com as 3 sessões. (4) As "20" e "166 sessões
restantes" somavam só os saldos positivos (SELECT na cópia: em 2026, 3 dos 24 com saldo negativo, -51, a soma de todos -31; no
histórico, 5 dos 118, -56, a soma 110): o D686, o D688, este parágrafo, o item 5 do `procedimento-de-corte.md` e a pergunta do
D688 dizem isso. (5) O comentário do ajudante `cancelarAVendaDoPacote` da 74d dizia que a porta deixa o pacote ativo: agora diz
que o `UPDATE` cru modela a venda cancelada antes da 74f (o D688). (6) Arrancar o `relerOPacote` do `encerrarOsDaVenda` não
deixava caso vermelho: o caso novo `testOPacoteJaCarregadoLeOEncerramento` carrega o pacote antes do cancelamento e o lê
encerrado sem limpar a unidade de trabalho. O `PacoteEncerradoNoCancelamentoTest` foi de 7 para 9 casos. **Os mutantes da
correção**, um por vez, cada reserva num diretório novo e vazio, devolvidos pelo sha1: N1 o `cancelar` sem transação e N2 a
transação só em volta do encerramento (o caso novo vermelho pela venda, "0 is identical to 1", e o de antes pelos pacotes, nos
dois bancos); N3 sem o `relerOPacote` (o caso novo, nos dois bancos); N4 a nota e o glossário sem a exceção (o caso do D687 e o
`RegrasDaCotaParteTest`); N5 o modal com a promessa antiga (o caso do modal); S1 o pacote da fixture vencendo antes do de A, como
em 2030 sem o conserto (as duas sessões do cenário vão a ele). **Os testes da correção:** as 72 classes que o grep achou
(o cancelamento, o pacote do cliente, a Cota-parte, os portões e o modal), **1.706 casos em cada banco, saída 0, nenhum pulado**;
no front, os 25 arquivos do glossário e da Cota-parte 560 de 560, e com `AMPLIFICAR_EFEITOS_MS=50` 554 de 560 -- os 6 vermelhos,
do `Console.teste.tsx` (a comanda e o caixa dentro do Console), caem iguais com o glossário do `HEAD`, e não são desta fatia; o
front inteiro **3.916 de 3.916**; tsc, lint e build (358,11 KB, +0,04 KB), saída 0; o `conferir-tudo.sh` sozinho, **72 portões,
saída 0**; o `contar-debitos.php` (de pé 193), o `conferir-debitos-repetidos.php`, o `conferir-referencias-docs.php` e o
`conferir-segredos.sh`, saída 0. O servidor não mudou de comportamento na correção (só testes e textos), e os ensaios HTTP e o
oráculo não foram refeitos. O navegador é do coordenador.

**No navegador** (por mim, contra o banco de contrato com as seis linhas da data e o oráculo da fatia rodado nele outra vez -- 25
conferências, 0 divergências --: o pacote vendido ao mesmo cliente em duas vendas, uma sessão de A usada, a venda A cancelada): o
banco com o pacote de A encerrado (`status` 0, a venda dele com status 0) e o de B ativo; no ERP antigo, pelo gerente de teste,
Vendas Realizadas, o modal "Cancelar" com a frase "Se a venda vendeu um pacote, cancelá-la encerra o pacote do cliente criado por
ela, com as sessões que restavam: ele não pode mais ser lançado numa comanda. A sessão dele que já estiver lançada numa comanda
aberta ainda é descontada dele ao finalizar." acima dos campos do gerente (fechado sem cancelar nada). A sessão do navegador foi
encerrada antes de o `local.php` voltar (conferido pelo sha1), e o banco de contrato foi remontado; na `franquiabv_dev`, os 118
pacotes ativos de venda já cancelada continuam como estavam (a regra vale daqui em diante; o D688 pergunta pelo passado).

## 03/11/2026 -- REL-INDICACAO-PORTA (a fatia 75 da fila das telas que faltam)

**O que muda para quem usa.** O Programa de Indicação passa a contar o convite do formulário pela porta gravada na linha do
convite (`indicacoes.porta`, `aplicativo` ou `formulario`), e não mais pela marca "Indicação - N" do cadastro: o amigo que
conclui o cadastro não some mais do mês em que foi indicado, e o convite do formulário de quem indica ativo, que desde 10/10/2026
grava a mesma ligação do aplicativo, conta no formulário (antes contava como aplicativo). O passado -- os amigos com a marca --
virou linha de convite uma vez, sem pagar nada a ninguém (a resposta 264 (b) do dono, o D613). O amigo de quem indica inativo
passa a ficar registrado com quem o indicou, sem a comissão (a 265 (b), o D615), e o teto de 10 indicações por dia o conta uma
vez. O cartão "Adesões" mostra a adesão pelo formulário a partir de hoje (03/11/2026, a data gravada), e o "Como se conta" diz a
regra nova. A lista de convites do aplicativo e a tela antiga do relatório ficam como estavam (o D689, ao dono).

**O que entrou.** A migração `scripts/migracao-indicacao-porta.sql` e o irmão em `postgres/` (a coluna, as linhas que já estavam
lá -- do aplicativo, menos a da 53 (c) com a marca de quem convidou, nenhuma --, e o passado pelo `INSERT ... SELECT`, sem
`clientes.indicacao`, `data_venda`, `comissao`, `primeira_venda` nem `data_adesao`, com a conferência no fim), no laço do passo 0
do `docs/procedimento-de-corte.md` (a linha `| 0 |`) e do ensaio geral; o `mysql.sql`, o `postgres/schema.sql` e o
`scripts/migrar-dados.sh` regerados pelos geradores (o último nunca rodado). A linha `indicacaoDoFormulario` de `colunas_gravadas`
num passo próprio (`scripts/colunas-gravadas-indicacao-do-formulario.sql` e o irmão), no passo 7 da etapa 1, no 7d do corte e no
ensaio geral, e a tabela "O que fica no banco" da etapa 1 com a volta. O caminho de volta é só o `DROP COLUMN porta`, escrito,
não rodado. O escritor: `Indicacao` com as constantes e a porta; o `AplicativoService` grava `aplicativo`; o
`FidelidadeService::indicarAmigo` grava a linha do formulário para todo amigo novo e a ligação só para quem indica ativo; a etapa 3
marca a adesão na linha do amigo sem a ligação (`IndicacaoRepository::doFormularioDoConvidado`); o teto com o `NOT EXISTS` pelo
convidado. O relatório: o formulário pelas linhas (o `programaFormulario`, o `linhasDoFormulario` e o `BUSCA_DO_FORMULARIO`
saíram), as adesões por porta, a porta do cliente novo pela ligação, pela linha do formulário e pela marca, nessa ordem. Portão
novo: `scripts/conferir-escritores-da-porta.php`, no `conferir-tudo.sh` e no `ci.yml`. Mudaram de propósito: o
`conferir-cobertura-por-par.php` (423 pares, o `programaFormulario` trocado pelo `doFormularioDoConvidado`, o sha1 conferido pela
troca; "sem menção" de 118 para 117, o `contarDoIndicadorNoDia` ganhou caso), o `conferir-booleano-em-coluna-inteira.php` (13 para
14, o `JOIN ... WITH` do `programaMarcas`, conferido a mão) e o `conferir-debitos-repetidos.php` (dois pares). O desenho ganhou a
nota FEITA da I3 no `docs/f3-relatorios/spec-indicacao.md`.

**Na cópia, antes (só SELECT, só contagem):** 183 marcas legíveis, todas de indicador existente, nenhuma com a ligação, nenhuma
com linha; 110 com `status = 0` e 73 com `status = 1`; 1 de indicador hoje inativo; 73 de antes de 2026 e, em 2026, 12 em abril,
28 em maio, 28 em junho, 30 em julho e 12 em agosto; `indicacoes` com 10 linhas, 6 com a ligação; 0 convites de 2026 mudam de
porta no relatório (nenhum do formulário com a ligação). Igual no PostgreSQL. **A migração**, duas vezes em cada cópia: 0,44 s e
0,21 s no MySQL, 0,47 s e 0,21 s no PostgreSQL; depois, 10 linhas do aplicativo e 183 do formulário (0 com a ligação), 0 marcas sem
linha, 0 de indicador inexistente; a segunda vez sem efeito. A regra da marca em SQL contra a do PHP nas 119.518 origens com "ndica"
das duas cópias: 0 divergências. Nos bancos descartáveis (`porta75_descartavel` e `porta75_descartavel_pg`, do `HEAD`, com dez
origens inventadas), o `ALTER` de verdade duas vezes, saída 0 e 0, as mesmas linhas nos dois; apagados. A linha da data, duas
vezes em cada cópia, as sete linhas com data. **A medida** (julho de 2026, quente, pelo controller, antes e depois): os números
de 0,08 s para 0,003 s no MySQL e de 0,02 s para 0,005 a 0,006 s no PostgreSQL, na unidade 1 e na rede, com a mesma lista (o mesmo
sha1 de porta, dia, convidado e situação: 11 convites na unidade 1, 3 e 8; 33 na rede, 3 e 30); os clientes novos iguais, com os
mesmos sha1 (antes, 0,144 s na unidade 1 e 0,75 a 0,77 s na rede no MySQL, 0,053 a 0,054 s e 0,43 a 0,52 s no PostgreSQL; depois,
0,145 a 0,148 s e 0,758 a 0,764 s no MySQL, 0,057 a 0,058 s e 0,429 a 0,443 s no PostgreSQL). O "depois" da primeira rodada (18:44)
era de antes do último ajuste das consultas (o `ORDER BY` tirado do `programaMarcas`, às 18:57); os números deste parágrafo são os
da rodada refeita na correção, às 19:22, com o código final. Nenhum DELETE; na `franquiabv_dev`, só a coluna, as 183 linhas e a
linha da data.

**O que rodou (a fatia é ímpar).** As classes da fatia e as vizinhas (o `IndicacaoDoFormularioTest`, novo, 18 casos; o
`D304EtapaSeisDaAdesaoTest`, o `ConsultasDaIndicacaoTest`, o `RegrasDoProgramaDeIndicacaoTest`, o `ProgramaDeIndicacaoTest`, o
`UnidadeDoConviteTest`, o `ColunasDoPdvTest`, o `IndicacoesNoRelatorioTest`, o `D312PontosDaIndicacaoTest`, os da fidelidade, do
aplicativo, do relatório e da família dos portões): 1.054 casos em cada banco, saída 0, nenhum pulado. O front: os arquivos da
Indicação e do glossário 64 de 64, também com `AMPLIFICAR_EFEITOS_MS=50`; o front inteiro 3.917 de 3.917; tsc, lint e build com
358,10 KB. Os ensaios de permissão, recusas, fluxo, escritas do aplicativo e fidelidade no contrato do MySQL e, com o
`medir-adm-api-no-postgres`, no do PostgreSQL: saída 0. O `conferir-tudo.sh` sozinho: 73 portões, saída 0. `contar-debitos.php` e
`conferir-debitos-repetidos.php`: saída 0.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio, devolvidos pelo sha1: o INSERT gravando `clientes.indicacao`
(M1, nos dois bancos: a primeira compra paga); sem o filtro do indicador existente (M2, nos dois: a chave estrangeira recusa); sem
o `NOT EXISTS` pelo convidado (M8, nos dois: duplica); a linha não gravada para o inativo (M4, nos dois e no portão); a ligação
também para o inativo (M5, nos dois e no portão); a adesão sem o `doFormularioDoConvidado` (M6, nos dois e no portão); o `NOT
EXISTS` tirado da segunda parcela do teto (M7, nos dois: o 6º do inativo é recusado); o relatório lendo a marca no lugar da linha
(M3a, nos dois: o amigo que aderiu sai do mês); a porta do novo sem a linha do formulário (M3b); a porta da linha ignorada (M11);
o `programaMarcas` sem a linha do formulário (M12, nos dois); as adesões sem a porta (M14); a lista do aplicativo e a tela antiga
sem o filtro da ligação (M10, M10c e M10d, nos dois); o passo da data sem o `NOT EXISTS` (M13); o cartão sem a adesão do
formulário (F1, F2). Todos vermelhos. O aplicativo sem gravar a porta (M9) só pelo portão: a coluna e a entidade dão o mesmo padrão;
o valor inicial `formulario` na entidade (M9b, o IND-M37) é equivalente desde que as duas portas gravam a porta e o portão as obriga.

**O oráculo**, no contrato do MySQL, em processo (o serviço do aplicativo, o do formulário e o controller da Indicação): um
amigo antigo só com a marca, a migração rodada no contrato, um convite pelo aplicativo, um pelo formulário de quem indica ativo e
um de quem indica inativo, a etapa 3 do amigo do inativo e o passo da data; o banco e o JSON do mês de hoje e do mês do antigo
contra um SELECT e a conta à mão: 17 conferências, 0 divergências; falsificado (o amigo do formulário com a ligação esperado no
aplicativo), 1. O banco de contrato foi remontado. **O navegador é do coordenador.**

**A correção da verificação** (dez achados confirmados, dois médios e oito baixos; todos tratados):

- **O contrato do aplicativo**, que a I3 manda manter verde, não tinha rodado (o `rodar-caracterizacao-*` roda só a suíte
  `caracterizacao`). Rodou: `rodar-contrato.sh` e `rodar-contrato-postgres.sh`, 74 de 74 em cada banco, saída 0, com os
  instantâneos `indicacoes-listar-autenticado` e `v4-clientes-indicacoes-autenticado` sem mudar (`tests/Contrato` limpo no git).
- **O UPDATE da migração** (a linha da 53 (c) que a coluna deixou no aplicativo, devolvida ao formulário pela marca de quem
  convidou) não tinha caso: o `testAMigracaoDevolveAoFormularioALinhaDa53cPelaMarcaDeQuemConvidou` grava essa linha com o padrão,
  a do amigo com a marca de OUTRO indicador e a do convidado do aplicativo; só a primeira vira formulário, a ligação fica, nenhuma
  linha nova nasce, a segunda vez não muda nada. Mutantes: o UPDATE sem efeito (N1, N1pg) e sem o `i.cliente` da marca (N2,
  N2pg), vermelhos nos dois bancos. O "0 na produção" do desenho é inferido (a 53 (c) não chegou lá), e agora diz isso.
- **O desempate pela linha de menor id** (`doFormularioDoConvidado` e `programaMarcas`) não tinha caso: o
  `testDuasLinhasDoFormularioParaOMesmoAmigoFicaADeMenorId` grava três linhas do formulário para um amigo, na ordem do meio, da
  menor e da maior -- com duas, o PostgreSQL devolvia a de menor id por último e o `continue` arrancado (N5) ficava verde; com três,
  vermelho nos dois. O `ORDER BY` invertido (N3) e o `>` trocado por `<` (N4), vermelhos nos dois bancos.
- **A adesão do formulário com a ligação, antes da data**, saía de "aplicativo" e ficava nula, e sumia do cartão. Agora, no mês
  antes da data (ou sem ela), o que ficou gravado da adesão do formulário soma em `adesoes.aplicativo`, como antes da porta (o
  "enquanto isso" do bloco 41b); depois da data, cada porta a sua. Casos: o `testOsNumerosDoMesComALista` (a regra) e o
  `testComALinhaDaDataAAdesaoDoFormularioEASua` (pela API, sem e com a linha de `colunas_gravadas`). Mutante N6 vermelho. O texto
  do `AindaNaoGravado` e o glossário `indicacao.adesao` dizem isso. Na cópia e na produção o efeito é zero (nenhuma linha assim).
- **O IND-M38** do desenho ("o `ColunasDoPdv` sem a data") não tinha mutante nem caso que o pegasse (a suíte não tem a linha da
  data): o caso novo acima a grava; a chave tirada do `ColunasDoPdv::CHAVES` (N7) e o controller devolvendo nula (N8), vermelhos.
  O M13 (o passo da data sem o `NOT EXISTS`) agora também no irmão do PostgreSQL (M13pg), vermelho no `ColunasDoPdvTest`. Os
  seis do desenho, pelo nome: IND-M33 é o M5, IND-M34 o M10 (com M10c e M10d), IND-M35 o M6, IND-M36 o M8, IND-M37 o M9b
  (equivalente) e IND-M38 o N7 e o N8.
- **O ensaio de escritas do aplicativo** passa a conferir `indicacoes.porta = 'aplicativo'` no caso 2, como a I3 pede nos
  Portões. Rodou no contrato do MySQL e no do PostgreSQL, saída 0; falsificado (o `AplicativoService` gravando `formulario`), saída 1 pela conferência
  nova. O limite, escrito no ensaio: o padrão da coluna é o mesmo valor, e quem prova que o serviço a grava é o portão.
- **O oráculo pelo HTTP**: as portas pela rota do aplicativo instalado (`POST /api/cliente/indicacao`) e pelo formulário público
  (`POST /fidelidade/salvarDados`, as duas etapas 6 e a etapa 3 do amigo do inativo), e o JSON pela `GET
  /adm/api/relatorios/indicacao` como o gerente de teste (com o privilégio do relatório concedido por usuário no contrato),
  contra um SELECT e a conta à mão: 17 conferências, 0 divergências; falsificado, 1. O banco de contrato foi remontado.
- **As medidas** ganharam a ressalva e os números refeitos (acima); o **D615** cita o caso pelo nome de hoje.

- **O portão de cobertura por par** desceu o "sem menção" de 117 para 116, por cobertura: o caso do desempate chama e afirma o
  `IndicacaoRepository::doFormularioDoConvidado`, que antes só aparecia em comentário.

Os testes da correção: as classes da fatia e as vizinhas (o mesmo filtro da primeira rodada, com os três casos novos), 1.057 casos
em cada banco, saída 0, nenhum pulado; o contrato 74 de 74 em cada banco; o front da Indicação, do glossário, dos componentes do
relatório e da lista 51 de 51, também com `AMPLIFICAR_EFEITOS_MS=50` (e o `ProgramaDeIndicacaoLigado` e o `ConsoleAbreIndicacao`, 13
de 13, amplificados); o front inteiro 3.917 de 3.917; tsc, lint e build com 358,16 KB (+0,06 KB); o ensaio de escritas do
aplicativo no contrato do MySQL e no do PostgreSQL, e o `medir-adm-api-no-postgres`, saída 0; o `conferir-tudo.sh` sozinho, 73 portões, saída 0; `contar-debitos.php`,
`conferir-debitos-repetidos.php` e `conferir-segredos.sh`, saída 0 (de pé, 192, sem mudança). Os mutantes da correção, um por vez,
a reserva num diretório novo e vazio, devolvidos pelo sha1: N1 a N5 nos dois bancos, N6 a N8 no MySQL, M13pg no PostgreSQL e o
E1 (o ensaio com a porta errada), todos vermelhos; nenhum equivalente.

**Débitos:** D613 e D615 CORRIGIDOS; D689 registrado, ao lote (a lista do aplicativo); de pé de 193 para 192. Nada da produção, do
BVIP, do OneSignal nem do crontab mudou.

**Conferido por mim depois da correção.** O ambiente: o `local.php` no `franquiabv_dev` (sha1), nenhum órfão no contêiner, as 9
travas que a suíte deixou em `/tmp` removidas. A fatia é ímpar: o ciclo inteiro não roda. As duas cópias com a coluna e o passado
(10 linhas do aplicativo e 183 do formulário em cada uma) e a linha `indicacaoDoFormulario` de `colunas_gravadas` (03/11/2026
18:44). **No navegador**, contra o banco de contrato com o oráculo da correção rodado nele pelo HTTP (17 conferências, 0
divergências; o roteiro da migração e as sete linhas da data aplicados), o gerente de teste abriu o Programa de Indicação pelo menu
da tela nova: em novembro, "Convites 3 · Aplicativo 1 · Formulário 2" e "Adesões 0 pelo aplicativo · 1 pelo formulário", a lista com
o convite do aplicativo, o do formulário de quem indica ativo ("Aguardando adesão") e o do amigo de quem indica inativo ("Aderiu em
03/11/2026", sem comissão); em outubro, o amigo antigo escrito pela migração como "Formulário" em 04/10/2026, e o cartão
"Adesões" só com o aplicativo (antes da data o formulário fica nulo); 375 px sem rolagem lateral. A sessão foi encerrada, o
`local.php` devolvido e conferido pelo sha1, o banco de contrato remontado.

## 03/11/2026 -- RESPOSTAS-286-E-287 (a fatia 74g: as respostas do dono de 03/11/2026)

As respostas 286 (a) e 287 (a) do dono, sobre o **D687** e o **D688** da 74f, as duas a recomendação.

- **286 (a), a comanda com a sessão de um pacote encerrado não finaliza.** O caixa que finalizar uma comanda com a sessão lançada
  num pacote que foi encerrado depois do lançamento (a venda do pacote cancelada, ou o pacote removido da ficha do cliente)
  recebe a recusa, com a frase dizendo qual pacote e o que fazer: "O pacote "<nome>" (<serviço>) foi encerrado depois que a sessão
  dele foi lançada nesta venda (...). Tire da venda o item da sessão e lance-o de novo: a sessão sai de outro pacote ativo do
  cliente, ou pelo preço." Nas duas portas: o PDV do ERP antigo (a frase no aviso) e a API da tela nova (409, o `motivo`
  `pacote_encerrado`; a tela mostra a frase inteira, sem mudar). As duas perguntam antes de chamar o finalizar
  (`VendaPagamentoService::pacotesEncerrados`, pelo `ClientePacoteRepository::sessoesDePacoteEncerradoNaVenda`), e o serviço
  recusa também: a baixa condicional só baixa o pacote ativo (fecha a corrida do cancelamento no meio). Nada é gravado na recusa.
  A comanda com duas sessões, uma de pacote ativo e outra do encerrado, é recusada inteira, nomeando o encerrado. O pacote vencido
  não é encerrado, e a regra dele não muda. Tirado o item e lançado de novo, a sessão sai do outro pacote ativo do cliente (que
  paga a cota a quem vendeu) ou pelo preço. O modal do cancelamento do ERP, o glossário `cota.quemVendeu`, a nota do arquivo da
  Cota-parte e o `spec-cota-parte.md` dizem a recusa no lugar da exceção da 74f.
- **287 (a), os pacotes de venda já cancelada, encerrados uma vez no corte.** `scripts/encerrar-pacotes-de-venda-cancelada.sql`
  e o irmão em `postgres/`: a contagem antes do UPDATE (quantos, quantos ainda válidos, as sessões restantes, os de saldo negativo,
  de quantos clientes e vendas), a lista com a `data_alteracao` de antes (o caminho de volta), o encerramento com a marca do
  cancelamento novo (`status` falso e `data_alteracao`; nada apagado) e a conferência (0). Roda no passo 7 da etapa 1, depois das
  linhas de `colunas_gravadas` e com o código da 74f no lugar, e no 7d do corte de uma vez só; fora do laço do passo 0; não é ponto
  de parada -- o número dos ainda válidos vai ao dono no relato da janela. No corte, os pacotes de vendas canceladas antes de
  03/11/2026 deixam de aparecer como ativos. **Na cópia não roda.**

**As contagens na cópia** (só SELECT, só contagem, nos dois bancos): **nenhuma comanda aberta com sessão de pacote encerrado**
(0 das 17 abertas no MySQL, 0 das 61 no PostgreSQL; nenhuma tem sessão de pacote nenhum); os da 287, conferidos de novo, iguais
nos dois: **118 pacotes ativos de venda cancelada, 0 ainda válidos, 69 com saldo positivo e 166 sessões restantes, 5 com o saldo
negativo, de 39 clientes e 66 vendas** (o mais novo venceu em 07/09/2026). **O roteiro num banco descartável** montado da cópia
(`clientes_pacotes` e `vendas`, apagado no fim), duas vezes em cada motor: 118 encerrados num instante só e a conferência 0, a
segunda rodada contando 0 e sem efeito, 0 pacotes de venda válida ou já encerrados tocados, 0 restantes mudados; no MySQL 0,48 s e
0,05 s, no PostgreSQL 0,06 s e 0,05 s (com o `docker exec`). O descartável do PostgreSQL não tinha RLS; o roteiro com o RLS forçado
foi medido na correção (abaixo).

**Os testes.** O `PacoteEncerradoNoCancelamentoTest` ganhou os casos da recusa (o serviço, o ERP e a API sem chamar o finalizar, a
corrida nas duas portas, as duas sessões, o removido pela ficha, o encerrado sem saldo com só a frase dele, o vencido que
finaliza, e o item tirado e lançado de novo, que sai de B e paga 8,00); o caso
`testASessaoLancadaAntesDoCancelamentoAindaSaiDoPacoteEncerrado` virou `testASessaoLancadaAntesDoCancelamentoRecusaOFinalizar` e
defende a recusa. O `EncerrarPacotesDeVendaCanceladaTest` (novo) roda o roteiro do banco do caso, duas vezes, e lê o lugar dele no
corte. As 79 classes do finalizar, do pagamento, da comanda, do pacote do cliente, do cancelamento, da Cota-parte, dos portões e o
que o grep achou: **1.550 casos em cada banco, saída 0, nenhum pulado**. O front: os 29 arquivos da comanda, do Console, do
glossário e da Cota-parte 737 de 737; com `AMPLIFICAR_EFEITOS_MS=50`, 7 vermelhos (as pílulas do `ComandasLigada`, o
D471, e 6 do `Console.teste.tsx`), com os nomes do registro e o caso novo verde -- comparados com o HEAD só na correção (abaixo); o front inteiro **3.918 de 3.918**; tsc, lint e
build com 358,19 KB (+0,03 KB). Os ensaios no contrato do MySQL (montado antes) e no do PostgreSQL: permissão 175 de 175, recusas 253
de 253 e o fluxo, 22 passos, saída 0 nos dois. O `conferir-tudo.sh` sozinho, **73 portões, saída 0** (o de cobertura por par de 423
para 424 pares e o ISVenda de 140 para 141, pelo par novo; o "sem menção" em 116); `contar-debitos.php`,
`conferir-debitos-repetidos.php`, `conferir-referencias-docs.php` e `conferir-segredos.sh`, saída 0.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio, devolvidos pelo sha1, todos vermelhos pela asserção, os de
servidor e os do roteiro nos dois bancos: M1 (o ERP sem a recusa) e M2 (a API sem a recusa) pelo serviço chamado; M3 (a baixa sem o
`status`) e M4 (a recusa depois da gravação) pelo retrato do banco; M5 (a pergunta pegando o ativo) e M5b (a baixa recusando o
ativo) pelos casos do D452; M6 (a pergunta só no primeiro item) pelas duas sessões; M7 (as sessões que faltam contando o
encerrado) pela frase única; R1 e R1pg (o roteiro encerrando o de venda válida) e R2 e R2pg (sem o filtro do ativo, reescrevendo a
data do já encerrado) pelo caso do roteiro; T1 (o modal), T2 (a nota) e T3 (o glossário) sem a frase nova. Nenhum equivalente.

**O oráculo** no banco de contrato do MySQL, pela API como o gerente de teste: o pacote vendido em duas vendas (A e B), a sessão de
A lançada na comanda aberta, a venda A cancelada, o finalizar recusado com 409, o `pacote_encerrado`, o pacote de A e a frase, e o
banco igual ao de antes (SELECT); o item tirado e lançado de novo sai de B, o finalizar passa, e a cota de quem vendeu sai 8,00
(10% de 80,00); depois, o roteiro do corte no contrato, com um pacote ativo de venda cancelada por UPDATE cru: a contagem e o
encerramento contra o SELECT, e a segunda rodada sem efeito -- **36 conferências, 0 divergências; falsificado, 1**. O banco de
contrato foi remontado.

**Débitos:** D687 e D688 CORRIGIDOS (o D688 pelo roteiro preparado: o encerramento acontece no corte); de pé de 192 para 190.
Nenhuma pergunta nova ao dono. Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na franquiabv_dev, só SELECT. O
navegador e o ciclo inteiro são do coordenador.

**A correção depois da verificação** (três lentes; seis achados confirmados, um médio e cinco baixos, todos tratados). (1) O 409
`pacote_encerrado` não estava no desenho da API do PDV: entrou no `docs/f2-fatia-pdv.md` (a nota de 03/11/2026), com o formato de
`detalhes.pacotes`, a ordem (antes do `sessoes_do_pacote`, do fiado e do caixa) e a corrida. (2) O médio: nenhum caso cobria o
escape da frase nova no ERP -- tirar o `htmlspecialchars`, ou passar `false` numa das duas chamadas do ERP, ficava verde; caso novo
`testOsNomesVaoEscapadosNoErpECrusNaApi` ("Pacote <b>VIP</b>" e "Corte <i>VIP</i>", os dois caminhos do ERP escapados e a API
crua). (3) A ordem antes do fiado na API sem caso: `testNaApiARecusaDoEncerradoVemAntesDaDoFiado`. (4) Três colunas da contagem
do roteiro sem um pacote que as separasse: o cenário do `EncerrarPacotesDeVendaCanceladaTest` ganhou o pacote sem validade, o
vencido com saldo e o segundo da mesma venda (ids 90008478 a 90008480), e a contagem esperada passou a 5 / 3 / 4 / 11 / 7 / 1 /
1 / 4. (5) A guarda da quantidade da pergunta nova sem caso: `testOItemDeQuantidadeZeroNoEncerradoNaoRecusa` (o item de
quantidade zero no pacote encerrado não recusa, e a API finaliza com 200 sem baixar nada). (6) As duas ressalvas, medidas: com
`AMPLIFICAR_EFEITOS_MS=50`, o `ComandasLigada.teste.tsx` e o `Console.teste.tsx` do HEAD (extraídos por `git archive` num
diretório de rascunho) dão **7 vermelhos e 301 verdes de 308**, e os da árvore **7 e 302 de 309**, os mesmos 7 pelo nome (o 309º
é o caso novo, verde); e o roteiro do PostgreSQL **com o RLS forçado**, num banco descartável com as duas tabelas, as políticas
do `schema.sql` e dois inquilinos (apagado no fim): como o `bvip_app`, só os 2 do inquilino 1 encerrados, a conferência 0, a
segunda vez 0; sem o `SET`, parado no primeiro SELECT sem mudar nada; **como o dono superusuário (o `psql` do 7d), o RLS não vale
e o `SET` não filtra: os 3, dos dois inquilinos** -- no corte é o mesmo, porque a base só tem o inquilino 1; o cabeçalho do irmão
em `postgres/`, o D688 e o D687 o dizem. Os mutantes da correção, um por vez, a reserva num diretório novo e vazio, devolvidos
pelo sha1, todos vermelhos pelo caso novo: C1 (a frase sem o escape), C2 e C3 (cada chamada do ERP sem o escape), C4 (a recusa
da API depois da do fiado) e C5 (a pergunta sem a guarda da quantidade) nos dois bancos; V1 e V1pg (os ainda válidos sem a
validade nula), V2 e V2pg (as sessões dos ainda válidos sem a validade) e V3 e V3pg (as vendas sem o `DISTINCT`). Os testes da
correção: as duas classes nos dois bancos, saída 0, nenhum pulado; as 79 classes da fatia de novo, **1.553 casos em cada banco**
(1.550 mais os três casos novos), saída 0, nenhum pulado; o `conferir-tudo.sh` sozinho, os débitos e os portões dos docs, saída 0.
Nenhuma pergunta nova ao dono. O navegador e o ciclo inteiro continuam do coordenador.

**O ciclo inteiro e o navegador, por mim, depois da correção.** O ambiente: o `local.php` no `franquiabv_dev` (sha1), nenhum
órfão no contêiner, as 27 travas que a suíte deixou em `/tmp` removidas, nenhum banco descartável de pé. O ciclo inteiro
(`medir-ciclo-todo.sh`, o `resumo.txt` lido etapa por etapa): caracterização 6.052 em cada banco, saída 0 (os mesmos 12 pulados);
contrato 74 e 74; os ensaios nos dois bancos, os portões (73) e o front 3.918 de 3.918, saída 0; o medidor dos relatórios no
PostgreSQL, saída 0; em 9 min 35 s. **No navegador**, contra o banco de contrato com o cenário do oráculo (a comanda aberta com a
sessão lançada do pacote de A pela API, e a venda A cancelada pela API: 4 conferências, 0 divergências), o gerente de teste abriu a
comanda em Comandas abertas e clicou "finalizar comanda": a tela mostrou a recusa inteira ("O pacote \"Pacote Teste\" (Corte
Teste) foi encerrado depois que a sessão dele foi lançada nesta venda (...). Tire da venda a sessão e lance-a de novo"), e o banco
ficou como estava (a comanda aberta, nenhum pagamento, o de A encerrado com as 4 e o de B ativo com as 5); 375 px sem rolagem
lateral. A sessão foi encerrada, o `local.php` devolvido e conferido pelo sha1, o banco de contrato remontado; na cópia, os 118
pacotes ativos de venda cancelada como estavam (o roteiro do corte não rodou nela).

## 03/11/2026 -- RELATORIOS-DA-138-DESENHO (a fatia 76 da fila das telas que faltam)

**Só documentação: nada mudou para quem usa.** A resposta 138 (a) do dono (11/10/2026) mandou os três relatórios que ficaram fora
do menu novo seguirem a proposta de cada um; a Fidelização já tinha saído (a fatia 41). Os outros três ganharam a especificação,
no molde das dos relatórios já feitos: a **Carteira de clientes** (o Clientes e a Evasão, `docs/f3-relatorios/spec-carteira.md`),
as **Datas da equipe** (o Aniversariantes, `docs/f3-relatorios/spec-datas-da-equipe.md`) e a **Capacidade × realizado** (o Potencial
de Franquia, `docs/f3-relatorios/spec-capacidade.md`), com a seção 9 nova em `docs/f3-relatorios.md` e as três linhas no README da
pasta.

**O que vai mudar quando as fatias rodarem.** O gerente ganha a Carteira: um retrato de hoje com oito faixas pelo tempo desde a
última venda finalizada com item, a visita do painel (até 30 dias, 31 a 45, 46 a 90, 91 a 180, 181 a 365, 1 a 2 anos, mais de 2 anos e os cadastrados sem
visita), uma régua só de "sumido" -- mais de 45 dias, a do painel e do Profissional Parceiro --, e a lista de cada faixa com o
telefone e o próximo agendamento, mais o resumo por colaborador da unidade; a Evasão (o PDF e a rotina das 04:10) sai. **O que se
perde, escrito:** o Clientes antigo (a lista com a busca e 26 filtros) só sai pela resposta do D696, porque a Carteira não a repete
e as Campanhas só contam; quem a decisão 3 recusa (1 caixa no Clientes, 1 caixa e 1 "outro" no Aniversariantes) perde também a tela
antiga quando a nova entrar no menu; a Capacidade não conta mês futuro nem intervalo livre (o Potencial antigo fica até a CR2). As Datas da equipe mostram o nascimento e o tempo de
casa da equipe no mês do calendário, e ao administrador também a inauguração das unidades e o aniversário dos sócios. O
administrador ganha a Capacidade × realizado: a capacidade da agenda pela régua da F4, a ocupação e o realizado do painel, por
unidade e na rede.

**Medido na cópia** (só SELECT de contagem, as consultas `p76-*.sql` no rascunho, iguais nos dois bancos salvo onde dito; hoje =
17/08/2026, o dia seguinte à última venda): 239.239 clientes ativos sem os sentinelas, 11.744 sem venda finalizada (11.700 sem a
unidade da última visita); a coluna `ultima_visita` no mesmo dia da última venda finalizada em 227.107, e 366 com a coluna no dia de
uma venda não finalizada (o D694); as faixas da rede 13.674 / 4.205 / 6.180 / 8.381 / 16.856 / 28.198 / 150.001 / 11.744; as réguas
de "sumido" de hoje: 495 no dia dos 30, 453 no dos 31, 209.755 com mais de 45 dias, 42.317 nas janelas da Evasão e 164.982 que ela
nunca vê; a Evasão contando 363.789 "vendas" para 232.802 e 36.164 pares para 33.645 clientes (o D691); 175 ativos sem o
consentimento de mensagens; as consultas pela coluna quentes, de 14 a 95 ms, e só a data da última venda da rede, 415 a 419 ms no
MySQL e 347 a 352 ms no PostgreSQL (a regra inteira da Carteira, medida na correção, abaixo, custa bem mais); 267 da equipe ativa, 32 unidades com a inauguração; o ticket do Potencial R$ 60,55 por item em julho, contra R$ 90,42
por venda com serviço, e o campo do cadastro igual ao da rotina nas 29 unidades no PostgreSQL e diferente em 28 no MySQL (as cópias
divergem, como divergem na fidelização: 0 e 122 fidelizados antigos); a agenda de julho da rede, 38.607 linhas, de 85 a 488 ms.

**Ao dono, no lote** ("TELAS-RESTANTES [relatorios] (03/11/2026)"): a Carteira exporta o contato? (D690, recomendo só quem aceitou
mensagens); os arquivos da Evasão com o contato dos clientes: apagar no corte? (D693, recomendo apagar); a Capacidade × realizado
mostra um potencial em reais? (D692, recomendo não). Nenhuma trava a primeira fatia de cada relatório.

**As fatias propostas** (para a fila, já com a correção): a Carteira em CA0 (as contas, ~600 linhas), CA1 (a medida primeiro, a
consulta, a API e a guarda do Clientes antigo, ~1.700), CA2 (a tela, ~1.900), CA3 (a Evasão sai, ~400, depois do D693) e CA4 (o
Clientes antigo sai, ~300, depois do D690 e do D696); as Datas da equipe em DE0 (o servidor, ~900), DE1 (a tela, ~1.100)
e DE2 (o ERP antigo sai, ~300); a Capacidade em CR0 (o servidor, ~1.300), CR1 (a tela, ~1.200) e CR2 (o ERP antigo, ~300, depois do
D692). **Débitos:** D690 a D696 registrados (o D696 na correção), o D306 PARCIAL; de pé 190 para 197. Rodaram o `conferir-referencias-docs.php`, o
`contar-debitos.php`, o `conferir-debitos-repetidos.php` (nove pares novos, cada um com o motivo), o `conferir-segredos.sh` e o
`conferir-tudo.sh` sozinho. Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`, só SELECT.

**A correção, no mesmo dia, depois da verificação em três lentes** (18 achados confirmados, cinco médios e treze baixos, todos
tratados só nos documentos; as consultas novas `p76-13-correcao-*.sql` a `p76-16-duas-etapas-*.sql`, só SELECT, e as saídas em
`p76/13-*` a `p76/16-*`). **A Carteira:** a visita passou a ser a do painel (a venda finalizada com algum item; a só de crédito não
conta), a unidade é a de quem abriu o caixa, e os números foram medidos PELA REGRA, iguais nos dois bancos -- a rede 13.670 / 4.205
/ 6.180 / 8.380 / 16.852 / 28.188 / 149.951 / 11.813, a unidade 1 com 22.321 (18.310 pela visita, 4.010 sem visita cadastrados nela
e 1 sem a unidade do caixa); o oráculo da CA1 lê o `hoje` da resposta, e não estes números, que são de 17/08/2026. **O tempo:** a
regra inteira leva 5,6 s no MySQL e 0,74 a 1,19 s no PostgreSQL na rede (a página, 5,6 a 7,4 s e 1,6 a 2,1 s); nenhuma forma que
segue a regra ficou abaixo de 1 s no MySQL (a janela sem o item 4,4 s; a data com o item 2,2 s; em duas etapas 2,0 s no MySQL e
mais de 10 min no PostgreSQL, cancelada) -- a primeira versão dizia "abaixo de 0,5 s" e estava errada; a CA1 começa medindo o índice
`vendas (cliente, status, data_criacao)` num banco descartável e, se não bastar, para e leva a escolha ao lote. **O que a Carteira
não repete:** a lista filtrável do Clientes (a busca, 26 filtros, 8 ordenações) -- as Campanhas só contam --, ao dono no D696, com a
saída do Clientes numa fatia própria (CA4); o resumo por colaborador da Evasão entrou no retrato da unidade (71 colaboradores e 134
clientes sem serviço na última visita, na unidade 1). **As guardas das telas antigas** vão no mesmo commit do `naTelaNova`: o Clientes
(as quatro ações) na CA1, o Aniversariantes na DE0, o Potencial na CR0 (efeito zero hoje). **A sobreposição dos privilégios** do
Clientes e da Evasão: 105 com os dois, 3 só com cada um, os 3 só da Evasão sem ser gerentes. **As Datas da equipe** mostram a data
inteira, como hoje, e o contato nos quatro blocos; **a Capacidade** escreve o que perde (o mês futuro e o intervalo livre). O D691
fecha PARCIAL na CA1 e CORRIGIDO na CA3; três motivos do `conferir-debitos-repetidos.php` passaram a dizer os radicais que o portão vê;
os planos de teste da tela ganharam os casos de cada gesto e estado, e os mutantes que eles matam. Os portões rodaram de novo sobre os
arquivos finais.

**Conferido por mim, depois da correção** (03/11/2026): os 18 achados das três lentes, lidos um a um, e o relato da correção; a árvore só com documentos e o `conferir-debitos-repetidos.php` (só motivos); os fins de linha em LF; nenhuma consulta pendurada no MySQL nem no PostgreSQL depois do `pg_cancel_backend` da correção; o `local.php` no `franquiabv_dev` pelo sha1. Na cópia, de novo, só SELECT: 239.239 ativos e 175 sem o consentimento de mensagens nos dois bancos, e 11.813 ativos sem venda finalizada com item no MySQL, como os desenhos dizem. O `contar-debitos.php` com 197 de pé e o `conferir-debitos-repetidos.php` sem par novo, saída 0. As fatias 76a a 76k entraram na fila logo depois da 76 (os relatórios vêm antes das contas a pagar); a 76d, a 76e e a 76k esperam as respostas. As quatro perguntas foram à página como a 289 (D690), a 290 (D693), a 291 (D692) e a 292 (D696). Só documentação: sem ciclo e sem navegador.

## 03/11/2026 -- RESPOSTA-288 (a fatia 75b: a resposta do dono de 03/11/2026)

**A resposta.** A 288 da página de perguntas, sobre o D689 da fatia 75 ("A lista de convites do aplicativo mostra o amigo indicado
pelo formulário?"): o dono escolheu a **(b) "Mostrar todos os do formulário"**, e não a (a) recomendada; sem observação.

**O que muda para quem usa.** No aplicativo novo (a v4, `GET /api/v4/clientes/:id/indicacoes`, com o Bearer do telefone), quem indicou
um amigo pelo formulário da adesão passa a ver esse amigo na lista de convites -- também os antigos, que a migração da 75 escreveu --,
sem comissão, com "aguardando" ou a data da adesão. O `IndicacaoRepository::listarPorClienteIndicador` traz toda linha de
`indicacoes` em que o cliente é quem indicou, das duas portas, pela data do convite, a mais nova primeiro. **O JSON não muda de
forma:** a linha nova chega com a `comissao`, a `data_venda` e (aguardando) a `data_adesao` nulas -- os tipos que a lista já mandava no
convite do aplicativo sem venda (na cópia, das 10 linhas de antes, 6 sem comissão, 8 sem venda, 5 sem adesão, 1 amigo sem telefone)
--, então nada foi trocado por valor vazio; nenhuma coluna nova, nenhuma escrita de dado. **A tela antiga do relatório**
(`listagemRelatorioIndex` e `totaisRelatorio`) fica como estava, só com a ligação, até sair na I4: a letra da 288 fala da lista do
cliente no aplicativo, e a tela nova já conta essas linhas no formulário. O teto de 10 por dia e o `indicarAmigo` não mudaram.

**A rota antiga com as dez abertas fica como estava, e vai ao dono (a verificação).** A lista do aplicativo instalado (`GET
/api/cliente/indicacoes/:id`) responde, pela 189 (b), sem login e a qualquer `:id` sequencial de cliente ativo. Com a 288 (b)
inteira ela entregaria a quem percorresse os ids o nome e o telefone de 182 amigos do formulário, 110 deles inativos, que nenhuma
rota sem login listava; antes ela entregava 10 linhas. Ela passa ao repositório o `$soALigacao` enquanto as dez estiverem abertas
(com elas fechadas, a sessão do ERP, mostra todas, como a v4), e a pergunta foi ao lote (seguranca, D689). Por isso o D689 fica
PARCIAL, e não CORRIGIDO. **Quem indica inativo não vê lista nenhuma** em nenhuma das rotas (a antiga pelo `getStatus()`, a v4 pela
barreira): a linha do amigo dele aparece quando ele voltar a ficar ativo. O item 4 do bloco FEITA da I3 e a nota da linha 102 do
`docs/f3-relatorios/spec-indicacao.md`, a I4 dele (o `SO_A_LIGACAO` e o parâmetro saem quando nada mais os usar), e o texto da volta
da fatia 75 no `docs/procedimento-de-corte-etapa-1.md` dizem a regra.

**Na cópia** (só SELECT, só contagem, iguais no MySQL e no PostgreSQL): 183 linhas novas, de 183 clientes, uma por cliente -- o maior
número de linhas que um cliente ganha é 1 --, todas "aguardando" (0 com a data da adesão), nenhuma com comissão, venda ou primeira
venda; nenhum desses clientes tinha lista antes. 182 dos indicadores estão ativos e as veem na v4; o 183º, inativo, não vê. Pela rota
antiga aberta, que segue só com a ligação, as mesmas 10 linhas de antes.

**Os testes.** O novo `ListaDeConvitesDoAplicativoTest` (8 casos; a rota antiga nos dois estados do interruptor e a v4, em processo,
com as sessões v4 do caso): as cinco linhas de quem indicou (o convite do aplicativo vendido e o que aguarda, o do formulário com a
ligação e dois do passado, um que aderiu) na ordem do convite, no repositório, na v4 e na rota antiga fechada; a rota antiga aberta só
com as três que são a ligação, e sem o nome nem o telefone dos amigos do passado no corpo; o convite de outro indicador -- o mais novo
de todos -- fora da lista, nas três, e o 403 da v4 com o telefone de quem indicou na lista do outro; os campos da linha do passado
contra o banco e com os tipos de hoje, campo por campo; a soma da comissão igual à das ligações, nas três; quem indica inativo sem
lista (a antiga nos dois estados, `sucesso` falso; a v4, 403), com a linha do amigo no repositório; a tela antiga só com a ligação, na
lista e nos totais. O `IndicacaoDoFormularioTest::testAListaDoAplicativoMostraOPassadoEATelaAntigaSoALigacao` (o caso da 75,
renomeado) com a linha do passado na lista do repositório, na ordem. A fatia (`ListaDeConvites`, `IndicacaoDoFormulario` e
`DezRotasDoAplicativoInstalado`): 62 casos em cada banco, saída 0. Os vizinhos -- o filtro de 27 classes da indicação, do aplicativo,
da sessão v4, do relatório e dos portões da família, mais o padrão `Fidelidade` -- 746 casos em cada banco, saída 0, nenhum pulado. O
contrato inteiro 74 de 74 em cada banco, saída 0, os instantâneos sem mudar (a fixture do contrato não tem linha do formulário sem a
ligação). Os ensaios HTTP de permissão e de escritas do aplicativo nos dois bancos de contrato, saída 0 (o MySQL remontado antes; as
travas `/tmp/*.lock` do contêiner contadas e apagadas antes de cada um). O front não lê essas rotas (grep em `frontend/src` e em
`tests`): não rodou. O `conferir-tudo.sh` sozinho, 73 portões, saída 0; o `contar-debitos.php` (197 de pé, como antes: o D689 PARCIAL segue de
pé) e o `conferir-debitos-repetidos.php` (nenhum par novo), saída 0.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, vermelhos pela asserção nos dois
bancos. Da fatia: M1 o `SO_A_LIGACAO` de volta no repositório; M2 e M3 cada rota filtrando só a ligação; M4 a lista sem o filtro do
indicador; M5 e M6 a lista e os totais da tela antiga sem o `SO_A_LIGACAO`; M7 a v4 mandando o telefone do amigo nulo; M8 a ordem
invertida. Da correção: N1 a rota antiga aberta pedindo todas (`testARotaAntigaAbertaSegueSoComALigacao`); N2 a rota antiga fechada
pedindo só a ligação (`testARotaAntigaFechadaMandaOPassadoComOsTiposDeHoje`); N3 o interruptor invertido (os dois); N4 o
`$soALigacao` sem efeito no repositório (o da aberta); N5 a rota antiga sem o `getStatus()` (`testQuemIndicaInativoNaoVeListaNenhuma`);
N7 a v4 sem o status nas duas camadas do `clientesDaSessao` (o mesmo caso). O N6, só o filtro do `clientesDaSessao` sem o status,
EQUIVALENTE: a consulta `appSelecionarClientesPorTelefone` já pede o ativo.

**O oráculo**, no banco de contrato, pelo HTTP como o aplicativo pede (a rota antiga sem sessão, com as dez abertas; a v4 com o Bearer
de uma sessão do caso): um indicador com um convite do aplicativo, um do formulário com a ligação e um amigo só com a marca, cuja linha
a própria `scripts/migracao-indicacao-porta.sql` escreveu; outro indicador com o seu convite. A v4 devolve os três, na ordem, com cada
campo contra um SELECT; a rota antiga aberta, os dois que são a ligação, e nem o nome nem o telefone do passado no corpo; o outro só vê
o seu, nas duas rotas; a v4 de quem indicou na lista do outro, 403: 48 conferências, 0 divergências; falsificado (o esperado com a
comissão zero na linha do passado), 1 divergência, na v4. O `local.php` devolvido ao `franquiabv_dev`, conferido pelo sha1.

**De fora, e por quê:** a lista antiga sem login com os amigos do formulário, ao dono (o D689 PARCIAL). **O navegador e o ciclo são
do coordenador.** Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`, só SELECT; os bancos de contrato
foram remontados.

**Conferido por mim, depois da correção** (03/11/2026): os seis achados das três lentes e o relato da correção; a data, que as minhas notas davam como 04/11 pelo UTC, corrigida para 03/11 no código, nos testes, nos documentos e na pergunta nova; o diff do `AplicativoController` e do `IndicacaoRepository` lido (o `$soALigacao` só na rota antiga com as dez abertas). O médio de segurança (a rota antiga sem login entregaria nome e telefone de 182 amigos do formulário) foi à página como a 293, recomendando (a); o D689 fica PARCIAL. O ciclo inteiro depois da correção, saída 0: caracterização 6.060 em cada banco (os mesmos 12 pulados), contrato 74 e 74, os ensaios nos dois bancos, os portões e o front 3.918 de 3.918, e o medidor dos relatórios no PostgreSQL, saída 0, em cerca de 9 min 39 s; o `local.php` no `franquiabv_dev` pelo sha1. Sem navegador: a fatia não muda tela; o oráculo da correção foi pelo HTTP como o aplicativo pede.

## 03/11/2026 -- CARTEIRA-CA0 (a fatia 76a da fila das telas que faltam)

**O que mudou para quem usa: nada.** A Carteira de clientes (o Clientes e a Evasão numa tela só, a resposta 138 (a) do dono) ganhou as
contas e a política, desligadas: sem rota, sem tela, sem item no menu; o Clientes e a Evasão antigos ficam como estavam. É a CA0 de
`docs/f3-relatorios/spec-carteira.md`, que agora a marca FEITA com o que saiu diferente do corpo.

**O que entrou.** `module/ISRelatorio/src/Regra/ContasDaCarteira.php` (nova, sem banco nem relógio): a régua `DIAS_PARA_SUMIR = 45`
(sumido é quem passou de 45 dias; o 45 é em dia), as oito faixas da E5 (`ate-30` a `mais-de-2-anos` e `sem-visita`, os anos em 365 e
730 dias), o `resumo` (as faixas, o em dia, o sumido, os sem visita e o total), o `porColaborador` (o cliente uma vez, no colaborador do
último serviço da última visita; a linha "sem serviço"; o cliente repetido é erro de quem chama), a `ultimaVisita` (a venda finalizada
com pelo menos um item, o id desempatando o mesmo instante; a cancelada, a aberta e a só de crédito não são visita), o
`colaboradorDaVisita` (o do último item `ser`, como o finalizar, a E7 -- corrigido na verificação, abaixo) e a `unidade` (a do caixa
da visita, ou a do cadastro). A visita depois de hoje é `InvalidArgumentException`. A
`PoliticaDoRelatorio::POLITICAS['carteira']` (o privilégio `relatorios/clientes/ler`, só ele; `naTelaNova` falso; período `agora`; o
caixa e os outros recusados; sem formato até a resposta do D690), e a forma `agora` no `RecorteDoRelatorio::periodo` (de e até são
hoje, para todo escopo, com o `?mes=`, o `?de=` e o `?ate=` ignorados; nenhum relatório que existia mudou: o caso `testSoACarteiraEAgora`).

**O que a política nova arrastou, e por quê** (o mínimo para os portões e testes que percorrem a `POLITICAS` ficarem verdes): a
chave `relatorioCarteira` no menu da sessão (`SessaoJson::menu` a deriva da política; o `SessaoApiTest` com a chave e os valores à mão:
falsa para o caixa, o colaborador e a sessão sem marca), no `MenuDaApi` do `frontend/src/api/sessao.ts`, no `GuardaDoMenu` e nos dublês
das guardas de 13 testes do Console e do menu e de duas prévias (nenhum item a lê); as constantes `CAMINHO_DA_CARTEIRA` (o `href` do
Clientes antigo) e `PRIVILEGIO_DA_CARTEIRA` no `menu-do-erp.ts`, sem item; no `RelatoriosNoMenuTest`, o caminho, o par `rel-clientes`
do `layout.phtml` (pelo privilégio, como hoje) e a lista `SEM_ITEM_NO_MENU` de volta, com o caso `testSemItemNoMenuSoDesligada` -- a
CA1, ao ligar o `naTelaNova`, põe o item no menu e tira a chave dali. Os portões `conferir-recorte-por-unidade.php`,
`ensaiar-permissao-adm-api.php` e `medir-adm-api-no-postgres.php` só leem a política com o `naTelaNova`: não mudam.

**O que rodou.** Antes da verificação, o filtro da fatia (`RegrasDaCarteiraTest`, `RegrasDoRecorteDoRelatorioTest`,
`RelatoriosNoMenuTest`, `SessaoApiTest`, `PortoesDaFamiliaDosRelatoriosTest`, `RelatorioAdmControllerTest`, as do painel e as do
Parceiro, e um caso de `MenuDosRelatoriosSemAsTelasQueSairamTest` e um de `UsuarioDadosPelaApiTest` que casam com o filtro), saída 0
nos dois bancos, nenhum pulado -- esta entrada dizia "529 casos" e que o filtro pegava o Caixa no ERP e o Atendimentos antigo: não
pegava (o achado 5 da verificação), e o mesmo filtro, rodado de novo depois da correção, dá 524 (520 sem os quatro casos novos).
Depois da correção, o filtro com as duas classes que faltavam (`AtendimentosAntigoTest` e `RelatorioDeCaixaNoErpTest`): 535 casos no
MySQL e 535 no PostgreSQL, saída 0, nenhum pulado. O front: os 14 arquivos tocados, 260 de 260;
com `AMPLIFICAR_EFEITOS_MS=50`, 6 vermelhos no `Console.teste.tsx`, os mesmos 6 pelo nome no HEAD (rodado numa cópia do HEAD por
`git archive`); o front inteiro, 3.918 de 3.918; tsc, lint e build (358,19 KB, como antes). O `conferir-referencias-docs.php`, o
`conferir-segredos.sh`, o `contar-debitos.php`, o `conferir-debitos-repetidos.php` e o `conferir-tudo.sh` sozinho, saída 0.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1 (regra pura: um banco, o MySQL das
fixtures): M1 a fronteira `>=` (`testOQuarentaECincoEEmDiaEOQuarentaESeisESumido`); M2 `DIAS_PARA_SUMIR = 30` (`testAReguaEADoPainel`);
M3 a venda de status 0 contada (`testAVendaCanceladaEAAbertaNaoSaoVisita`); M4 a venda sem item contada
(`testAVendaSoDeCreditoNaoEVisitaECaiNaAnteriorComItem`); M5 o colaborador do primeiro item (`testOColaboradorDoUltimoItemDeServico`);
M6 a recusa do cliente repetido arrancada, a venda de dois colaboradores contando o cliente em cada um
(`testOClienteRepetidoEErroDeQuemChama`); M7 a forma `agora` lendo o `?mes=` (`testOAgoraEDaCarteiraEIgnoraOPedido`); M8 `JANELA_PADRAO = 60`
no Parceiro (`testOAlarmeDaJanelaPadraoDoParceiro`, com a mensagem do alarme); M9 a faixa dos sem visita trocada
(`testQuemNuncaVeioESemVisita`); M10 o resumo sem somar o total (`testOResumoSomaOTotal`); M11 o desempate sem o id
(`testODesempateNoMesmoInstanteEPeloId`); M12 a unidade do cadastro para quem veio (`testAUnidadeEADaVisitaOuADoCadastro`); M13 o
`naTelaNova` ligado sem o item (`testSemItemNoMenuSoDesligada`); M14 a política sem a guarda do menu (o `SessaoApiTest`); M15 o
privilégio da Evasão na política (`testATelaDeclaraOCaminhoEAGuardaDaPolitica`); M16 o `outros` como `proprio` (`testAMatrizDoEscopo`);
M17 o fim da faixa exclusivo (`testCadaFronteira`, pela exceção "Nenhuma faixa da Carteira tem 30 dias" da própria regra, e não por
asserção); M18 a visita de amanhã aceita (`testAVisitaDeAmanhaEErroDeQuemChama`). Todos vermelhos no caso nomeado.

**A verificação em três lentes** confirmou 8 achados, todos baixos, tratados: (1) cinco comentários (a `POLITICAS`, o
`menu-do-erp.ts`, o `sessao.ts`, o `SessaoApiTest` e o docblock da `SEM_ITEM_NO_MENU`) diziam que o item do menu da Carteira entra na
CA2, e o `testSemItemNoMenuSoDesligada` obriga a CA1: corrigidos, e o corpo da CA2 no spec emendado; (2) o `colaboradorDaVisita`
contava o `lavavip` e pulava o `ser` sem colaborador, fugindo da E7 ("a mesma escolha do finalizar"): agora é o último `ser`, e o
nulo quando ele não tem colaborador (a linha "sem serviço") -- na cópia (só SELECT), 0 itens de serviço sem colaborador em venda
finalizada e os 7 `lavavip` finalizados do histórico (o mais novo de 2022) com visita depois: nenhum número muda; (3) nenhum caso
separava a ordem dos colaboradores pelo nome da ordem pelo id: o `testAOrdemEPeloNomeEOIdDesempata`; (4) o `formatos => []` da
Carteira (sem arquivo até o D690) não estava preso: o `testACarteiraNaoExportaArquivoAteOD690`; (5) o "O que rodou" acima; (6) a
recusa da data de venda que não se lê, sem caso: o `testADataDaVendaQueNaoSeLeERecusada`; (7) o bloco "TODA POLITICA TEM ITEM NO
MENU" do `RelatoriosNoMenuTest`, ao lado da lista que voltou: reescrito; (8) o `porColaborador` lia o nome do colaborador da chave
`nome`, que na lista da CA1 é o nome do CLIENTE: agora lê `nomeDoColaborador` e recusa (`LogicException`) a linha com colaborador
sem ela, e o oráculo põe o nome do cliente em `nome`. O `RegrasDaCarteiraTest` foi de 37 para 41 casos (contando cada fronteira).
Os mutantes da correção, um por vez, a reserva num diretório novo e vazio, devolvidos pelo sha1, no MySQL (regra pura): N1 o
`lavavip` de volta como serviço e N2 o `ser` sem colaborador pulado (`testOQueEServicoNaVisita`); N3 a ordem só pelo id e N4 só
pelo nome (`testAOrdemEPeloNomeEOIdDesempata`); N5 `formatos => ['xls']` (`testACarteiraNaoExportaArquivoAteOD690`); N6 a data
que não se lê como instante 0 (`testADataDaVendaQueNaoSeLeERecusada`); N7 o nome lido de `nome` (`testOOraculoPorColaborador`,
`testONomeDoColaboradorNaoEONomeDoCliente` e `testAOrdemEPeloNomeEOIdDesempata`); N8 a recusa da linha sem `nomeDoColaborador`
arrancada (`testONomeDoColaboradorNaoEONomeDoCliente`) -- todos vermelhos por asserção no caso nomeado. O M5, o M6, o M11 e o M12,
que tocam as funções mudadas, rodados de novo: vermelhos. Depois: tsc, lint e build (358,19 KB), o `menu-do-erp.teste.ts` e o
`Console.teste.tsx` (188 de 188); só comentários mudaram no front.

**O oráculo** não tem consulta: são doze clientes inventados, com as datas, as vendas e os itens no teste e a conta à mão no
docblock (a rede, as duas unidades e o resumo por colaborador de cada uma).

**De fora, e por quê:** a consulta, a API, a guarda do Clientes antigo e o item do menu são da CA1; a tela, da CA2. Nenhuma pergunta
nova ao dono, nenhum débito aberto ou fechado. **O navegador e o ciclo são do coordenador.** Nada da produção, do BVIP, do OneSignal
nem do crontab mudou; na `franquiabv_dev`, só um SELECT de contagem por tipo de item; o `local.php` não foi trocado à mão (os
roteiros de teste o devolveram ao `franquiabv_dev`).

**Conferido por mim, depois da correção** (03/11/2026): os oito achados das três lentes e o relato da correção (os comentários que diziam CA2 onde o caso obriga a CA1; o serviço pela escolha do finalizar, só o `ser`; o nome do colaborador numa chave própria; a ordem por nome com o id no desempate; o `formatos` preso; a data ilegível com caso); a política nova lida no diff, com o `naTelaNova` falso. O ciclo inteiro depois da correção, saída 0: caracterização 6.117 em cada banco (os mesmos 12 pulados), contrato 74 e 74, os ensaios nos dois bancos, os portões e o front 3.918 de 3.918, e o medidor dos relatórios no PostgreSQL, saída 0, em cerca de 9 min 33 s; o `local.php` no `franquiabv_dev` pelo sha1. Sem navegador: nada muda na tela.

## 04/11/2026 -- CARTEIRA-CA1 (a fatia 76b da fila das telas que faltam)

**Nada muda para quem usa: a fatia parou na medida, antes do código, e a escolha foi ao lote (o D697).** A CA1 de
`docs/f3-relatorios/spec-carteira.md` começa por medir a regra da E2 (a última venda finalizada com algum item de cada cliente, e a
unidade de quem abriu o caixa dela) e para se nenhuma forma ficar abaixo de 1 s quente na rede em algum motor. Na cópia, só SELECT,
com o "hoje" em 17/08/2026, três rodadas de cada consulta, todas com as faixas da 1.5 do desenho (13.670 / 4.205 / 6.180 / 8.380 /
16.852 / 28.188 / 149.951 / 11.813 na rede, 22.321 na unidade 1, iguais nos dois bancos): no MySQL, a melhor (uma subconsulta por
cliente que acha o id pelo índice e volta à venda pela chave) leva 1.688 a 1.758 ms com o buffer pool de fábrica e 1.179 a 1.269 ms com
2 GB (o pool crescido e devolvido aos 128 MB na mesma rodada, conferido); no PostgreSQL, medido como a aplicação (o `bvip_app`, com o
RLS), a melhor (o `DISTINCT ON`) leva 979 a 1.114 ms com o JIT padrão e 941 a 998 ms sem ele -- no limite. O retrato da unidade 1 com o
resumo por colaborador (a T7): 1.846 a 1.848 ms no MySQL de fábrica, 1.218 a 1.255 ms com 2 GB, 996 a 1.077 ms no PostgreSQL; a página
de 50 de uma faixa com o colaborador e o próximo agendamento (a T8): 1.362 a 1.452 ms no MySQL de fábrica (a primeira, fria, 3.076),
873 a 938 ms com 2 GB, 714 a 993 ms no PostgreSQL. O `LATERAL`, a janela da 1.7 e o `MAX` com a volta ficaram acima. As consultas e as
saídas estão no rascunho da sessão, `p76b/14-*` a `p76b/18-*`.

**O índice não era novo:** o `vendas (cliente, status, data_criacao)` existe nas duas cópias desde 29/09/2026 (o D308), e a 1.7 já
o tinha medido; o que falta é a produção, porque o `mysql.sql` e o passo 0 da etapa 1 têm o índice de uma coluna -- num banco
descartável com a `vendas` da cópia e esse índice (montado e apagado na mesma rodada), a melhor forma leva 6.780 a 6.880 ms. Pô-lo no
passo 0 da etapa 1 muda a decisão do dono no D308 (o índice de produção na migração final), e a pergunta diz isso, com o precedente da
196 (b). E, no PostgreSQL, medir como o dono do banco engana: sem o RLS, nenhum índice serve (todos começam pelo `inquilino_id`), e o
`LATERAL` passou de 10 min -- cancelado pelo `pg_cancel_backend`, só SELECT.

**Ao dono**, a pergunta "A carteira de clientes demora 1 a 2 segundos para abrir: aceitar, ou trocar a conta?" (D697): (a) aceitar a
espera com a conta exata e o índice no passo 0 da etapa 1, recomendada; (b) a conta guardada por 10 minutos (só as contas e os ids, ou
uma cópia por papel: a lista leva o contato); (c) uma tabela de resumo gravada pelo finalizar e pelo cancelar (não medida); (d) a
coluna `clientes.ultima_visita` com o D694 consertado antes (hoje ela põe 278 ativos noutra faixa, 203 pelo D694; depois do conserto,
cerca de 75). O desenho ganhou a nota MEDIDA na CA1 e uma linha na 1.7; o D697 está registrado; o D691 não muda (fica PARCIAL quando a
Carteira entrar no ar).

**De fora, e por quê:** a consulta, o controller, a rota, o `CarteiraJson`, o `naTelaNova`, o item do menu, a guarda das quatro ações do
Clientes antigo, os testes, os mutantes, o oráculo e o medidor -- todos esperam a resposta do D697. Nenhum teste, ensaio ou mutante
rodou: não há código. Rodaram o `conferir-referencias-docs.php`, o `contar-debitos.php`, o `conferir-debitos-repetidos.php` e o
`conferir-segredos.sh`, saída 0, e o `conferir-tudo.sh` sozinho, 73 portões, saída 0. **O navegador e o ciclo são do coordenador.**
Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`, só SELECT (e o buffer pool do MySQL local, 2 GB
durante a medida e de volta aos 128 MB); os bancos descartáveis `p76b_descartavel` foram apagados; o `local.php` não foi trocado.

**A correção depois da verificação (04/11/2026).** As três lentes confirmaram 11 achados, todos de documento e da medida, dois
repetidos: a T8 (a lista) não medida em nenhum motor e a T7 só no MySQL, com a pergunta prometendo "1 a 2 segundos" só pelo retrato;
as saídas da primeira medida não guardadas (dois achados), e formas com uma ou duas rodadas; a (d) contra a 1.7 (o desenho põe a coluna
com o D694 consertado antes, e a pergunta dizia que ela ficava com os 366); a pergunta sem dizer que a (a) e a (b) mudam a decisão do
D308; o texto ao dono pondo sob a memória de fábrica o 1,1 s, que é de 2 GB, o 0,1 s da (c) como fato e um "166 clientes" sem fonte
(dois achados); o prazo da (b) em "alguns minutos" no D697 e "10 minutos" na pergunta; o título do D697 dizendo "nos dois bancos"; e a
(b) sem dizer que a lista guardada leva o contato e o e-mail que só o administrador vê. A correção refez a medida inteira, com três
rodadas de cada consulta e a saída guardada (`p76b/14-*` a `p76b/18-*`, gerados por `p76b/gerar-remedida.py`), mediu a T7 e a T8 nas
formas novas e a coluna contra a regra cliente a cliente nos dois bancos (278, 203 e cerca de 75: o "166" não tinha fonte e saiu), e
reescreveu o D697, a nota MEDIDA e a linha da 1.7 do desenho e a pergunta ao dono com os números da remedida. A parada não muda: no
MySQL nenhuma forma fica abaixo de 1 s na rede. Sem código, nenhum teste nem mutante se aplica; rodaram de novo o
`conferir-referencias-docs.php`, o `contar-debitos.php` (de pé 198, sem mudança), o `conferir-debitos-repetidos.php` (nenhum par
novo) e o `conferir-segredos.sh`, saída 0, e o `conferir-tudo.sh` sozinho, 73 portões, saída 0, em 67 s; o `local.php` no
`franquiabv_dev` pelo sha1, e o buffer pool do MySQL local de volta aos 128 MB, conferido.

**Conferido por mim** (04/11/2026): os 11 achados das três lentes e o relato da correção; a fatia parou antes do código, como a nota 2 manda, e a escolha foi à página como a 294 (D697), recomendando (a). O buffer pool do MySQL local de volta aos 128 MB (134217728) e nenhum banco `p76b%` sobrando; o `local.php` no `franquiabv_dev` pelo sha1. Na fila, a 76b e a 76c esperam a 294; a fila segue pela 76f (o servidor das Datas da equipe). Só documentação: sem ciclo e sem navegador.

## 04/11/2026 -- DATAS-DA-EQUIPE-DE0 (a fatia 76f da fila das telas que faltam)

**O que entrou.** A DE0 de `docs/f3-relatorios/spec-datas-da-equipe.md` (a resposta 138 (a) do dono: "Aniversariantes vira 'Datas
da equipe'"): a política `datasDaEquipe` ligada, a forma de período nova `mesDoAno` (`?mes=1` a `?mes=12`, vazio é o mês de hoje, sem
ano; o 0 e o 13 são o 422), a regra pura `ISRelatorio\Regra\DatasDaEquipe` (o dia no ano de hoje, o 29 de fevereiro no dia 28 com a
nota no ano que não é bissexto, os anos completados no ano de hoje, a marca da data no futuro com os anos nulos, a ordem pelo dia, o
nome sem caso e o id), as quatro consultas novas com os status separados por tipo (o D97) e o sócio inativo fora do bloco das
unidades (a E5, no `WITH` do `LEFT JOIN`), e o `Adm\DatasDaEquipeController`: `GET /adm/api/relatorios/datas-da-equipe` e
`.../unidades`, com a equipe (nascimento e tempo de casa) para o gerente e o administrador e a `rede` (inaugurações e sócios) só para
o administrador, o telefone, o e-mail e a data inteira nos quatro blocos. No mesmo commit (a E6): o item "Datas da equipe" no menu
Relatórios da tela nova (o primeiro do grupo, com a `relatorioDatasDaEquipe` na sessão), a linha da `TELA_DO_RELATORIO` com a tela não
migrada até a DE1, o item "Aniversariantes" do ERP antigo pelo `veOMenu`, e o `AniversariantesController::indexAction` antigo com a
mesma política. O que saiu diferente do texto do desenho (a `nota` na linha, os anos nulos no futuro, a unidade `{id, nome}`, a frase
da recusa do mês, a unidade da sessão por padrão para o administrador) está na DE0 do desenho, marcada FEITA.

**O que muda para quem usa.** O gerente e o administrador com o privilégio "Relatórios de aniversariante" passam a ver "Datas da
equipe" no menu Relatórios da tela nova, que por enquanto abre a tela "ainda não migrada" com o caminho para a versão antiga (a tela é
a DE1). No ERP antigo, o caixa que não é gerente e quem não é gerente nem administrador deixam de ver o item "Aniversariantes" e o
endereço passa a responder 404: na cópia (só SELECT, só contagem, igual nos dois bancos), 1 caixa e 1 "outro" dos 107 não
administradores ativos com o privilégio; os 105 gerentes e os administradores seguem como estavam.

**Os testes.** Novos: `RegrasDasDatasDaEquipeTest` (11 casos, sem banco), `DatasDaEquipeTest` (10 casos, em processo: o gerente sem a
`rede`, o administrador com ela, com uma unidade e com a da sessão, as guardas na ordem, o mês, as unidades do filtro, o contato e a
data inteira dos quatro blocos lidos do banco, o 29 de fevereiro em 2027 e 2028 e as quatro consultas) e `AniversariantesNaPoliticaTest`
(4 casos, a tela antiga em processo), sobre o `CenarioDasDatasDaEquipe` (ids 90002400 a 90002479); e os casos do `mesDoAno` no
`RegrasDoRecorteDoRelatorioTest`. Mudaram com a política: o `RelatoriosNoMenuTest`, o `SessaoApiTest`, o `PortoesDaFamiliaDosRelatoriosTest`,
o `menu-do-erp.teste.ts` (52 destinos, a lista do dono com as Datas da equipe, 13 guardas), o `Console.teste.tsx` (6 destinos na tela
não migrada; o exemplo de endereço fora do menu passou ao Consultor Visagista), os dublês da sessão dos 14 testes do Console e as
prévias (`MenuPrincipal`, `_console`, `TelaNaoMigrada`). Os testes da fatia e os vizinhos, 813 casos em cada banco, saída 0, com os
mesmos 3 pulados por falta da cópia (`AniversariantesNoRelatorioTest::testORecortePorUnidadeVale`,
`DescontosNoRelatorioTest::testOsTotaisNaoSomamOsDeOutraFranquia`, `VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador`).
O front dos arquivos tocados (Console, menu e Moldura), 260 de 260; amplificado com `AMPLIFICAR_EFEITOS_MS=50`, os mesmos 6 vermelhos
do `Console.teste.tsx` que a HEAD, pelo nome; o front inteiro uma vez, 3.918 de 3.918 em 161 arquivos; tsc, lint e build com 358,25 KB.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1 (e o `git diff` igual ao de antes),
vermelhos pela asserção nos dois bancos: M1 a rede para o gerente; M2 e M2b o sócio inativo de volta (inaugurações e aniversários); M3
o mês 13 aceito; M4 e M4b o inativo dentro; M5 os anos do ano do relógio; M6 e M6b o tempo de casa sem o telefone e sem o e-mail; M7 e
M7b a data sem o ano; M8 o `indexAction` antigo sem a guarda; M9 o 29 de fevereiro no 1º de março; M10 a ordem só pelo nome; M11 a
unidade inativa dentro (só na consulta do nascimento da equipe; a dos sócios ficou para a correção, abaixo); M12 a unidade da sessão no lugar da do recorte; M13 a marca do futuro invertida; M16 a rede pelo escopo da
unidade; M17 o sócio da inauguração sem o telefone; M18 o `WITH` no `WHERE` (a unidade sem sócio ativo some); M19 e M19b cada consulta
da equipe sem o recorte da unidade; e, só no MySQL, M14 o
item do layout pelo privilégio só e M15 a política desligada. No front, F1 (o item do menu sem o `quando`) fica vermelho no
`menu-do-erp.teste.ts` e no `RelatoriosNoMenuTest`; F2 (a linha do Console fora) é equivalente na tela -- sem a linha o Console cai na
mesma tela não migrada -- e fica vermelho só no `RelatoriosNoMenuTest`.

**O oráculo** contra o JSON do controller em processo, como administrador, nas duas cópias (o PostgreSQL como `bvip_app`, com o
`local.php` trocado e devolvido pelo sha1), a rede e a unidade 1 em novembro, fevereiro e julho, com um SELECT próprio por bloco (ids,
ordem, e a data, o contato, os anos e a marca pelo sha1, nada impresso): 171 conferências em cada banco, 0 divergências; falsificado
(os anos do ano anterior), 61. Julho bate com o 1.4 do desenho (18 aniversários na rede, 2 na unidade 1, 22 contratações). Quente,
novembro, em duas medidas de três rodadas: a rede de 1,6 a 1,7 ms no MySQL e de 1,6 a 1,7 ms no PostgreSQL, a unidade 1 em 0,6 ms no
MySQL e de 0,8 a 0,9 ms no PostgreSQL.

**Os portões e os ensaios.** O `conferir-recorte-por-unidade.php` com as vigias novas (423 métodos, 12 relatórios na família), o
`conferir-cobertura-por-par.php` de 424 para 428 pares (os quatro novos, citados com a classe no `testAsQuatroConsultas`; tirando-os, o
sha1 volta ao de antes), o `conferir-injecao-dql.php` (a base fica em 22), o `conferir-rotas-sem-login.php` e o `conferir-guarda-do-erp.php`,
saída 0. O `ensaiar-permissao-adm-api.php` parou na primeira rodada com "mais de 10 funcionalidades": a faixa das concessões passou a 20
ids (90002520 a 90002539); depois, ele e o `ensaiar-recusas-adm-api.php` nos dois bancos de contrato (o MySQL montado antes), saída 0, e o
`medir-adm-api-no-postgres.php` com a entrada nova (março, `equipe.aniversarios`), saída 0; o medidor dos relatórios no PostgreSQL,
saída 0 (a tela antiga 200 para o `gerente.teste`). O `conferir-tudo.sh` sozinho ficou vermelho na primeira rodada no `booleano em
coluna inteira` (as não resolvidas de 14 para 15): o recorte da unidade das duas consultas da equipe estava num ajudante sem o `FROM`, e
o portão não acha o apelido; o pedaço voltou para dentro de cada consulta (os mutantes M4, M4b, M11, M19 e M19b rodados de novo contra
essa versão), e o portão voltou a 14; depois, o `conferir-tudo.sh` sozinho, 73 portões, saída 0, em 61 s. Débitos: o D306 ganhou a marca PARCIAL de hoje (as Datas da equipe no menu novo);
nenhum novo; de pé 198, sem mudança. Nada da produção, do BVIP, do OneSignal nem do crontab mudou; no `franquiabv_dev`, só SELECT; o
`local.php` no `franquiabv_dev` pelo sha1. O navegador e o ciclo inteiro são do coordenador.

**A correção depois da verificação** (três lentes, 7 achados confirmados, um médio e seis baixos, todos tratados). O médio: tirar o
`uni.status` da `UnidadeSocioRepository::aniversariosDosSocios` não deixava caso vermelho -- o cenário não tinha sócio na unidade
inativa, e na cópia há 3 sócios ativos com nascimento em unidades inativas (de 7 com nascimento; só SELECT COUNT), que iriam com o
contato ao bloco do administrador; entrou o S4, sócio ativo da W, e o caso o procura na rede e pedindo a W. Os baixos: a inauguração
ganhou a marca `futura` (o `anos` nulo dela agora diz por quê, como no tempo de casa); o `anos` 0 de quem entrou no ano de hoje
ficou escrito na E2 e na regra ("entrou neste ano"; na cópia, 64 dos 239 ativos com a contratação), com o caso
`testOAnoDeHojeDaZero`; a ordem dos sócios de cada inauguração presa por nomes que a caixa troca de lugar; a frase da recusa do mês
e a abertura do administrador em `todas` na tela da DE1 escritas no desenho; e o M11 acima, que dizia mais do que mediu. Os
mutantes da correção, um por vez, a reserva num diretório novo e vazio, devolvidos pelo sha1, vermelhos pela asserção: N1 os sócios
das unidades inativas de volta, N5 a inauguração sem a marca e N6 a marca sempre falsa, nos dois bancos; N2 os sócios pelo nome com
caso, N3 só pelo id, N4 sem o desempate pelo id e N7 o zero virando nulo (regra pura). Os testes da fatia, 26 casos em cada banco,
saída 0; com os vizinhos, 431 casos em cada banco, saída 0, com 5 pulados por falta da cópia, os mesmos nos dois
(`AniversariantesNoRelatorioTest::testORecortePorUnidadeVale`, `ComboboxDeAnosNoRelatorioTest::testOComboDoPainelDeduplicaOsAnos`,
`DescontosNoRelatorioTest::testOsTotaisNaoSomamOsDeOutraFranquia`, `VendasNoRelatorioTest::testODescontoNaoSomaODeOutraUnidade`,
`VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador`). O oráculo do MySQL de novo, com a marca da inauguração na conta:
171 conferências, 0 divergências (o do PostgreSQL não rodou de novo: a mudança é da regra pura, e os testes da fatia passam nos
dois bancos). O front não mudou. O `conferir-tudo.sh` sozinho, 73 portões, saída 0; o `contar-debitos.php` (de pé 198, sem
mudança), o `conferir-debitos-repetidos.php`, o `conferir-referencias-docs.php` e o `conferir-segredos.sh`, saída 0. Nenhuma
pergunta nova ao dono.

**Conferido por mim, depois da correção** (04/11/2026): os sete achados das três lentes e o relato da correção (o filtro da unidade ativa nos sócios agora com caso, o S4; a ordem dos sócios presa; a inauguração no futuro com a marca; o "entrou neste ano"; o 422 escrito no desenho; a DE1 abrindo o administrador em `todas`; o M11 descrito como foi). O ciclo inteiro depois da correção, saída 0: caracterização 6.158 em cada banco (os mesmos 12 pulados), contrato 74 e 74, os ensaios nos dois bancos, os portões e o front 3.918 de 3.918, e o medidor dos relatórios no PostgreSQL, saída 0, em cerca de 9 min 32 s. No navegador, contra o banco de contrato, como o gerente de teste com o privilégio de aniversariante concedido por linha de `privilegios_usuarios` (o banco remontado no fim): o item "Datas da equipe" no grupo Relatórios do menu novo, abrindo a tela "ainda não migrada" com o link para a versão antiga; a API de novembro com o recorte `mesDoAno` (11/2026, em andamento, a unidade do gerente, escopo unidade) e sem a chave `rede`; o mês 13 com o 422 "O mês vai de 1 a 12. Recebido: 13"; e a tela antiga do Aniversariantes abrindo para o gerente, com o item no menu do ERP. O `local.php` no `franquiabv_dev` pelo sha1.

## 04/11/2026 -- RESPOSTAS-289-A-293 (a fatia 76r: as respostas do dono de 04/11/2026)

**Nada muda hoje para quem usa: só documentação.** O dono respondeu as perguntas 289 a 293 da página: a **289 (b)** ao D690 ("Como
hoje": a Carteira exporta o contato de todos, também de quem recusou mensagens; não foi a (a) recomendada), a **290 (b)** ao D693
("Ficam, como os da Fidelização": os arquivos da Evasão na produção não são apagados no corte; não foi a (a)), a **291 (a)** ao D692
("Sem potencial em reais", a recomendada), a **292 (b)** ao D696 ("A carteira ganha os filtros": a busca, o convênio, o mês do
aniversário e o consumo; não foi a (a)) e a **293 (a)** ao D689 ("Só no aplicativo novo", a recomendada: a rota antiga sem login
segue só com a ligação até ser fechada, que é o estado que a 75b deixou). A 294 (o D697, o tempo da Carteira) segue sem resposta.

**O que mudou nos desenhos.** No `docs/f3-relatorios/spec-carteira.md`: a seção nova 1.9 desenha os quatro filtros da lista (o
`?busca=` sobre `clientes.tags`, o `?convenio=` com o `todos`, o `?aniversario=` de 1 a 12 -- e não `?mes=`, que a forma `agora`
ignora --, e o `?consumoDe=`/`?consumoAte=` sobre a coluna `clientes.consumo`, com o nulo como zero), com o mesmo sentido do filtro das
Campanhas e um teste que os amarra, a faixa opcional na lista, os convênios numa ação própria e a tabela do que fica FORA (17
parâmetros, as 8 ordenações, as colunas de NPS e de fidelizado); a seção nova 1.10 desenha a exportação "como hoje" (o TXT, o PDF e a
planilha de 19 + 4 colunas, da lista que está na tela, e não da sessão), com o contato de quem recusou escrito como escolha do dono, o
e-mail como hoje (no PDF e na planilha também para o gerente, a letra da (b)) e o tamanho do PDF e da planilha ao dono (o D700); as
fatias novas **CA5** (os quatro filtros, ~1.300 linhas, depois da CA2) e **CA6** (a exportação, ~900 linhas, depois da CA5 e da
resposta do D700); a CA3 sem espera e sem passo de apagar (o procedimento de corte não muda); e a CA4 depois da CA5 e da CA6. Os quatro filtros valem com qualquer das quatro opções da
294, e a 1.9 diz o que cada uma muda. No `spec-capacidade.md`: a E4 definitiva, a CR2 sem espera, tirando o Potencial e com ele a
conta do mês futuro e da semana, e a frase da redução na tela de Unidades (o D695) que passa a ser verdade no commit da CR2. No
`spec-indicacao.md` e no `procedimento-de-corte-etapa-1.md`: a 293 (a) no lugar de "até a resposta". A seção 9 do
`docs/f3-relatorios.md` e o README dos relatórios dizem as respostas.

**O que mudará quando as fatias rodarem:** a lista da Carteira com a busca e os três filtros (CA5); o botão do TXT, do PDF e da
planilha da lista na tela, com o telefone de todos (CA6); a Evasão, o PDF e a rotina das 04:10 fora do ERP, com os arquivos dela
ficando na produção sem atualização (CA3); o Potencial de Franquia fora do ERP, e com ele o potencial em reais e a capacidade de um mês
futuro ou de uma semana (CR2); e o Clientes antigo fora, com os filtros e as ordenações que a 292 (b) não lista (CA4).

**Medido na cópia** (só SELECT de contagem, nos dois bancos, iguais salvo onde dito, com o "hoje" de 17/08/2026; as consultas e as
saídas em `p76r-01-*` a `p76r-06-*` do rascunho da sessão): a maior lista de um gerente pela regra da Carteira é a unidade inteira (a
lista sem faixa), a 1, com 22.321 clientes, e 21 das 59 unidades passam de 5.000; com uma faixa, a maior é a de mais de 2 anos da
unidade 1, 11.804 (8 pares (unidade, faixa) passam de 5.000, todos nessa faixa), e fora dela a dos sem visita da unidade 1, 4.010. Os filtros: 14.093 ativos com convênio (429 convênios; 44 com um inativo); 10
sem nascimento; 17 sem `tags`; a busca de "maria" com 897 no MySQL e 894 no PostgreSQL (o acento, o D119). A lista da rede com um
filtro, quente, três rodadas, sem a volta à unidade do caixa: de 107 a 233 ms no MySQL e de 438 a 672 ms no PostgreSQL -- mais barata
que sem ele; a soma do consumo na hora, 4,9 s no MySQL e 0,36 a 0,37 s no PostgreSQL, contra 64 e 20 ms da coluna. **A coluna do
consumo está atrasada em 4.477 ativos**, contados em centavos (4.445 abaixo da soma das vendas finalizadas, 2.918 deles com zero e
venda; 32 acima; 4.406 sem venda há mais de um ano): o **D698**, ao lote. Num documento SINTÉTICO de 23 colunas, sem dado da cópia (`p76r-04-medir-arquivo.php`,
no contêiner, sem limite): 5.000 linhas -- a planilha em 1,4 s e 103 MB de pico, o PDF em 0,7 s e 68 MB --, 11.804 linhas -- 3,3 s e
242 MB, 1,6 s e 154 MB --, 22.321 linhas (a unidade 1 inteira) -- 6,3 s e 394 MB, 2,9 s e 302 MB; o TXT leva sempre a lista inteira, e
o tamanho do PDF e da planilha é o D700. Nenhum nome, telefone ou e-mail saiu de consulta nenhuma.

**Ao dono, no lote:** "O consumo do cadastro está atrasado em 4.477 clientes: consertar a conta?" (D698, recomendo consertar; a
pergunta diz também que a Carteira conta o nulo como zero), "O arquivo da carteira leva o e-mail do cliente para o gerente?" (D699: a
opção (a) da 289 dizia que o e-mail só ia ao administrador, e no PDF e na planilha de hoje vai também ao gerente; até a resposta vale
a letra da (b), e recomendo a (b)) e "O PDF e a planilha da carteira: a unidade inteira, como hoje, ou até 5.000 clientes?" (D700,
recomendo a unidade inteira; a CA6 espera). **Débitos:** o D689 CORRIGIDO (293 a); o D693
DECIDIDO (290 b); o D690, o D692 e o D696 DECIDIDOS com o que falta EM ABERTO (a CA6, a CR2, a CA5 e a CA4); o D698 e o D699
registrados e, na verificação, o D700; de pé 199 (dois fecharam, três nasceram). Cinco pares novos no `conferir-debitos-repetidos.php` (dois da verificação), cada um com o
motivo, e dois que sumiram tirados da lista. Os comentários que ainda diziam "o D689, ao dono" ou "até a resposta do D690"
(`AplicativoController`, `IndicacaoRepository`, `ListaDeConvitesDoAplicativoTest`, `PoliticaDoRelatorio`, `RegrasDaCarteiraTest`)
foram trocados na verificação -- só comentários, nenhuma linha de código. Nada da produção,
do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`, só SELECT; o `local.php` não foi trocado. Rodaram, depois da
última edição: o `conferir-referencias-docs.php`, o `contar-debitos.php`, o `conferir-debitos-repetidos.php` e o
`conferir-segredos.sh`, saída 0; e o `conferir-tudo.sh` sozinho (nada mais rodando, conferido por `ps`), 73 portões, saída 0.

**A verificação em três lentes confirmou 13 achados, três médios e dez baixos, todos de documento, tratados:** o teto do PDF e da
planilha partia de uma "maior lista de gerente" de 11.804 que a F5 desmente (a lista sem faixa é a unidade inteira, 22.321) e cortava o
arquivo de hoje em 21 das 59 unidades sem perguntar (os dois médios iguais: virou o D700, ao lote, medido com 22.321 linhas, e a CA6
espera a resposta); a pergunta do D699 recomendava com uma premissa falsa (o médio: o arquivo dá ao gerente muito do que a tela não
mostra, e a frase do e-mail estava na opção recusada -- reescrita, e o padrão até a resposta passou a ser a letra da 289 (b), o e-mail
como hoje); o corpo do D691 com a CA3 ainda à espera; o nulo do consumo descrito ao dono como "igual"; a lista sem faixa sem a entrada
nem o vazio na tela (a F5 e a CA5); a pontuação da planilha sem regra (a X6, como hoje: a soma da unidade do recorte, na rede a de
todas); a trava do arquivo dita "da base" (a base não trava: a CA6 sobrescreve o `travaDoArquivo`, a X4); os 4.478 que não eram a soma
das partes (um caso de 1 centavo de ponto flutuante: 4.477, recontado em centavos, `p76r-06-*`); o teste de amarração às Campanhas que
cairia pelo aviso do `fidelizado` sem `isset` e pelo `removeMask` (o pedido passa pelo `FiltrosDaAudiencia::conferir`); os comentários
e a linha da CA0 que ainda diziam "até a resposta do D690"; e o convênio aceito por `find`, que distinguia os ids de outras unidades (a
lista oferecida e a aceita são a mesma, a F7). Depois da correção, em série: o `RegrasDaCarteiraTest`, o `ListaDeConvitesDoAplicativoTest` (os
arquivos de teste cujos comentários mudaram), o `RegrasDoRecorteDoRelatorioTest` e o `RelatoriosNoMenuTest`, 258 casos em cada banco,
saída 0, nenhum pulado; o `conferir-referencias-docs.php`, o `contar-debitos.php` (de pé 199), o `conferir-debitos-repetidos.php`
(nenhum par novo) e o `conferir-segredos.sh`, saída 0; e o `conferir-tudo.sh` sozinho (conferido por `ps`), 73 portões, saída 0.
Não há mutante novo: nenhuma linha de código mudou; os números novos vêm de SELECT e de medida guardados no rascunho
(`p76r-06-consumo-centavos-*` e as linhas de 22.321 do `p76r-04-medir-arquivo.txt`).

**Conferido por mim, depois da correção** (04/11/2026): os 13 achados das três lentes e o relato da correção; o diff do código só com comentários (conferido linha a linha: nenhuma linha de código fora de comentário mudou); as três perguntas novas na página como a 295 (D698), a 296 (D699) e a 297 (D700); na fila, a CA5 (76e1) e a CA6 (76e2) entraram antes da CA4, a CA3 e a CR2 ficaram sem espera, e a CA6 espera a 297. O `contar-debitos.php` com 199 de pé; o `local.php` no `franquiabv_dev` pelo sha1. Só documentação e comentários: sem ciclo e sem navegador.

## 04/11/2026 -- DEFEITOS-DE-TELA-DA-SINCRONIZACAO (a fatia 76s: os defeitos de tela que a re-sincronizacao do design achou)

**O que muda para quem usa:** as telas de relatório ficam com os recuos, o seletor e os blocos alinhados como as irmãs; nenhum
número, nenhum pedido e nenhum comportamento muda. A re-sincronização do design de 04/11/2026 reprovou 36 células por defeito do
próprio app (a lista em `p76s-defeitos-do-app.md`, do rascunho), e cada item foi conferido no código de hoje e consertado em
`frontend/src/App.css` e nas telas, no molde das irmãs (o `.cartao` não tem padding: as notas soltas recuam 0,93rem, como nos
Descontos, nas Canceladas, nos Royalties, no NPS e na Indicação), só com tokens do tema:

- **O Caixa, o cartão "O período"** (`dl.totais.caixa-resumo`): a regra `.relatorio-caixa .caixa-resumo` com o `display: grid`, o
  recuo e o `dd` sem margem do `.caixa .totais` -- o rótulo e o valor na mesma linha, nas duas colunas (o `dl` de 514 para 318,5 px
  de altura no `CaixaNoDia`; a margem do `dd` de 40 px para 0).
- **As notas soltas do Caixa e do detalhe** (o "ainda não gravado"): `:is(.relatorio-caixa, .relatorio-caixa-detalhe) .cartao >
  .painel-base`, com o vão de baixo do último; **a lista de vendas do detalhe** (`.caixa-vendas`) com o recuo do título.
- **A nota do rótulo da Conferência** ("(dinheiro: inicial + ...)"), medida e decidida: na coluna estreita do detalhe a 1280 px ela
  quebrava em 3 linhas com 27 px de passo para a letra de 11 px (a entrelinha do `dt.total` de 1,2rem), e a do "Gaveta ao fechar" em
  4 linhas com 20,9 px; virou bloco com a entrelinha dela (`.relatorio-caixa-detalhe .totais dt .nota`): 67 para 42,7 px de altura.
- **O selo "inativo" do Caixa**: o espaço saiu de dentro da borda (o texto da célula segue "Davi Caixa inativo"; o vão do nome ao
  selo, de 0 para 3,6 px), no molde das Mensagens e dos Convênios.
- **As Canceladas, "Sem nova venda"**: o valor quebra no "·" e antes do "—", e só neles (o molde do `ValorQueQuebraNoPonto` do
  painel, com as classes `.painel-quebra-no-ponto` e `.painel-parte` que já existiam), sem mudar o texto nem o número: a 1280 px
  o texto passava 80,9 px do cartão e agora cabe (2 linhas); a 905 px passava 174,6 px, e as outras classes também 24 a 30 px --
  todas cabem agora.
- **O Catálogo**: a `.catalogo-vendidos-nota` com o recuo lateral (de 1 para 14,9 px, igual ao texto do título).
- **O Atendimentos**: o `.relatorio-filtro` novo (o rótulo ao lado do seletor, no recuo do título), o seletor "Número" na forma dos
  seletores da escolha da unidade e do intervalo (a borda `--linha-forte`, o raio, o padding; antes, o cru do navegador em Arial) e
  a nota "Você vê só a sua linha" no recuo.
- **A Cota-parte, o extrato, o Produtos / Serviços e a Retirada**: as notas soltas do cartão (a de quem ficou de fora, o "ainda não
  gravado", a das saídas) no recuo do título; **o repasse do extrato** (`.cota-parte-repasse`) no molde da `.faturas-conta`: o
  rótulo em cima, em 0,8rem, e o valor embaixo, alinhado a ele (antes o `dd` ia à direita, numa linha própria, na letra maior).
- **A linha da rede do NPS e dos Royalties**: o seletor passou a levar a `.relatorio-tabela` -- o fundo opaco do mês preso
  (`.relatorio-tabela th:first-child`, 0,2,1) vencia o da linha (0,1,1), e a primeira célula ficava branca.
- **A reticência dupla**: o grep pelo padrão (a frase que termina em "…" seguida do `<ValorDeDepois estado="carregando" />`) achou
  cinco, todas consertadas -- "Carregando os itens…" e "Atualizando os itens…" (Descontos), "Carregando os clientes…" e
  "Atualizando os clientes…" (NPS) e "Contando os clientes novos…" (Indicação): a frase fica, e o `ValorDeDepois` ao lado dela
  saiu (a região `role="status"` já a anuncia). Os specs do NPS e da Indicação pedem só a frase; o dos Descontos
  (`spec-descontos.md`, os estados da lista) pedia também o `ValorDeDepois` "na soma", e foi dele que veio o "… …" -- a linha do
  spec mudou na correção (abaixo).
- **A lista dos Descontos relendo** esmaece (`opacity: 0.55`) como as outras; e a do NPS, que a lista também citava.

**O que não mudou:** nenhum dado, API, número ou comportamento; as prévias do design (`frontend/.design-sync/`) não são desta fatia;
o D322 não tem nenhum destes itens (lido), e fica como estava; nenhum "defeito" era decisão escrita num comentário do `App.css`, e
nenhum pediu decisão do dono.

**O provado.** O CSS não chega ao vitest: a prova de CSS foi a medida no Chromium do Playwright (o do `.ds-sync` do worktree do
design, só lido), num arnês do rascunho (`p76s/harness/`) que monta as células das prévias com o `index.css` e o `App.css` lidos na
hora, a 1280 e a 905 px; o "antes" com uma cópia do HEAD tirada por `git archive` (`p76s/head/`), o "depois" com a árvore de hoje
(`p76s/medida-antes.json`, `medida-depois.json`, `comparacao-antes-depois.txt` e as fotos em `p76s/fotos-depois/`). Nenhuma célula
medida rola de lado. Os mutantes de CSS, cada regra nova arrancada uma por vez (a reserva num diretório novo e vazio, devolvida pelo
sha1): C1 a C21 vermelhos pela medida, quase todos de volta ao valor de antes (`p76s/mutantes-css.txt`); o C22 (a `.painel-parte`
sem o `nowrap`, regra que já existia) fica verde nas duas larguras -- as partes cabem inteiras, e o caso de quebra dentro da parte
não aparece nestas células. Os de texto, T1 a T8 (a reticência dupla de volta em cada uma das cinco frases, o espaço de volta dentro
do selo, o "do balcão" de volta no `.num` e sem parte própria), vermelhos pela asserção nova de cada tela. O front dos arquivos
tocados e vizinhos, 23 arquivos, 521 de 521; com `AMPLIFICAR_EFEITOS_MS=50`, 2 vermelhos, os dois do `CatalogoLigado.teste.tsx` (o
D471), iguais na cópia do HEAD (rodada duas vezes); o front inteiro 3.919 de 3.919; tsc, lint e build com 358,27 KB (+0,02 KB). Os
portões de texto que leem o front (`conferir-fosseis.php`, `conferir-guarda-do-caixa.php`), o `conferir-referencias-docs.php`, o
`contar-debitos.php`, o `conferir-debitos-repetidos.php` e o `conferir-segredos.sh`, saída 0; e o `conferir-tudo.sh` sozinho
(conferido por `ps`), 73 portões, saída 0, em cerca de 1 min. Sem servidor: a suíte PHP não roda (nada do PHP mudou). O navegador do coordenador, a recaptura do design e o ciclo são do
coordenador.

**A correção da verificação (quatro achados, todos baixos; dois eram o mesmo).**
- **O item 13 pela metade**: a nota "Mais N em cortesia, em N comandas" do DETALHE da Retirada mora na `section.retiradas-detalhe`,
  e não direto no `.cartao`, e a regra `... .cartao > .painel-base` não a pegava: ficava a 1 px da borda, com o título a 15 px.
  A regra nova `.retiradas .retiradas-detalhe > .painel-base` a põe no recuo. Nenhuma célula de prévia abre o detalhe com cortesia
  (os produtos com cortesia não têm saída), então o arnês ganhou uma célula própria (`p76s/harness/extras.tsx`,
  `RetiradaDetalheComCortesia`, que dá 5 cortesias ao produto que tem saídas) e uma medida que clica o "ver as saídas"
  (`p76s/harness/medir-detalhe-retirada.mjs`): a 1280 e a 905 px, de 1 px para 14,9 px (o título, 15), com 14,9 px livres à
  direita e embaixo, sem rolar de lado (`p76s/medida-detalhe-retirada-antes.json` e `-depois.json`).
- **O spec dos Descontos contra a tela** (dois achados): a linha dos estados da lista em `spec-descontos.md` agora diz "Carregando
  os itens…" no lugar da soma, sozinha, com o porquê e a outra saída pesada (o `ValorDeDepois` sem o "…" da frase faria o
  `role="status"` ler "Carregando os itens contando"); a frase do roadmap acima, que dizia "como a letra dos specs", foi corrigida.
- **As regras de CSS sem guarda no repositório**: o caso novo `frontend/src/telas/recuosDosRelatorios.teste.ts` (11 casos) lê o
  `App.css` do disco, sem os comentários (o molde do `AvisosDoDia.teste.tsx`), e confere cada regra desta fatia com o valor medido:
  o resumo do Caixa contra o `.caixa .totais`, o seletor do "Número" no mesmo bloco do `.relatorio-unidade select`, a linha da rede
  do NPS e dos Royalties DEPOIS do `.relatorio-tabela th:first-child` (o empate de especificidade, 0,2,1, se decide pela ordem), e
  que cada tela ainda pinta a classe que o seletor procura. Não prova a geometria -- isso segue sendo o navegador. Os mutantes K1 a
  K24 (cada regra arrancada, o seletor devolvido ao de antes, o `App.css` inteiro do HEAD, a regra do detalhe só num comentário e
  a linha da rede dos Royalties movida para antes do mês preso), um por vez, a reserva num diretório novo e vazio a cada um,
  devolvidos pelo sha1: todos vermelhos (`p76s/mutantes-guarda/resultado.txt`; o roteiro em `p76s/mutantes-guarda.py`).
- **O que rodou depois da correção:** o front dos arquivos tocados e vizinhos (26 arquivos, o caso novo, o `AvisosDoDia` e o
  `NotificacoesLigado`, que leem o `App.css` do mesmo jeito, inclusive), 625 de 625; com `AMPLIFICAR_EFEITOS_MS=50`, os mesmos 2
  vermelhos do `CatalogoLigado.teste.tsx` que o HEAD, pelo nome; o front inteiro 3.930 de 3.930; tsc, lint e build com 358,27 KB;
  o `conferir-referencias-docs.php`, o `conferir-fosseis.php`, o `conferir-guarda-do-caixa.php`, o `contar-debitos.php` (de pé
  199, sem mudança), o `conferir-debitos-repetidos.php` e o `conferir-segredos.sh`, saída 0; o `conferir-tudo.sh` sozinho (73), saída
  0. Sem servidor: a suíte PHP não roda (nada do PHP mudou).

**Conferido por mim, depois da correção** (04/11/2026): os quatro achados das três lentes (três distintos) e o relato da correção; o diff do `App.css` lido -- as regras que mudaram são as cinco que a lista apontava (a nota dos vendidos, a linha da rede do NPS e dos Royalties, a lista de vendas do detalhe do caixa), nenhuma `.agenda-*` tocada (o template do dono no Claude Design as consome); os arquivos tocados em LF; o `local.php` no `franquiabv_dev` pelo sha1. A fatia é só do front e o front inteiro rodou depois da correção (3.930 de 3.930): sem o ciclo inteiro. A prova visual é a recaptura das prévias do design, que é minha e vem a seguir, já com estes consertos.

## 04/11/2026 -- DOIS-DEFEITOS-DA-RECAPTURA (a fatia 76t: os dois defeitos de tela que a recaptura do design achou depois da 76s)

**O que muda para quem usa:** o aviso da tela ainda não migrada passa a dizer a verdade, e o caixa vê o cabeçalho da Cota-parte
alinhado como as outras telas. Nenhum número, pedido ou comportamento muda; só o front.

- **O aviso da tela ainda não migrada** (`frontend/src/telas/Console.tsx`, `TelaNaoMigrada`): no menu ele dizia "o conteúdo entra
  quando **<grupo>** for migrado", e os grupos já têm telas novas -- na recaptura, "quando Relatórios for migrado" num grupo com 13
  dos 14 destinos na tela nova. Agora ele diz sempre "o conteúdo entra quando ela for migrada", como já dizia fora do menu (o D322,
  de 11/10/2026). Contados hoje no Console (o roteiro e a saída em `p76t/contagem/`, do rascunho, renderizando cada destino do
  menu): 6 destinos ainda caem na tela não migrada -- 2 em grupos que já têm tela nova (as Datas da equipe, em Relatórios; o VIP
  Class da administração, em Configurações), onde a frase do grupo mentia, e 4 em grupos todos antigos (as duas Contas a Pagar, o
  Assistentes VIP e o VIP Class), onde ela era verdade e a da tela também é; mais todo caminho fora do menu. Dizer o grupo só quando
  ele é todo antigo pediria uma lista do que já migrou, que envelheceria sozinha (o `descreverTela` já recusa uma assim).
- **O cabeçalho do caixa na Cota-parte** (`CotaParte.tsx`, o escopo `dia`): o dia e a nota vinham num `<div>` sem classe, que anulava
  o `margin-left: auto` do `DiaDeHoje` -- o dia, a nota e os botões de exportar ficavam colados ao título, com o lado direito vazio.
  O bloco ganhou a classe `.relatorio-dia-de-hoje-com-nota` (`App.css`, logo depois da `.relatorio-dia-de-hoje`), com a margem. O
  fragmento do Relatório de Caixa não serviu: poria a nota ao lado do dia, e no estreito ela quebraria para a esquerda. A nota
  deixou de repetir o "No caixa," que o `DiaDeHoje`, logo acima, já diz: "A Cota-parte mostra o valor do dia de cada colaborador,
  sem a cota." (o `spec-cota-parte.md` diz o mesmo).
- **A medida** (o arnês da 76s, copiado em `p76t/harness/`, com o `App.css` lido na hora, no Chromium do Playwright; antes e depois
  em `p76t/medida-antes.json` e `p76t/medida-depois.json`): a 1280 px, no `OCaixaNoDia`, o vão do título ao dia de 15 para 446,3 px
  e o lado direito vazio depois dos botões de 377,3 para 0 px (os botões de 603,6-887,8 para 980,8-1265 px, as mesmas posições do
  `OGerente`); a 375 px nada muda (o bloco ocupa a linha inteira, como antes); o `CaixaNoDia` do Relatório de Caixa igual nas duas
  medidas; nenhuma célula rolando de lado.
- **O que rodou:** o front dos arquivos tocados e vizinhos (17 arquivos: o `Console`, os `ConsoleAbre*`, os da Cota-parte, o
  `recuosDosRelatorios`, o `menu-do-erp` e o `textosDaCotaParte`), 312 de 312; com `AMPLIFICAR_EFEITOS_MS=50`, os mesmos 6 vermelhos
  do `Console.teste.tsx` que o HEAD, pelo nome (a cópia do HEAD por `git archive` deu também 2 do `menu-do-erp.teste.ts`, que lê o
  `module.config.php` que a cópia só do `frontend/` não tem); o front inteiro 3.934 de 3.934; tsc, lint e build com 358,26 KB. A
  suíte PHP não rodou: nada do PHP mudou.
- **Os casos novos:** o `Console.teste.tsx` confere a frase em quatro destinos (o de fora do menu, as Datas da equipe, o VIP Class da
  administração e as Contas a Pagar) e que nenhum diz "for migrado"; o `recuosDosRelatorios.teste.ts` ganha o caso da 76t (a regra
  do bloco e a tela pondo o `DiaDeHoje` dentro dele). Os mutantes, um por vez, a reserva num diretório novo e vazio a cada um,
  devolvidos pelo sha1 (`p76t/mutantes.py` e `p76t/mutante-M*.txt`): M1, a frase do grupo de volta -- 4 vermelhos no
  `Console.teste.tsx`; M2, o `<div>` sem classe de volta -- o caso da 76t vermelho, e a medida de volta ao vão de 15 px com 431,3 px
  vazios; M3, a regra arrancada do `App.css` -- o caso da 76t vermelho, e a mesma medida; M4, o "No caixa," de volta -- 3 vermelhos
  (`CotaParte`, `CotaParteLigado` e `ConsoleAbreCotaParte`).
- **Os portões:** o `conferir-referencias-docs.php`, o `contar-debitos.php` (de pé 199, sem mudança), o
  `conferir-debitos-repetidos.php` e o `conferir-segredos.sh`, saída 0; o `conferir-tudo.sh` sozinho (73), saída 0. Nenhum débito
  aberto nem fechado: os dois defeitos foram achados e consertados no mesmo dia, sem registro.

A recaptura das prévias do design e o navegador são do coordenador; esta fatia não escreveu no `ds-bundle` nem no `.design-sync`.

**A correção da verificação** (04/11/2026): as três lentes confirmaram 5 achados, 3 distintos, todos baixos, os três tratados.
(1) Dois comentários de teste citavam no presente a frase antiga do aviso, "quando for migrado" (`Console.teste.tsx`, no caso do
que fica na versão antiga, e `menu-do-erp.teste.ts`, no caso do `novaAba`): agora citam "quando ela for migrada"; o que cita a
frase antiga no passado (o caso do aviso e o `debitos-tecnicos.md`) fica. (2) O comentário da `CotaParte.tsx` dizia que o
`DiaDeHoje` "já começa" pelo "no caixa", e ele começa por "Hoje, <dia>:": agora diz que ele já o diz. Os dois são só comentário,
sem caso novo: o texto da tela já é conferido pelos casos de antes. (3) O `min-width: 0` da `.relatorio-dia-de-hoje-com-nota`
não tinha guarda, mutante nem medida: medido com e sem ele (`p76t/correcao/`, no rascunho, o mesmo arnês com as larguras por
variável), a 1280, 905, 375, 320 e 160 px as caixas das três células são as mesmas ao décimo de px, e só a 100 px a nota muda
1 px (a palavra mais longa, "colaborador,", passa da largura) -- o bloco só tem texto. A declaração saiu, com o porquê no
`App.css`, e o caso da 76t no `recuosDosRelatorios.teste.ts` confere a regra inteira (`toEqual`), e não só a margem. A medida
depois da correção é a mesma da `medida-depois.json` a 1280 e a 375 px (o vão de 446,3 px e o lado direito vazio de 0 px),
nenhuma célula rolando de lado. Os mutantes novos, um por vez, a reserva num diretório novo e vazio, devolvidos pelo sha1: M5, o
`min-width: 0` arrancado antes da correção -- a medida igual de 160 a 1280 px (a prova de que era equivalente); M6, ele de volta
depois -- o caso da 76t vermelho; e o M3 (a regra arrancada) refeito com o trecho novo, vermelho, com a medida de volta ao vão de
15 px. O front dos 17 arquivos tocados e vizinhos 312 de 312 e, amplificado, os mesmos 6 vermelhos do `Console.teste.tsx`, pelo
nome; o front inteiro 3.934 de 3.934; tsc, lint e build com 358,26 KB; o `conferir-tudo.sh` sozinho (73), o
`conferir-referencias-docs.php`, o `contar-debitos.php` (de pé 199), o `conferir-debitos-repetidos.php` e o
`conferir-segredos.sh`, saída 0. A suíte PHP não rodou: nada do PHP mudou.

**Conferido por mim, depois da correção** (04/11/2026): os achados das três lentes (três distintos, baixos: dois comentários de teste com a frase antiga no presente, o comentário da Cota-parte sobre o começo do `DiaDeHoje`, e o `min-width: 0` sem guarda, que saiu depois de medido sem efeito); o diff só do front e dos documentos; o `local.php` no `franquiabv_dev` pelo sha1. A fatia é só do front e o front inteiro rodou (3.934 de 3.934): sem o ciclo inteiro. A recaptura das prévias do design, que é minha, vem a seguir, com a `TelaNaoMigrada` e a `CotaParte` mudadas.

## 04/11/2026 -- DATAS-DA-EQUIPE-DE1 (a fatia 76g da fila das telas que faltam)

**O que muda para quem usa:** o gerente e o administrador abrem as Datas da equipe (Relatórios › Datas da equipe) na tela nova,
no lugar da tela ainda não migrada que a 76f deixou: a escolha do mês (os doze, sem ano, com o de hoje marcado), os aniversários
e o tempo de casa da equipe e, para o administrador, que abre na rede como na tela antiga, as inaugurações das unidades com os
sócios e os aniversários dos sócios. Cada linha com a data inteira e o contato, o telefone num link que liga. A tela antiga
continua no ERP até a DE2. Nada do servidor mudou.

- **O que entrou** (a DE1 de `docs/f3-relatorios/spec-datas-da-equipe.md`, marcada FEITA lá com o que saiu diferente):
  `frontend/src/api/datasDaEquipe.ts`, `telas/DatasDaEquipe.tsx`, `telas/DatasDaEquipeLigada.tsx`, a regra da tela em
  `telas/textosDasDatasDaEquipe.ts`, a linha da `TELA_DO_RELATORIO` no `Console.tsx`, as regras `.datas-*` no `App.css` (a lista
  e a nota no recuo do título, a linha que quebra), o `export` no `src/index.ts`, e a prévia nova
  (`.design-sync/previews/DatasDaEquipe.tsx`, o dado em `_datas-da-equipe.ts`, a entrada no `componentSrcMap` e o viewport
  1280x900 no `config.json`). Os testes: `DatasDaEquipe.teste.tsx` (13), `DatasDaEquipeLigada.teste.tsx` (11),
  `ConsoleAbreDatasDaEquipe.teste.tsx` (3), a carga em `src/testes/datasDaEquipe.ts`, o caso novo do `recuosDosRelatorios.teste.ts`
  e o `Console.teste.tsx` com 5 destinos na tela não migrada (eram 6).
- **As decisões pelo molde:** o primeiro pedido leva o mês do relógio da tela, e o "(este mês)" é o do servidor quando ele já
  disse; o administrador abre em `todas` (o `RoyaltiesLigado.tsx`); a escolha do mês na classe da escolha da unidade, sem trava; sem
  tabela; sem fila da rede.
- **O que rodou:** o front dos 18 arquivos novos, tocados e vizinhos, 303 de 303; com `AMPLIFICAR_EFEITOS_MS=50`, os mesmos 6
  vermelhos do `Console.teste.tsx` que uma cópia do HEAD, pelo nome, e nenhum nos arquivos novos; o front inteiro 3.962 de 3.962;
  tsc, lint e build (o JS com 1.444,14 kB, 359,71 kB em gzip; o CSS com 99,35 kB); os tipos da prévia contra o código, zero erros (um
  tsconfig temporário, apagado). A suíte PHP do que lê o `Console.tsx` (o `RelatoriosNoMenuTest`) e das Datas
  (`DatasDaEquipeTest`, `RegrasDasDatasDaEquipeTest`, `AniversariantesNaPoliticaTest`): 97 casos em cada banco, saída 0. Os ensaios
  de permissão (`ensaiar-permissao-adm-api.php`) nos dois bancos de contrato, saída 0, com as Datas recusadas ao colaborador e ao
  caixa.
- **A medida** (o arnês da 76t copiado em `p76g/harness/` do rascunho, no Chromium do Playwright, o `App.css` lido na hora): as oito
  células da prévia a 375, 905 e 1280 px, nenhuma rolando de lado, nenhum filho de linha passando do cartão, o nome, a nota e a
  frase do vazio a 14,9-15 px da borda, como o título. Os mutantes de CSS: a lista sem a regra, o nome a 41 px; a nota sem a
  regra, a 1 px; a quebra do e-mail arrancada não muda nada a 375 px nestas células, e a 320 px o e-mail mais longo passa 48 px do
  cartão -- os três vermelhos também no caso do `recuosDosRelatorios`.
- **Os mutantes**, um por vez, a reserva num diretório novo e vazio, devolvidos pelo sha1, todos vermelhos: M1 a rede desenhada
  para o gerente (o gerente sem as listas da rede); M2 o mês pedido errado (a troca do mês); M3 a lista vazia sem a frase (a lista
  vazia); M4 a marca do futuro invertida (o tempo de casa, a rede); M5 o telefone sem o `tel:` (o contato); M6 o administrador
  abrindo na unidade da sessão (a abertura na rede, no invólucro e no Console); M7 o "entrou neste ano" tirado (o tempo de casa);
  M8 o mês do relógio sem o +1 (11 casos); M9 o "este mês" só do relógio da tela (o do servidor); M10 a linha da
  `TELA_DO_RELATORIO` de volta à tela não migrada (o Console); M11 a unidade fora da linha da rede; M12 a data sem o ano (4 casos).
- **O oráculo** pelo HTTP como o gerente de teste, no banco de contrato MySQL (`p76g-oraculo.sh` no rascunho: monta, semeia o
  `CenarioDasDatasDaEquipe` com os colaboradores da unidade U na unidade do gerente e o privilégio numa linha de
  `privilegios_usuarios`, troca e devolve o `local.php` conferido pelo sha1, remonta): novembro, fevereiro (o 29 com a nota),
  dezembro e julho contra o SELECT, com a regra escrita à mão -- 103 conferências, 0 divergências, a chave `rede` ausente nos
  quatro, o mês 13 com 422 e o `?unidade=todas` do gerente com 422; falsificado (o SELECT sem a equipe ativa), 42 divergências.
  No PostgreSQL, só o ensaio de permissão: o oráculo em processo nos dois bancos é o da DE0.
- **Os portões:** o `conferir-tudo.sh` sozinho (73, "Nenhuma falha inesperada."), o `conferir-referencias-docs.php`, o
  `contar-debitos.php` (de pé 199, sem mudança), o `conferir-debitos-repetidos.php` e o `conferir-segredos.sh`, saída 0. Nenhum
  débito aberto nem fechado (o desenho não tem débito na DE1): o D306 ganhou a marca PARCIAL da tela (resta a DE2); o menu e o
  Console com os comentários de "até a DE1" postos no passado; nenhuma pergunta nova ao dono.
- **A correção da verificação** (três lentes, seis achados confirmados, uma média e cinco baixas, todos de guarda e de texto,
  nenhum de comportamento): (1 e 4) a regra pura sem caso para "inaugurada neste ano", "faz 1 ano de casa" e "faz 1 ano de
  inaugurada" -- o `textosDasDatasDaEquipe.teste.ts` novo, 7 casos; (2, a média) o caso do "este mês" do servidor dublava a
  resposta de novembro ao pedido de outubro, que o servidor não dá (`RecorteDoRelatorio::mesDoAno` devolve o mês pedido, com o
  `emAndamento` dele), e o spec dizia que o aparelho atrasado marcava o mês do servidor: o caso refeito com a resposta real (o
  relógio marca até alguém pedir o mês do servidor, e então a marca passa a ele, como nas telas irmãs) e o spec corrigido; (3) a
  troca da unidade, a do mês e o "tentar de novo" com o outro valor já trocado; (5) o gerente com a unidade da abertura, que vai
  sem unidade; (6) as regras `.datas-socios`, `.datas-socio`, `.datas-futura` e o `flex-basis` da nota e dos sócios no
  `recuosDosRelatorios.teste.ts`. O `DatasDaEquipeLigada.teste.tsx` passa de 11 para 13 casos, e a carga ganha o outubro.
  Os mutantes novos, um por vez, a reserva num diretório novo e vazio, devolvidos pelo sha1, todos vermelhos: N1 o "inaugurada
  neste ano" arrancado, N2 e N3 o singular arrancado (a regra pura); N4 e N5 a troca da unidade voltando ao mês do relógio ou ao
  da abertura, N9 a troca do mês voltando à unidade da abertura (o caso do administrador); N6 o "tentar de novo" pedindo o da
  abertura (a falha depois de trocar o mês); N7 o gerente levando a unidade da abertura; N8 o "este mês" só do relógio (o caso
  refeito); C4 a C7 cada regra nova arrancada (o caso de recuos). Nenhum código da tela mudou.

O navegador, o ciclo e a recaptura do design são do coordenador; esta fatia não rodou o resync nem o package-build. Nenhum banco do
dev escrito; nada da produção, do BVIP, do OneSignal nem do crontab mudou.

**Conferido por mim, depois da correção** (04/11/2026): os seis achados das três lentes (um médio, o caso do "este mês do servidor" com uma resposta que o servidor não dá, refeito com a resposta real; e cinco baixos de cobertura e de guarda, com o `textosDasDatasDaEquipe.teste.ts` novo e os casos acrescentados) e o relato da correção; o `local.php` no `franquiabv_dev` pelo sha1. No navegador, contra o banco de contrato, como o gerente de teste com o privilégio de aniversariante concedido e as datas de nascimento e de contratação dele postas em novembro (o banco remontado no fim): Relatórios › Datas da equipe abriu a tela nova em novembro "(este mês)", com o aniversário 12/11/1990 e "faz 7 anos de casa" (20/11/2019), o e-mail na linha; trocar o mês para dezembro trouxe as duas frases de vazio; a 375 px, nada rola de lado (a largura da página igual à da tela). A fatia é só do front (mais o config e a prévia do design) e o front inteiro rodou depois da correção: sem o ciclo inteiro.

## 04/11/2026 -- DATAS-DA-EQUIPE-DE2 (a fatia 76h da fila das telas que faltam)

**O que muda para quem usa:** o "Aniversariantes" saiu do menu Relatórios do ERP antigo, e o endereço dele
(`/mod-relatorios/rel-aniversariantes/index`) responde 404: as Datas da equipe moram só na tela nova (Relatórios › Datas da equipe,
desde a 76g), para o gerente e o administrador, com a mesma permissão de antes. No menu da tela nova, o item "Datas da equipe" é
um botão, e não mais um link para a tela antiga: o clique do meio levaria ao 404. O menu Relatórios do ERP antigo deixou de abrir
pela permissão das Datas: com só ela, ele abriria vazio; mas a conta do menu é uma lista curta (o D702), e quem tivesse a das Datas
com a de um dos seis itens de fora da lista, e nenhuma dos nove, perderia o grupo com o item junto -- na cópia, ninguém.

- **O que saiu** (a DE2 de `docs/f3-relatorios/spec-datas-da-equipe.md`, marcada FEITA lá com o que saiu diferente): o item do
  `layout.phtml`; o `AniversariantesController` (com a guarda da E6 da 76f), a `AniversariantesControllerFactory`, o invokable e o
  alias `rel-aniversariantes` do `module/ISRelatorio/src/Module.php` (a "rota": o ERP antigo a alcançava pelo curinga); o
  `Form/Aniversariante.php`; a view `is-relatorio/aniversariantes/index.phtml`; as linhas do `SessaoAcl::menuAcesso` e do
  `::menuAtivo`; as quatro `relatorioSelecionarAniversariantes*` e as quatro `selecionarAniversariantes*` sem chamador do D73 (o de
  `ClienteRepository`, do parabéns do cron, FICA). Saíram também os dois testes da tela antiga, o `AniversariantesNoRelatorioTest`
  (as consultas que saíram; o que ele guardava, o `DatasDaEquipeTest` guarda nas novas) e o `AniversariantesNaPoliticaTest` da DE0.
- **O que ficou:** o privilégio `relatorios/aniversariantes` (o da política `datasDaEquipe`, no cadastro dos grupos) e o caminho
  `/isrelatorio/rel-aniversariantes/index` como a CHAVE da tela nova no front (`CAMINHO_DAS_DATAS_DA_EQUIPE`: o item do menu, a
  linha da `TELA_DO_RELATORIO`, o `RelatoriosNoMenuTest`), sem endereço no ERP: ele entrou no `SO_NA_TELA_NOVA` (`menu-do-erp.ts`),
  e o `descreverTela` do `Console.tsx` não oferece a ponte "abrir na versão antiga" a quem está nessa lista.
- **Os portões:** o `conferir-injecao-dql.php` de 22 para 14 sítios (os 8 do desenho, medidos: 2 por método morto; as
  `relatorioSelecionar*` contavam 0), o `conferir-booleanos-em-consulta.php` de 8 para 6 inofensivas (os dois `usu.status = 1` dos
  mortos de `UsuarioRepository`, medido com o arquivo de antes), o `conferir-cobertura-por-par.php` de 428 para 424 pares (o
  ISRelatorio de 188 para 184; o sha1 confere com os quatro devolvidos), o `conferir-recorte-por-unidade.php` sem as duas vigias do
  controller antigo (421 vigiados), o inventário regerado (351 ações), o `medir-relatorios-no-postgres.php` sem a marca da tela, e os
  exemplos do `segmentos-de-controlador.php` e do `medir-telas-no-postgres.php`. O `conferir-rotas-sem-login.php`, o
  `conferir-guarda-do-erp.php`, o `conferir-links-mortos.php` e o `conferir-referencias-docs.php` não pediram mudança (saída 0).
- **Os testes:** o `AniversariantesSaiuTest` novo (5 casos, no molde do `FidelizacaoDeClientesSaiuTest`: o endereço antigo que o
  curinga casa e o gerente de controllers não atende, com a API das Datas e o vizinho de controle; a view; as oito consultas fora e
  as cinco irmãs que ficam; o layout desenhado com o gerente e o privilégio, sem o item; o menu que não abre nem acende) e o caso
  novo do `RelatoriosNoMenuTest` (`SAIRAM_DO_LAYOUT`: o par `rel-aniversariantes/index` não volta ao layout). No front, o
  `ConsoleAbreDatasDaEquipe.teste.tsx` com o caso do botão (sem `href` para o ERP; o clique abre a tela nova) e o da guarda falsa
  conferindo também o botão; o `Console.teste.tsx` com o exemplo de tela antiga trocado pelo Caixa e a ponte nula para as Datas; o
  `menu-do-erp.teste.ts` com as Datas no `SO_NA_TELA_NOVA`.
- **Rodou:** os testes PHP das Datas, do menu, da sessão, do recorte, da Fidelização e do parabéns, 276 em cada banco, saída 0; os
  376 vizinhos (os que leem o layout, os `Module.php`, o inventário ou o menu, e os que citavam o Aniversariantes), saída 0 nos dois
  bancos, 2 pulados nos dois por falta da cópia (`VendasNoRelatorioTest::testODescontoNaoSomaODeOutraUnidade`,
  `VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador`). O front inteiro 3.973 de 3.973; amplificado com
  `AMPLIFICAR_EFEITOS_MS=50` nos arquivos tocados e vizinhos, os mesmos 6 vermelhos do `Console.teste.tsx` (a comanda e o caixa)
  que o HEAD, e nenhum nos tocados; tsc, lint e build (359,73 KB em gzip). Os ensaios de permissão e de recusas nos dois bancos de
  contrato, saída 0 (o colaborador e o caixa com 403 nas Datas); as travas `/tmp/*.lock` contadas e apagadas (4 da suíte, 5 dos
  ensaios). O `conferir-tudo.sh` sozinho, 73 portões, "Nenhuma falha inesperada."; `contar-debitos.php` (de pé 200: o D701 novo) e
  `conferir-debitos-repetidos.php`, saída 0; `conferir-segredos.sh`, saída 0.
- **Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, todos vermelhos:

  | Mutante | Vermelho |
  |---|---|
  | M1 o invokable e o alias `rel-aniversariantes` de volta no `Module.php` | `AniversariantesSaiuTest::testOEnderecoAntigoNaoTemControllerEAApiFica`, nos dois bancos |
  | M2 o item de volta no `layout.phtml` | `AniversariantesSaiuTest::testOItemSaiuDoMenuDoErpAntigo` e `RelatoriosNoMenuTest::testOItemDaTelaAntigaQueSaiuNaoVoltaAoLayout`, nos dois |
  | M3 a linha do `menuAtivo` de volta | `testOMenuRelatoriosNaoAbreNemAcendePeloAniversariantes` ("ainda acende"), nos dois |
  | M4 a linha do `menuAcesso` de volta | o mesmo caso (o menu que abre só pelo das Datas), nos dois |
  | M5 uma consulta morta de volta (a do sócio, no arquivo CRLF) | `conferir-injecao-dql.php` ("SUBIU de 14 para 16") e `testAsConsultasSairamEAsIrmasFicaram`, nos dois |
  | M6 o caminho da chave trocado no front | 5 casos do front (o item que abre a tela nova, o administrador na rede, a ponte, o `SO_NA_TELA_NOVA`) e `RelatoriosNoMenuTest::testATelaDeclaraOCaminhoEAGuardaDaPolitica`, nos dois |
  | M7 as Datas fora do `SO_NA_TELA_NOVA` | 3 casos do front (o botão, a ponte nula, a lista do `SO_NA_TELA_NOVA`) |
  | M8 a ponte sem o filtro do `SO_NA_TELA_NOVA` | `Console.teste.tsx`, "o endereço na versão antiga é o do item do menu" |
  | M9 a linha de base do portão de injeção de volta a 22 | `conferir-injecao-dql.php` ("DESCEU de 22 para 14") |

- **O oráculo** pelo HTTP, no banco de contrato MySQL (`medir-p76h-oraculo.sh` no rascunho da sessão: troca e devolve o `local.php`
  conferido pelo sha1, dá ao gerente de teste o privilégio das Datas e o dos Descontos por `privilegios_usuarios`, e o contrato foi
  remontado depois), como o gerente de teste: o endereço antigo 404 nos três formatos (com `?mes=11` e sem a ação), o vizinho
  `rel-descontos` 200 (o 404 não é o da ACL) e sem o item no menu, a API das Datas 200 com o JSON (`mesDoAno`, a equipe, sem a
  rede) em novembro e fevereiro e nas unidades, o mês 13 com 422; sem sessão, o endereço antigo dá a mesma página de entrar que o
  vizinho. 10 conferências, 0 divergências. No PostgreSQL, só os ensaios.
- **Ao dono:** o D701 -- a permissão das Datas segue com o nome "Relatórios de aniversariante" no cadastro dos grupos (o ACL casa
  pelo nome; renomear é uma linha da produção no corte): a pergunta foi ao lote, "A permissão das Datas da equipe continua com o
  nome 'Relatórios de aniversariante' no cadastro dos grupos?". Os débitos: o D306 e o D73 com a marca PARCIAL da DE2 (o D73 sem
  nenhum dos sítios sem chamador; ficam os de valor interno e a prova por HTTP).

O navegador e o ciclo são do coordenador; esta fatia não abriu navegador, nem tocou no `.design-sync` (a prévia do menu desenha o
botão sozinha). No `franquiabv_dev`, nada escrito; nada da produção, do BVIP, do OneSignal nem do crontab mudou.

**A correção depois da verificação** (três lentes, quatro achados baixos, todos confirmados no código e tratados):

- **O `menuAcesso` não tira só o menu vazio.** A conta do grupo Relatórios no ERP antigo é uma lista de nove privilégios, e seis itens
  do mesmo menu (Atendimentos, NPS, Retirada de Produtos, Evasão, Consultor Visagista, VIP Class) pedem um de fora dela: quem tinha o
  das Datas com um desses, e nenhum dos nove, perde o grupo e o item. Na cópia, só SELECT, dos 108 usuários ativos não
  administradores com o das Datas, todos têm um dos nove, e ninguém tem um dos seis sem um dos nove: hoje ninguém perde item. A
  lista curta é anterior à fatia e virou o **D702** (o conserto e o cuidado do `veOMenu` estão lá); o comentário do `SessaoAcl`, o
  spec, o D306 e o parágrafo de cima dizem o efeito de verdade, e o caso novo
  `AniversariantesSaiuTest::testSemOAniversariantesNaContaOMenuEscondeTambemOItemDeForaDaLista` o diz no código (o helper `soCom`
  saiu do caso de antes para os dois).
- **A pergunta do D701 cobria só as Datas.** O Profissional Parceiro ("Relatórios dashboard colaboradores"), a Carteira ("Relatórios
  de clientes") e a Cota-parte ("Relatórios de vendas") também têm a permissão com o nome da tela antiga: a pergunta no lote passou
  a dizer isso e ganhou a (c), as quatro de uma vez no corte (a recomendada agora); o D701 e o spec contam o alcance.
- **O comentário do grupo Relatórios no `menu-do-erp.ts`** punha o Aniversariantes entre os "oito que continuam no ERP antigo": agora
  são sete, e o oitavo mora só na tela nova.
- **O exemplo do Relatórios no caso da frase da tela não migrada** (o `Console.teste.tsx`) é o Caixa, que tem tela nova e nunca cai
  nela: o comentário diz isso, e o caso dos "5 destinos" passou a conferir que nenhum destino do Relatórios no menu cai na tela não
  migrada (o spec também).

Os mutantes novos, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, todos vermelhos: M10 (o Caixa
sem a linha da `TELA_DO_RELATORIO`, com a conta do caso subida a 6 para só a guarda nova pegar: "expected [ '/isrelatorio/rel-caixas/index' ]
to deeply equal []"); M11 (o `atendimentos` na conta do `menuAcesso` sem mudar o caso: o caso novo, nos dois bancos); M12 (o
`aniversariantes` de volta na conta: o caso de antes e o novo, nos dois). Rodou depois: os testes PHP da fatia e dos vizinhos do menu
(`AniversariantesSaiuTest`, `MenuDosRelatoriosSemAsTelasQueSairamTest`, `RelatoriosNoMenuTest`, `DatasDaEquipeTest`,
`RegrasDasDatasDaEquipeTest`, `SessaoApiTest`, `FidelizacaoDeClientesSaiuTest`), 120 em cada banco, saída 0 (os 6 do `AniversariantesSaiuTest` entre eles); o front inteiro, 3.973 de 3.973 (a guarda nova é
uma asserção no caso que já existia); amplificado com `AMPLIFICAR_EFEITOS_MS=50` nos três arquivos tocados, os mesmos 6 vermelhos da
comanda e do caixa do `Console.teste.tsx` que o HEAD; tsc, lint e build (359,73 KB em gzip); o `conferir-tudo.sh` sozinho, 73
portões, "Nenhuma falha inesperada."; `conferir-segredos.sh`, `conferir-referencias-docs.php`, `contar-debitos.php` (de pé 201: o
D702 novo) e `conferir-debitos-repetidos.php`, saída 0. A pergunta do D701 foi reescrita no lugar dela, no fim do lote.

**Conferido por mim, depois da correção** (04/11/2026): os quatro achados das três lentes e o relato da correção (o D702 da lista curta do `menuAcesso`, anterior à fatia e sem efeito hoje na cópia; a pergunta do D701 com as quatro permissões de nome antigo, à página como a 298; os dois comentários). O ciclo inteiro depois da correção, saída 0: caracterização 6.152 em cada banco (11 pulados: o caso que pulava no `AniversariantesNoRelatorioTest` saiu com ele), contrato 74 e 74, os ensaios nos dois bancos, os portões e o front 3.973 de 3.973, e o medidor dos relatórios no PostgreSQL, saída 0, em cerca de 9 min 37 s. No navegador, contra o banco de contrato, como o gerente de teste com o privilégio de aniversariante e o dos Descontos concedidos por linha de `privilegios_usuarios` (o banco remontado no fim): no ERP antigo, o menu Relatórios com Descontos e sem Aniversariantes; o endereço antigo `/mod-relatorios/rel-aniversariantes/index` com 404 e o vizinho dos Descontos com 200 (o 404 não é falta de privilégio); só com o privilégio de aniversariante, o menu Relatórios do ERP nem aparece; no menu novo, "Datas da equipe" é botão e abre a tela nova. O `local.php` no `franquiabv_dev` pelo sha1.

## 04/11/2026 -- CAPACIDADE-CR0 (a fatia 76i da fila das telas que faltam)

**O que mudou para quem usa.** O administrador vê "Capacidade × realizado" no menu Relatórios da tela nova, depois do NPS -- o
Potencial de Franquia com o nome que a resposta 138 (a) deu, só para o administrador; até a CR1 o item abre a tela ainda não migrada,
com o caminho para a versão antiga. No ERP antigo, o Potencial de Franquia continua, e passa a abrir (o item do menu e o endereço)
só para o administrador, pela mesma política da tela nova (a E6); na cópia, nas duas, nenhum não administrador ativo tem o privilégio
`relatorios/franquias/ler` (contado só com SELECT, pelo grupo e por usuário): ninguém perde nada hoje. Ele sai do ERP na CR2. Nenhum
número em reais de "potencial" (a 291 (a)).

**O que entrou** (a CR0 de `docs/f3-relatorios/spec-capacidade.md`, marcada FEITA, com o que se afastou do texto). A política
`capacidade` (o mês do painel, `soAdministrador`, sem arquivo); a regra pura `ISRelatorio\Regra\ContasDaCapacidade`; o
`Adm\CapacidadeController` (`GET /adm/api/relatorios/capacidade` e `.../unidades`): uma linha por unidade ativa e a da rede, cada
uma com a agenda nas duas réguas a parte (`agenda: {nova, antiga}`, a antiga com o `gravadoDesde`), a cobertura, as pessoas com
agenda, os atendimentos, o faturamento e o ticket por atendimento. A capacidade é o total da agenda do Profissional Parceiro da
mesma unidade (a régua da F4, pela vigência do horário de cada dia, e não o horário de hoje), lida uma vez para o recorte e separada
pela unidade do cadastro (a `parceiroHorarios` devolve agora a `unidade_do_cadastro`); o realizado é o do painel da mesma unidade. O
mês que ainda não começou é o 422 (a E5). A rede soma cada régua a parte e deixa a taxa nula se faltar cobertura a alguém -- e por
isso ela sai nula em julho e agosto de 2026 na cópia (a pergunta do **D703**). A rede é a soma das unidades ativas: as 35 pessoas
com agenda no cadastro de uma unidade inativa (julho de 2026) ficam fora de toda linha. Sem trava da rede, e sem arquivo.

**As medidas** (em processo, na cópia, julho de 2026, três rodadas): a rede em 0,49 a 0,55 s no MySQL e 0,70 a 0,75 s no
PostgreSQL (o primeiro pedido do processo, 1,0 a 1,2 s), 116 a 120 MB de pico, e passando com `memory_limit=128M` nos dois (lidos de
uma vez, os itens chegavam a 158 MB e caíam no cache do Doctrine com 128M: ficaram em duas metades, soltos linha a linha, com a
consulta aquecida antes da agenda; o D631 ganhou o parágrafo); a unidade 1 em 0,07 s. As respostas das duas cópias são as mesmas
linha a linha, por id; a ordem das linhas troca em 7 das 36 posições (a da `popularCombobox`, pela colação).

**O oráculo** (`p76i-oraculo.py` no rascunho da sessão: a régua da F4 escrita de novo a partir do `oraculo-f43`, unidade a unidade, e
o `PainelDaUnidadeController` em processo para o realizado), contra o JSON da rede de julho de 2026: 183 conferências em cada cópia,
0 divergências; falsificado (a régua antiga somada à nova), 19 divergências em cada.

**O que rodou.** Os testes novos `RegrasDaCapacidadeTest` (6 casos), `CapacidadeDoRelatorioTest` (8, sobre o `CenarioDaCapacidade`,
março de 2025, ids 90002600 a 90002862) e `PotencialNaPoliticaTest` (3), 17 casos e 179 asserções em cada banco, saída 0; com os
vizinhos (`RelatoriosNoMenuTest`, `RegrasDoRecorteDoRelatorioTest`, `PortoesDaFamiliaDosRelatoriosTest`, `SessaoApiTest`,
`ProfissionalParceiroTest`, `RegrasDaAgendaDoParceiroTest`, `PainelDaUnidadeTest`, `RelatorioAdmControllerTest`,
`RelatorioDeRoyaltiesTest`, `D367RoyaltiesDaUnidadeInativaTest`, `FranquiasNoRelatorioTest`, `DatasDaEquipeTest`,
`AniversariantesSaiuTest`, `UsuarioHorarioVigenciaTest`, `UsuarioAgendaPelosControllersTest`, `RegrasDaCarteiraTest`,
`D14RaizDoModuloDeVendasTest`), 695 casos em cada banco, saída 0, nenhum pulado. Mudaram dois casos vizinhos, de propósito: o
`RelatorioDeRoyaltiesTest` esperava o Potencial aberto ao gerente (agora o 404), e o `ProfissionalParceiroTest` recusava `u.unidade`
em qualquer lugar da consulta do escopo próprio (agora só o filtro, `u.unidade =`). O front inteiro 3.973 de 3.973; amplificado com
`AMPLIFICAR_EFEITOS_MS=50`, os mesmos 6 vermelhos do `Console.teste.tsx` que o HEAD (comparados pelo nome, o HEAD exportado à parte);
tsc, lint e build (359,78 KB em gzip). Os ensaios de permissão e de recusas nos dois bancos de contrato (o MySQL remontado antes; as
9 travas `/tmp/*.lock` de root apagadas antes), e o `medir-adm-api-no-postgres.php` com a entrada nova (o gerente no 403, o
administrador com 200 na rede), saída 0. Os portões `conferir-recorte-por-unidade.php` (430 métodos, 13 relatórios na família e 13
políticas ligadas), `conferir-cobertura-por-par.php` (424 pares; o ISRelatorio de 184 para 190 chamadas), `conferir-rotas-sem-login.php`
e `conferir-guarda-do-erp.php`, saída 0.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, os de servidor nos dois bancos,
todos vermelhos: M1 (a capacidade com o horário de hoje) e M2 (a população só `ext_int`) e M3 (a régua antiga somada à nova) pelos
casos da unidade e da rede; M4 (o gerente aceito) pelo `testSoOAdministrador` e pelo da tela antiga; M5 (o realizado pelo preço de
tabela) pelo da unidade e pelo do painel; M6 (a `potenciaisAction` sem a guarda) pelo `PotencialNaPoliticaTest`; M7 (o item do layout
sem o `veOMenu`) pelo `RelatoriosNoMenuTest`; M8 (o mês futuro aceito, o intervalo livre) pelo do mês; M9 (a taxa da rede sem a
cobertura) pelo controller e pela regra; M10 (o `gravadoDesde` pelo primeiro dia) pela regra; M11 (sem o `deOutraUnidade`) com o
erro do `totais`; M12 (a linha do Console fora) só pelo `RelatoriosNoMenuTest` -- na tela é equivalente, o destino cai na tela não
migrada de todo jeito, como o F2 da 76f; M13 (o item do menu novo sem o `exige`) pelo `menu-do-erp.teste.ts`, pelo
`Console.teste.tsx` e pelo `RelatoriosNoMenuTest`.

**A verificação em três lentes** confirmou 3 achados, um médio e dois baixos, tratados. O médio: as linhas são as unidades ativas
de HOJE, e a unidade fechada depois sumia calada também dos meses em que trabalhou (na cópia do MySQL, só com SELECT: de 2023 a
dezembro de 2025 todo mês tem unidade hoje inativa com venda; março de 2025, 6 unidades e 1.537 das 21.700 vendas concluídas da
rede; julho de 2026, o mês do oráculo, nenhuma). Mostrá-las é escolha do dono (o **D704**, a pergunta no lote, recomendando a (b),
como os Royalties); até lá, a rede as APONTA, sem somá-las (`rede.foraDasLinhas`; em processo na cópia, março de 2025 com
`[2, 16, 18, 31, 33, 49]` e 128M de `memory_limit`, julho de 2026 com `[]`), e o `CenarioDaCapacidade` ganhou a venda S7 de W, a
inativa. Os baixos: a condição de quem está no total no `gravadoDesde`, e o `pessoasComAgenda` só de quem tinha agenda, sem caso
(o `testOGravadoDesdeEAsPessoasSoDeQuemEstaNoTotal` novo); e a história do comentário dos destinos não migrados do
`Console.teste.tsx`, que parava em 5. Os mutantes novos, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo
sha1, nos dois bancos, todos vermelhos: M14 (as que venderam sem apontar) pelo caso da rede; M15 (o `gravadoDesde` sem o filtro do
total), M16 (o total sem a lista) e M17 (o `pessoasComAgenda` contando quem está no total sem agenda) pelo caso novo da regra; M18
(os apontados fora de ordem) pelo `testAsQueVenderamENaoSaoLinhaFicamApontadas`. Depois da correção: os testes da fatia 19 casos e
190 asserções em cada banco, com os vizinhos 697 casos em cada (5.635 asserções no MySQL, 5.634 no PostgreSQL), saída 0, nenhum
pulado; o `Console.teste.tsx` e o `menu-do-erp.teste.ts`, 191 de 191 (no front, só o comentário mudou); os portões
`conferir-recorte-por-unidade.php` (430 métodos), `conferir-cobertura-por-par.php`, `conferir-rotas-sem-login.php`,
`conferir-guarda-do-erp.php`, `conferir-referencias-docs.php`, `conferir-segredos.sh`, `contar-debitos.php` e
`conferir-debitos-repetidos.php`, e o `conferir-tudo.sh` sozinho (73 portões, "Nenhuma falha inesperada."), saída 0. Os ensaios HTTP
não rodaram de novo: nenhuma rota nem guarda mudou.

**Débitos:** o D306 PARCIAL de 04/11 (a Capacidade voltou ao menu novo com a API); o D631 com a medida; o **D703** novo (a taxa da
rede nula sem cobertura; a pergunta no lote); o **D704** novo (as inativas de hoje fora dos meses em que trabalharam; a pergunta no
lote); de pé 203. O D692 e o D695 fecham na CR2. Nada da produção, do BVIP, do OneSignal nem do crontab
mudou; na `franquiabv_dev`, só SELECT; o banco de contrato foi remontado. **O navegador e o ciclo inteiro são do coordenador.**

**Conferido por mim, depois da correção** (04/11/2026): os achados das três lentes e o relato da correção (o médio, as unidades hoje inativas fora até dos meses em que trabalharam, agora apontadas em `rede.foraDasLinhas` e ao dono como a 300, o D704; o caso do `gravadoDesde` só de quem está no total; o comentário do Console); a pergunta da taxa da rede sem cobertura à página como a 299 (D703). O ciclo inteiro depois da correção, saída 0: caracterização 6.183 em cada banco (os mesmos 11 pulados), contrato 74 e 74, os ensaios nos dois bancos, os portões e o front 3.973 de 3.973, e o medidor dos relatórios no PostgreSQL, saída 0, em cerca de 9 min 40 s. No navegador, contra o banco de contrato (remontado no fim): com o grupo do gerente de teste marcado como administrador, a API de outubro de 2026 com 200 e o recorte da unidade, dezembro com o 422 "não mostra mês que ainda não começou", o item "Potencial de Franquia" no ERP antigo e "Capacidade × realizado" no menu novo abrindo a tela ainda não migrada; sem o papel de administrador, mas com o privilégio de franquias concedido, a API com 403, o Potencial antigo com 404 e o item fora do menu do ERP. O `local.php` no `franquiabv_dev` pelo sha1.

## 04/11/2026 -- CAPACIDADE-CR1 (a fatia 76j da fila das telas que faltam)

**O que muda para quem usa:** o administrador abre Relatórios › Capacidade × realizado na tela nova, no lugar da tela ainda não
migrada que a CR0 deixou (a CR1 de `docs/f3-relatorios/spec-capacidade.md`; a resposta 138 (a) do dono, "só para o
administrador", e a 291 (a), sem potencial em reais). A tela abre na rede, no mês de hoje pelo servidor, com a navegação do mês (o
› desligado no mês em andamento, a E5) e a escolha da unidade. A tabela das unidades, em ordem do nome, rola dentro do cartão: as
pessoas com agenda, a capacidade (os horários da agenda de cada pessoa naquele dia, com os bloqueados dentro), os bloqueados, os
ocupados e a ocupação, e o realizado do painel (os atendimentos, o faturamento e o ticket por atendimento), com a linha da rede
embaixo. A unidade com as duas réguas tem duas linhas -- a do horário da época e a da "régua antiga", marcada --, e as duas nunca se
somam, nem na rede. O número que não existe é o travessão, nunca 0%: a ocupação sem base, a da rede sem a cobertura de todos (dita
numa frase, o D703) e o ticket sem atendimento. O cartão do mês diz quantas unidades hoje fechadas venderam no mês e não estão na
conta (a (a) do D704 até a resposta), e a régua antiga com a data desde quando o horário é gravado. Embaixo da tabela, o que fica
fora da conta de cada unidade e da rede (as férias, o agendado fora do horário semanal, o agendado e o bloqueado além do que o Dash
gravou), como o Profissional Parceiro diz. O "Como os números são contados" abre com a frase do desenho. **O Potencial de Franquia antigo continua no ERP até a CR2**, só para o administrador desde a
CR0; o gerente e os outros não veem o item.

**O que entrou:** `frontend/src/api/capacidade.ts`, `telas/Capacidade.tsx`, `telas/CapacidadeLigada.tsx` e a regra pura
`telas/textosDaCapacidade.ts` (a ordem das linhas e as frases), as cargas em `testes/capacidade.ts`, os testes `Capacidade` (19),
`CapacidadeLigada` (9), `ConsoleAbreCapacidade` (3) e `textosDaCapacidade` (7), as seis chaves `capacidade.*` do glossário com o
`ITENS_DA_CAPACIDADE` (e o caso dele no `glossario.teste.tsx`), as regras `.capacidade*` no `App.css` (e o caso delas no
`recuosDosRelatorios.teste.ts`), o export no `index.ts`, e a prévia do design (`.design-sync/previews/Capacidade.tsx`, seis células,
com o dado em `_capacidade.ts`, que fecha pela regra do servidor) com a entrada no `componentSrcMap` e o viewport medido (`1280x760`,
e `1280x880` depois da correção)
-- sem captura nem nota (a próxima rodada do design). Mudaram a linha da `TELA_DO_RELATORIO` no `Console.tsx` e o
`Console.teste.tsx` (5 destinos na tela não migrada, nenhum do Relatórios; o exemplo do Relatórios da frase dela voltou ao Caixa).
**Onde a fatia se afastou da nota dela:** o primeiro pedido vai sem mês, no molde dos Royalties e do NPS, e o mês de hoje é o do
servidor -- a tela não lê relógio (o relógio de outro fuso pediria o mês que, para o servidor, ainda não começou, e o 422 é dele).

**O que rodou:** o front dos arquivos novos e tocados e os vizinhos (o Console, o menu, o glossário, os recuos e os ConsoleAbre dos
Royalties, dos Relatórios e das Datas), 273 de 273, e, amplificado com `AMPLIFICAR_EFEITOS_MS=50`, 267 de 273: os 6 vermelhos do
`Console.teste.tsx` (a comanda e o caixa dentro do Console), os mesmos 6 do HEAD pelo nome (o `Console.teste.tsx` do HEAD rodado
amplificado numa cópia por `git archive`); o front inteiro 4.013 de 4.013 (170 arquivos); o tsc das prévias com um tsconfig
temporário (saída 0, apagado); `npm run lint` (saída 0, os 8 avisos de antes, nenhum dos arquivos da fatia) e `npm run build` (saída
0, 362,45 KB em gzip). A medida no Chromium do Playwright a 375, 905 e 1.280 px nas seis células: nenhuma rola de lado, a tabela
rola dentro do cartão (1.166 px; com 36 unidades de nomes longos, só na medida, 1.188,8 px, que cabem a 1.280), as notas do cartão
do mês a 14,9 px da borda como o título (15), o conteúdo mais alto em 704 px a 1.280. Os testes PHP que leem o Console e os da
Capacidade (`RelatoriosNoMenuTest`, `CapacidadeDoRelatorioTest`, `RegrasDaCapacidadeTest`, `PotencialNaPoliticaTest` e o
`RelatorioDeRoyaltiesTest`), 101 casos e 744 asserções em cada banco, saída 0, nenhum pulado. O ensaio de permissão nos dois bancos
de contrato, saída 0 (as 2 travas `/tmp/*.lock` da suíte contadas e apagadas antes). O `conferir-tudo.sh` sozinho, 73 portões,
"Nenhuma falha inesperada."; o `conferir-segredos.sh`, o `conferir-referencias-docs.php`, o `contar-debitos.php` e o
`conferir-debitos-repetidos.php`, saída 0.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, todos vermelhos pela asserção de
um caso nomeado: M1 o mês pedido errado (o ‹ e o › do `CapacidadeLigada` e o Console); M2 o "próximo" ligado no mês corrente (o caso
da E5); M3 a régua antiga sem a marca (as duas réguas e a linha da rede); M4 a taxa nula como 0% (o caso da taxa nula); M5 a linha
da rede somando a régua antiga (o caso da rede); M6 o aviso do `foraDasLinhas` sumido; M7 o administrador abrindo na unidade (oito
casos); M8 a linha do Console de volta à tela não migrada; M9 a ordem do nome tirada; M10 a frase da cobertura da rede sumida; M11 a
escolha da unidade sem guardar o mês; M12 o ticket sem atendimento como R$ 0,00; C1 e C2 as regras da linha da rede e da régua
antiga arrancadas do CSS (o caso do `recuosDosRelatorios.teste.ts`).

**O oráculo:** no contrato MySQL, pelo HTTP como o gerente de teste com o grupo administrador (só no contrato), com o
`CenarioDaCapacidade` semeado: a rede e a unidade U de março de 2025 com 200, o mês que vem com o 422, desenhados pela tela pura com
a resposta real -- 98 conferências (a ordem, cada célula de cada régua de cada linha, o realizado, as frases), 0 divergências;
falsificado (a régua antiga somada à nova na rede), 2. O banco de contrato foi remontado depois.

**Débitos:** o D306 PARCIAL de 04/11 (a tela da Capacidade; resta a CR2); o D703 e o D704 com o que a tela desenha até as
respostas 299 e 300; de pé 203. Nenhuma pergunta nova ao dono. Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na
`franquiabv_dev`, nada (só os bancos descartáveis dos testes e os de contrato). **O navegador e o ciclo inteiro são do
coordenador.**

**A correção depois da verificação em três lentes** (7 achados confirmados, dois médios e cinco baixos; seis tratados, um aceito
como está): (1, baixo) a tela não dizia o que fica fora da conta -- as férias e o agendado fora do horário semanal da régua nova, o
agendado e o bloqueado além do que o Dash gravou na antiga, que a resposta da CR0 manda e o Profissional Parceiro diz à parte; o mês
do Dash com zero horários gravados e agendamentos saía "0, 0, —" sem aviso. Agora uma nota por unidade embaixo da tabela, e a da
rede por último ("Fora da conta de VIP Centro: 4 horários de férias; na régua antiga, 2 horários agendados e 3 horários bloqueados
além dos que o Dash gravou."), pela regra pura `fraseDoForaDaConta`, e a `capacidade.capacidade` e a `capacidade.reguaAntiga` do
glossário dizem onde. (2, baixo) a rede sem unidade com agenda mostra a tabela com o realizado, e não "a tabela vazia" do desenho:
mantido de propósito (a tabela vazia apagaria os atendimentos e o faturamento do mês), e agora escrito no spec e no comentário da
tela. (3, médio) nenhum caso provava que trocar de mês guarda a unidade escolhida: o caso novo escolhe a VIP Centro e anda o mês. (4,
médio) nenhum caso lia o `rowspan` das duas réguas: o caso novo lê o de cada célula da unidade, da rede e das de uma régua. (5,
baixo) a moeda da linha sem caso: a VIP Centro em euro. (6, baixo) o `gravadoDesde` mais tarde entre as linhas sem caso com mais de
uma: o caso novo põe a mais cedo primeiro. (7, baixo) o afastamento da nota 4(c) (a tela não lê o relógio, o mês de hoje vem do
servidor): aceito como está, já escrito -- é o molde real dos Royalties e do NPS, e nenhum teste depende da data. Os mutantes da
correção, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, todos vermelhos: M13 trocar de mês
voltando à rede; M14 o `rowSpan` tirado; M15 a moeda da linha ignorada; M16 e M17 o `gravadoDesde` pelo mais cedo e pela primeira
linha; M18 e M19 a nota do fora da conta sumida, e a da rede; M20 a M23 cada parte dela fora da frase; M24 a frase do glossário
arrancada. A medida de novo no Chromium do Playwright, a 375, 905 e 1.280 px nas seis células: nenhuma rola de lado, as notas novas a
14,9 px da borda como as do mês, nenhuma passando do cartão; o conteúdo mais alto termina em 820,2 px a 1.280, daí o viewport
`1280x880`. O front dos arquivos tocados e vizinhos 279 de 279, e amplificado os mesmos 6 vermelhos do `Console.teste.tsx` que o HEAD,
pelo nome; o front inteiro 4.019 de 4.019; o tsc das prévias (tsconfig temporário, apagado), `npm run lint` (os 8 avisos de antes) e
`npm run build` (362,66 KB em gzip), saída 0; os testes PHP da fatia, 101 casos e 744 asserções em cada banco, saída 0. Nada de PHP
mudou; nenhum banco tocado na correção.

**Conferido por mim, depois da correção** (04/11/2026): os achados das três lentes e o relato da correção (dois médios, a unidade guardada ao trocar de mês e o `rowspan` das duas réguas, e os baixos, cada um com o caso; o afastamento da nota 4(c), o mês vindo do servidor como nos Royalties e no NPS, aceito). No navegador, contra o banco de contrato, com o grupo do gerente de teste marcado como administrador só ali (o banco remontado no fim): Relatórios › Capacidade × realizado abriu a tela nova em novembro de 2026 "até hoje", na rede, com a frase do mês em andamento, a tabela das unidades e a linha "Todas as unidades", "ninguém com agenda no mês" em cada uma e o travessão na ocupação. **Um defeito achado por mim e consertado aqui**: sem ninguém "sem o horário da época", a frase da cobertura da rede dizia "de 5 outras não se sabe se tinham agenda" -- o "outras" sem ninguém antes delas, e o caso da regra pura fixava a frase errada ("de 1 outra"); agora o `fraseDaCoberturaDaRede` diz "de 5 pessoas" quando a primeira parte não existe, com o caso do 1 corrigido e o do 5 novo. Os testes da Capacidade 44 de 44; o mutante (a frase antiga de volta) vermelho no caso, devolvido pelo sha1; tsc e lint, saída 0. A fatia é só do front (mais o config e a prévia do design): sem o ciclo inteiro; o front inteiro roda antes do commit.

## 04/11/2026 -- CAPACIDADE-CR2 (a fatia 76k da fila das telas que faltam)

**O que muda para quem usa:** o administrador não tem mais o Relatórios › Potencial de Franquia no ERP antigo (a CR2 de
`docs/f3-relatorios/spec-capacidade.md`; a resposta 291 (a) do dono, "sem potencial em reais"). Com ele saem o valor em reais
(horários × ticket do cadastro × 70% × (1 − redução)) e a única conta da capacidade de um mês que ainda não chegou ou de um
intervalo livre, como uma semana (a E5). A Capacidade × realizado fica só na tela nova: o item do menu novo é um botão, e o caminho
do Potencial antigo ficou só como a chave dela. O endereço antigo responde 404, também ao administrador; os Royalties, do mesmo
controller, não mudam. A meta da Yungas e a rotina do ticket das 04:10 ficam como estavam (não rodaram), e a frase da tela de
Unidades sobre a redução do potencial ("só a integração da Yungas a usa") passou a ser verdade.

**O que saiu, derivado do código:** a `FranquiasController::potenciaisAction` (com a guarda da E6 da CR0), o `calcularPotenciais`,
o `totalHorariosDisponiveisAgenda` do controller, o `Form/FranquiaPotencial.php`, a view `is-relatorio/franquias/potenciais.phtml`, o
item do `layout.phtml` e o `PotencialNaPoliticaTest` (sem a ação, a guarda não tem o que proteger). **O que ficou:** o controller e
o alias `rel-franquias` (os Royalties), o privilégio `relatorios/franquias/ler`, as consultas que outros leem
(`selecionarUnidadesRelatorioFranquias`, `selecionarUsuariosAgenda`, `popularCombobox`), a meta da Yungas
(`CronJobService::yungasFaturamentoAtualizar`, que conta pelo `yungasTotalHorariosDisponiveisAgenda`), o
`CronJobService::totalHorariosDisponiveisAgenda` (o mesmo nome do método que saiu, mas sem chamador no `module/`: só os testes da F4
o leem por reflexão, como a régua "da rotina do Dash"; não é da meta da Yungas), e a `SessaoAcl` (o `Rel-franquias` e o `franquias` são também dos
Royalties; o D702 diz que a lista não muda). Novo: o `PotencialSaiuTest` (6 casos: o endereço que casa com o controller que fica e
a ação que saiu, o 404 do administrador com os Royalties abrindo no mesmo arnês, a view, as contas e as irmãs, a redução lida só pela
meta da Yungas, sem os comentários, e o item fora do menu desenhado como o administrador). Mudados: o `RelatorioDeRoyaltiesTest` (o
404 do Potencial também ao administrador), o `RelatoriosNoMenuTest` (o par no `SAIRAM_DO_LAYOUT`), o `FranquiasNoRelatorioTest` (dois
comentários); no front, o `SO_NA_TELA_NOVA` com a Capacidade, o `menu-do-erp.teste.ts`, o `Console.teste.tsx` (a ponte nula para o
caminho dela, e a dos Royalties) e o `ConsoleAbreCapacidade.teste.tsx` (o item como botão, e o caso novo do clique); comentários no
`CapacidadeController`, na `PoliticaDoRelatorio`, no `module.config.php`, na `ContasDaCapacidade`, no `UnidadeRepository` e no
`Console.tsx`.

**Os portões:** as duas vigias da `potenciaisAction` fora do `conferir-recorte-por-unidade.php`; o `conferir-cobertura-por-par.php` com o
`ISRelatorio` de 190 para 187 chamadas (os 424 pares e o sha1 não mudam); a marca do Potencial fora do
`medir-relatorios-no-postgres.php` (rodado depois da verificação, abaixo); o inventário regerado (351 para 350 ações). O `conferir-injecao-dql.php` (14), o
`conferir-booleanos-em-consulta.php` (6), o `conferir-rotas-sem-login.php` e o `conferir-guarda-do-erp.php` não mudaram, saída 0.

**O que rodou:** os testes da fatia e vizinhos, 354 casos e 2.253 asserções em cada banco, e mais 207 casos e 2.209 asserções dos
vizinhos da agenda e do Parceiro, saída 0, nenhum pulado; o front dos arquivos tocados 232 de 232 e, amplificado com
`AMPLIFICAR_EFEITOS_MS=50`, os mesmos 6 vermelhos do `Console.teste.tsx` que o HEAD (a comanda e o caixa); o front inteiro 4.020 de
4.020; tsc, lint e build (362,66 KB em gzip), saída 0; os ensaios de permissão e de recusas nos dois bancos de contrato, saída 0; o
`conferir-tudo.sh` sozinho, 73 portões, saída 0; o `conferir-segredos.sh`, saída 0. **Os mutantes**, um por vez, a reserva num
diretório novo e vazio a cada um, devolvidos pelo sha1, os de servidor nos dois bancos: M1 a `potenciaisAction` de volta; M2 o item de
volta no layout; M3 o formulário de volta; M4 a view de volta; M5 o `totalHorariosDisponiveisAgenda` de volta no controller; M6 outro
leitor da redução; F1 o caminho da chave trocado no front; F2 a Capacidade fora do `SO_NA_TELA_NOVA`; G1 a linha de base antiga do
portão -- todos vermelhos; o controle M6b (o leitor só num comentário) ficou verde, como devia. **O oráculo** pelo HTTP no contrato
MySQL, como o gerente de teste com o grupo administrador (só no contrato, remontado depois; o `/autenticacao/sair` por GET deu 404, e a sessão do oráculo, que sobreviveu à volta do
`local.php`, foi apagada à mão no contêiner logo depois, sem nenhum pedido nela): o endereço
antigo com 404 em três formas, os Royalties com 200 e o menu sem o item, a API da Capacidade com 200 na rede, num mês fechado e numa
unidade, as unidades com 200, e o mês que vem com 422 -- 12 conferências, 0 divergências.

**Débitos:** o D692 e o D695 CORRIGIDOS, o D306 PARCIAL (resta o que segue no ERP antigo), o D702 com a nota de que a lista não muda;
de pé 203 para 201. Nenhuma pergunta nova ao dono. Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`,
nada (só os bancos descartáveis dos testes e os de contrato). **O navegador e o ciclo inteiro são do coordenador.**

**Depois da verificação (três lentes, 5 achados baixos, todos tratados).** (1) O `CronJobService::totalHorariosDisponiveisAgenda`
estava descrito como "da meta da Yungas" no caso, no desenho e aqui; no `module/` ninguém o chama (a meta conta pelo
`yungasTotalHorariosDisponiveisAgenda`), e só os testes da F4 o leem por reflexão. A descrição foi corrigida no `PotencialSaiuTest`,
no `spec-capacidade.md` e nesta entrada, e o `testAsContasSairamEAsIrmasFicaram` passou a cobrar que o nome só apareça na própria
definição, sem os comentários (o mutante N5, um chamador na meta da Yungas, vermelho). (2 e 3) A nota da CR2 no D306 tinha tirado o
Dash. Faturamento (a A4) da lista do que resta: voltou, e os PDFs e o XLS com o nome da nota anterior (da Cota-parte, a Vendas Ver.
2). (4) O caso do D695 só pegava a forma `['reducaoPotencial']` e comparava a lista de arquivos: agora conta toda menção ao campo no
`module/`, sem os comentários, arquivo por arquivo, contra um mapa fixo, e confere que as duas leituras de conta do `CronJobService`
estão dentro da `yungasFaturamentoAtualizar`; os mutantes N1 (o getter `getReducaoPotencial()` num controller), N2 (a conta na DQL),
N3 (uma segunda conta noutra rotina do `CronJobService`) e N3b (a conta movida para um ajudante do mesmo arquivo) vermelhos, e o
controle N4 (a menção só num comentário) verde, cada um sozinho, a reserva num diretório novo e vazio, devolvido pelo sha1, nos dois
bancos; o D695 e o desenho dizem o que o caso pega. (5) O medidor dos relatórios no PostgreSQL (`p48-medir-relatorios-pg.sh`, fora do
ciclo) rodou: saída 0, nenhum relatório quebrou, 23 relatórios (eram 24: o Potencial não é mais pedido, porque saiu do inventário),
18 com a marca de dado conferida, os Royalties com a recusa ao gerente conferida, e a linha "A MEDIÇÃO FICOU CEGA" da rodada de antes
(o `rel-franquias/potenciais` sem a marca) sumiu; o `local.php` voltou ao `franquiabv_dev`, conferido pelo sha1. Os testes da fatia e
vizinhos depois da correção: 134 casos e 964 asserções em cada banco, e os da agenda e do Parceiro (`ProfissionalParceiroTest`,
`RegrasDaAgendaDoParceiroTest`) 144 casos e 1.784 asserções em cada banco, saída 0, nenhum pulado. O front não mudou na correção.


**Conferido por mim, depois da correção** (04/11/2026): os 5 achados das três lentes, lidos um a um, e o relato da correção. O ciclo
inteiro depois da correção: caracterização 6.186 em cada banco, saída 0 (11 pulados); contrato 74 e 74; os ensaios, os portões e o front
4.020 de 4.020, saída 0; o medidor dos relatórios no PostgreSQL, saída 0; em cerca de 9 min 47 s. No navegador, contra o banco de
contrato, como o administrador de teste: o `/mod-relatorios/rel-franquias/potenciais` com 404, os Royalties com 200 e a página inteira
(51 KB, com o formulário), o "Potencial de Franquia" fora do menu do ERP e os Royalties nele; no menu novo, "Capacidade × realizado" é
botão e abre a tela nova, sem rolagem lateral. **Achado por mim no navegador e consertado:** a marca da cobertura em cada linha dizia
"de 3 pessoas não se sabe se tinha agenda" (o verbo sem concordância; a frase da rede já concordava desde a 76j), e dois casos do
`Capacidade.teste.tsx` fixavam a frase errada; agora "tinham" no plural, com o caso novo no `textosDaCapacidade.teste.ts` e os dois
casos corrigidos, o mutante (o verbo fixo) vermelho em 3 casos, devolvido pelo sha1; os testes da Capacidade 41 de 41, tsc e lint
saída 0, e a tela recarregada com 3 plurais e 2 singulares certos. A lista de unidades mostra duas "Brasil - SC - Itajai - Centro":
é o D530 (na cópia, 36 ativas e 34 rótulos), sem mudança. Na limpeza, apaguei 130 arquivos de sessão dos dois usuários de teste no
`/tmp` do contêiner (quase todos dos ensaios do ciclo; não contei antes de apagar); o `local.php` de volta ao `franquiabv_dev`,
conferido pelo sha1, e o banco de contrato remontado.

## 04/11/2026 -- RESPOSTAS-294-A-300 (a fatia 76u: as respostas do dono de 04/11/2026)

**Nada muda hoje para quem usa: só documentação** (e o texto de oito comentários, em cinco arquivos de código). O dono respondeu as perguntas 294 a 300 da
página, as sete na opção recomendada, sem observação: a **294 (a)** ao D697 ("Aceitar a espera, com a conta exata"), a **295 (a)** ao
D698 ("Consertar a conta"), a **296 (b)** ao D699 ("Como hoje"), a **297 (a)** ao D700 ("A unidade inteira, como hoje"), a **298
(c)** ao D701 ("As quatro passam ao nome da tela no dia da virada"), a **299 (b)** ao D703 ("A rede mostra a ocupação com o aviso") e
a **300 (b)** ao D704 ("Como nos Royalties").

**O que mudou nos desenhos.** No `docs/f3-relatorios/spec-carteira.md`: a CA1 deixa de esperar -- a espera de 1 a 2 s é aceita, com
o "carregando", a forma da consulta escolhida por motor, e a migração do índice das vendas por cliente entra nela (a 1.7, a CA1); a
CA6 deixa de esperar -- o PDF e a planilha da unidade inteira, a rede só com uma unidade, uma faixa ou um filtro, o limite de memória
subido só na montagem do arquivo (o `ini_set` que as planilhas antigas já usam), e a rede com uma faixa grande ao dono (o D706, da
correção abaixo; até a resposta, um teto técnico) (a X3); a X2 marcada (o e-mail como hoje); e a fatia nova **CA7**, o consumo do cadastro consertado (a
rotina da 01:10 refaz também quem teve venda cancelada, e o corte refaz todos uma vez). No `docs/f3-relatorios/spec-capacidade.md`: a
fatia nova **CR3** -- a ocupação da rede pela soma, com "N pessoas sem o horário gravado ficaram fora da conta" (e o singular) na
linha da rede -- e a **CR4** (da correção abaixo) -- a unidade hoje desativada que vendeu no mês como linha marcada, na soma da rede e
no filtro; o `rede.foraDasLinhas` e a frase das fechadas saem nela. No `docs/f3-relatorios/spec-datas-da-equipe.md`: a fatia nova **DE3**, as quatro permissões com o nome da tela. No
`docs/procedimento-de-corte-etapa-1.md`: o índice das vendas por cliente no passo 0 (o passo 4 e o item 15), o consumo refeito uma vez
e as quatro permissões renomeadas no passo 7 (os itens 16 e 17), o inverso das permissões no item 4 da volta e na tabela "O que quebra
a volta", o consumo em "O que fica no banco", e as três respostas na tabela das decisões. Nada foi executado: os passos estão escritos.

**O que se achou, e por que importa no dia.** O nome da permissão é lido pelo código: o `SessaoAcl::setAcl` pergunta ao ACL pelos
quatro nomes no login de todos, e o ACL do Laminas LANÇA com um nome que a tabela não tem -- renomear a linha sem o código, ou o
código sem a linha, derrubaria o login de todo mundo, e o código antigo pergunta pelos nomes antigos. Por isso a DE3 faz o código
aceitar os dois nomes, e a volta da etapa 1 devolve os nomes antigos antes de o código antigo servir. O D308 mudou de decisão (o índice
de produção, que ficava "para a migração final", entra no passo 0 da etapa 1) e voltou a EM ABERTO até a migração existir.

**As medidas, só leitura** (no rascunho da sessão, `p76u-01-*` a `p76u-07-*`, o `.sql` ou o `.php` e o `.txt` de mesmo nome; só ids,
contagens e números de unidade): os ids das quatro permissões (49, 40, 15 e 17, iguais nas duas cópias) e o índice das vendas nas duas
cópias; o refazer do consumo muda 4.707 clientes nas duas cópias (os 4.477 ativos do D698), e a seleção nova da rotina leva menos de
0,5 s; o arquivo sintético de 28.188 e de 50.000 linhas (521 MB e 914 MB na planilha); e a Capacidade em processo na cópia do MySQL --
a ocupação da rede em julho de 2026 passa de "--" a 41,37% (a régua nova) e 42,33% (a antiga), e em março de 2025 as 6 desativadas que
venderam entram (21.655 atendimentos em vez de 20.118). A desativada 49 traz, em março de 2025, 1.624 horários na régua nova sem
ocupação (quem atendeu está hoje noutra unidade), e a ocupação da régua nova da rede cairia de 60,05% para 12,29% -- foi ao dono na
correção (o D705).

**As fatias propostas** (para a fila; os relatórios vêm antes das contas a pagar, a 77): a **CA7** (o consumo, ~550 linhas, sem
dependência, antes do corte); a **CR3** (a 299 (b), ~350 linhas, depende da CR2, feita); a **CR4** (a 300 (b), ~450 linhas, depende
da CR3 e da resposta do D705); a **DE3** (as permissões, ~420 linhas depois da correção, depende da DE2, feita; o `UPDATE` no corte).
A migração do índice é da CA1, que deixa de esperar. A CA4 passa a esperar também a resposta do D706.

**Débitos:** o D697, o D698, o D700, o D701, o D703 e o D704 DECIDIDOS e EM ABERTO até as fatias deles; o D699 DECIDIDO (como hoje); o
D308 EM ABERTO (a migração do passo 0 da etapa 1); de pé continuam 201 (203 depois da correção, abaixo). Os portões dos débitos, das referências dos docs e dos segredos, saída 0; o `conferir-tudo.sh` inteiro, sozinho, 73 portões, saída 0; os testes dos arquivos de código com o comentário mudado, 134 casos em cada banco, e o vitest da Capacidade, 41 de 41, saída 0.
Nada da produção mudou; na `franquiabv_dev`, só SELECT.

**A correção depois da verificação em três lentes (04/11/2026).** Dez achados confirmados, oito distintos (dois vieram em dobro),
todos tratados só em documentação. **Dois foram ao dono, no lote:** o **D705** (a CR3 de antes punha na ocupação da rede a agenda
vazia de quem ficou no cadastro da unidade fechada -- em março de 2025, a 49 com 1.624 horários e nenhum ocupado derrubava a régua
nova da rede de 60,05% para 12,29% --, o efeito que a recomendação da 300 usou para descartar a (c); três formas medidas em processo
na cópia do MySQL, `p76u-09-*`, recomendada a (c), a agenda da fechada só nas réguas com algum horário ocupado: 60,05% e 42,85%) e o
**D706** (o teto técnico da Carteira na rede restringia o que a letra da 297 (a) libera -- a rede com a faixa de mais de 2 anos,
149.951 clientes --, e o "não tira nada de hoje" não estava provado; medido agora o cache de células em disco num documento
sintético, `p76u-08-*`: 50.000 linhas em 402 MB mas 148 s, e 149.951 sem terminar em 580 s; recomendada a (a), o teto). Por isso a
300 (b) passou da CR3 para a fatia nova **CR4**, que espera o D705, e a CR3 fica só com a 299 (b); a CA6 segue a (a) do D706 até a
resposta, e a CA4 espera. **No desenho:** a frase da 299 (b) vai na linha da rede, ao lado da porcentagem, e não no cartão do mês, e
as marcas das unidades passam ao termo da letra ("sem o horário gravado"); a 1.8 e a seção 4 do `spec-carteira.md` não dizem mais que
a CA6 espera; as planilhas antigas que sobem o limite são seis, e não oito; o D701 cita os leitores pelo nome que faltavam (o
`PrivilegioDaTelaNovaTest`, o comentário do `ensaiar-permissao-adm-api.php` e os `.teste.tsx`); **a DE3 ganhou duas guardas** -- as
fixtures das quatro permissões cedem também pelo nome antigo (trocadas só no nome, elas entrariam ao lado das linhas da cópia nos
clones do ensaio e no `franquiabv_dev`, os gerentes perderiam as quatro telas, e o `UPDATE` do ensaio criaria o nome repetido que
derruba o login), e o roteiro do corte para sem mudar nada se o nome novo já existir em outro id --; e o texto do procedimento e da
DE3 não conta mais com o nome único do PostgreSQL, que a cópia local não tem (só a `funcionalidades_pkey`, `p76u-10-*`, só SELECT
no catálogo). Débitos: o D705 e o D706 registrados; o D704 EM ABERTO até a CR4; de pé 201 para 203. Os portões dos débitos, das
referências dos docs e dos segredos, saída 0. Nada da produção mudou; na `franquiabv_dev`, só SELECT; o arquivo sintético não leu
banco nenhum, e o diretório do cache em disco foi apagado.


**Conferido por mim, depois da correção** (04/11/2026): os 11 achados das três lentes (10 confirmados, 8 distintos), lidos um a um, e
o relato da correção; o `git diff -U0` do código só com linhas de comentário (fora a lista de pares conhecidos do
`conferir-debitos-repetidos.php`); nenhum PHP órfão no contêiner (lido pelo `/proc`, que o contêiner não tem `ps`) depois do que a
correção matou pelo pid, e as quatro travas do `/tmp` apagadas; o `local.php` no `franquiabv_dev` pelo sha1; o `contar-debitos.php`
com 203 de pé (104 abertos, 4 medidos, 95 parciais; 501 de 704 não estão mais de pé). As perguntas do D705 e do D706 foram à página
como a 301 (recomendada a (c)) e a 302 (recomendada a (a)). As fatias novas entraram na fila, logo depois das da Capacidade e da
Carteira e antes da 77: a 76l (CAPACIDADE-CR3, a 299 (b), ~350), a 76m (CAPACIDADE-CR4, a 300 (b), ~450, espera a 301), a 76n
(DATAS-DA-EQUIPE-DE3, a 298 (c), ~420) e a 76o (CARTEIRA-CA7, a 295 (a), ~550, antes da CA5); a 76b e a 76e2 deixaram de esperar, e
a 76e espera também a 302. Só documentação e comentários: sem ciclo inteiro e sem navegador.

## 04/11/2026 -- Quanto falta e o ritmo, remedidos no Mac (o pedido do dono)

O dono pediu: "atualize manualmente quanto falta, ritmo (faz apenas 30h que migrei para o mac, quero medir o ganho de velocidade
aqui principalmente) e dai conseguir estimar melhor". Medido por mim e refeito por agentes que não viram a minha conta, com um
crítico em cada rodada (os roteiros no rascunho da sessão: `medir-ritmo-0411.py`, `medir-etapas-0411.py`, `ritmo-medir/`,
`ritmo-falta/`, `ritmo-critico/` e `debitos-0411/`), pela mesma régua de 16/10 (linhas de código com os testes, sem documentos,
dumps, carga, fixtures, lockfiles nem o inventário gerado).

**O ritmo.** No Mac (02/11 às 23h28 ao commit da 76k, 32,4 h de relógio): 30 commits, 35.038 linhas, **1.082 linhas por hora**, um
commit a cada 0,9 h (mediana). No Windows: 794 no melhor trecho (26 a 29/10), 519 na fila inteira (14/10 a 02/11) e 349 no fim (01 a
02/11). Só nos intervalos de até 4 h, 1.247 no Mac contra 842 no melhor trecho do Windows: **a máquina rende cerca de 1,2 a 1,5
vez**; o resto do ganho de relógio (2 vezes a fila do Windows) vem de não parar -- 42 paradas acima de 4 h somaram 311 das 471 h do
Windows, com os reinícios, o Docker fora e a rede caindo escritos nos commits; no Mac, uma, de 5,8 h. A fatia leva 45 min de mediana
no fluxo de agentes (28 rodadas: o implementador 28, as lentes 5, a correção 12) e perto de 1 h com o ciclo, o navegador e o commit;
o ciclo inteiro caiu de ~157 min para ~9 min 40 s. Contra: a amostra é de 32 h, os commits do Mac são menores (1.168 linhas contra
2.202), e as fatias G da VIP Class ainda não foram medidas aqui.

**A fila.** 26 blocos (os 22 que faltavam, contados com a régua consertada -- o roteiro antigo perdia a 76e1 e a 76e2, contava os
cabeçalhos 92 e 93 e dava a 76b, que parou na medida, como feita -- e as quatro da 76u), cerca de 42 mil linhas previstas; a razão
real sobre o previsto é 1,34 desde 01/11 e 1,53 desde 14/10: **~56 a 64 mil linhas**.

**Os débitos, remedidos.** O custo de 700 a 960 linhas por débito, de 21/10, nunca tinha sido medido. Pelo histórico, fechar um
débito custou **cerca de 600 linhas** (na fila só de débitos, 07 a 14/10: 71 fechados em 44.387 linhas, mediana 516 e média 625;
nos avulsos desde 15/10, mediana 451); e, lidos um a um, dos 201 de pé (a foto de antes da 76u) só cerca de 72 pedem código fora
da fila -- 13 já estão numa fatia da fila, 35 esperam o dono, 35 são passo do corte ou da produção, 31 são de depois do corte, 7 são
externos e 6 não serão feitos. A soma de baixo para cima saiu baixa: o crítico refez um em cada oito pelo código e por consertos
parecidos já feitos e achou os tamanhos 1,9 vez maiores (os testes são 65 a 70% das linhas, e o tirar de uma tela antiga custa
cerca de 400 linhas); corrigida, **~26 a 103 mil linhas, com o centro perto de 64 mil**; pelo histórico, 72 a 100 débitos de
código a 450-800 linhas, 32 a 80 mil. Uso **~40 a 90 mil** (eram 133 a 182 mil). Dois corpos estavam defasados: o D285 e o D123
têm a resposta do dono desde 12/10 (a 87 (a) e a 99 (a)) e o registro ainda diz "decidir".

**Quanto falta: cerca de 81% feito, entre 78% e 85%** (~616 mil feitas; faltam ~106 a 174 mil: a fila 56 a 64, os débitos 40 a 90,
o preparo do corte 10 a 20). Era 73% na primeira conta de hoje e 70% em 01/11: **a maior parte do salto é a medida dos débitos, não
trabalho feito**. As horas, no ritmo do Mac: cerca de 130 a 195 (a fila que eu faço sozinho, ~59 h; a que espera o dono, ~13 h;
os débitos, 37 a 83 h; as respostas que mudam código fora da fila, 10 a 20 h; o preparo do corte, 9 a 18 h) -- **de 9 a 12/11** no
ritmo destas 32 h, **de 15 a 20/11** com metade do relógio parado. Fora da conta: as janelas do corte na produção, que dependem do
dono. O Artifact do roadmap foi republicado com estas contas.

## 04/11/2026 -- CAPACIDADE-CR3 (a fatia 76l da fila das telas que faltam)

**O que muda para quem usa:** na Relatórios › Capacidade × realizado, o administrador passa a ver a ocupação da rede também nos
meses em que falta o horário gravado de alguém (a CR3 de `docs/f3-relatorios/spec-capacidade.md`; a resposta 299 (b) do dono, "A
rede mostra a ocupação com o aviso"). Até hoje a linha "Todas as unidades" ficava com o travessão nesses meses, e eles eram quase
todos: em julho de 2026, 8 de 148 pessoas bastavam. Agora ela mostra a porcentagem da soma das unidades, cada régua à parte (julho
de 2026: 41,4% no horário da época e 42,3% na régua antiga; março de 2025: 60,0% e 43,9%), e, ao lado, quantos ficaram fora da conta:
"8 pessoas sem o horário gravado ficaram fora da conta" (ou "1 pessoa ... ficou ..."; nada quando todos têm o horário). A frase
longa do cartão do mês saiu. Nas linhas das unidades, a marca passou ao mesmo termo ("3 pessoas sem o horário gravado"), e o "Como
os números são contados" também; "horário da época" ficou só como o nome da régua. As unidades hoje fechadas que venderam no mês
continuam fora das linhas, com a frase de antes: elas são a CR4, que espera a resposta 301 (o D705).

**O que mudou no código:** a `ContasDaCapacidade::rede` (a taxa da soma sem a condição da cobertura; a sem base continua nula), o
`textosDaCapacidade.ts` (a frase da letra, as marcas com o termo novo, o porquê do travessão só sem base), o `Capacidade.tsx` (a
marca da rede na linha dela, a frase fora do cartão), o glossário (`capacidade.cobertura`), os comentários do `api/capacidade.ts`, e
os testes: `RegrasDaCapacidadeTest` (com o caso novo da taxa sem base), `CapacidadeDoRelatorioTest` (o
`testATaxaDaRedeSemCoberturaENula` virou `testATaxaDaRedeSemCoberturaEADaSoma`: o mutante M9 da 76i é agora o conserto),
`textosDaCapacidade.teste.ts`, `Capacidade.teste.tsx`, `glossario.teste.tsx` e a carga `testes/capacidade.ts`. Nenhuma consulta,
forma de resposta, rota nem esquema mudou.

**O provado:** os testes da Capacidade, do Parceiro, dos Royalties, do menu e do recorte, 424 casos em cada banco, saída 0, nenhum
pulado; o front dos arquivos tocados e do Console, 234 de 234, e amplificado com `AMPLIFICAR_EFEITOS_MS=50` os mesmos 6 vermelhos
do `Console.teste.tsx` de antes (a comanda e o caixa); o front inteiro 4.022 de 4.022; tsc, lint e build (362,58 KB em gzip), saída
0. Os mutantes, um por vez, a reserva num diretório novo a cada um, devolvidos pelo sha1, todos vermelhos pela asserção: a condição
da cobertura de volta na taxa da rede e as duas réguas somadas na rede (nos dois bancos), a frase de volta ao cartão do mês, o verbo
fixo no plural, a marca antiga nas unidades, a frase com a cobertura completa, o N só com quem está sem o horário, o travessão de
volta na tela e o glossário antigo. A medida em processo nas duas cópias (julho e agosto de 2026, março de 2025 e de 2024): as taxas
iguais nos dois bancos e às da tabela da CR3, a soma das linhas igual à da rede (24 conferências em cada), o tempo e o pico como
antes (0,53 s e 0,75 s quente; 118 a 122 MB). O oráculo da 76i com a taxa pela soma: 183 conferências em cada mês e cada cópia, 0
divergências (com a regra antiga, 1). Pelo HTTP no contrato MySQL, como o administrador de teste, o cenário de março de 2025 com uma
pessoa sem cobertura: 30 conferências, 0 divergências (falsificado, 1); a sessão apagada e o contrato remontado no fim. O ensaio de
permissão nos dois contratos e o `medir-adm-api-no-postgres.php`, saída 0. O navegador e o ciclo inteiro ficam para o coordenador.
Débitos: o D703 CORRIGIDO e o D306 com a marca; de pé 203 para 202. Nada da produção mudou; na `franquiabv_dev`, só SELECT.

**A correção da verificação** (três lentes, 4 achados baixos, todos confirmados no código): o glossário `capacidade.cobertura`
agora diz que o N da linha da rede SOMA os dois grupos -- os sem o horário gravado e aqueles de quem não se sabe se tinham agenda --,
porque a marca de cada unidade conta só os primeiros e quem somasse as marcas acharia menos que a rede diz (na carga de julho dos
testes, 1 nas unidades e 3 na rede); a frase da letra na rede não muda. O `glossario.teste.tsx` fixa o texto novo, e o mutante G2 (o
fim antigo de volta) deixa 1 caso vermelho. O docblock do `CapacidadeDoRelatorioTest` e o comentário do
`CenarioDaCapacidade::cobrirTodos`, que ainda descreviam a taxa da rede nula da CR0, foram reescritos (só comentário). A prévia do
design (`_capacidade.ts`, a taxa da rede ainda nula sem a cobertura) fica para o coordenador. Depois da correção: o
`RegrasDaCapacidadeTest` e o `CapacidadeDoRelatorioTest`, 17 casos em cada banco, saída 0; o front dos arquivos tocados e do
Console, 234 de 234; tsc, lint e build, saída 0; os portões dos débitos, das referências e dos segredos e o `conferir-tudo.sh`
sozinho, saída 0.


**Conferido por mim, depois da correção** (04/11/2026): os 4 achados das três lentes, todos baixos e confirmados, e o relato da
correção. O quarto, a prévia do design (`frontend/.design-sync/previews/_capacidade.ts`) ainda com a taxa da rede nula sem a
cobertura de todos, é meu e foi consertado: a `somar()` refaz a taxa pela soma, sem o parâmetro da cobertura, e o comentário de julho
diz o que a tela nova diz; os tipos da prévia com a da Capacidade conferidos por um `tsc` com um tsconfig temporário (apagado), sem
erro. O ciclo inteiro depois da correção: caracterização 6.187 em cada banco, saída 0 (11 pulados); contrato 74 e 74; os ensaios, os
portões e o front 4.022 de 4.022, saída 0; o medidor dos relatórios no PostgreSQL, saída 0; em cerca de 9 min 44 s. No navegador,
contra o banco de contrato, como o administrador de teste, a 1280 px: novembro de 2026 na rede, com a frase "5 pessoas sem o horário
gravado ficaram fora da conta" na linha de "Todas as unidades", a frase longa fora do cartão do mês, nenhuma marca "sem o horário da
época", e nada rolando de lado (o mês da cópia de contrato não tem agenda: a porcentagem com a cobertura incompleta foi conferida pelo
oráculo HTTP do implementador, não por mim na tela). Na limpeza, contei antes de apagar: 130 arquivos de sessão dos dois usuários
de teste no `/tmp` do contêiner, quase todos dos ensaios do ciclo; o `local.php` de volta ao `franquiabv_dev`, conferido pelo sha1, e
o banco de contrato remontado.

## 04/11/2026 -- DATAS-DA-EQUIPE-DE3 (a fatia 76n da fila das telas que faltam)

**O que muda para quem usa:** nada hoje. As cópias e a produção seguem com os nomes de hoje das quatro permissões, e o login segue
igual. No corte (o passo 7 da etapa 1, a resposta 298 (c) do dono, "As quatro passam ao nome da tela no dia da virada"), o cadastro
dos grupos e a janela de permissões da pessoa passam a mostrar "Relatórios de datas da equipe" (era "Relatórios de aniversariante"),
"Relatórios do profissional parceiro" (era "Relatórios dashboard colaboradores"), "Relatórios da carteira de clientes" (era
"Relatórios de clientes") e "Relatórios da cota-parte" (era "Relatórios de vendas"); quem tinha a permissão continua com ela (os
privilégios ligam pelo id). A "Relatórios dashboard colaboradores - plano carreira" não muda.

**O que mudou no código** (a DE3 de `docs/f3-relatorios/spec-datas-da-equipe.md`): o `SessaoAcl` pergunta ao ACL pelo nome novo das
quatro se a tabela o tem, e senão pelo antigo (`salvarAcessoPeloNomeNovo`; sem nenhum dos dois, lança, como hoje) -- assim o código
sobe antes do `UPDATE`, e a volta devolve os nomes antes de o código antigo servir. Novos: o roteiro do corte
`scripts/renomear-permissoes-das-telas-novas.sql` e o inverso `scripts/devolver-nomes-das-permissoes-das-telas-novas.sql` (os quatro
pares numa tabela temporária, um `UPDATE` pelo id e pelo nome inteiro, o `MUDOU`, as contagens de moldes e de pessoas antes e
depois, e duas paradas que saem com 1 sem mudar nada: o nome novo já em outro id, antes, e o id que não fica com o nome novo, dentro
da transação; `utf8mb4` explícito); o ensaio `scripts/ensaiar-permissoes-com-o-nome-da-tela.php` (no `conferir-tudo.sh`; fora do CI,
declarado no `conferir-lacuna-do-ci.sh`); e o `tests/Caracterizacao/PermissoesComONomeDaTelaTest.php` (6 casos). As quatro fixtures
com o nome novo e um `nome_antigo`, cedendo pelos dois (os ids não mudaram: o 90002039 segue o das Datas), e a marca regravada. Com o
nome novo: o `PrivilegioDaTelaNovaTest`, o `scripts/ensaiar-links-consertados-do-erp.php`, o comentário do
`scripts/ensaiar-permissao-adm-api.php` e o texto de carga do `UsuariosLigado.teste.tsx` e do `privilegioDaTela.teste.ts` (nenhum caso
mudou). Os leitores do `SessaoAcl` pelo texto: o `nomesDasFuncionalidadesDeRelatorio` devolve o nome novo (o caso no
`PortoesDaFamiliaDosRelatoriosTest`), e o `scripts/medir-funcionalidades-orfas.php` conta o par (na cópia, só SELECT: 2 órfãs, as
de antes, 0 fantasmas). O procedimento do corte cita os dois roteiros no passo 7, na tabela dos passos, no item 17 e na volta.
Nenhum esquema mudou; **as cópias locais não foram renomeadas**.

**O provado:** o `PermissoesComONomeDaTelaTest`, o `PrivilegioDaTelaNovaTest`, o `PortoesDaFamiliaDosRelatoriosTest` e os vizinhos
(as classes que leem o `SessaoAcl`, as funcionalidades ou as quatro, e os das Datas, da Carteira, da Cota-parte e do menu), 1.376 casos em
cada banco, saída 0, 1 pulado em cada (o `RecorteDeUnidadeNaAgendaTest::testComOPrivilegioGeralAListaAlcancaAsOutrasUnidades`, sem a
cópia). O ensaio novo, saída 0: a ida e a volta duas vezes cada (o `MUDOU` 4 e depois 0, as contagens iguais, a "plano carreira" e a
"de vendas canceladas" intactas), dez paradas sem mudar nada (o nome novo de cada uma das quatro em outro id, o id com outro nome --
os outros três não ficam renomeados --, e o mesmo no inverso), e as fixtures nos dois bancos, por cima do catálogo antigo (nenhum
nome novo, nenhum repetido, 58 das 63) e do vazio (as 63, com os quatro novos). A suíte de contrato, 74 e 74. Os ensaios de permissão
e de recusas nos dois contratos e o dos links consertados no MySQL, saída 0 (as travas `/tmp/*.lock` contadas e apagadas: 27 e 5). O
front dos arquivos tocados, 121 de 121, e amplificado com `AMPLIFICAR_EFEITOS_MS=50`, 121 de 121; tsc e lint, saída 0. O oráculo pelo
HTTP no contrato MySQL, como o gerente de teste (com as quatro concedidas por mim) e o colaborador (sem elas, o controle): a sessão
com as oito marcas das quatro e as quatro telas (as APIs das Datas, do Parceiro e da Cota-parte com 200 e JSON, o Clientes antigo com
200; o colaborador com 403 e 404) -- com o nome novo e, depois de um `UPDATE` meu só no contrato, com os quatro antigos: 52
conferências, 0 divergências, os dois lados iguais; falsificado (o ajudante só com o nome novo), o login com os nomes antigos cai. As
sessões do oráculo apagadas no contêiner, e o contrato remontado no fim.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, todos vermelhos: o ajudante só
com o nome novo (o login com o nome antigo lança) e só com o antigo (o do nome novo), nos dois bancos; o `UPDATE` sem o "e o nome
antigo" (o id com outro nome é renomeado); o nome casado por `LIKE`, sem o id (a "plano carreira" e a "de vendas canceladas"
renomeadas); uma das quatro fora do roteiro; a guarda do nome repetido tirada (o nome novo fica em dois ids); a conferência de
depois tirada, e posta depois do `COMMIT` (os outros três ficam renomeados); os três do inverso (sem o "e o nome novo", sem a guarda
do repetido, uma das quatro fora); a fixture cedendo só pelo nome novo (por cima do catálogo antigo entram os quatro novos, nos dois
bancos), uma fixture com o nome antigo (o catálogo vazio e o caso dos pares, nos dois bancos); e o leitor dos nomes dos relatórios
sem o ajudante. Os portões: o `conferir-tudo.sh` sozinho, 74 portões, saída 0 (o novo entre eles); `contar-debitos.php`,
`conferir-debitos-repetidos.php`, `conferir-referencias-docs.php` e `conferir-segredos.sh`, saída 0. O navegador e o ciclo inteiro
ficam para o coordenador. Débitos: o D701 PREPARADO (falta o `UPDATE` do passo 7 e o ensaio num clone, o item 11); de pé 202 para
201. Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na `franquiabv_dev`, só SELECT.

**A verificação em três lentes** confirmou 6 achados, um médio e cinco baixos, todos tratados. **O médio:** na volta, o roteiro que
devolve os nomes era chamado pelo caminho relativo da pasta `scripts` depois da troca das árvores, e ele só existe na árvore nova, que agora é a
`public_html.novo-<data>`: o `mysql` nem rodaria, os quatro nomes ficariam os novos, e o `SessaoAcl` antigo lançaria no login de todos.
Agora o item 4 da volta o chama pelo caminho absoluto da árvore nova, e a tabela dos passos e "O que quebra a volta" dizem o mesmo
("antes de o código antigo servir", com a manutenção no ar). **Os baixos:** o critério "o roteiro sai com 0" do passo 7 lido no `$?`
do `| tee`, que sai com 0 mesmo quando o roteiro para (agora o `echo` do `PIPESTATUS[0]` na mesma linha, e o roteiro pelo caminho
absoluto da árvore no lugar); o texto do passo 7 e do item 17 prometendo conferências que o roteiro não faz (agora diz o que para --
o nome novo em outro id, antes, e o id sem o nome novo, depois -- e o que só mostra, para o operador ler); a regra do
`medir-funcionalidades-orfas.php` sem caso e o texto dele dizendo que o nome que falta "fecha a tela", quando o login lança (a regra
passou para o novo `scripts/regras-das-funcionalidades-lidas.php`; na cópia, só SELECT, as mesmas 2 órfãs e 0 fantasmas); e o `SET
NAMES utf8mb4` dos roteiros sem caso (o ensaio roda a ida e a volta também com o cliente em latin1). Três casos novos no
`PermissoesComONomeDaTelaTest` (agora 9): o medidor conta os dois nomes do par (e usa a regra, lido sem comentários), a volta chama o
roteiro pela árvore nova, e o passo 7 lê o `PIPESTATUS`. Os mutantes novos, um por vez, a reserva num diretório novo e vazio a cada um,
devolvidos pelo sha1, todos vermelhos: a volta com o caminho relativo, o `PIPESTATUS` tirado, o passo 7 com o caminho relativo, a
regra sem a leitura do ajudante, o medidor com a leitura antiga copiada, os fantasmas sem o par, e o `SET NAMES` tirado de cada
roteiro (o ensaio). Os testes da fatia (`PermissoesComONomeDaTelaTest`, `PrivilegioDaTelaNovaTest` e
`PortoesDaFamiliaDosRelatoriosTest`), 42 casos em cada banco, saída 0; o ensaio novo, saída 0, em cerca de 8 s. O D701 segue
PREPARADO, com a nota. O navegador e o ciclo inteiro ficam para o coordenador.


**Conferido por mim, depois da correção** (04/11/2026): os 6 achados das três lentes (um médio, a volta chamando o roteiro inverso
por um caminho que não existe depois da troca das árvores), lidos um a um, e o relato da correção. A correção apontou um defeito
anterior no mesmo passo 7: as chamadas dos roteiros `colunas-gravadas-*` e do encerrar-pacotes usam o caminho relativo depois do
`cd` da troca, e a do encerrar-pacotes passa pelo `| tee` sem ler a saída do `mysql`; o conserto entrou nas notas da 76o (a CA7, que
põe o roteiro do consumo no mesmo passo). O ciclo inteiro depois da correção: caracterização 6.196 em cada banco, saída 0 (11
pulados); contrato 74 e 74 (as fixtures com os nomes novos); os ensaios, os portões (74, com o ensaio novo) e o front 4.022 de 4.022,
saída 0; o medidor dos relatórios no PostgreSQL, saída 0; em cerca de 9 min 51 s. No navegador, contra o banco de contrato (que nasce
com os nomes novos), como o administrador de teste: o login e a `/adm/api/sessao` com 200 e JSON; em Configurações › Grupos e
privilégios › por funcionalidade, as quatro com o nome novo, uma vez cada, nenhuma com o antigo, e a "Relatórios dashboard
colaboradores - plano carreira" e a "Relatórios de vendas canceladas" intocadas. Na limpeza, as sessões dos dois usuários de teste
contadas e apagadas no contêiner; o `local.php` de volta ao `franquiabv_dev`, conferido pelo sha1, e o banco de contrato remontado.

## 04/11/2026 -- CARTEIRA-CA7 (a fatia 76o da fila das telas que faltam)

A CA7 do `docs/f3-relatorios/spec-carteira.md`, pela resposta 295 (a) do dono ("Consertar a conta"; o D698). **O que muda para quem
usa:** a partir da rotina da 01:10 do dia seguinte ao código no ar, o cliente que teve uma venda cancelada nos dois últimos dias tem o
consumo do cadastro refeito, mesmo sem voltar -- antes, a venda cancelada seguia somada até ele aparecer de novo; e no corte, uma vez,
todos os clientes ficam com o total certo das vendas finalizadas (na cópia, 4.707 mudam: os 4.477 ativos do D698 e 230 entre os
inativos e os dois sentinelas). O Clientes antigo, as Campanhas e, quando vier, a Carteira leem esse número. Hoje, nas cópias, nada
muda: o refazer só roda no corte e nos bancos descartáveis.

**O código:** a rotina (`CronJobService::clientesAtualizarConsumo`) lê a seleção por `clientesDoConsumoARefazer()`, que junta aos que
vieram os clientes ativos com uma venda de `cancelado_em` de anteontem a hoje -- a consulta nova
`ClienteRepository::selecionarClientesComVendaCanceladaNoPeriodo`, em DQL, com os parâmetros ligados (o booleano do cliente e os
instantes) --, sem repetir quem está nas duas; o log diz as duas partes. O cancelar não mudou. O roteiro do corte,
`scripts/refazer-consumo-dos-clientes.sql` (só MySQL; a carga da etapa 2 leva o número refeito): a contagem antes, a lista dos que
mudam com o número de antes, o `UPDATE` só onde a coluna difere da soma em mais de 1 centavo (em centavos inteiros; o nulo sem venda
fica nulo), e a conferência (0); idempotente. No passo 7 de `docs/procedimento-de-corte-etapa-1.md`, logo depois dos pacotes. E o
conserto que a 76n apontou no mesmo passo: as sete chamadas de `colunas_gravadas`, a dos pacotes e o `verificar-local-php.php` usavam
o caminho relativo, que não existe no diretório da troca das árvores, e a dos pacotes lia o `$?` do `tee`; agora todo roteiro do passo
7 vai pelo caminho da árvore no lugar e diz "o mysql saiu com" o `PIPESTATUS[0]`. O portão `conferir-cobertura-por-par.php` com o par
novo (424 para 425 pares; o ISApi de 106 para 107 chamadas; o "sem menção" não mudou, 116).

**O provado:** o `tests/Caracterizacao/ConsumoDoClienteTest.php` novo, 11 casos (quem veio, o cancelado ontem sem voltar, a comanda
aberta cancelada pela rotina das 23:58 com o mesmo número, o de três dias atrás fora, o inativo fora, o cliente nas duas partes uma
vez com o log, a seleção pelo repositório, e os quatro do roteiro), com o `EncerrarPacotesDeVendaCanceladaTest` e o
`PermissoesComONomeDaTelaTest` (o caso do passo 7 estendido a todo roteiro): 24 casos, saída 0 no MySQL; no PostgreSQL, saída 0 com 2
pulados (os dois que rodam o roteiro, que é do MySQL). Os vizinhos (as rotinas, as canceladas, as colunas do PDV, a listagem de
vendas, os segredos, o marketing, a indicação), 234 casos em cada banco, saída 0. Os mutantes, um por vez, a reserva num diretório novo
e vazio a cada um, devolvidos pelo sha1, todos vermelhos: a seleção nova arrancada, a janela de três dias, o inativo dentro e a junção
repetindo (nos dois bancos); o `UPDATE` sem a régua de 1 centavo, o nulo sem venda virando zero, o `COALESCE` tirado do `SET` e da
comparação; o roteiro do consumo, o `verificar-local-php.php` pelo caminho relativo de volta e o `PIPESTATUS` tirado de uma linha.
**O oráculo**, num clone descartável da cópia do MySQL (`clientes` e `vendas` inteiras, apagado no fim): a contagem antes 4.707, igual
à do desenho; o `UPDATE` refez 4.707, a conferência e a segunda rodada deram 0; os 20 sorteados com a soma; nenhuma outra coluna
mudou; cerca de 19 s. A contagem antes, só SELECT, nas duas cópias: os mesmos oito números. Os portões `conferir-injecao-dql.php`,
`conferir-booleanos-em-consulta.php`, `conferir-referencias-docs.php`, `conferir-cobertura-por-par.php`,
`conferir-relogio-nos-testes.php` e `conferir-segredos.sh`, saída 0; `contar-debitos.php` e `conferir-debitos-repetidos.php`, saída 0.
Débitos: o D698 PREPARADO (falta o `UPDATE` no corte e o ensaio da etapa 1); de pé 201 para 200. A tela não muda (a frase da Carteira
nasce na CA5). O ciclo inteiro e o commit ficam para o coordenador. Nada da produção, do BVIP, do OneSignal nem do crontab mudou; na
`franquiabv_dev`, só SELECT.

**A correção da verificação (04/11/2026, as três lentes; 8 achados, 7 distintos, todos baixos, todos confirmados no código).** (1) A
seleção nova era um `EXISTS` partindo de `clientes`, que o MySQL da cópia resolvia em 4,2 a 4,4 s (os 237 mil clientes pelo
`FK_VENDAS_cliente`; o `IN (subconsulta)` e o `DISTINCT` caem no mesmo plano), e o "0,26 a 0,45 s" do desenho era a leitura de
`vendas` sozinha: agora são duas leituras, os ids das vendas canceladas no intervalo, sem `DISTINCT` (0,23 a 0,25 s), e os clientes
ativos pela chave; no PostgreSQL, cerca de 50 ms nas duas formas (só SELECT, `p76o-corr/` no rascunho da sessão). (2) No
procedimento, o parágrafo do consumo com o tempo medido (cerca de 19 s no clone) no lugar do "não foi medido", e a linha 7 da linha
do tempo contando o refazer. (3) O passo 8 chamava `sh scripts/bloquear-migrar-senhas.sh --aplicar` pelo caminho relativo, no
diretório da troca do 7, onde `scripts/` não existe (no texto e na linha 8 da tabela da janela): agora pelo caminho da árvore no
lugar, e o caso do passo 7 vai até o próximo `cd` e lê as linhas 7 a 13 da tabela. (4) A descrição da rota na documentação da `/api`
(`ApiDocsController`) e (5) a frase da F4 da Carteira, com a venda cancelada. (6) O caso do cancelamento à meia-noite de anteontem
pela rotina. (7) O cliente sem nenhuma venda finalizada fica com o nulo pela rotina e com zero pelo roteiro, e o filtro antigo trata
os dois diferente: **é escolha do dono** -- o **D707** novo e a pergunta no lote ("O cliente que não tem nenhuma compra fica com o
consumo zero ou em branco?"), recomendada a (a), zero nos dois; até a resposta os dois lados ficam como o desenho os pôs, com um caso
fixando o nulo da rotina. Os casos novos no `ConsumoDoClienteTest` (15 casos agora): a meia-noite de anteontem, a seleção em duas
leituras (com o plano no MySQL), o nulo da rotina e os textos; e o `PermissoesComONomeDaTelaTest` estendido. A fatia (o Consumo, os
pacotes e as permissões), 28 casos, saída 0 no MySQL; no PostgreSQL, saída 0 com os mesmos 2 pulados. Os vizinhos (as rotinas, as
canceladas, os cupons e o push das rotinas, a injeção do relatório de clientes, os segredos, a campanha cancelada, o desativar do
cliente), 158 casos em cada banco, saída 0 (2 pulados no PostgreSQL). Os mutantes N1 a N12, um por vez, a reserva num diretório novo e
vazio a cada um, devolvidos pelo sha1, todos vermelhos pela asserção: o `EXISTS` de volta, o `DISTINCT`, o começo da janela em
ontem, o `>` no lugar do `>=`, o zero na rotina, a segunda leitura sem nenhum id (os de servidor nos dois bancos); o passo 8 e a
linha 8 da tabela pelo caminho relativo, a linha do tempo sem o refazer, o "não foi medido" de volta, a descrição antiga da `/api`
e a frase antiga da F4. Os portões `conferir-injecao-dql.php`, `conferir-booleanos-em-consulta.php`, `conferir-referencias-docs.php`,
`conferir-cobertura-por-par.php`, `conferir-relogio-nos-testes.php`, `conferir-segredos.sh`, `contar-debitos.php` e
`conferir-debitos-repetidos.php` (com o par D698|D707 registrado), saída 0. Débitos: o D707 registrado; de pé 200 para 201. O ciclo
inteiro, o navegador e o commit ficam para o coordenador.


**Conferido por mim, depois da correção** (04/11/2026): os 8 achados das três lentes (7 distintos, todos baixos), lidos um a um, e o
relato da correção; a pergunta do D707 (o consumo zero ou em branco de quem não tem compra finalizada) foi à página como a 303,
recomendada a (a), e nada na fila espera por ela. O ciclo inteiro depois da correção: caracterização 6.211 em cada banco, saída 0 (11
pulados no MySQL, 13 no PostgreSQL: os dois a mais são os casos do roteiro do corte, só do MySQL, pulados pelo nome com o motivo);
contrato 74 e 74; os ensaios, os portões (74) e o front 4.022 de 4.022, saída 0; o medidor dos relatórios no PostgreSQL, saída 0; em
cerca de 9 min 48 s. Sem navegador: a fatia não tem tela. O `local.php` no `franquiabv_dev`, conferido pelo sha1. Débitos: de pé 201
(o D698 PREPARADO e o D707 registrado).

## 04/11/2026 -- CAPACIDADE-CR4 (a fatia 76m da fila das telas que faltam)

**O que mudou para quem usa.** Na Capacidade × realizado, num mês passado, o administrador passa a ver também as unidades que hoje
estão fechadas e trabalharam naquele mês, como linhas marcadas "desativada", na ordem do nome com as outras e na soma da rede -- as
pessoas, a cobertura e o realizado delas --, e pode escolher uma delas no filtro de unidade (o filtro passa de 36 a 64 unidades, com
" (desativada)" no nome); a escolhida num mês em que não vendeu vem como linha, e o cartão do mês diz "Esta unidade está desativada e
não vendeu neste mês: a rede não a conta." A fechada que só tinha agenda no mês e não vendeu continua fora. A frase do cartão "N
unidades hoje fechadas venderam neste mês e não estão na conta." saiu. **Pela resposta 301 (c) do dono** (o D705, de 04/11/2026), a
agenda da fechada entra na rede só na régua em que algum horário foi ocupado no mês: a linha dela mostra a agenda inteira, e a marca
diz, régua a régua, o que ficou fora ("1.624 horários sem ocupação ficaram fora da rede"; no singular, "1 horário ... ficou ..."). Na
cópia, março de 2025 passa a ter 6 fechadas (21.655 atendimentos na rede, e não 20.118), e a ocupação da régua nova da rede fica em
60,0%, e não nos 12,3% que a agenda vazia da 49 traria; a frase da linha da rede soma a cobertura delas (23 fora da conta, e não 18).
O glossário (`capacidade.rede`) diz a regra. Os arquivos: `ContasDaCapacidade`, `CapacidadeController`, `UnidadeRepository`
(a consulta nova `capacidadeDesativadasComVenda`), `api/capacidade.ts`, `textosDaCapacidade.ts`, `Capacidade.tsx`, `glossario.tsx`.

**Onde se afastou do desenho, medido:** as linhas da rede são lidas uma vez, antes do pico, e não numa segunda leitura depois dos
itens -- a segunda caía no `OutOfSpaceException` do cache de consultas com `memory_limit = 128M`; e o `EXISTS` do filtro foi para o
`SELECT`, porque no `WHERE` o MySQL o fazia semi-join e levava 2,8 s em todo pedido (no `SELECT`, 2 a 13 ms). A marca da agenda fora
da rede diz a régua e os horários, e não a frase genérica sugerida, porque a (c) é régua a régua.

**O provado.** `RegrasDaCapacidadeTest` e `CapacidadeDoRelatorioTest` (com o `CenarioDaCapacidade` ganhando X, Y e Z): 19 casos e 219
asserções em cada banco, saída 0; os vizinhos (Royalties, Parceiro, a família dos relatórios, os portões, as unidades), 629 casos em
cada banco, saída 0, 1 pulado pelo nome (`RecorteDeUnidadeNaAgendaTest::testComOPrivilegioGeralAListaAlcancaAsOutrasUnidades`, o
banco sem a cópia). O front: os tocados e o Console, 276 casos; com `AMPLIFICAR_EFEITOS_MS=50`, os da Capacidade verdes e os mesmos
6 casos da comanda e do caixa do `Console.teste.tsx` vermelhos que a 76l já registrava (não tocados aqui); o front inteiro 4.024 de
4.024; tsc, lint e build, saída 0. Os mutantes S1 a S8 (a desativada fora das linhas, a só com agenda como linha, a agenda inteira na
rede, a agenda toda fora, a (c) pela unidade e não pela régua, o filtro sem as desativadas, o filtro com a que nunca vendeu, o
`foraDasLinhas` de volta), nos dois bancos, e F1 a F6 e G1 (a marca tirada, o verbo fixo, a frase das fechadas de volta, a frase
da sem venda tirada, as marcas da agenda na sem venda, as duas réguas somadas na marca, o glossário antigo), um por vez, a reserva
num diretório novo e vazio a cada um, devolvidos pelo sha1, todos vermelhos pela asserção. A medida em processo nas duas cópias,
março de 2025 e de 2024 e julho de 2026, igual à tabela (c) do desenho nos dois bancos (30 conferências em cada, 0 divergências), a
rede em 0,52 a 0,59 s no MySQL e 0,68 a 0,75 s no PostgreSQL, o pico de um pedido 118 a 122 MB, cada mês num processo novo (na
medida dos três meses num processo só, julho, o último, deu 124 MB nos dois bancos: o pico acumulado do processo; a nota no D631). O
oráculo por SELECT, unidade a
unidade com as desativadas: 258 conferências (março de 2025) e 264 (março de 2024) em cada cópia, 0 divergências; falsificado com a
(a), 1. Pelo HTTP no contrato MySQL, como o administrador de teste: 32 conferências, 0 divergências; falsificado, 1; o contrato
remontado. O ensaio de permissão nos dois bancos de contrato e o medidor da `/adm/api` no PostgreSQL, saída 0. Os portões
`conferir-recorte-por-unidade.php` (as vigias novas do `montar` e do `unidadesDoFiltro`; falsificado, vermelho) e
`conferir-cobertura-por-par.php` (o par novo, de 425 para 426, e o ISRelatorio de 187 para 189), saída 0; `conferir-injecao-dql.php`,
`conferir-booleanos-em-consulta.php`, `conferir-rotas-sem-login.php` e `conferir-guarda-do-erp.php`, saída 0. Débitos: o D704 e o
D705 CORRIGIDOS, o D306 PARCIAL; de pé 201 para 199. A prévia do design ficou como estava (é da rodada do design: ela ainda tem o
`foraDasLinhas`). Nada da produção mudou; na `franquiabv_dev`, só SELECT. O navegador, o ciclo inteiro e o commit são do coordenador.

**A verificação em três lentes confirmou 5 achados (4 distintos), todos baixos, tratados:** o comentário do `CapacidadeController::
montar` citava um `$faltam` que a leitura única deixou de ter (agora aponta o `array_filter` pelos `$itensPorUnidade`); o começo da
CR4 no desenho ainda dizia que ela esperava o D705 e que a tela seguia a (a) do D704, e as dependências da seção 5 não diziam que a
301 (c) chegou; nenhum caso pegava as duas condições do `EXISTS` do filtro -- o `CenarioDaCapacidade` ganhou Q (só com venda
cancelada e comanda aberta, com item) e T (só com venda concluída sem item), fora do filtro, fora das linhas e com o 422, e os
mutantes SQ (a condição da concluída neutralizada) e ST (o `JOIN` dos itens tirado), vermelhos pela asserção nos dois bancos; e os
números da medida -- o docblock da consulta do filtro dizia 4 a 13 ms e os documentos 2 a 13 (agora 2 a 13 em todos; remedida, 2 a
4 ms), o D704 dava outro tempo da rede, e o pico de 124 MB de julho guardado na medida não estava escrito: remedido cada mês num
processo novo, 118 a 122 MB no MySQL e 118 em julho no PostgreSQL, e os 124 são o acumulado dos três meses num processo só (a nota
no D631). Depois da correção: `RegrasDaCapacidadeTest` e `CapacidadeDoRelatorioTest`, 19 casos e 231 asserções em cada banco, saída
0.


**Conferido por mim, depois da correção** (04/11/2026): os 6 achados das três lentes (5 confirmados, todos baixos), lidos um a um, e o
relato da correção. A prévia do design (`frontend/.design-sync/previews/_capacidade.ts` e `Capacidade.tsx`), que a fatia deixou de
fora por não ser dela, foi acertada por mim: sem o `foraDasLinhas` na rede, cada linha com `unidade.ativa` e `agendaForaDaRede`, e os
comentários sem a frase das fechadas no cartão e sem o travessão da rede sem cobertura; os tipos conferidos por um `tsc` com um
tsconfig temporário (apagado), sem erro. A desativada ainda não tem célula na prévia: fica para a próxima rodada do design. O ciclo
inteiro depois da correção: caracterização 6.213 em cada banco, saída 0 (11 pulados no MySQL, 13 no PostgreSQL, os dois do roteiro do
consumo); contrato 74 e 74; os ensaios, os portões (74) e o front 4.024 de 4.024, saída 0; o medidor dos relatórios no PostgreSQL,
saída 0; em cerca de 9 min 55 s. No navegador, contra o banco de contrato, como o administrador de teste, a 1280 px: a tela abre sem
erro, sem a frase das fechadas, com o filtro de 7 opções (o contrato não tem unidade inativa: a desativada como linha, na soma e no
filtro foi conferida pelo oráculo HTTP da fatia com o cenário semeado, não por mim na tela), a linha da rede como na CR3 e nada
rolando de lado. Na limpeza, as sessões dos dois usuários de teste contadas e apagadas no contêiner; o `local.php` de volta ao
`franquiabv_dev`, conferido pelo sha1, e o banco de contrato remontado.

## 04/11/2026 -- CARTEIRA-CA1 (a fatia 76b da fila das telas que faltam)

**O que mudou para quem usa.** O grupo Relatórios do menu novo ganhou o item "Carteira de clientes", no lugar do Clientes no layout
(depois do Caixa), para o gerente e o administrador; ele abre a tela ainda não migrada, com a ponte para o Clientes antigo, até a
CA2 (a tela). O Clientes antigo passou a recusar, nas quatro ações (a lista, o PDF, a planilha e o TXT), quem tem o privilégio e não
é gerente nem administrador -- como a API nova recusa (a E10 de `docs/f3-relatorios/spec-carteira.md`); o item dele no menu antigo
segue a mesma política (`veOMenu`). A API nova existe: `GET /adm/api/relatorios/carteira` (o retrato de hoje: as oito faixas, o em
dia, o sumido, os sem visita; na unidade, o resumo por colaborador do último serviço; na rede, uma linha por unidade ativa e a conta
de quem a regra põe numa unidade hoje fechada), `.../clientes` (a lista de uma faixa ou de um colaborador, 50 por página, com o
contato, o próximo agendamento e a marca de quem não aceita mensagens; o e-mail só para o administrador) e `.../unidades`. A resposta
294 (a) do dono: a conta exata, com a espera de 1 a 2 s.

**A medida, em processo, como o administrador, `memory_limit=128M`, na cópia, hoje = 04/11/2026** (o primeiro pedido do processo; os
seguintes), refeita na correção com o código final (o SQL em `SqlDaCarteira` e a lista da linha "sem serviço"): a rede em 2,19 s;
1,78 s no MySQL de fábrica (pico 33,7 MB) e 1,18 s; 1,06 s no PostgreSQL (18 a 20 MB); a unidade 1 em 2,05 s; 1,96 a 1,98 s e 1,27 s;
1,21 s (38 MB); a lista de uma faixa da unidade 1 em 1,33 s; 1,29 s e 0,90 s; 0,77 s; a da linha "sem serviço" da unidade 1 (134
clientes) em 1,85 a 1,87 s e 1,63 a 1,64 s. A primeira medida, com o SQL ainda no `ClienteRepository` (o mesmo texto, movido por
roteiro depois dela), deu a rede em 1,98 s; 1,82 s e 2,40 s; 1,26 a 1,43 s, a unidade 1 em 2,95 s; 1,99 a 2,00 s e 1,63 s; 1,19 a
1,27 s, e a de uma faixa da rede em 1,36 a 1,39 s e 0,82 a 0,84 s (essa não foi refeita). A rede não carrega os 239 mil clientes:
eles chegam um a um à regra.

**O que entrou.** A consulta (`ClienteRepository::carteiraDaRede`, `::carteiraDaUnidade` e `::carteiraLista`: SQL nativo pelo DBAL,
uma constante literal por motor em `ISCadastro\Entity\SqlDaCarteira` -- a subconsulta por cliente no MySQL e o `DISTINCT ON` no PostgreSQL; fora do repositório porque o `FROM clientes c` dela confundia o apelido da DQL vizinha nos portões das ordenações e dos booleanos, que declaram o SQL nativo fora do que leem --, só até hoje, os booleanos
separados dos inteiros, os sentinelas fora); a regra pura ganhou o `porUnidade` (a rede numa passada, por um iterador), o
`intervaloDaFaixa` e o `filtroDaLista` (`ContasDaCarteira`); o `Adm\CarteiraController` (as guardas da base, o filtro antes de soltar
a sessão, a trava da rede por pessoa no retrato e na lista, cada linha conferida pela regra), o `CarteiraJson`, a rota e a fábrica; a
política com o `naTelaNova` ligado, o item do menu com o `quando` da guarda, a linha da `TELA_DO_RELATORIO` com a tela não migrada e o
item do `layout.phtml` pelo `veOMenu`; a guarda nas quatro ações do `ClientesController`. **A migração do índice das vendas por
cliente** (`scripts/migracao-vendas-indice-cliente-status-data.sql`, a 294 (a), o D308) no laço do passo 0 do
`docs/procedimento-de-corte.md` e do `docs/ensaio-geral-do-corte.md`, com o `KEY` de três colunas no `mysql.sql`; o
`conferir-passo-zero-do-corte.php` passou a cobrar também as colunas do índice que o `mysql.sql` tem com o mesmo nome (falsificado
com o `KEY` de volta a uma coluna: antes do trecho novo dava 0, agora 1); o `postgres/schema.sql` regerado é o mesmo, byte a byte.
Ensaiada num banco descartável (a `vendas` da cópia, os ids dos clientes, a chave estrangeira e o índice de uma coluna): 2,3 s, a
segunda vez sem efeito em 0,08 s, e com o índice noutra forma a parada com saída 1; apagado no fim. Nas cópias o índice já estava (o
D308; conferido no `information_schema` e no `pg_indexes`), e nada foi rodado nelas.

**Os testes.** Novos: `CarteiraDoRelatorioTest` (24 casos, com o `CenarioDaCarteira`, ids 90002100 a 90002282) e
`ClientesAntigoNaPoliticaTest` (6 casos); o `RegrasDaCarteiraTest` com 4 casos novos (45 no todo). Acertados: o `RelatoriosNoMenuTest`
(a `SEM_ITEM_NO_MENU` vazia, e o caso dela com uma asserção), o `RegrasDoRecorteDoRelatorioTest` (a Carteira na lista das ligadas), o
`PortoesDaFamiliaDosRelatoriosTest` (as três rotas) e o `D203TextoDigitadoNasTelasDoErpTest` (a sessão dupla devolve o usuário
inteiro, como a `SessaoAcl`). Os da fatia e os vizinhos (26 arquivos), 780 casos em cada banco, saída 0 (pulados: no MySQL o
`VendasNoRelatorioTest::testODescontoNaoSomaODeOutraUnidade`, sem a cópia; no PostgreSQL ele e os dois do roteiro do consumo, só do
MySQL). O front: 4.024 de 4.024; amplificado (`AMPLIFICAR_EFEITOS_MS=50`), os mesmos 6 vermelhos do `Console.teste.tsx` que o HEAD,
pelo nome; tsc, lint e build, saída 0.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, todos vermelhos: M1 (a consulta
pela coluna `ultima_visita`, cada motor), M2 (a venda sem item contada), M3 (o sem visita fora, cada motor), M4 e M4b (a unidade do
cadastro para quem veio, no filtro e na regra do controller: vermelhos pela conferência de cada linha), M5 (o e-mail para o gerente),
M6 e M7 (o `aceitaMensagens` invertido e sempre verdadeiro), M8 (o telefone da linha vizinha), M9 (o sentinela dentro), M10 (o filtro
de colaborador ignorado), M11 (uma linha por colaborador de serviço da venda, cada motor: a `LogicException` da regra), M12 (a consulta
antes das guardas), M13 a M16 (cada ação do Clientes antigo sem a guarda), M17 (a data da venda lida em UTC, cada motor: a venda das
23h30 de ontem caía em hoje), M18 (o "só até hoje" tirado), M19 (a trava da rede tirada), M20 (o intervalo de outra faixa), nos dois
bancos; P1 a P4 (a regra pura: a rede pelo cadastro, a memória do dia, o fim da faixa, a página 0); F1 a F4 (a linha do Console -- na
tela equivalente, vermelha só pelo `RelatoriosNoMenuTest` --, o `quando` do menu, o item do layout pelo privilégio, o `naTelaNova`
desligado); G1 a G4 (a migração fora do laço, o `KEY` de uma coluna, a migração com outra coluna, o TXT antigo sem a guarda no portão do
recorte). As tabelas e os vermelhos por nome no rascunho da sessão (`p76b2/mutantes*.txt`).

**O oráculo** (Python, só SELECT, a regra escrita de novo pela janela `ROW_NUMBER`, contra o JSON do controller em processo, com o
"hoje" da resposta): as faixas da rede e de cada unidade, o de fora das linhas, as faixas e o resumo por colaborador da unidade 1, e a
página 1 de duas faixas da unidade 1 pelos ids e pelos colaboradores -- 353 conferências por banco e dia, 0 divergências, em hoje e em
17/08/2026 (o dia da 1.5 do desenho: a rede deu a tabela da 1.5 inteira, e a unidade 1 os 22.321, 71 colaboradores e 134 sem serviço);
falsificado (a venda sem item como visita), 56 e 57 divergências. Essa rodada foi com o SQL ainda no `ClienteRepository`; a correção
o rodou de novo com o código final e com a página 1 da linha "sem serviço" da unidade 1: 357 conferências por banco e dia, 0
divergências, nos dois bancos, em hoje e em 17/08/2026; falsificado, 58 e 59.

**Os portões:** `conferir-passo-zero-do-corte.php`, `conferir-recorte-por-unidade.php` (as vigias novas da Carteira e das quatro ações
antigas), `conferir-cobertura-por-par.php` (426 para 429 pares), `conferir-injecao-dql.php`, `conferir-rotas-sem-login.php`,
`conferir-guarda-do-erp.php`, `conferir-links-mortos.php`, `conferir-remocao-por-post.php`, `conferir-ordenacao-nulos.php`, `conferir-booleano-em-coluna-inteira.php`, `conferir-booleanos-em-consulta.php`, `conferir-referencias-docs.php`, `conferir-segredos.sh`, `contar-debitos.php` e
`conferir-debitos-repetidos.php`, saída 0; o `medir-adm-api-no-postgres.php` (as entradas da Carteira, 200) e os ensaios de permissão
e de recusas nos dois bancos de contrato, saída 0. **Débitos:** o D691 PARCIAL (a Evasão sai na CA3), o D697 e o D308 PREPARADOS (a
migração no passo 0), e o D708 registrado -- os 56.781 ativos (24%) que a regra põe numa unidade hoje fechada ficam fora das linhas da
rede, contados à parte (a pergunta ao dono, recomendada a (a), uma linha marcada por unidade fechada, como a 300 (b) da Capacidade); de
pé 199 para 198. **O navegador e o ciclo inteiro são do coordenador.** Nada da produção mudou; na `franquiabv_dev`, só SELECT; o banco
de contrato foi remontado.

**A verificação em três lentes confirmou 10 achados, um médio e nove baixos, todos tratados.** O médio: nenhum caso tinha uma visita
de HOJE (a cópia não tem venda depois de 16/08/2026, e o oráculo também não a via), e o limite `< amanhã` podia voltar um dia, ou
virar uma régua pelo relógio, com tudo verde -- o gerente que abre a Carteira à tarde não veria quem veio de manhã. O cenário ganhou
o K11, que veio hoje às 23h59min59s, e o caso `testAVisitaDeHojeEDeHoje`. Os baixos: a linha "sem serviço na última visita" do
resumo não abria lista, contra a letra da E7 ("o filtro `colaborador` da lista abre os clientes de uma linha do resumo") -- a lista
aceita agora `colaborador=sem-servico` (quem veio e não teve serviço com colaborador na última visita; quem nunca veio não), com a
conferência de cada linha no controller e o caso `testALinhaSemServicoAbreALista`; o `foraDasLinhas` afirmado só como "pelo menos
1", agora exato contra a E6 escrita à mão; o `fechamento IS NULL` do próximo agendamento sem caso (o cenário ganhou dois horários
fechados); o trecho novo do portão do passo 0 sem caso permanente (o `PassoZeroDoIndiceDasVendasTest`, que roda o portão sobre uma
cópia do `mysql.sql` com o `KEY` de uma coluna, e pula só no clone raso do CI, perguntando ao git pelo commit do dump); o D306 sem o
PARCIAL da Carteira no título; a pergunta do D708 descrevendo ao dono uma frase de tela que ainda não existe (o texto do lote
consertado: a tela é a CA2, e a frase está escrita no desenho); o desenho sem o D708 na seção 4, o D697 ainda "EM ABERTO", a seção 5
dizendo que nenhuma fatia espera e a CA2 sem a frase do `foraDasLinhas`; a forma da linha de cada unidade da rede
(`{unidade: {id, nome}, ...}`) diferente do corpo sem aviso na nota FEITA; e a medida e o oráculo dados como do código final, quando
rodaram antes de o SQL ir para `SqlDaCarteira` (refeitos, acima). Os mutantes novos, um por vez, a reserva num diretório novo e vazio a
cada um, devolvidos pelo sha1, todos vermelhos: N1 (o limite um dia antes) e N10 (a régua pelo relógio), nos dois bancos, pelo
`testAVisitaDeHojeEDeHoje`; N2 (o `foraDasLinhas.unidades` contando todas) pelo `testOAdministradorNaRede`; N3 (sem o `fechamento IS
NULL`) pelo `testOProximoAgendamento`; N4 (o trecho do portão arrancado) pelo `PassoZeroDoIndiceDasVendasTest`; N5 a N9 (o `sem-servico`
recusado, com quem nunca veio -- com e sem a conferência da linha --, ignorado na consulta, sem o colaborador do filtro) pelo
`testALinhaSemServicoAbreALista`; e o M10 da 76b de novo, com o SQL novo. Os da fatia e os vizinhos (27 arquivos), 784 casos em cada
banco, saída 0 (pulados os mesmos: um no MySQL, três no PostgreSQL); os portões que a fatia toca, saída 0; o `medir-adm-api-no-postgres`
(as três rotas da Carteira, 200) e os ensaios de permissão e de recusas nos dois bancos de contrato, saída 0. Sem tela: o front não
mudou. Débitos: o D306 PARCIAL; de pé, 198.


**Conferido por mim, depois da correção** (04/11/2026): os 12 achados das três lentes (10 confirmados, um médio: nenhuma visita de
hoje no cenário nem no oráculo), lidos um a um, e o relato da correção; a pergunta do D708 (os 56.781 clientes ativos que vieram pela
última vez numa unidade hoje fechada) foi à página como a 304, recomendada a (a), e a CA2 não espera por ela. O ciclo inteiro depois
da correção: caracterização 6.251 em cada banco, saída 0 (11 e 13 pulados); contrato 74 e 74; os ensaios, os portões (74) e o front
4.024 de 4.024, saída 0; o medidor dos relatórios no PostgreSQL, saída 0; em cerca de 9 min 51 s. No navegador, contra o banco de
contrato, como o gerente de teste com o privilégio de clientes (a funcionalidade 90002023, por `privilegios_usuarios`, só no
contrato): a `/adm/api/relatorios/carteira` com 200 e JSON, na unidade da sessão, com as 8 faixas, em 80 ms (o contrato é pequeno);
o Clientes antigo com 200 e a página inteira (43 KB, com o formulário); no menu novo, "Carteira de clientes" em Relatórios abrindo a
tela ainda não migrada, com a ponte para a versão antiga, como deve ser até a CA2. O tempo na cópia é o da fatia (a unidade 1 em cerca
de 2 s quente no MySQL, no limite da espera que a 294 (a) aceitou), não medido por mim. Na limpeza, as sessões dos dois usuários de
teste contadas e apagadas no contêiner; o `local.php` de volta ao `franquiabv_dev`, conferido pelo sha1, e o banco de contrato
remontado.

## 04/11/2026 -- CARTEIRA-CA7-ZERO (a fatia 76p da fila das telas que faltam)

**O que mudou para quem usa.** Nada nas cópias hoje: a rotina e o roteiro só servem depois da troca do código e do passo 7 do corte.
Depois deles, pela resposta 303 (a) do dono ao D707 (*"Zero nos dois"*), quem não tem nenhuma compra finalizada fica com o consumo
zero, e não em branco -- pela rotina da 01:10 e pelo refazer do corte, como o cadastro novo já nasce. Quem nunca comprou, ou teve a
única compra cancelada, passa a aparecer em "gastou até R$ X" no Clientes antigo e nas Campanhas (o filtro não muda: o em branco
ficava fora só pela regra do SQL). Na Carteira nada muda: ela já contava o em branco como zero.

**O que entrou.** O `VendaRepository::selecionarConsumoPorCliente` devolve zero sem venda finalizada (`?? 0`; quem chama é só a
rotina); o `scripts/refazer-consumo-dos-clientes.sql` pega todo nulo (`c.consumo IS NULL OR` a régua de 1 centavo) na contagem, na
lista, no `UPDATE` e na conferência, e a contagem `nulos_sem_venda_que_ficam` virou `nulos_ate_1_centavo` (os nulos de soma das
finalizadas até 1 centavo, dentro de `mudam`, nem acima nem abaixo da soma). No `ConsumoDoClienteTest` (19 casos): o `testAUnicaFinalizadaCanceladaDeixaZeroNaRotina` mudou de
sentido, o `testQuemVeioSemCompraComONuloFicaComZeroNaRotina` e o `testONuloDeQuemNuncaComprouVaiAZeroNoRoteiro` são novos, e o do
roteiro conta sete; da correção, o `testAConferenciaDeDepoisContaOsMesmosQueAContagemDeAntes` e o
`testONuloDeSomaAteUmCentavoEstaNaContagemEVaiASoma`. O comentário do roteiro, a CA7 e a F4 do `docs/f3-relatorios/spec-carteira.md` (com a linha 303 (a) nas
decisões), o passo 7 e o item 16 do `docs/procedimento-de-corte-etapa-1.md` e o D707 dizem a resposta.

**O provado.** Na cópia, só SELECT, igual no MySQL e no PostgreSQL: o roteiro muda **9.184** clientes (eram 4.707), dos quais
**4.477 nulos de quem nunca comprou** (3.508 ativos); 7.985 ativos ao todo; num "até R$ 100", 3.515 ativos com o nulo passam a
entrar. Num clone descartável da cópia do MySQL (apagado no fim): 9.184 refeitos em cerca de 18 s, a conferência 0, a segunda rodada
0, os nulos de 4.633 para 0, nenhuma outra coluna mudada, e 20 de 20 sorteados com o consumo igual à soma das finalizadas ou zero.
A fatia e os vizinhos (o Consumo, as Notificações, a injeção do relatório de clientes, as permissões do passo 7, a API do cliente):
124 casos, saída 0 no MySQL, e 124 no PostgreSQL, saída 0, com os 3 do roteiro pulados pelo nome (só MySQL). O anel maior (os 41
arquivos que tocam o `CronJobService` ou o consumo): 961 casos em cada banco, saída 0 (1 pulado no MySQL, o
`RegrasDosAtendimentosTest::testBancadaDoAno`; 4 no PostgreSQL). Os mutantes, um por vez, a reserva num diretório novo e vazio,
devolvidos pelo sha1, todos vermelhos pela asserção: o nulo de volta no repositório e o zero só para quem tinha valor na rotina (nos
dois bancos); o nulo fora do `UPDATE`, o nulo fora de tudo e o `SET` que devolve o nulo no roteiro (MySQL). O `conferir-tudo.sh`
inteiro, sozinho (74 portões), o `conferir-segredos.sh`, o `contar-debitos.php` e o `conferir-debitos-repetidos.php`, saída 0.
A verificação em três lentes confirmou 3 achados, todos baixos, tratados: o corpo da CA7 e o do D698 ainda dizendo
que o nulo sem venda fica nulo (marcados "superado pela 303 (a)"); o nulo da conferência sem caso (o caso novo roda a
conferência antes do `UPDATE`); e a contagem dos nulos sem caso contra a soma nula, com o nome impreciso (a coluna
`nulos_ate_1_centavo` e o caso do nulo de 1 centavo). Os mutantes da correção, N1 e N2, no MySQL, vermelhos pela asserção.
Débitos: o D707 PREPARADO (o código novo e o refazer, os dois no passo 7); de pé 198 para 197. Sem tela, sem navegador. Nada da
produção mudou; na franquiabv_dev, só SELECT. O ciclo inteiro e o commit ficam para o coordenador.


**Conferido por mim, depois da correção** (04/11/2026): os 5 achados das três lentes (3 confirmados, todos baixos), lidos um a um, e o
relato da correção. O ciclo inteiro depois da correção: caracterização 6.255 em cada banco, saída 0 (11 pulados no MySQL, 16 no
PostgreSQL: os cinco a mais são os casos do roteiro do consumo, só do MySQL, pulados pelo nome com o motivo); contrato 74 e 74; os
ensaios, os portões (74) e o front 4.024 de 4.024, saída 0; o medidor dos relatórios no PostgreSQL, saída 0; em cerca de 9 min 50 s.
Sem navegador: a fatia não tem tela. O `local.php` no `franquiabv_dev`, conferido pelo sha1. Débitos: de pé 198 para 197 (o D707
PREPARADO).

## 04/11/2026 -- CARTEIRA-CA2 (a fatia 76c da fila das telas que faltam)

**O que mudou para quem usa.** O gerente e o administrador abrem Relatórios › Carteira de clientes na tela nova, no lugar da tela
ainda não migrada (a CA2 de `docs/f3-relatorios/spec-carteira.md`; a resposta 138 (a) do dono). A tela é um retrato de hoje, com o
"hoje" do servidor no cabeçalho e o "atualizar": o em dia (até 45 dias), o sumido, os cadastrados sem visita e o total em destaque, e
oito cartões, um por faixa, que são botões -- clicar numa abre a lista dela. Na unidade, a tabela por colaborador da última visita
(cada cliente uma vez), com a linha "sem serviço na última visita"; o nome abre a lista dele. Na rede (o administrador abre nela, e
escolhe a unidade), a tabela das unidades em ordem do nome, com a linha de todas e, embaixo, a frase de quantos a regra põe numa
unidade hoje fechada -- pela última visita, ou pelo cadastro de quem nunca veio ("N clientes de M unidades hoje fechadas entram no
total da rede e não têm linha"; a resposta 304 (a) do dono ao D708 chegou e a troca por uma linha marcada na CA8, a fatia 76q). A
lista: 50 por página, com os filtros de faixa e de colaborador, o link da ficha, a faixa e os dias, o colaborador, o próximo
agendamento, o telefone, a marca "não aceita mensagens" em quem recusou e, só para o administrador, o e-mail. A conta leva um ou dois
segundos (a 294 (a)): a tela diz "carregando", e com os números na tela os de antes ficam marcados "atualizando"; a resposta de um
pedido que a tela já trocou não é desenhada. O Clientes antigo e a Evasão continuam no ERP até a CA4 e a CA3. Sem exportação até a CA6.

**O que entrou.** Novos: `frontend/src/api/carteira.ts`, `telas/Carteira.tsx`, `telas/CarteiraLigada.tsx`, a regra pura
`telas/textosDaCarteira.ts`, as cargas `testes/carteira.ts`, os testes `Carteira`, `CarteiraLigada`, `textosDaCarteira` e
`ConsoleAbreCarteira`, e a prévia do design (`.design-sync/previews/Carteira.tsx` com o dado em `_carteira.ts`, que fecha pela regra
do servidor), com a entrada no `componentSrcMap` e o viewport `1280x1340` medido. Mudados: a linha da `TELA_DO_RELATORIO` no
Console, o `Console.teste.tsx` (5 destinos na tela não migrada, nenhum do Relatórios), as filas `carteira` e `carteiraLista` no
`filaDaRede.ts` (as duas travas da rede da CA1), sete chaves `carteira.*` no glossário com o caso, as regras `.carteira*` no
`App.css` (os filtros da lista na regra dos campos) com o caso no `recuosDosRelatorios.teste.ts`, e o export no `index.ts`. O servidor
não mudou. A CA2 marcada FEITA no desenho; o D306 e o D691 com a marca nova.

**O provado.** O front inteiro, 4.071 de 4.071 em 174 arquivos, saída 0; os arquivos novos e tocados (12, 310 casos) com
`--maxWorkers=4`, saída 0, e com `AMPLIFICAR_EFEITOS_MS=50` os mesmos 6 vermelhos do `Console.teste.tsx` que o HEAD tem amplificado
(conferidos pelo nome numa cópia do HEAD), e nenhum nos arquivos novos; o tsc e o lint, saída 0 (as mesmas 8 advertências do HEAD,
nenhuma nos arquivos novos); o build com 366,76 KB em gzip; os tipos da prévia num tsconfig temporário, saída 0 (apagado). Os testes
PHP que leem o Console e os da Carteira (o `RelatoriosNoMenuTest`, o `CarteiraDoRelatorioTest`, o `RegrasDaCarteiraTest` e o
`ClientesAntigoNaPoliticaTest`), 143 casos em cada banco, saída 0. O ensaio de permissão nos dois bancos de contrato, saída 0. No
Chromium do Playwright, a prévia a 375, 905 e 1280 px: a página nunca rola de lado; as tabelas de 950 a 1.095 px rolam dentro do
cartão a 375 e a 905, e cabem a 1280.

**Os mutantes** (um por vez, a reserva num diretório novo e vazio, devolvidos pelo sha1, todos vermelhos pela asserção):

| Mudança | Caso vermelho |
|---|---|
| M1 a faixa clicada pede outra | "clicar numa faixa pede a lista dela", "a lista sob pedido" |
| M2 a lista sem a página | "a página 2 é pedida com pagina=2 e desenhada" |
| M3 o colaborador do filtro não enviado | "o filtro de colaborador é pedido com o id" |
| M4 a linha do resumo abre a lista sem o colaborador | "a linha do colaborador abre a lista dele" (a vista e o invólucro) |
| M5 o e-mail sem a guarda do escopo | "o e-mail só para o administrador: o gerente não o vê nem se a chave viesse" |
| M6 a marca do consentimento invertida | "a marca 'não aceita mensagens' só em quem recusou" |
| M7 a lista velha desenhada por cima (o `vigente` do pedido de depois) | "trocar de faixa no meio da espera descarta a resposta antiga" |
| M8 o retrato velho desenhado por cima (o `vigente` do pedido) | "trocar de unidade no meio da espera descarta o retrato antigo" |
| M9 a frase do `foraDasLinhas` sem a concordância do verbo | "nos dois singulares", "diz o singular" |
| M10 o hoje pelo relógio da tela | "o cabeçalho diz o hoje do servidor" |
| M11 a linha do Console de volta à tela não migrada | o `ConsoleAbreCarteira` e o "5 destinos" do Console |
| M12 o colaborador fica num retrato que não o tem | "trocar de unidade com a lista de um colaborador aberta" |
| M13 os cartões pedem com outro retrato no ar | "com outro retrato no ar, os cartões e as linhas não pedem nada" |
| M14 o administrador abre na unidade da sessão | "o administrador abre na rede" (o invólucro e o Console) |
| M15 o gerente manda a unidade do recorte na lista | "a lista sob pedido" e o "pedido da lista" da regra pura |
| M16 a frase do `foraDasLinhas` com zero clientes | "ausente com zero clientes" |
| C1 a regra dos cartões em quatro colunas arrancada | o caso das regras de tela da 76c |
| C2 os filtros da lista fora da regra dos campos | o caso das regras de tela da 76c |

**O oráculo**, no banco de contrato do MySQL, com o `CenarioDaCarteira` semeado no hoje do contêiner: pelo HTTP como o gerente de
teste (com a funcionalidade 90002023 por `privilegios_usuarios`, só no contrato) e como o administrador (o grupo dele com
`administrador = 1`, só no contrato), o retrato da unidade do gerente e a lista dele (sem a chave do e-mail), a rede, a unidade U e
quatro listas (a faixa de 46 a 90 de U, a linha "sem serviço" de U, a Ana em U, a página 2 dos sem visita de X, a faixa de 1 a 2
anos da rede): 28 conferências contra o cenário, 0 divergências; e a tela pura desenhada com as nove respostas reais, cada número,
nome, marca, coluna e célula conferidos contra o JSON com formatadores do próprio oráculo: 293 conferências, 0 divergências;
falsificado (a marca do consentimento invertida na tela), 16. A sessão se apagou no contêiner nas duas entradas; o banco de contrato
foi remontado no fim; as travas `/tmp/*.lock` das minhas rodadas, contadas (2 e 7) e apagadas.

**Os portões.** O `conferir-tudo.sh` inteiro, sozinho, 74 portões, saída 0; o `conferir-segredos.sh`, o `contar-debitos.php` e o
`conferir-debitos-repetidos.php`, saída 0. Débitos: o D306 e o D691 PARCIAIS com a marca nova; de pé 197, sem mudança. Nenhuma
pergunta nova ao dono. Nada da produção mudou; na franquiabv_dev, nada (a fatia não leu a cópia). O navegador e o ciclo inteiro
ficam para o coordenador, e o commit também.

**A correção depois da verificação** (em três lentes; 8 achados confirmados, 7 distintos, um médio e seis baixos, todos tratados):

- **O médio: a espera da lista sem caso.** O "Atualizando os clientes…" com a página de antes na tela, a paginação desligada
  enquanto outra página está no ar, o "Carregando os clientes…" e o "Carregando a carteira…" podiam ser revertidos com tudo verde.
  Entraram os casos na vista e no invólucro (a página 2 presa no dublê: a 1 fica, marcada, e a 2 entra no lugar quando chega).
- **As duas filas da rede sem caso**, e os documentos as davam como certas: entraram quatro casos no `CarteiraLigada.teste.tsx`,
  com a fila ocupada por uma leitura de outra tela -- o retrato da rede espera a fila dele e o da unidade não; a lista da rede espera
  a dela e não a do retrato; a faixa trocada e a lista fechada na espera nem saem. O 429 do servidor não é dublado: o caso prova
  que o segundo pedido não sai com o primeiro no ar.
- **"Fechar a lista", o "atualizar" e o foco no título da lista sem caso**: entraram (o "atualizar" com a lista de um colaborador
  aberta relê o retrato e a mesma página dela; depois de fechar, só o retrato).
- **O glossário `carteira.rede`** dizia que a frase da rede conta quem "veio pela última vez" numa unidade fechada; o
  `foraDasLinhas` conta também quem se cadastrou nela e nunca veio (a E6; na cópia, 54.244 e 2.537 dos 56.781). O texto diz as duas
  coisas, com o caso; o mesmo no D306 e acima.
- **O D708 dado como sem resposta**: a resposta 304 (a) do dono chegou às 14:25 de 04/11/2026 (uma linha marcada por unidade
  fechada, como a CR4 da Capacidade), e a fatia 76q (CARTEIRA-CA8) está na fila. A tela segue com a frase até ela, como o desenho
  manda; a nota FEITA da CA2, o D708 (com DECIDIDO (304 a), EM ABERTO: a CA8 no fim do título) e o D306 dizem isso. O
  `conferir-debitos-repetidos.php` acusou seis pares novos com o D708 pelas marcas (D690, D696, D698, D700, D704 e D366), todos
  assuntos distintos, registrados com o motivo, e dois conhecidos que sumiram (D642 e D668), tirados.
- **O cartão dos sem visita dizia "nunca veio" com qualquer número**: agora "nunca vieram", e "nunca veio" só com 1, com o caso.
- **A lista vazia de um colaborador diz "Nenhum cliente nesta lista."**, contra a letra da F5 ("nesta faixa" também para o
  colaborador): a frase fica -- a lista de um colaborador é a de todas as faixas -- e o desvio está escrito na nota FEITA da CA2 e na
  F5.

Os mutantes da correção, N1 a N16, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, todos
vermelhos: o "nunca veio" fixo; o glossário antigo; o "relendo" da lista desligado; a lista de antes não guardada; a paginação sem o
"relendo"; a frase "Atualizando os clientes…" tirada; o "fechar a lista" sem efeito; o fechar sem largar o pedido; o "atualizar" sem
efeito; o foco no título tirado; o retrato da rede fora da fila; a lista da rede fora da fila; a lista na fila do retrato; o retrato
da unidade na fila; a lista num retrato novo sempre na página 1; e a frase do "Carregando a carteira…" encurtada. Os números depois
da correção: o front inteiro, 4.083 de 4.083 em 174 arquivos, saída 0; os sete arquivos da fatia e do Console, 261 casos, saída 0, e
com `AMPLIFICAR_EFEITOS_MS=50` os mesmos 6 vermelhos do `Console.teste.tsx` (a comanda e o caixa), nenhum nos da Carteira; o tsc e o
lint, saída 0 (as 8 advertências do HEAD); o build com 366,81 KB em gzip; os testes PHP da fatia (e o dos textos do consumo, que lê o
desenho), 144 casos no MySQL e 144 no PostgreSQL, saída 0. O servidor não mudou. O navegador, o ciclo inteiro e o commit seguem com o
coordenador.


**Conferido por mim, depois da correção** (04/11/2026): os 9 achados das três lentes (8 confirmados, 7 distintos, um médio: o estado
"atualizando" da lista sem caso), lidos um a um, e o relato da correção. O ciclo inteiro depois da correção: caracterização 6.255 em
cada banco, saída 0 (11 e 16 pulados); contrato 74 e 74; os ensaios, os portões (74) e o front 4.083 de 4.083, saída 0; o medidor dos
relatórios no PostgreSQL, saída 0; em cerca de 9 min 54 s. No navegador, contra o banco de contrato, como o gerente de teste com o
privilégio de clientes (a 90002023 por `privilegios_usuarios`, só no contrato), a 1280 px: Relatórios › Carteira de clientes abre a
tela nova na Unidade Teste Centro, com o hoje do servidor (04/11/2026), as faixas somando o total (3 sumidos e 4 sem visita, 7
ativos), a tabela por colaborador somando os sumidos (1 + 2) e a linha "sem serviço" com zero; o cartão de 181 a 365 dias abre a lista
com os 3 clientes, o foco no título "Os clientes", a ficha de cada um numa aba nova, nenhum e-mail para o gerente, a página 1 de 1, e
nada rolando de lado. Notado, sem mudar: o telefone sai como gravado ("+5547900002000" ao lado de "+55 (48) 3400-0000"), como nas
outras telas da F3 (as Datas da equipe dão o `tel:` com o texto como gravado). Na limpeza, as sessões dos dois usuários de teste
contadas e apagadas no contêiner; o `local.php` de volta ao `franquiabv_dev`, conferido pelo sha1, e o banco de contrato remontado.

## 04/11/2026 -- CARTEIRA-CA8 (a fatia 76q da fila das telas que faltam)

**O que mudou para quem usa.** Na Carteira de clientes da rede, o administrador passa a ver as unidades hoje fechadas que têm
cliente na carteira -- quem veio nelas pela última vez, ou se cadastrou nelas e nunca veio --, cada uma numa linha marcada
"desativada", e a soma das linhas é a rede: na cópia, as 25 fechadas com os 56.781 clientes delas, ao lado das 36 abertas. A frase
"N clientes de M unidades hoje fechadas entram no total da rede e não têm linha" saiu. O filtro de unidade do administrador oferece
essas fechadas, com " (desativada)" no nome, e escolhida uma, o retrato e a lista são os dela (o e-mail, como em todo o resto, só para
o administrador). O gerente não muda nada: vê só a unidade dele. É a resposta 304 (a) do dono ao D708, no molde da Capacidade e dos
Royalties (a CA8 de `docs/f3-relatorios/spec-carteira.md`).

**O que entrou.** No servidor: a consulta das fechadas com cliente (`SqlDaCarteira::DESATIVADAS_COM_CLIENTES`, uma forma nos dois
motores, e `ClienteRepository::carteiraDesativadasComClientes`, rotulada pelo `UnidadeRepository::rotuloDaDesativada`, que passou a
público); o `CarteiraController::unidadesDoFiltro` sobrescrito, só no escopo da rede; as linhas da rede pela lista do filtro com a
situação de hoje (`unidade.ativa`) e o `LogicException` se um cliente cair fora delas; o `foraDasLinhas` fora do `CarteiraJson`. Na
tela: a marca "desativada" (`marcaDaUnidade`, `.carteira-marca`), sem a frase (`fraseDoForaDasLinhas` saiu), e o glossário da rede. O
`CenarioDaCarteira` ganhou a Y (fechada só pelo cadastro de quem nunca veio), a Z (fechada com história e sem cliente) e a T (aberta,
para onde o cliente da Z voltou). Os portões `conferir-recorte-por-unidade.php` (três vigias novas: o filtro da base, o escopo da rede
e as linhas pela lista) e `conferir-cobertura-por-par.php` (de 429 para 430 pares, o ISRelatorio de 194 para 195).

**O provado.** O `CarteiraDoRelatorioTest` (29 casos) e os vizinhos (a Capacidade, os Royalties, o Clientes antigo, as regras, o menu,
os portões da família e o consumo: 427 casos) nos dois bancos, em série, saída 0 (no PostgreSQL, os cinco do roteiro do consumo
pulados, só do MySQL). O front inteiro, 4.081 de 4.081 (os cinco casos da frase saíram e os três da marca e da soma entraram); os da
Carteira também com `AMPLIFICAR_EFEITOS_MS=50`, 55 de 55; o build (tsc e vite) com 366,76 KB em gzip, e o lint com as 8
advertências do HEAD. A medida em processo, `memory_limit=128M`, o HEAD contra a CA8: a rede em 1,67 a 1,71 s e 1,69 a 1,73 s no
MySQL, 1,10 a 1,19 s e 1,15 a 1,28 s no PostgreSQL; a unidade 1 em 1,85 a 1,87 s e 1,85 a 1,91 s, e 1,15 a 1,27 s e 1,25 a 1,26 s; o
filtro do administrador de 0 para 14 a 15 ms e 85 a 92 ms. O oráculo nas duas cópias (as 61 linhas, a soma igual aos 239.239, o
filtro e a fechada 2 escolhida, pelo retrato e pela página 1): 643 conferências por banco, 0 divergências; falsificado, 68. Pelo HTTP
no contrato MySQL, como o gerente de teste e como o administrador: 30 conferências, 0 divergências. Os mutantes S1, S1b e S2 a S8
(a fechada fora das linhas, com e sem a defesa; a soma sem as fechadas; a fechada sem a marca; o filtro sem as fechadas e com a sem
cliente; o gerente vendo uma fechada; o `foraDasLinhas` de volta; o cadastro sem visita fora), SQ1 e SQ2 (as duas condições da
consulta das fechadas), nos dois bancos, e F1 a F3 no front, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos
pelo sha1, todos vermelhos; o S6 também deixa o `conferir-recorte-por-unidade.php` vermelho. Os ensaios HTTP de permissão nos dois
bancos de contrato, saída 0. Débitos: o D708 CORRIGIDO, o D306 PARCIAL; de pé 197 para 196. A prévia do design ainda monta a rede com
o `foraDasLinhas`: é do coordenador. Nada da produção mudou; na `franquiabv_dev`, só SELECT; o banco de contrato foi remontado.

**A correção da verificação (três lentes, seis achados, todos baixos).** (1) A fechada escolhida pelo administrador não tinha a marca
em lugar nenhum (o retrato dela não tem linha de unidade): o `recorte.unidade` da Carteira leva `ativa`
(`CarteiraController::recorteDaResposta`), e o cabeçalho diz o nome com " (desativada)"; na lista da rede, a unidade de cada cliente
também, e a coluna "Unidade" a marca (`nomeComAMarca`). (2) O comentário do `lerUnidadesDaCarteira` dizia "as ativas". (3) Os corpos
da CA1 e da CA2 no desenho marcados "superado pela 304 (a)", e a CA8 na ordem de rodar. (4) As condições da consulta das fechadas
sem dado que as separasse: a Z do `CenarioDaCarteira` passou a guardar o cliente ativo (o K14, inativo), os sentinelas (o 2,
cadastrado nela e com a última venda nela) e a venda finalizada com item até hoje (o K15, com a última visita em T e, depois, uma
venda sem item, uma cancelada e uma de amanhã na Z), com o caso `testAsArmadilhasDaFechadaSemCliente`. (5) O `LogicException` da
soma das linhas com caso (`testUmClienteForaDasLinhasEDefeito`, o filtro sem a W por um dublê). (6) A prévia do design segue do
coordenador, e agora precisa também do `ativa` no `recorte.unidade` e na unidade de cada cliente da lista da rede. O
`CarteiraDoRelatorioTest` com 31 casos, e com o `ClientesAntigoNaPoliticaTest` e o `RegrasDaCarteiraTest`, 82 casos em cada banco,
saída 0; o front inteiro 4.083 de 4.083; os da Carteira com `AMPLIFICAR_EFEITOS_MS=50`, 54 de 54, e o do Console, 3 de 3; o build com
366,80 KB em gzip; o lint com as mesmas 8 advertências. Os mutantes novos, um por vez, a reserva num diretório novo e vazio a cada
um, devolvidos pelo sha1, os de servidor nos dois bancos: Q1 a Q8 (o ativo e os sentinelas de cada parte, o item, o status e o "só
até hoje" da última visita na parte do cadastro, o item na das vendas), Q10b e Q12b (o status e o "só até hoje" tirados da venda e
da última visita juntos), L1 (o `LogicException` sozinho), R1 a R3 (a marca da escolhida e a do cliente da lista da rede) e F4 a F6
no front, todos vermelhos; Q9 a Q12 (um só dos dois status, ou um só dos dois "só até hoje", na parte das vendas) ficaram verdes e são
equivalentes. Os quatro portões da fatia e o `conferir-segredos.sh`, saída 0.


**Conferido por mim, depois da correção** (04/11/2026): os 7 achados das três lentes (6 confirmados, todos baixos), lidos um a um, e o
relato da correção. A prévia do design da Carteira (`frontend/.design-sync/previews/_carteira.ts` e `Carteira.tsx`), que a fatia
deixou de fora por não ser dela, foi acertada por mim: o `ativa` em toda unidade (o recorte, as linhas e a lista da rede), três
fechadas com cliente como linhas marcadas na rede, que passa a ser a soma das linhas, sem o `foraDasLinhas`, e os comentários; os
tipos das prévias da Carteira e da Capacidade conferidos por um `tsc` com um tsconfig temporário (apagado), sem erro. O ciclo inteiro
depois da correção: caracterização 6.260 em cada banco, saída 0 (11 e 16 pulados); contrato 74 e 74; os ensaios, os portões (74) e o
front 4.083 de 4.083, saída 0; o medidor dos relatórios no PostgreSQL, saída 0; em cerca de 9 min 54 s. No navegador, contra o banco de
contrato, como o administrador de teste, a 1280 px: a Carteira abre na rede, com as 5 linhas somando o total (11 clientes ativos), sem
a frase das fechadas, e nada rolando de lado (o contrato não tem unidade inativa: a fechada como linha marcada, na soma e no filtro
foi conferida pelo oráculo e pelo HTTP da fatia com o cenário semeado, não por mim na tela). Na limpeza, as sessões dos dois usuários
de teste contadas e apagadas no contêiner; o `local.php` de volta ao `franquiabv_dev`, conferido pelo sha1, e o banco de contrato
remontado.

## 04/11/2026 -- CARTEIRA-CA3 (a fatia 76d da fila das telas que faltam)

**O que mudou para quem usa.** O relatório **Evasão sai do ERP antigo** (a CA3 de `docs/f3-relatorios/spec-carteira.md`; a
resposta 138 (a) do dono): o item, que estava escondido no menu Relatórios, a tela de filtro e o PDF por colaborador -- o "Total de
vendas" que contava linha de item (o S10) e o "sem retorno" contado uma vez por colaborador que atendeu o cliente. O lugar dele é a
**Carteira de clientes**, na tela nova, com o cliente contado uma vez (o resumo por colaborador da última visita). O endereço antigo
(`/mod-relatorios/rel-evasao/index` e `.../pdf`) responde 404, também a quem tem o privilégio. **Os 3 usuários que só têm o
privilégio da Evasão** (nenhum gerente; contados de novo, só com SELECT, iguais nas duas cópias: 105 com os dois, 3 só com o do
Clientes, 3 só com o da Evasão) perdem o PDF sem ganhar a Carteira (a E9 do desenho). **No corte**, a rotina das 04:10 da Evasão deixa
de rodar; a do ticket médio, no mesmo horário, fica. Os arquivos que a rotina já gravou na produção ficam (a 290 (b) do dono).

**O que saiu** (a lista derivada do `Module.php`, do `module.config.php` e do `git grep`): o `EvasaoController`, a fábrica, o
invokable e o alias `rel-evasao`, o `Form/Evasao.php`, a view, o `Pdf/PdfEvasao.php`, o item do `layout.phtml`, a linha `Rel-evasao`
do `SessaoAcl::menuAtivo`; a rotina `CronJobService::relatoriosEvasaoAtualizar`, a ação do `CronJobController`, a rota, a entrada da
documentação da API e as duas linhas das rotas públicas; as consultas `VendaRepository::evasaoPorColaborador` e
`::evasaoPorColaboradorTotalVendas`. **O que ficou:** o privilégio "Relatórios de evasão", sem tela (o quinto, no D485); o Clientes
antigo até a CA4; e, sem chamador, o `DataHora::removerUmAno` e o parâmetro do grupo de `UsuarioRepository::selecionarUsuariosComVendas`
(no D485). O caminho da Evasão não é chave de tela nenhuma do front.

**O corte.** O primeiro bloco do passo 9 de `docs/procedimento-de-corte.md` e o `grep -vE` do recuo do passo 12 da etapa 1 tiram a
linha da Evasão do crontab guardado da produção pelo nome da rotina, e não pelo horário: o crontab do corte fica com 21 das 25 linhas
de 27/08/2026. A tabela do crontab em `docs/f1-6-agendamento.md` ficou com as 04:10 só do ticket médio. Nada foi mexido na produção
nem no BVIP, e nenhuma das duas rotinas das 04:10 rodou.

**Os portões que a fatia tocou, e o que a saída pediu:** o `conferir-cobertura-por-par.php` de 430 para 428 pares (as duas consultas
da Evasão, que nenhum teste citava: o "sem menção" de 116 para 114, e não por cobertura), o ISApi de 107 para 103 chamadas e o
ISRelatorio de 195 para 193, o sha1 novo conferido (as duas de volta dão o de antes); os dois caminhos da Evasão fora do
`conferir-caminhos-postgres.php` (14 verdes); as linhas da rota fora do `conferir-rotas-sem-login.php` e do `rotas-publicas.php`; as
duas marcas fora do `medir-relatorios-no-postgres.php`; o caso 4 do `ensaiar-links-consertados-do-erp.php` passou a conferir o 404
da tela, do PDF e do morto de antes, com o privilégio dado; o `scripts/ensaiar-crontab-do-passo-9.sh` com a linha da Evasão e a do
ticket no crontab inventado; o inventário regerado (350 para 347 ações). O `SegredosForaDoCodigoTest` com os pisos de 23 para 22
ações com token e de 19 para 18 rotinas inofensivas; o `D105CacheVazioDosRelatoriosTest` sem o caso da Evasão; o
`RelatoriosNoMenuTest` com o `SAIRAM_DO_LAYOUT` como lista de pares por política (a Carteira junta duas telas antigas que saem em
fatias diferentes); o caso do D702 com cinco itens de fora da lista, e não seis. No rascunho, o `medir-p47c-caches.php` (o do
medidor dos relatórios no PostgreSQL) deixou de chamar a rotina da Evasão, que não existe mais.

**Provado.** O `EvasaoSaiuTest` novo (8 casos: o endereço sem controller, com o Clientes, os Descontos e a API da Carteira que
ficam; a view; a rota da rotina e a da irmã das 04:10; a ação, a rotina e as consultas que saíram, com as irmãs que ficam; nenhum
código de `module/` e `config/`, lido sem os comentários, cita a pasta da Evasão; o layout desenhado sem o item; o `menuAtivo`; os
dois filtros do crontab e a tabela das 04:10). Os testes da fatia e os vizinhos, 269 casos em cada banco, saída 0 (1 pulado no MySQL e
6 no PostgreSQL: o `VendasNoRelatorioTest::testODescontoNaoSomaODeOutraUnidade`, sem a cópia, e no PostgreSQL também os cinco do
roteiro do consumo, que é só do MySQL). O front inteiro 4.083 de 4.083; o `menu-do-erp.teste.ts` e o `Console.teste.tsx` com
`AMPLIFICAR_EFEITOS_MS=50`, os 6 vermelhos do Console que as fatias anteriores registram no HEAD (a mudança no front é só de
comentário, e não os comparei contra o HEAD nesta fatia); tsc, lint e build (366,80 KB em gzip). Os mutantes, um por
vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, os de servidor nos dois bancos, todos vermelhos: a ação
de volta (e o `conferir-agendamento.php`), o item do menu, o `Rel-evasao` no `menuAtivo`, o filtro do passo 9 e o do recuo tirando
também o ticket, a tabela sem a linha das 04:10 (e o `conferir-agendamento.php`), o filtro sem a Evasão, o controller, uma consulta
morta, a rota, a rotina com a pasta e a view; o controle, a pasta só num comentário, verde. O oráculo pelo HTTP no banco de contrato
MySQL, como o gerente de teste com o privilégio da Evasão, o da Carteira e o dos Descontos: 14 conferências, 0 divergências (a tela e
o PDF antigos com 404, os Descontos com 200 e o menu sem a Evasão, a Carteira com 200 e as oito faixas, a rota da rotina com 404 e a
do ticket, sem token, com 401 -- nada rodou); a sessão apagada no contêiner. Os ensaios de permissão e de recusas nos dois bancos de
contrato e o dos links consertados no MySQL, saída 0 (travas contadas e apagadas antes); o medidor dos relatórios no PostgreSQL,
saída 0; o `conferir-tudo.sh` sozinho, 74 portões, saída 0; `contar-debitos` e `conferir-debitos-repetidos`, saída 0.

**Débitos:** o D691 CORRIGIDO; o D306 e o D485 PARCIAIS (o D485 com o quinto privilégio sem tela e as duas sobras); o D702 e o D693
com a nota; de pé 196 para 195. O navegador e o ciclo inteiro são do coordenador. Nada da produção mudou; na `franquiabv_dev`, só
SELECT; o banco de contrato foi remontado; o `local.php` de volta ao `franquiabv_dev`, conferido pelo sha1.

**A correção, depois da verificação em três lentes** (3 achados confirmados, todos baixos, tratados): **(1)** o motivo da cópia
do `data/` no corte (a etapa 1) e o das rotinas paradas em `docs/ambiente-teste.md` diziam ainda que "as telas de Atendimentos e
Evasão" leem o cache das rotinas: desde a CA3, só a de Atendimentos lê, e as duas frases o dizem (a cópia do `data/` continua igual,
com os arquivos da Evasão junto, pela 290 (b)); o caso novo `testNenhumDocumentoDizQueAEvasaoAindaLeOCache` lê os `docs/**.md` pelo
presente. **(2)** O `RelatoriosNoMenuTest` tinha afrouxado para toda política a regra de não estar nas duas listas: agora só a da
`METADE_QUE_FICA` (a Carteira, com o `rel-clientes`/`index`, até a CA4) pode, e com o par dela; o caso exige também que a linha saia
dali quando o Clientes sair. **(3)** O caso do crontab no `EvasaoSaiuTest` julgava só a regra das rotinas que saíram, e uma regra a
mais pelo horário passava: agora o `awk` inteiro do passo 9 e a linha do `grep -vE` do recuo rodam pelo `sh`, numa pasta
descartável, sobre um crontab inventado, e nenhuma outra linha de código das duas seções pode nomear o arquivo que eles gravam; o
comentário diz o que só o `ensaiar-crontab-do-passo-9.sh` cobre. Os mutantes novos, um por vez, a reserva num diretório novo e
vazio, devolvidos pelo sha1, nos dois bancos, todos vermelhos pela asserção: a regra `/^10 4 / { next }` no `awk` do passo 9, um
`sed -i` depois do recuo e outro depois do `awk`, a Capacidade de volta à `PARES_DO_LAYOUT` com outro par, a `METADE_QUE_FICA` com o
par errado, e a frase antiga de volta em cada um dos dois documentos; o controle, o caso de antes com a regra pelo horário, verde
(o achado). Os testes da fatia e os vizinhos, 304 casos em cada banco, saída 0 (os mesmos 1 e 6 pulados); o
`ensaiar-crontab-do-passo-9.sh` no Mac e no contêiner, o `conferir-referencias-docs.php` e o `conferir-segredos.sh`, saída 0.


**Conferido por mim, depois da correção** (04/11/2026): os 3 achados das três lentes, todos baixos e confirmados, lidos um a um, e o
relato da correção; a mudança no roteiro do rascunho que o medidor dos relatórios no PostgreSQL usa (`medir-p47c-caches.php`, que
chamava a rotina apagada) lida pelo diff, com a cópia de antes guardada. O ciclo inteiro depois da correção: caracterização 6.268 em
cada banco, saída 0 (11 e 16 pulados); o contrato 74 de 74 no MySQL, e no PostgreSQL **um vermelho fora da fatia**:
`ApiV4Test::testRegistrarERetirarAparelhoAutenticado` ("o registrado acima sai": o registro do aparelho com 200 e a retirada logo
depois com `retirado: false`). A suíte de contrato do PostgreSQL repetida sozinha duas vezes, 74 de 74 nas duas; nenhum ciclo
anterior teve essa falha (procurado nos logs do rascunho). O que descartei: o inquilino do RLS (fixo pela configuração e declarado em
toda conexão, o `DeclararInquilino`), e a poda do teto de 10 aparelhos (o contrato não tem aparelho de fixture). Fica anotado aqui
como a primeira ocorrência; na segunda, uma rodada de causa, pela regra de que o que passa sozinho tem causa. Os ensaios, os portões
(74) e o front 4.083 de 4.083, saída 0; o medidor dos relatórios no PostgreSQL, saída 0; em cerca de 9 min 52 s. No navegador, contra
o banco de contrato, como o gerente de teste com os privilégios da Evasão, da Carteira e dos Descontos (por `privilegios_usuarios`,
só no contrato): a Evasão antiga e o PDF dela com 404, os Descontos com 200 e a página inteira, a API da Carteira com 200 e o Clientes
antigo com 200; nenhum item "Evasão" no menu (a palavra aparece só no nome do barbeiro de fixture "Barbeiro Evasao Teste"). Na
limpeza, as sessões dos dois usuários de teste contadas e apagadas no contêiner; o `local.php` de volta ao `franquiabv_dev`, conferido
pelo sha1, e o banco de contrato remontado.

## 04/11/2026 -- CARTEIRA-CA5 (a fatia 76e1 da fila das telas que faltam)

**Para quem usa:** o gerente e o administrador filtram a lista da Carteira de clientes pela busca (nome, telefone ou e-mail),
pelo convênio, pelo mês do aniversário e pelo consumo, como no Clientes antigo (a resposta 292 (b) do dono, "A carteira ganha os
filtros"). A barra dos filtros fica abaixo dos cartões das faixas; "Filtrar" sem faixa clicada abre a lista de todas as faixas; a faixa
e o colaborador clicados viram etiquetas que se tiram ("Faixa: de 46 a 90 dias ×"); cada filtro volta à página 1; o vazio diz "Nenhum
cliente com estes filtros." com o "Limpar os filtros" ao lado; e o "Como os números são contados" ganhou a frase do consumo, já com a
CA7 e a 76p ("o consumo do cadastro, recalculado de madrugada para quem veio ou teve uma venda cancelada; quem nunca comprou conta como
zero"). O Clientes antigo continua no ERP até a CA4 (depois da exportação, a CA6).

**O que mudou:** a regra pura nova `ISRelatorio\Regra\FiltrosDaCarteira` (as recusas da F6; o `?mes=` nem é lido, a F2); as quatro
condições dentro da subconsulta dos clientes de `SqlDaCarteira`, antes da conta da visita (a `VISITAS_MYSQL` em duas metades, o texto
dela, do retrato e da rede o mesmo de antes, conferido pelo sha1), e a busca, uma condição por palavra, com só o índice no nome do
parâmetro e o `normalizarTags` ao lado da `LIKE`; no PostgreSQL, com filtro, o `DISTINCT ON` só das vendas dos clientes filtrados;
`ConvenioRepository::daCarteira` e a ação nova `GET /adm/api/relatorios/carteira/convenios` (a F7), a mesma lista que a guarda do
`?convenio=` confere (o que não existe, o inativo e o só de outra unidade, o mesmo 422); o eco `filtros` na lista; na tela, a barra
(`Carteira.tsx`), os convênios pedidos na primeira vez que a pessoa chega à barra (`CarteiraLigada.tsx`), o pedido, o eco, as
etiquetas e as frases (`textosDaCarteira.ts`), a chave `carteira.consumo` no glossário e as regras `.carteira-etiqueta*` no `App.css`.
O D696 e o D306 PARCIAIS; de pé 195. Nada da produção mudou; na `franquiabv_dev`, só SELECT.

**A medida** (em processo, 128M, quente, a cópia, hoje = 04/11/2026; a lista sem filtro e com cada um): no MySQL, a unidade 1 de 1,55 a
1,57 s sem filtro e de 0,06 a 0,65 s com um (0,86 s com a faixa e o mês), a rede de 1,67 s para 0,06 a 0,52 s; no PostgreSQL, a unidade
1 de 0,96 a 1,07 s para 0,47 a 1,01 s -- a busca de uma palavra (0,79 a 1,01 s) e o consumo de 100 a 500 (0,96 s) custam o mesmo que
sem filtro, os outros de 0,47 a 0,91 s --, a rede de 1,03 a 1,07 s para 0,47 a 0,88 s. No MySQL cada filtro barateia a lista; no
PostgreSQL, a busca e o consumo largo não. A primeira forma no
PostgreSQL (o filtro só fora do `DISTINCT ON`) não ganhava nada, e a busca de duas palavras na unidade 1 levou 34 s: por isso a segunda.

**O provado:** `FiltrosDaCarteiraTest` (8) e `CarteiraFiltrosTest` (12: cada filtro, os quatro juntos com a faixa e o colaborador, o
mesmo 422 dos três convênios campo a campo, o `?mes=` ignorado, o total da lista filtrada com a página 2, os convênios do gerente e do
administrador, e a amarração às Campanhas pelo `FiltrosDaAudiencia::conferir`, com as duas diferenças escritas); com os vizinhos, 764
casos em cada banco, saída 0 (pulados: no MySQL o `VendasNoRelatorioTest::testODescontoNaoSomaODeOutraUnidade`; no PostgreSQL ele e os
cinco do roteiro do consumo, só do MySQL). O front 4.095 de 4.095 (o `CarteiraFiltros.teste.tsx`, 8; quatro casos novos no
`textosDaCarteira.teste.ts`), e com `AMPLIFICAR_EFEITOS_MS=50` os da Carteira, 69 de 69; tsc, lint e build (368,37 KB em gzip). Os
mutantes, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, os de servidor nos dois bancos: S1 (as
palavras em OU), S2 (a regra sem o `normalizar`), S3 (o `todos` como sem filtro), S4 (o id ignorado), S5 (o convênio por `find`), S6 (o
mês pelo dia), S7 (o mês do `?mes=`), S8 (o "de" exclusivo), S9 (o nulo fora), S10 (o total da página), S11 (a faixa obrigatória), S12
(o inativo no `<select>`), S13 (o gerente com os convênios da rede; e o `conferir-recorte-por-unidade.php` vermelho com ele), S14 (os
filtros fora da consulta), e no front F1 (o filtro não enviado), F2 (a página que não volta à 1), F3 ("Filtrar" sem faixa sem pedido),
F4 (a frase do vazio trocada), F5 (a etiqueta que não tira), F6 (a máscara do telefone), F7 (a faixa clicada sem os filtros, vermelho
depois do caso novo) e F8 (o convênio de outra unidade mantido) -- todos vermelhos; o S2b (o `normalizarTags` tirado do repositório) é
equivalente enquanto a chave do D119 estiver desligada. **A amarração medida na cópia**, nos dois bancos: em seis filtros, todo id das
Campanhas está na Carteira, e a Carteira só tem a mais quem recusou mensagens e, em "até R$ 100", os 3.516 com o consumo nulo (101.314
contra 97.752). **O oráculo** Python (só SELECT, a regra pela janela `ROW_NUMBER` e os filtros aplicados em Python, a busca como marca
calculada no banco) contra o JSON do controller em processo: 63 conferências por banco, 0 divergências; falsificado (o nulo fora), 2.
Pelo HTTP no contrato MySQL como o gerente de teste, com três clientes e três convênios semeados só lá: 25 conferências, 0 divergências;
a sessão apagada no contêiner e o contrato remontado. Os ensaios de permissão e de recusas nos dois contratos e o
`medir-adm-api-no-postgres.php` (com a ação nova e a lista filtrada), saída 0; os portões `conferir-injecao-dql.php` (de 14 para 15, o
índice da busca, com o motivo), `conferir-cobertura-por-par.php` (de 428 para 429 pares, o "sem menção" de 114 para 113, pela
amarração), `conferir-recorte-por-unidade.php` (duas linhas novas), `conferir-booleanos-em-consulta.php` e `conferir-busca-por-tags.php`,
e o `conferir-tudo.sh` inteiro (74), saída 0. **Não provado:** o navegador (do coordenador); a prévia do design ainda sem a barra (do
coordenador: o `spec-carteira.md`, a CA5, diz o que ela precisa).

**A correção** (a verificação em três lentes confirmou 6 achados, um médio e cinco baixos, todos tratados): **a barra mostra o que a
lista usou** -- o rascunho volta aos filtros da lista a cada lista nova pedida, e não só quando os filtros mudam (antes, o texto escrito
sem "Filtrar" ficava na barra depois do clique numa faixa, que sai com os filtros de antes); **os convênios que não vieram são pedidos
de novo** na próxima vez que a pessoa chega à barra (antes, só trocando de unidade; o médio: o "carregando", a falha e a resposta de
outra unidade descartada estavam sem caso); o caso do "de R$ 0" no servidor (o nulo como zero também no limite "de", e os K com a
mesma última visita); os casos de tela do "Limpar" sem lista e do "Filtrar" com o retrato relendo; o comentário do glossário, que
prometia na tela a diferença do nulo para as Campanhas; e a frase da medida, que dizia que cada filtro barateia a lista "nos dois
motores". `CarteiraFiltrosTest` com 13 casos e `CarteiraFiltros.teste.tsx` com 14. Os mutantes novos, um por vez, a reserva num
diretório novo e vazio a cada um, devolvidos pelo sha1: N1 (o `COALESCE` tirado do "de", nos dois bancos) e N2 (o `DISTINCT ON` do
PostgreSQL sem o `COALESCE`, só nele), G1 (o rascunho guardado só pelos filtros), G2 (a falha sem pedir de novo), G3 (a falha sem o
estado de erro), G4 (a resposta de outra unidade aceita), G5 (sem o "carregando"), G6 (o "Limpar" pedindo sem lista), G6b (o "Limpar"
lendo a lista nula: o erro do tratador recolhido da janela) e G78 (o "Filtrar" sem as duas guardas do retrato relendo), todos
vermelhos; o G7 (só a guarda do `pedir` tirada) e o G8 (só o `preventDefault` do botão tirado) ficam verdes, cada um coberto pela
outra guarda. Depois da correção: os testes da Carteira e vizinhos (o filtro `Carteira|Convenio|Relatorio|Campanha|Audiencia|`
`BuscaPorTags|Consumo|Portoes|Cliente|Menu`), 1.409 em cada banco, saída 0 (pulados: no MySQL o
`ComboboxDeAnosNoRelatorioTest::testOComboDoPainelDeduplicaOsAnos`, o `DescontosNoRelatorioTest::testOsTotaisNaoSomamOsDeOutraFranquia`,
o `VendasNoRelatorioTest::testODescontoNaoSomaODeOutraUnidade` e o `VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador`; no
PostgreSQL eles e os cinco do roteiro do consumo do `ConsumoDoClienteTest`, só do MySQL); o front 4.101 de 4.101, e com
`AMPLIFICAR_EFEITOS_MS=50` os da Carteira, 75 de 75; tsc, lint e build (368,37 KB em gzip); os portões que a fatia toca, o
`conferir-segredos.sh`, o `contar-debitos.php` (de pé 195) e o `conferir-debitos-repetidos.php`, saída 0.


**Conferido por mim, depois da correção** (04/11/2026): a primeira rodada desta fatia foi cortada pela queda de energia depois de
7 minutos, só com leituras (o transcrito lido ação por ação: nenhum arquivo, banco descartável nem `local.php` mexido); na volta, os
contêineres de pé, o `local.php` no `franquiabv_dev` pelo sha1, a árvore limpa, e a fatia refeita do começo depois de a carga da
máquina baixar e a aplicação aquecer. Os 6 achados das três lentes (um médio: o carregando, o erro e a resposta atrasada dos convênios
sem caso), lidos um a um, e o relato da correção. A prévia do design da Carteira acertada por mim: o eco `filtros` nas respostas de
lista e uma célula nova da lista filtrada (a busca "silva", a etiqueta da faixa e o eco); os tipos conferidos por um `tsc` com um
tsconfig temporário (apagado), sem erro. O ciclo inteiro depois da correção: caracterização 6.289 em cada banco, saída 0 (11 e 16
pulados); contrato 74 e 74; os ensaios, os portões (74) e o front 4.101 de 4.101, saída 0; o medidor dos relatórios no PostgreSQL,
saída 0; em cerca de 10 min. No navegador (o Vite subido de novo depois da queda), contra o banco de contrato, como o gerente de teste
com o privilégio de clientes: a barra dos filtros abaixo dos cartões; a busca "historico" sem resultado, com a frase "Nenhum cliente
com estes filtros." (o cliente de fixture não tem `tags`: no contrato 12 dos 13 ativos estão sem, e na cópia só 17 dos 239.239 -- a
busca é pela coluna, como no Clientes antigo, não um defeito); a busca "jose", sem acento, achando "José Acentuado Teste" em todas as
faixas, com a contagem de 1 cliente; nada rolando de lado. Na limpeza, as sessões dos dois usuários de teste contadas e apagadas no
contêiner; o `local.php` de volta ao `franquiabv_dev`, conferido pelo sha1, e o banco de contrato remontado.

## 04/11/2026 -- CARTEIRA-CA6 (a fatia 76e2 da fila das telas que faltam)

**Para quem usa:** o gerente e o administrador baixam a lista da Carteira de clientes que está na tela em TXT, PDF e planilha
(`.xlsx`), como no Relatórios › Clientes antigo -- os botões ficam embaixo da lista aberta, e o arquivo é o da faixa, do colaborador e
dos filtros dela, inteiro, e não o da última janela aberta (o S29). O arquivo leva também quem recusou mensagens (a 289 (b)), com a
coluna "Aceita mensagens" na planilha; o e-mail vai no PDF e na planilha para os dois papéis, e no TXT só para o administrador (a
296 (b)). A unidade inteira sai sempre (a 297 (a)); na rede, o PDF e a planilha pedem uma unidade, uma faixa ou um filtro, e acima de
40.000 clientes pedem mais um filtro -- a tela então oferece só o TXT, com a frase (a 302 (a), registrada nesta fatia); o TXT sai sempre
inteiro. O Clientes antigo continua até a CA4, que deixa de esperar.

**A medida primeiro** (em processo, pela Carteira, `memory_limit=128M` no começo e o limite subido a 1 GB pelo código só no PDF e na
planilha; a cópia, hoje = 17/08/2026; MySQL / PostgreSQL; nenhum arquivo da cópia guardado, só tamanho, linhas, tempo e memória): a
unidade 1 inteira (22.321) -- o TXT em 2,8 s e 20 MB / 5,5 s e 18 MB; o PDF em 3,5 s e 108 MB / 2,6 s e 108 MB; a planilha em 8,2 s e
350 MB / 7,1 s e 350 MB (coube: a fatia não parou); a faixa de 1 a 2 anos da rede (28.188) -- o PDF em 3,6 s e 130 MB / 2,5 s e 128 MB,
a planilha em 10,6 s e 497 MB / 9,7 s e 499 MB; o TXT da rede inteira, 239.236 linhas (os ativos menos os 3 sem telefone, contados
por SELECT), em 11,4 a 11,7 s e 71,7 MB / 6,4 s e 37 MB, sem subir o limite. Sem o teto, só para medir: 37.583 linhas, a planilha em
11,1 s e 599 MB; 48.286, 14,5 s e 727 MB; 56.484, 17,9 s e 934 MB. **O teto: 40.000** (a pior densidade medida, 17,6 KB por linha,
põe 40.000 perto de 700 MB; acima da maior unidade inteira). A primeira medida da faixa da rede caiu no `OutOfSpaceException` do cache
de memória do Doctrine (64 MB, a metade do limite do começo do processo): as leituras leves foram para antes da lista inteira.

**O que entrou:** a política com `['pdf', 'xlsx', 'txt']` e a rota `.../carteira/exportacao`; no `CarteiraController`, o `contatos`
(o TXT por iterador), o `documento` (o 422 da rede sem filtro sem ler o banco, o teto pela primeira linha, o limite subido, as leituras
leves antes), o `travaDoArquivo` por pessoa (`carteiraArquivo-<id>`) e a conferência de cada linha pela regra (`conferida`, a mesma da
página); a regra pura `ContasDaCarteira::recusaDoDocumento`, que responde ao arquivo e à lista (o eco novo `recusaDoDocumento`, para a
tela não abrir uma aba com o erro); `ClienteRepository::carteiraDoArquivo` (a mesma consulta da lista, sem a página) e
`::carteiraCadastroDoArquivo` (as colunas do cadastro e a pontuação do recorte, a X6, em `SqlDaCarteira::CADASTRO_DO_ARQUIVO`); o
`CarteiraJson::documento` (8 colunas no PDF; 19 de hoje mais 4 na planilha, 23 na rede); o nome `carteira-<dia>-<unidade-N|rede>`
pela forma `agora` na base. Na tela, o `BotoesDeExportacao` na lista aberta, com o endereço da mesma lista sem a página
(`buscaDoArquivo`, `enderecoDoArquivoDaCarteira`, `formatosDaLista`). Os portões: o `conferir-cobertura-por-par` (429 para 431 pares;
ISRelatorio 195 para 198), três vigias novas no `conferir-recorte-por-unidade` (o arquivo, a conferência e a pontuação do recorte), a
entrada no `medir-adm-api-no-postgres` e a folha nova no `PortoesDaFamiliaDosRelatoriosTest`.

**O que rodou:** o `CarteiraExportacaoTest` (14 casos) e o `testARecusaDoDocumento`, com os vizinhos (902 casos nos dois bancos, saída 0;
pulados o `RegrasDosAtendimentosTest::testBancadaDoAno` nos dois e, no PostgreSQL, os cinco do roteiro do consumo do
`ConsumoDoClienteTest`, só do MySQL); o front 4.109 de 4.109, e com `AMPLIFICAR_EFEITOS_MS=50` os da Carteira, 83 de 83 (o
`Console.teste.tsx` amplificado com os 6 vermelhos da comanda e do caixa que as fatias anteriores já anotavam no HEAD); tsc, lint e
build (368,54 KB em gzip); os ensaios de permissão e de recusas nos dois bancos de contrato e o medidor da `/adm/api` no PostgreSQL (os
três formatos da Carteira com 200), saída 0. **Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos
pelo sha1, todos vermelhos: S1 o arquivo sem os filtros, S2 o consentimento filtrando, S3 o e-mail fora do PDF do gerente, S4 o e-mail
no TXT do gerente, S5 a pontuação de todas no arquivo da unidade, S6 a da unidade da visita, S7 a trava da base, S8 a rede sem filtro
aceita, S9 o teto ignorado, S10 o teto no TXT, S11 o limite subido no TXT, S12 o TXT pela sessão, S13 o "Aceita mensagens" invertido,
S14 o teto abaixo da maior unidade, S15 a lista sem a recusa, S16 o teto na unidade, S17 o limite que não sobe (nos dois bancos); F1 a
recusa ignorada na tela, F2 o arquivo com a página, F3 os botões ligados com outra página no ar, F4 o endereço sem os filtros, F5 a
frase da recusa fora. **O oráculo** pelo HTTP no contrato MySQL, como o gerente e como o administrador, com o `CenarioDaCarteira`
semeado: os três arquivos, as linhas do TXT e da planilha contra a lista inteira da API (pelos telefones, pelos nomes e pelas
contagens), o e-mail onde deve estar, quem recusou presente, a pontuação da unidade e da rede, a rede sem filtro e com uma faixa -- 28
conferências, 0 divergências; falsificado (o consentimento filtrando), 11. Na cópia, só a contagem: o TXT da unidade 1 com as 22.321
linhas da lista e o da rede com as 239.236 do SELECT. Sem pergunta nova ao dono. Débitos: o D690, o D699, o D700 e o D706 CORRIGIDOS (o
D706 com a 302 (a) registrada), o D306 PARCIAL. Nada da produção mudou; na `franquiabv_dev`, só SELECT; o banco de contrato remontado.

**A correção depois da verificação** (três lentes, 9 achados confirmados, um médio e oito baixos, todos tratados). **Para o dono:**
na rede, não é só a faixa de mais de 2 anos que passa do teto -- um filtro sozinho, sem faixa, também pode passar (o consumo de
R$ 100 a R$ 250 da rede inteira tem 56.484 clientes), e aí a tela também oferece só o TXT, com a frase; é a letra da 302 (a) ("mais
um filtro ou uma unidade"), e nada mudou no código. O que mudou: (1) o formato do documento é o que a base conferiu
(`$pedido['formato']`, aparado), e não o `?formato=` cru -- "pdf " passava na guarda e saía um PDF com as 22 colunas da planilha,
o endereço junto; o `CarteiraJson::documento` recusa outro texto; (2) o médio: o conserto do cache do Doctrine ganhou
caso (`testDepoisDaListaSoOSqlDoCadastro`: antes da consulta da lista, as leituras leves; depois, só o SQL nativo do cadastro); (3) o
teto pela primeira linha ganhou caso (as linhas lidas: 1 acima do teto, 0 na rede sem filtro; `linhasDoArquivo` passou a protegida
para o caso as contar); (4) o "nunca desce" do limite e o `G` do `bytes()` ganharam caso (2G e 1536M ficam); (5) o limite subido a
1024M não vaza mais para o resto da suíte (o arnes o devolve a cada pedido, e o `tearDown` também); (6) os comentários, o D706 e o
desenho deixam de vender streaming do banco: o PDO traz o resultado inteiro, e o iterador poupa só os arrays do PHP; (7) o desenho
sem a CA4 "esperando o D706" em sete lugares, e o D700 da seção 4 com a marca; (8) o colaborador como filtro na rede, escrito como
escolha da fatia que nenhuma tela alcança. **Rodou:** o `CarteiraExportacaoTest` com 16 casos, e os vizinhos (a família dos
relatórios, a Carteira, a Cota-parte, o Atendimentos, o NPS, o consumo, a listagem de vendas e os segredos), 1.055 casos em cada
banco, saída 0 (pulados 5 no MySQL, os declarados sem a cópia e a bancada dos Atendimentos, e 10 no PostgreSQL, os mesmos e os cinco
do roteiro do consumo); os portões da fatia e o `conferir-segredos.sh`, saída 0. **Os mutantes novos**, um por vez, a reserva num
diretório novo e vazio, devolvidos pelo sha1, nos dois bancos, todos vermelhos: N1 o formato cru (o `LogicException`), N2 o documento
sem a recusa do formato, N8 os dois juntos (o de antes: as colunas da planilha no PDF), N3, N9 e N10 as leituras leves depois da
lista (todas, só o convênio, só as unidades), N4 o teto conferido depois de ler tudo, N5 o limite sem a comparação, N6 o `G` contado
como `M`, N7 o arnes sem devolver o limite. O front não mudou.


**Conferido por mim, depois da correção** (04/11/2026): os 9 achados das três lentes (todos confirmados, um médio: o conserto do
cache do Doctrine sem caso), lidos um a um, e o relato da correção; a prévia do design da Carteira conferida por um `tsc` com um
tsconfig temporário (apagado), sem erro (o `recusaDoDocumento` é opcional). O ciclo inteiro depois da correção: caracterização 6.306
em cada banco, saída 0 (11 e 16 pulados); contrato 74 e 74; os ensaios, os portões (74) e o front 4.109 de 4.109, saída 0; o medidor dos
relatórios no PostgreSQL, saída 0; em cerca de 9 min 54 s. No navegador, contra o banco de contrato, como o gerente de teste com o
privilégio de clientes: a lista da faixa de 181 a 365 dias com os três botões embaixo ("abrir o PDF", em outra aba; "baixar a
planilha (XLSX)"; "baixar os contatos (TXT)"), e os três endereços da exportação, pedidos pela sessão, com 200: o PDF com 14,8 KB, a
planilha com 7,2 KB e o nome `carteira-2026-11-04-unidade-90001000.xlsx`, o TXT `clientes.txt` com 3 linhas, uma por cliente da faixa
(os mesmos 3 da lista); o conteúdo é da fixture sintética e não foi impresso nem guardado. Na limpeza, as sessões dos dois usuários
de teste contadas e apagadas no contêiner; o `local.php` de volta ao `franquiabv_dev`, conferido pelo sha1, e o banco de contrato
remontado.

## 04/11/2026 -- CARTEIRA-CA4 (a fatia 76e da fila das telas que faltam)

**Para quem usa:** o relatório Clientes some do ERP antigo -- o item do menu Relatórios, a lista com a busca, os 26 filtros e as 8
ordenações, e o PDF, a planilha e o TXT dela. `/mod-relatorios/rel-clientes/index`, `.../pdf`, `.../xls` e `.../txt` respondem 404,
também a quem tem o privilégio. O lugar dele é a Carteira de clientes, na tela nova: a busca, o convênio, o mês do aniversário e o
consumo (a CA5), e o TXT, o PDF e a planilha da lista que está na tela (a CA6). No menu da tela nova, "Carteira de clientes" vira um
botão, e não mais um link para o ERP (o caminho do Clientes antigo ficou só como a chave da tela nova). **O que a tela antiga tinha e a
Carteira não repete, e sai com ela** (a tabela FORA da 1.9 do `spec-carteira.md`, pela 292 (b)): os 17 filtros que a resposta não
lista (fidelizado, perfil do NPS, estado, idade, origem, cadastro, programa, marketing, pontuação, tipo, área e serviço comprados,
último acesso ao aplicativo...), as 8 ordenações (a Carteira tem uma, a da última visita) e as colunas de NPS e de fidelizado na tela;
o nascimento, o consumo, o convênio e a pontuação continuam na planilha da Carteira. **As Campanhas continuam** contando a audiência
com todos os filtros de antes (`marketingPesquisarClientes`, pelo `filtrosListagemIndexRelatorio`, com o formulário `ISRelatorio\Form\Cliente`
na tela delas). No menu Relatórios do ERP antigo, quem só tivesse o privilégio do Clientes não abre mais o grupo (ele ficaria vazio);
na cópia, os 108 ativos fora dos administradores com o privilégio têm outro dos oito da conta, e ninguém perde o grupo (só SELECT,
iguais nos dois bancos).

**O que saiu** (derivado do `Module.php`, do `module.config.php` e do `git grep`): o `ISRelatorio\Controller\ClientesController` (as
quatro ações, com a guarda da E10 da CA1), a fábrica, o invokable e o alias `rel-clientes`, a view `clientes/index.phtml`, o
`Pdf/PdfClientes.php`, o `Xls/Clientes.php`, o item do `layout.phtml`, a linha `Rel-clientes` do `SessaoAcl::menuAtivo`, o
`clientes` do `menuAcesso('relatorios')` (nenhum outro item do grupo o lê, conferido no layout), o
`ClienteRepository::listagemIndexRelatorio` (sem outro chamador), o `ClientesAntigoNaPoliticaTest` da CA1, o caso "relatório de
clientes" das ordenações do `D203TextoDigitadoNasTelasDoErpTest`, as cinco vigias da E10 do `conferir-recorte-por-unidade.php` e as três
marcas do `medir-relatorios-no-postgres.php`. **O que ficou:** o privilégio `relatorios/clientes` (o da Carteira; o `SessaoAcl` segue
lendo o nome novo da DE3 e, sem ele, o antigo), o `ISRelatorio\Form\Cliente` (as Campanhas o usam -- a nota da fila não o listava, e o
grep o achou), o `filtrosListagemIndexRelatorio` e o `marketingPesquisarClientes`. No front, a Carteira entrou no `SO_NA_TELA_NOVA`.
O `RelatorioClientesInjecaoTest` (o D73) passou a cobrar os filtros pela porta das Campanhas, com as mesmas contagens.

**Os portões:** o `conferir-cobertura-por-par` de 431 para 430 pares (o `listagemIndexRelatorio`; pondo-o de volta, o sha1 é o de
antes) e o ISRelatorio de 198 para 192 chamadas; o inventário regerado (347 para 343 ações); o `conferir-referencias-docs` com o
`ClientesController` explicado (a tabela do dia 0 conta o passado); o `conferir-debitos-repetidos` sem o par D696|D698, que deixou de
parecer com a marca nova; sem mudança de linha de base o `conferir-rotas-sem-login`, o `conferir-guarda-do-erp`, o
`conferir-links-mortos`, o `conferir-caminhos-postgres`, o `conferir-injecao-dql`, o `conferir-booleanos-em-consulta`, o
`conferir-booleano-em-coluna-inteira`, o `conferir-ordenacao-empates` (42 listagens) e o `conferir-remocao-por-post`; o
`conferir-tudo.sh` inteiro, sozinho, saída 0.

**O que rodou:** o novo `ClientesAntigoSaiuTest` (6 casos) e os vizinhos (o `RelatoriosNoMenuTest` com o par `rel-clientes` no
`SAIRAM_DO_LAYOUT` e sem a `METADE_QUE_FICA` da CA3, o `EvasaoSaiuTest` com os controles nos Descontos e no Programa de Indicação, o
`RelatorioClientesInjecaoTest`, o `D203`, o `AniversariantesSaiuTest`, o `PermissoesComONomeDaTelaTest`, o `CampanhaAudienciaTest` e
outros), 163 casos em cada banco, saída 0; a caracterização inteira, 6.305 em cada banco, saída 0 (11 e 16 pulados); o front 4.110 de
4.110, e com `AMPLIFICAR_EFEITOS_MS=50` os do menu e do Console (os mesmos 6 vermelhos da comanda e do caixa do `Console.teste.tsx`
que o HEAD já tinha); tsc, lint e build (368,54 KB em gzip); os ensaios de permissão, de recusas e da sessão no contrato MySQL e os oito
do PostgreSQL, saída 0, com as 15 travas `/tmp/*.lock` do contêiner contadas e apagadas. **Os mutantes**, um por vez, a reserva num
diretório novo e vazio a cada um, devolvidos pelo sha1, os de servidor nos dois bancos, todos vermelhos: M1 o item do menu de volta,
M2 a linha `Rel-clientes` de volta no `menuAtivo`, M3 o `clientes` de volta no `menuAcesso`, M4 o `filtrosListagemIndexRelatorio`
apagado junto (as Campanhas vermelhas, pelo D73), M5 o controller de volta, M6 a view de volta, M7 a consulta da lista de volta; F1 o
caminho da chave trocado no front (também o `RelatoriosNoMenuTest`), F2 a Carteira fora do `SO_NA_TELA_NOVA`; o controle (o
`Rel-clientes` só num comentário) verde. **O oráculo** pelo HTTP no contrato MySQL, como o gerente de teste com o privilégio do
Clientes e o dos Descontos (por `privilegios_usuarios`, só no contrato): as quatro ações antigas com 404 (nem o login, nem arquivo), os
Descontos com 200 e o menu sem o item, a Carteira com 200 e as oito faixas, e a exportação dela em TXT com 200 e o anexo -- 22
conferências, 0 divergências; a sessão apagada no contêiner, o `local.php` de volta ao `franquiabv_dev` pelo sha1, o banco de contrato
remontado. Sem pergunta nova ao dono. Débitos: o D696 CORRIGIDO, o D306 PARCIAL, o D702 com a nota; de pé 192 para 191. Nada da
produção mudou; na `franquiabv_dev`, só SELECT. O navegador e o ciclo inteiro são do coordenador.

**A verificação em três lentes confirmou 7 achados, todos baixos, tratados:** o comentário do `Module.php` dizendo que o formulário
saiu junto (o `ISRelatorio\Form\Cliente` fica: é o das Campanhas); a `clientesTabelaOrdenacao` do `mod-relatorios.js`, que o layout
carrega em toda página, morta com a view que a chamava (saiu, o `?cache=` do layout de 165 para 166, e o caso novo
`testOScriptDasOrdenacoesSaiuDoLayout` lê os scripts do layout sem os comentários); a nota FEITA da CA4 pondo a unidade e a última
visita entre os 17 filtros (a tabela FORA da 1.9 os chama de trocados pelo recorte, pelo colaborador e pelas faixas: a nota agora diz
os 17 certos e os três trocados à parte); os comentários que falavam do Clientes antigo no presente (o `SqlDaCarteira`, o
`CarteiraJson`, o `CarteiraController`, o `FPDFTable`, o `CarteiraExportacaoTest` e o `RegrasDaCarteiraTest`, com o caso novo
`testNenhumComentarioFalaDaTelaQueSaiuNoPresente`); o D306 contando oito fatias da Carteira (são as nove seções CA0 a CA8, em dez
fatias, com a 76p); os pulados sem nome; e o oráculo HTTP sem falsificação. **Os pulados, pelo nome**, na caracterização inteira
depois da correção (6.307 em cada banco, saída 0): os 11 dos dois bancos são o `ComboboxDeAnosNoRelatorioTest` (1), o
`D199AtivarRemoverPorPostTest` (3: o vipclass e as mensagens), o `DashboardFaturamentoAnosTest` (2), o `DescontosNoRelatorioTest`
(1), o `RecorteDeUnidadeNaAgendaTest` (1), o `RegrasDosAtendimentosTest::testBancadaDoAno` (1), o `VendasNoRelatorioTest` (1) e o
`VipClassNoRelatorioTest` (1) -- os da cópia da produção, os das telas sem o botão e a bancada --; no PostgreSQL, mais os 5 do
`ConsumoDoClienteTest` (o roteiro do corte é só do MySQL, desde a 76o). Nenhum é de arquivo que esta fatia tocou. **Os mutantes da
correção**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, nos dois bancos: N1 a
`clientesTabelaOrdenacao` de volta, N2 e N3 o comentário do `SqlDaCarteira` e o do `CarteiraJson` de volta ao presente, N4 o controle
do passado tirado, todos vermelhos; o controle C2 (o nome só num comentário do script) verde. **O oráculo falsificado:** com o lado do
servidor da fatia desfeito (os nove arquivos do HEAD), o mesmo roteiro deu 9 divergências em 22 (as quatro ações antigas com 200, os
três arquivos como anexo e o menu com o link e o item); de volta, 22 conferências, 0 divergências, e o banco de contrato remontado. O
`conferir-tudo.sh` sozinho, 74, saída 0; o `contar-debitos`, o `conferir-debitos-repetidos`, o `conferir-referencias-docs` e o
`conferir-segredos`, saída 0; as 60 travas `/tmp/*.lock` que a suíte deixou como root, contadas e apagadas.


**Conferido por mim, depois da correção** (04/11/2026): os 9 achados das três lentes (7 confirmados, todos baixos), lidos um a um, e o
relato da correção. O ciclo inteiro depois da correção: caracterização 6.307 em cada banco, saída 0 (11 e 16 pulados); contrato 74 e 74;
os ensaios, os portões (74) e o front 4.110 de 4.110, saída 0; o medidor dos relatórios no PostgreSQL, saída 0; em cerca de 9 min 55
s. No navegador, contra o banco de contrato, como o gerente de teste com os privilégios de clientes e dos Descontos (por
`privilegios_usuarios`, só no contrato): os quatro endereços antigos do Clientes (o index, o pdf, o xls e o txt) com 404, os Descontos
e a API da Carteira com 200, nenhum `rel-clientes` no menu do ERP e nenhuma `clientesTabelaOrdenacao` na página (o
`mod-relatorios.js` novo); no menu novo, "Carteira de clientes" é botão e abre a tela nova, sem a ponte para a versão antiga. Na
limpeza, as sessões dos dois usuários de teste contadas e apagadas no contêiner; o `local.php` de volta ao `franquiabv_dev`, conferido
pelo sha1, e o banco de contrato remontado.

## 04/11/2026 -- SAIDAS-LEITURA (a fatia 77 da fila das telas que faltam)

**Para quem usa:** Contas a Pagar abre na tela nova, para ler -- os dois itens do menu, "Gerenciar" e "Nova", que até aqui caíam na
tela "ainda roda na versão antiga". A lista abre no mês de hoje, uma linha por parcela com o nome da despesa ao lado, e diz o que a
antiga escondia: a vencida (pelo dia: a que vence hoje ainda é aberta), a paga sem a marca, a quitada, a quitada sem pagamento e a
"quitada com R$ X a menos", cada uma com o seu selo; a falta só na aberta e na vencida; os totais ao pé (a falta só das abertas); a
marca "sangria do caixa" na despesa que veio de uma retirada; e o aviso "Há N parcelas vencidas antes deste mês, desde novembro de 2025
(R$ X a pagar)" (o mês de 12 meses atrás, lido do servidor; a paga sem a marca não conta como vencida), com o atalho para as vencidas de todos os meses. O mês anda com ‹ e › (para a frente também) e pelo ano; os filtros
são a situação (com "vencidas"), o tipo, a descrição e "todos os meses"; e "despesas sem parcela" mostra as que a lista do mês nunca
mostrou. A despesa abre com as parcelas, o extrato de cada uma (o estornado riscado, "estornado em dd/mm/aaaa"), os totais e, na da sangria, "Veio da
retirada de R$ X do caixa, em ...", com "ir ao caixa" para quem abre o caixa. **As escritas continuam na tela antiga até a fatia 78** (a T2): quem escreve ganha "lançar despesa
nova na tela antiga" e, na despesa, "abrir esta despesa na tela antiga", em aba nova -- o molde das Unidades em leitura (16/10/2026);
o "Nova" diz que lançar ainda é lá. Quem só lê vê a mesma tela, sem os caminhos, e o "Nova" dele, que na antiga era um 404, abre a
lista com "você pode ver, mas não lançar". **Na cópia (só SELECT, só contagem):** dos 366 usuários ativos, 115 abrem a tela -- 105 que
leem e escrevem (todos os não administradores com o privilégio escrevem; nenhum só lê) e 10 administradores --, e 251 não têm o
privilégio; os 115 escrevem, e escrevem na tela antiga.

**Na API** (a S2 do desenho, `docs/f3-telas-restantes/tela-saidas.md`): `GET /adm/api/saidas/tipos`, `/anos` e `/sem-parcela`, e
`POST .../parcelas/:parcela/quitacao-sem-pagamento` com o motivo obrigatório (a 139 (b): também na parcela sem dinheiro; 409 na já
quitada e na excluída); a `situacao=vencidas`; o `tipo`, a `origem`, a falta e o dispensado na linha; a `moeda` da unidade e o
`vencidasAntes` na lista com o mês; a `origem` e a `moeda` na despesa. Tudo da unidade da sessão (o administrador abre a de outra só
pelo id, como antes).

**O que entrou:** `SaidaRepository::vieramDaSangria`, `::semParcela` e `::vencidasAntes`, `SaidaTipoRepository::ativos`,
`SaidaParcelaRepository::anosComParcela`, `SaidaJson::semParcela` e `::origem`, as quatro ações e as quatro rotas; no front,
`api/saidas.ts`, `ContasAPagar.tsx`, `ContasAPagarLigado.tsx`, a regra pura `regraDasContas.ts` (o nome do desenho, `contasAPagar.ts`,
colidia com o `.tsx` no disco que não distingue a caixa), o Console (`CAMINHO_DAS_SAIDAS`, `CAMINHO_DA_NOVA_SAIDA`,
`PRIVILEGIO_DE_EDITAR_SAIDAS`), o bloco CONTAS A PAGAR do `App.css` e a prévia (`.design-sync/previews/ContasAPagar.tsx`, com o
`_contasAPagar.ts`, o `componentSrcMap` e um viewport de 1280x1300, estimado, e não medido). O ensaio novo
`scripts/ensaiar-saidas-adm-api.php` (a faixa 90004700-90004799), no `medir-telas-no-postgres.sh`, no CI e no
`conferir-lacuna-do-ci.sh`; as quatro rotas no `conferir-rotas-sem-login.php` e no `ensaiar-permissao-adm-api.php`; o
`conferir-cobertura-por-par.php` de 430 para 435 pares (os cinco novos; tirando-os, o sha1 é o de antes) e o ISFinanceiro de 3 para 9
chamadas.

**O que rodou:** o `SaidaApiLeiturasTest` (26 casos) e os vizinhos (Contas a Pagar, a sangria, o caixa, os anos), 358 casos em cada
banco, saída 0 (1 pulado nos dois: `ComboboxDeAnosNoRelatorioTest::testOComboDoPainelDeduplicaOsAnos`, sem a cópia, declarado); o
ensaio novo 33 de 33 (20 recusas e 13 controles, cada controle contra um SELECT) nos dois bancos de contrato, e o de permissão (as
quatro rotas com 403) e o de recusas (253 de 253) nos dois; o front 4.159 de 4.159 (49 novos), o `tsc`, o lint (nenhum aviso nos
arquivos da fatia) e o build; com `AMPLIFICAR_EFEITOS_MS=50`, os arquivos novos verdes, e no `Console.teste.tsx` os mesmos 6
vermelhos da comanda que o HEAD tem, conferidos pelo nome com o `Console.tsx` do HEAD; os tipos de todas as prévias com o tsconfig
temporário, 0 erros; o `conferir-tudo.sh` inteiro, sozinho, 74 portões, saída 0; o `conferir-segredos.sh`, saída 0.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1, todos vermelhos pela asserção: 21
do servidor nos dois bancos (a origem sempre "tela", o tipo inativo, as sem parcela de outra unidade, as vencidas com a de hoje, o
"dar por quitada" sem o 422 do motivo, a guarda de `ler` fora de cada uma das três leituras novas, o aviso sem o alcance, com as
quitadas e de outra unidade, o dispensado zero, a falta na quitada, os anos com a despesa excluída, a origem da despesa sempre "tela",
as vencidas sem filtro, a moeda vazia, a sangria nunca achada, as sem parcela com parcela ativa, a ação sem a parcela da rota e a
vencida pelo instante); 7 deles pelo HTTP, no ensaio (S3 só ficou vermelho depois que o ensaio ganhou a despesa sem parcela da outra
unidade); e 20 do front (cada selo trocado, os totais com a falta das quitadas, a marca da sangria fora, o foco que não volta, a falta
na quitada, o aviso fora, o "ver as vencidas" sem tirar o mês, o estornado sem risco, a lista e a despesa atrasadas entrando, os
caminhos da tela antiga a quem só lê, o Console sem a tela, sem a frase do "Nova" e dando o escrever, a moeda ignorada, o ano fora do
seletor e a paginação calada).

**O oráculo** pelo HTTP no contrato MySQL, como o gerente de teste com o privilégio por uma linha de `privilegios_usuarios` (ler, sem
escrever) e um cenário na faixa 90004800: a lista do mês, o aviso, as vencidas, as sem parcela e quatro despesas contra SELECTs escritos
no oráculo, 97 conferências, 0 divergências; falsificado (a de hoje como vencida), 3 divergências. O banco de contrato foi remontado,
e as sessões dos usuários de teste apagadas no `/tmp` do contêiner.

**A verificação em três lentes** confirmou 10 achados, dois médios e oito baixos, todos tratados: o extrato sem o dia do estorno (a
API ganhou o `estornadoEm`, a alteração que o estorno grava -- na cópia os 126 estornados a têm --, e a tela diz "estornado em
dd/mm/aaaa"); a despesa da sangria sem o caminho para o caixa ("ir ao caixa", pelo privilégio do item do menu, como na comanda); o
aviso e o filtro "vencidas" contando a parcela que o selo diz "paga, sem a marca" (o aviso dizia "R$ 0,00 a pagar" dela; agora a
`SaidaRepository::NAO_PAGA_SEM_A_MARCA`, o complemento exato do selo, nos dois); o texto "Na tela antiga" prometendo lançar parcelas
e estornar na despesa da sangria com a retirada ativa (as respostas 174 (b), 171 (a) e 175 (a): agora a frase diz o que a antiga
recusa); o front sem caso para a falha da lista com o "tentar de novo", a falha, o vazio e a paginação das sem parcela e as cargas (o
médio); o LEFT JOIN do tipo sem caso (o médio: a despesa sem tipo na lista); o aviso de um mês futuro sem caso (o corte em hoje); as
guardas de despesa e de parcela excluída no aviso, a da parcela só coberta até 30/11/2026 pela data da fixture; a moeda só conferida
como "R$" (a unidade de Portugal, na lista da sessão e na despesa aberta pelo administrador); e a frase do aviso escrevendo os 12 meses
sem ler o `desde` (agora "desde novembro de 2025", do servidor: a P6 muda só a constante). Depois da correção: o
`SaidaApiLeiturasTest` com 32 casos (6 novos), e os vizinhos 641 casos em cada banco, saída 0 (1 pulado nos dois, o mesmo
`ComboboxDeAnosNoRelatorioTest::testOComboDoPainelDeduplicaOsAnos`); o ensaio novo 33 de 33 nos dois bancos de contrato; o front
4.174 de 4.174 (15 casos novos), o `tsc`, o lint (nenhum aviso novo) e o build, saída 0; os arquivos novos com
`AMPLIFICAR_EFEITOS_MS=50`, 64 de 64; os tipos das prévias com o tsconfig temporário, 0 erros. Os mutantes novos, um por vez, a
reserva num diretório novo e vazio, devolvidos pelo sha1, todos vermelhos: 11 do servidor nos dois bancos (V1 a V11: a paga sem a
marca no filtro, no aviso e sem o ramo do previsto zero, o INNER JOIN, o aviso futuro sem o corte, sem a guarda da despesa e sem a da
parcela, a moeda da sessão fixa e a da despesa pela sessão, o `estornadoEm` nulo e pela criação) e 16 do front (G1 a G16: o estornado
sem o dia, o "ir ao caixa" fora, dado a quem não abre o caixa e não passado pelo invólucro, a frase da sangria trocada, ignorando o
estado e sem a origem, o aviso com o mês escrito, o "tentar de novo" mudo, a página das sem parcela parada, o erro e o vazio delas
calados e as quatro cargas caladas). O oráculo refeito com a regra nova e o instante do estorno: 107 conferências, 0 divergências;
falsificado (a de hoje como vencida), 3. O banco de contrato foi remontado.

**Débitos:** o D709 REGISTRADO (o aviso olha 12 meses; a P6 do desenho foi ao lote, "Contas a Pagar: o aviso das parcelas vencidas
conta só o último ano, ou todas?"); o D205 com a nota da rota nova (continua PREPARADO: a coluna na produção, e a tela pede o motivo
na 78); de pé 191 para 192. Nenhum esquema mudou; nada da produção; na `franquiabv_dev`, só SELECT. O navegador e o ciclo inteiro são
do coordenador.


**Conferido por mim, depois da correção** (04/11/2026): os 13 achados das três lentes (10 confirmados, dois médios: o erro, o vazio e
a paginação do front sem caso, e o LEFT JOIN do tipo sem caso), lidos um a um, e o relato da correção; a pergunta do D709 (o alcance do
aviso das vencidas) foi à página como a 305, recomendada a (a), e nada na fila espera por ela. O ciclo inteiro depois da correção:
caracterização 6.339 em cada banco, saída 0 (11 e 16 pulados); contrato 74 e 74; os ensaios, os portões (74) e o front 4.174 de 4.174,
saída 0; o medidor dos relatórios no PostgreSQL, saída 0; em cerca de 9 min 56 s. No navegador, contra o banco de contrato, como o
gerente de teste com o "Financeiro saídas" só de ler (a 90002021 por `privilegios_usuarios`, só no contrato): Contas a Pagar ›
Gerenciar abre a tela nova no mês de hoje, com o aviso "Há 1 parcela vencida antes deste mês, desde novembro de 2025 (R$ 1.500,00 a
pagar). ver as vencidas"; o atalho mostra a parcela de 10/03/2026 com o selo VENCIDA e os totais; a despesa abre com o foco no nome,
as duas parcelas (a quitada com o extrato, a vencida com "nenhum"), e nenhum botão de escrever. **Dois defeitos de tela achados por mim
e consertados**: (1) os filtros usavam a grade do catálogo, cujo `:first-child` estica o primeiro campo na linha inteira -- a
"Situação" ocupava a linha, os outros espremiam em 9rem, o "todos os meses" quebrava longe da caixa e o "filtrar" ficava sozinho no
meio; agora a trilha de 10,5rem das Unidades, o primeiro campo numa trilha só, os botões numa linha própria e a caixa na largura dela
(o `width: 100%` dos campos do catálogo a esticava); (2) a ficha da despesa (o tipo, a unidade e a data) usava a classe
`detalhe-dados`, sem regra nenhuma no CSS, e saía colada na borda do cartão com o recuo do navegador; agora a `identificacao` das fichas
das outras telas. Os dois com o caso em `recuosDosRelatorios.teste.ts` (as regras, a ordem que vence o empate, e a tela pintando as
classes), o mutante (a regra do primeiro campo arrancada) vermelho e devolvido pelo sha1; medidos de novo no navegador a 1280 px (os
quatro campos numa linha, os botões embaixo) e a 375 px (uma trilha, nada rolando de lado). Depois do conserto, o front inteiro 4.175 de
4.175, tsc, lint e build (373,42 KB em gzip), saída 0. Na limpeza, as sessões dos dois usuários de teste contadas e apagadas no
contêiner; o `local.php` de volta ao `franquiabv_dev`, conferido pelo sha1, e o banco de contrato remontado.

## 04/11/2026 -- SAIDAS-ESCRITAS (a fatia 78 da fila das telas que faltam)

**O que entrou:** a T2 de `docs/f3-telas-restantes/tela-saidas.md` (as escritas de Contas a Pagar na tela nova) e a R, menos o
navegador e o ciclo inteiro, que são do coordenador. As respostas 139 (b), 140 (a), 141 (c), 142 (a), 143 (a), 18 (a) e 171 a 175 do
dono, seguidas pela letra.

**O que muda para quem usa:** quem tem "Financeiro saídas" com o escrever faz tudo na tela nova -- lança a despesa (o "Nova" do menu
abre o formulário, com o contador da descrição como fica gravada), altera o tipo e a descrição, lança parcelas vendo antes os
vencimentos ("4 parcelas de R$ 1.500,00: 31/01/2026, 28/02/2026, 31/03/2026 e 30/04/2026."), paga na linha da lista do mês ou na
despesa (a caixa "quitada" vem marcada quando o valor cobre o que falta e desmarcada no parcial, e a tela diz antes "a parcela fica
quitada com R$ X a menos" e "R$ X acima do previsto"), dá por quitada com o motivo, edita e exclui a parcela sem pagamento, estorna com
o motivo (dizendo antes o que o estorno faz) e exclui a despesa sem dinheiro (dizendo quantas parcelas saem junto). Na despesa da
sangria com a retirada ativa, o "lançar parcelas" não aparece, e no lugar a frase do servidor; o estorno dela diz antes que só
cancela a retirada com o caixa ainda aberto e de quem estorna, e, recusado com o caixa fechado ou por quem não opera aquele caixa, diz
a frase do serviço. O extrato mostra o dia e o motivo do estorno (quem estornou fica gravado; mostrar o nome é do dono, o D711); a
quitada com motivo, o motivo. Na linha da paga sem a marca, o "dar por quitada" no lugar do "pagar". Quem só lê vê a mesma tela sem botão de escrever. Os links para a tela antiga, que a 77 dava a quem escreve, saíram. **A tela
antiga continua no ERP**, com as mesmas regras do servidor: nenhuma fatia da fila a tira, e quando ela sai foi ao dono (D710).

**O servidor:** nenhuma regra nova -- toda escrita vai à API da S1 e da S2. A despesa e as respostas das escritas da parcela passaram a
publicar `quitacaoMotivo` e, no extrato, `estornoMotivo` (sem quem estornou, desde a verificação: o D711), lidos das colunas fora do
mapeamento com a guarda delas (`SaidaParcelaRepository::motivosDaQuitacao`, `SaidaParcelaPagamentoRepository::estornos`, um pedido por
tabela para a despesa inteira); nulos no banco sem as colunas. Era o resto do D207.

**O front:** `api/saidas.ts` (as nove escritas e os tipos novos); `telas/formularioDasContas.ts` (as regras puras: o tamanho gravado,
a conferência da descrição e do motivo com as frases do servidor, o valor digitado com vírgula, os vencimentos do lote pelo
`mesesDepois`, a caixa sugerida e os dois avisos, o que cada parcela e a despesa oferecem, a frase do estorno feito, os corpos);
`telas/EscritasDasContas.tsx` (os cinco formulários, no molde dos cupons: o 422 no campo com o foco no primeiro marcado, o botão
`aria-disabled` com o pedido no ar); `ContasAPagar.tsx` (o "lançar despesa nova", o "pagar" na linha, as ações da despesa e das
parcelas, a região de status das escritas, a pergunta no botão das duas exclusões) e `ContasAPagarLigado.tsx` (as escritas: uma de
cada vez, pela trava numa referência; a recusa de campo ao formulário, o resto à região de status com a despesa relida; a lista relida
na volta, se algo foi gravado); o bloco do CSS; a prévia do design com cinco células novas (a despesa nova, pagar, estornar, lançar e
a sangria de quem escreve) e o dado com os motivos, os tipos de todas as prévias conferidos por um tsconfig temporário (zero erros) --
o viewport das células não foi medido aqui.

**Os testes:** novos `formularioDasContas.casos.json` (os casos dos dois lados: 12 descrições, 6 motivos, 11 lotes, 5 pagamentos e as
frases), `formularioDasContas.teste.ts` (35), `ContasAPagarEscritas.teste.tsx` (28) e `tests/Caracterizacao/ContasAPagarTelaNovaTest.php`
(7: cada lote LANÇADO e cada pagamento PAGO pela API em processo, contra o caso; as frases; os motivos publicados; as chaves da
`ParcelaDaDespesa` e do `PagamentoDaParcela` presas ao recurso pelo `TiposDaTelaNova`), e um caso no `ContasAPagarSemAsColunasTest` (os
motivos escritos à mão na coluna que o banco "não tem" não aparecem); os da T1 ajustados ao que saiu (os caminhos da tela antiga). Nos
dois bancos, em série, o filtro de Contas a Pagar e do caixa (`Saida|ContasAPagar|D375|ColunasForaDoMapeamento|D490OrdemDasColecoes|Caixa`):
646 de 646 no MySQL e 646 de 646 no PostgreSQL, saída 0, nenhum pulado. O front: os seis arquivos de Contas a Pagar 124 de 124 com `--maxWorkers=4` e com
`AMPLIFICAR_EFEITOS_MS=50`; o front inteiro 4.235 de 4.235 (182 arquivos), saída 0; tsc, lint (saída 0, só os avisos de antes) e build (380,67 KB em gzip).

**Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1: no front, 29 -- a caixa marcada no
parcial, cada aviso fora, editar e excluir na parcela com pagamento, excluir a despesa com dinheiro, o contador medindo o digitado, a
prévia com o mês encadeado (o D209), o 422 fora do campo, o lançar na sangria ativa, o motivo vazio aceito, o 409 fora da região de
status, a despesa não relida no 409, o pagar na quitada, o pagamento ativo não segurando a edição, quem só lê vendo as escritas, a
caixa que não segue o valor, a caixa desmarcada fora do corpo, a lista não relida na volta e depois do pagar na linha, o estorno da
sangria sem dizer que cancela a retirada, a frase do estorno sem ler a despesa excluída, a pergunta da exclusão sem a conta, o milhar
com vírgula, o extrato sem o motivo, o "não excluir" sem devolver o foco, a excluída oferecendo escrita, o "Nova" sem o formulário --
27 vermelhos; as duas travas do duplo clique (a do invólucro e a do formulário) ficaram VERDES cada uma sozinha, porque uma cobre a
outra, e as duas arrancadas juntas ficaram vermelhas (o caso do duplo clique). No servidor, 12 distintos (S1 a S11 e S1b; a
primeira redação dizia 13, contando duas vezes o S11): os motivos da quitação não lidos, o estorno sem quem estornou, a despesa e a
resposta do estorno sem os motivos, a chave `quitacaoMotivo` fora do recurso, o lote mensal encadeado no servidor, a frase da sangria
mudada de um lado, o teto medido antes de normalizar, a caixa "quitar" ignorada, o tipo da tela sem o `estornadoPor`, e as duas
leituras sem a guarda da coluna -- todos vermelhos. RODARAM NOS DOIS BANCOS só o S1, o S2, o S3 e o S11; do S4 ao S10 e o S1b, só no
MySQL (a primeira redação dizia "os de leitura nos dois bancos", e o S1b, de leitura, rodou só no MySQL). A leitura do estorno sem a
guarda (o S11) ficou VERDE na primeira rodada: nenhum caso abria a despesa no banco sem as colunas; o caso novo do
`ContasAPagarSemAsColunasTest` a deixou vermelha, nos dois bancos. O S2 e o S10 falam do `estornadoPor`, que a verificação tirou.

**O ensaio** `scripts/ensaiar-saidas-adm-api.php` nos dois bancos de contrato, saída 0, com o de permissão (saída 0 nos dois) e o de
recusas (MySQL, saída 0); o contrato montado antes, as travas `/tmp/*.lock` do contêiner contadas e apagadas quatro vezes (8, 5, 5 e 8
arquivos; a primeira redação listava três). **O oráculo** pelo HTTP como o gerente das fixtures com o ler e o escrever (uma linha de `privilegios_usuarios`, 90004800, da parte
livre do bloco da 77, desfeita no fim), num roteiro do rascunho copiado para `scripts/` só durante a rodada: lançar a despesa, três
parcelas mensais a partir de 31/01/2027, pagar parcial com a caixa e total, dar por quitada com motivo, estornar, excluir a parcela, a
despesa com dinheiro recusada e a sem dinheiro excluída -- cada efeito contra um SELECT, e os esperados da prévia, do "a menos" e do
contador vindos da regra pura da tela (empacotada pelo esbuild e rodada no node): 33 conferências, 0 divergências, no MySQL e no
PostgreSQL; falsificado (a prévia do mês encadeado), 2 divergências nos dois. As linhas criadas apagadas no fim, a sessão do gerente
apagada no contêiner, e o banco de contrato remontado.

**Os portões:** o `conferir-tudo.sh` inteiro, sozinho, 74 ok, saída 0 -- na primeira rodada, o `conferir-cobertura-por-par.php` saiu 1: o controller passou a chamar os dois pares novos (de 435 para 437, o `ISFinanceiro` de 9 para 11, declarados com o teste que os cita; tirando os dois da lista, o sha1 é o de antes). O `conferir-debitos-repetidos.php` perdeu os pares D162|D207 e D163|D207 (a marca nova do D207 os pôs abaixo
do limiar, o molde do D88|D91) e ganhou D709|D710, D391|D710 e D653|D710; o `contar-debitos.php` e ele, saída 0.

**Débitos:** o D205 e o D207 CORRIGIDOS (a marca no fim do título, e a nota no corpo); o D710 registrado (a pergunta ao lote, "Contas
a Pagar: a tela antiga sai do ERP agora, ou fica até o corte?"); de pé 192 para 193 (o D205 e o D207 já estavam fora da conta, como
PREPARADOS). Nada da produção mudou; na `franquiabv_dev`, só SELECT -- as escritas só nos bancos de contrato e nos da suíte. O navegador
e o ciclo inteiro são do coordenador.

**A verificação (04/11/2026), e o que mudou:** onze achados confirmados, dez consertados e um ao dono.
- **O estorno da sangria prometia antes o que o servidor recusa** (médio): a frase dizia, sem condição, que o estorno cancela a
  retirada -- e com o caixa já fechado (o caso de quase toda sangria que chega ao financeiro, a 171 (a)) ou aberto por outra pessoa
  (a 175 (a)) ele é recusado; a `origem` não traz o estado do caixa. Agora: "Se o caixa que fez a retirada ainda estiver aberto e
  for você quem o opera, ... Com o caixa já fechado, ou aberto por outra pessoa, o estorno é recusado e nada muda."
- **O "estornar" que confirma saía `.primaria`**, igual ao "lançar": agora `.destrutiva`, como manda `docs/f3-desenho-da-interface.md`.
- **Na linha da paga sem a marca**, o "pagar" abria vazio: agora é o "dar por quitada", com o motivo e a lista relida (o desenho, 1.3);
  a explicação diz "O pago já cobre o previsto" (`oQueAQuitacaoFaz`, que a despesa também usa).
- **Sem caso, agora com caso:** a caixa que deixa de seguir o valor depois de tocada (médio, nos dois sentidos); o 409 do "pagar" da
  linha da lista com a região de status e a lista relida (médio); a quitada sem "pagar" nem "dar por quitada" na despesa; as respostas
  das escritas da parcela com os motivos (`testAsRespostasDasEscritasDaParcelaTrazemOsMotivos`); e a trava por referência do invólucro
  sozinha (dois envios no mesmo instante, antes do novo desenho) -- que achou um defeito: o envio barrado devolvia "acabou" e o
  formulário fechava com o pedido no ar; agora ele espera o mesmo pedido (`escritaNoAr`).
- **Os tipos do formulário da despesa**: a carga, a falha (com "carregar os tipos de novo") e o vazio ditos embaixo do campo.
- **Quem estornou saiu do extrato** (o `estornadoPor`): a tela antiga nunca mostrou, e as respostas mandam gravar, não mostrar. O
  autor continua gravado; a pergunta foi ao dono (o **D711**, "Contas a Pagar: o extrato mostra quem estornou?", recomendada a (a)).
- **Este parágrafo** dizia 13 mutantes de servidor "nos dois bancos": eram 12, e só quatro nos dois (corrigido acima).

Os números da verificação: os casos novos no `ContasAPagarEscritas.teste.tsx` (8, de 28 para 36), no `formularioDasContas.teste.ts`
(1) e no `ContasAPagarTelaNovaTest` (1, e o dos motivos sem o nome); o filtro de Contas a Pagar e do caixa 647 de 647 no MySQL e 647
de 647 no PostgreSQL, saída 0, nenhum pulado; os sete arquivos de Contas a Pagar do front 136 de 136 com `--maxWorkers=4` e com
`AMPLIFICAR_EFEITOS_MS=50`; o front inteiro 4.244 de 4.244 (182 arquivos); tsc, lint (só os 8 avisos de antes, nenhum nos arquivos
tocados) e build (381,16 KB em gzip); os tipos das prévias por um tsconfig temporário, 0 erros. Os mutantes novos, um por vez, a
reserva num diretório novo e vazio, devolvidos pelo sha1: 18 no front (a frase sem a condição, o botão sempre `.primaria` e o estorno
sem a `destrutiva`, o "pagar" na paga sem a marca, a quitação da linha relendo a despesa, o `pagaSemAMarca` só acima do previsto, a
explicação da paga sem a marca, a caixa que segue o valor tocada, o 409 da linha relendo a despesa, a região de status fora da lista,
a quitada com "pagar", os três silêncios dos tipos, a falha engolida, o "de novo" fora, a trava por referência fora e o barrado
devolvendo nulo) e 6 no servidor, nos dois bancos (cada uma das quatro respostas da parcela sem os motivos, o `estornadoPor` de volta
no recurso, o motivo do estorno não lido) -- todos vermelhos. O ensaio `ensaiar-saidas-adm-api.php` de novo nos dois contratos: 33 de
33 nos dois; o contrato remontado depois. O oráculo da implementação não foi repetido (as escritas do servidor não mudaram; só saiu
uma chave da leitura). De pé: 193 para 194 (o D711). O navegador e o ciclo inteiro continuam do coordenador.


**Conferido por mim, depois da correção** (05/11/2026): os 12 achados das três lentes (11 confirmados, três médios: o estorno da
sangria prometendo o que o servidor recusa, a caixa "quitada" tocada sem caso e o 409 do "pagar" da linha sem caso), lidos um a um, e
o relato da correção; as perguntas do D710 (a tela antiga sai agora ou fica até o corte) e do D711 (o extrato mostra quem estornou)
foram à página como a 306 (recomendada a (b)) e a 307 (recomendada a (a); o nome já saiu da tela e da API até a resposta). Conferido
por SELECT que a linha de privilégio do oráculo (90004800) não ficou na `franquiabv_dev`. O ciclo inteiro depois da correção:
caracterização 6.348 em cada banco, saída 0 (11 e 16 pulados); contrato 74 e 74; os portões (74) e o front 4.244 de 4.244, saída 0; o
medidor dos relatórios no PostgreSQL, saída 0; em cerca de 9 min 44 s -- **com os ensaios do MySQL atravessando a meia-noite**: o
resumo dizia EXIT=0, mas dentro estavam o `ensaiar-recusas-adm-api` com 2 e o `ensaiar-fluxo-adm-api` com 1 (o banco de contrato
montado antes das 00:00, a falsa conhecida); a etapa repetida sozinha, com o contrato montado de novo, deu os 22 ensaios com 0. No
navegador, contra o banco de contrato, como o gerente de teste com o ler e o escrever (a 90002021 por `privilegios_usuarios`, só no
contrato): Contas a Pagar › Nova abre o formulário da despesa; a despesa lançada ("Internet de teste", gravada "Internet de Teste"
pela regra do texto digitado, como na tela antiga) abre na hora, sem parcela; lançar com R$ 120,00 a partir de 31/01/2027, mensal, e a
prévia "Vai lançar 3 parcelas de R$ 120,00: 31/01/2027, 28/02/2027 e 31/03/2027." (a 141 (c)), gravadas com essas datas; pagar a
primeira com a caixa "quitada" marcada em R$ 120,00 e desmarcada ao digitar R$ 50,00; "Pagamento registrado. A parcela continua
aberta.", a linha com R$ 50,00 pago e R$ 70,00 de falta, sem "editar" nem "excluir" na parcela paga e sem "excluir a despesa"; nada
rolando de lado. Na limpeza, as sessões dos dois usuários de teste contadas e apagadas no contêiner; o `local.php` de volta ao
`franquiabv_dev`, conferido pelo sha1, e o banco de contrato remontado.

## 05/11/2026 -- VIPCLASS-ALUNO-SERVIDOR (a fatia 79 da fila das telas que faltam)

A F0 de `docs/f3-telas-restantes/tela-vipclass.md` (o DV-1, o DV-2 sem o índice, o DV-8 e o DV-10), na tela antiga do aluno, que está em uso; as respostas 49 (a), 144 (a) e 213 (a) do dono. **Para quem usa:** nada novo na tela -- a tela nova do aluno é a fatia 80. Muda o que o servidor aceita: as aulas, o checkpoint e o envio dele só abrem o curso publicado para a unidade e o grupo de quem entrou, ativo e com o pré-requisito feito (antes, qualquer curso pelo id); quem já concluiu não conclui de novo (o envio repetido ouve a recusa e volta ao curso, onde está o certificado); o certificado de quem concluiu segue saindo sempre. O vídeo sai pelo id do YouTube, e os textos do administrador saem escapados. Na cópia (só SELECT, só contagem): 254 não administradores ativos em unidades ativas com "VIP Class - Edu" (253 com escrever, 1 só com ler, que segue sem abrir as aulas: a guarda da tela antiga não mudou) e 63 com o relatório; 8 administradores; 21 cursos, todos ativos; 68 aulas (todas do YouTube, 1 com `?si=`); 66 perguntas ativas; 14 materiais; 2.131 conclusões de 341 pessoas, 167 pares repetidos.

**O que entrou:** a regra (`RegraDoCursoVipClass`: `publicadoPara`, `ativo`, `paraOAluno`, `nota`, `aprovado`, `videoDoYoutube`), `VipClassRepository::cursoDoAluno`, `VipClassCheckpointUsuarioRepository::concluiu`, o envio sob a trava da pessoa, o fundo do certificado e o material pelo `Armazenamento`, as três views escapadas, o nome do requisito num `data-` (`mod-configuracoes.js?cache=170`), e a fixture com três cursos (90079001 a 90079003). O índice único fica para a fatia 85 (apaga linha). Nada de esquema.

**O provado:** os testes da fatia e os vizinhos (`VipClass`, `D373`, `D203TextoDigitado`, `CertificadoDaVipClass`, `RecorteNosServicos`, `D237AgendaRecorte`): 99 casos em cada banco, saída 0 (3 pulados: os dois "vipclass" do `D199AtivarRemoverPorPostTest` e o `VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador`, sem a cópia). Os mutantes M1 a M17 (cada condição da regra, o vínculo vazio como nenhuma unidade, o corte com `>`, a trava, a recusa de quem concluiu, o requisito no envio, as aulas e o checkpoint pelo `find`, a leitura direta do disco do certificado e do material, os escapes, o `src` pelo link, o checkpoint de quem concluiu), um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1: todos vermelhos no MySQL, e os de servidor também no PostgreSQL. O M12 achou que o caso do certificado lia o `padrao.jpg` do disco no mutante e terminava a suíte com saída 0 sem o resumo; o caso passou a usar um fundo que não está no disco. Os portões: o `conferir-recorte-por-unidade` com cinco entradas novas (falsificado, saída 1), o `conferir-cobertura-por-par` de 437 para 439 pares, o `conferir-escape-nas-views` com as três views, o inventário regerado, e o `conferir-tudo.sh` sozinho, saída 0. **Não feito por mim:** o navegador e o ciclo inteiro (são do coordenador).

**A verificação** confirmou 12 achados (três médios, nove baixos; alguns repetidos entre as lentes), todos tratados. Os médios: (1) a recusa de quem já concluiu não chegava à tela -- com o endereço preenchido, o `mod-configuracoes.js` só redireciona, e a página do curso não desenhava o flash; agora desenha (`partials/notificacoes` no `aulas.phtml`), e a pessoa volta ao curso com "Você já concluiu este curso"; (2) a trava do envio só era provada dentro da transação do teste, que segurava a trava sozinha: dois casos novos rodam fora dela, com um gancho no `SQLLogger` que age por outra conexão no instante certo (no `INSERT`, a pessoa está presa; a conclusão gravada por outro envio quando o `FOR UPDATE` sai é vista, e o envio ouve a recusa); (3) o `.text()` do modal do cadeado não tinha caso: `frontend/src/telas/vipClassDaTelaAntiga.teste.ts` roda a função do arquivo servido (o `.html()` deixava o PHP verde). Os baixos: a lista dos cartões tinha o próprio filtro em DQL, e agora ela e o curso pelo id passam pelo mesmo `VipClassRepository::situacaoParaOAluno` (sem grupo na sessão, o curso com grupo sai também do cartão; na cópia, 0 ativos sem grupo ou sem unidade); o vídeo por `youtube-nocookie.com` (as decisões 4 e 9 do desenho); a decisão técnica 7 -- com o armazenamento de objetos e a `url_publica`, o material redireciona ao endereço direto (`Armazenamento::enderecoDireto`, novo; `null` no disco local e no bucket sem a url pública), em vez de carregar até 87 MB na memória; a capa do cartão pelo `url()` com caso; o DV-8 e o DV-10 com número, o D712 e o D713 (PARCIAIS: o resto é da administração, a fatia 82: as capas e os certificados escritos à mão, a pergunta e a resposta cruas no checkpoint da administração, e o link sem conferência ao gravar), com a linha no `README.md` da pasta e a nota no item 3 do D33; e o oráculo. Os testes depois da correção, com o filtro de antes mais `Armazenamento`: 116 casos e 541 asserções em cada banco, saída 0 (os mesmos 3 pulados). O `VipClassAlunoPeloErpTest` foi a 17 casos e o `VipClassArquivosTest` a 10; o vitest do arquivo novo, 3 de 3. Os mutantes N1 a N9 (o partial fora, o `www.youtube.com`, o serviço sem a transação, a conclusão relida antes da trava, a lista com o `FORA`, a capa escrita à mão, o redirecionamento retirado, o endereço direto sem a url pública, o redirecionamento sem conferir que o arquivo existe), nos dois bancos, e o J1 (o `.html()` no modal) no vitest, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1: todos vermelhos pela asserção. **O oráculo pelo HTTP**, nos bancos de contrato MySQL e PostgreSQL, como o Gerente Teste e o Colaborador Teste com "VIP Class - Edu" (uma linha de `privilegios_usuarios` para cada um, desfeita no fim): o cartão, as aulas e os vídeos de cada curso contra a publicação, o requisito e as aulas lidos por SELECT; o checkpoint e o envio de quem concluiu, a frase na página seguinte e as conclusões antes e depois; o cadeado no envio com o nome do requisito do banco; a primeira conclusão com uma linha e a nota 100, a segunda recusada; o certificado (o PDF de quem concluiu, 404 de quem não): 34 conferências, 0 divergências em cada banco; falsificado (o restrito esperado aberto ao gerente), 3. As sessões apagadas no contêiner, o `local.php` de volta ao `franquiabv_dev` pelo sha1, e o banco de contrato MySQL remontado. Os portões (`conferir-recorte-por-unidade` com as entradas da regra no lugar novo, `conferir-cobertura-por-par` 439, `conferir-escape-nas-views`, `conferir-injecao-dql`, os dois de booleanos no contêiner, o inventário, os segredos), saída 0; `contar-debitos` com o de pé de 194 para 196 (o D712 e o D713), e `conferir-debitos-repetidos`, saída 0.

**Conferido por mim, depois da correção** (05/11/2026): os achados das três lentes lidos um a um, e o diff da correção contra cada um. O ciclo inteiro depois da correção: caracterização 6.387 em cada banco, saída 0 (11 e 16 pulados); contrato 74 e 74; os ensaios do MySQL e do PostgreSQL, os portões (74) e o front 4.247 de 4.247, saída 0 (cada `EXIT=` dos ensaios lido, nenhum diferente de 0); o medidor dos relatórios no PostgreSQL, saída 0; em cerca de 10 min. **No navegador**, contra o banco de contrato, como o Gerente Teste com "VIP Class - Edu" (a linha de `privilegios_usuarios` só no banco de contrato, remontado no fim): a VIP Class antiga do aluno (`/mod-cadastros/vipClassEdu/index`) com os três cartões que a publicação dá à unidade e ao grupo dele (o "Curso VIP Restrito (teste)", publicado para outro grupo, fora do cartão e com 404 pelo endereço); as aulas do aberto com os dois vídeos por `www.youtube-nocookie.com/embed/...`, o certificado e o material pelo endereço com o hash; e o curso com requisito abrindo para quem tem a conclusão do requisito (a nota 100 da fixture), com o "Fazer o Checkpoint". As sessões dos usuários de teste apagadas no contêiner (130), o `local.php` de volta pelo sha1. **O D709 CORRIGIDO pela resposta 305 (a) do dono** (o aviso das vencidas antigas fica nos últimos 12 meses, como entrou na 77): a nota no registro. Débitos: o D712 e o D713 PARCIAIS, o D709 CORRIGIDO; de pé 194 para 195 (`contar-debitos`, CONTINUAM DE PÉ). Nada da produção mudou; na `franquiabv_dev`, só SELECT.

## 05/11/2026 -- VIPCLASS-ALUNO-TELA (a fatia 80 da fila das telas que faltam)

A F1 de `docs/f3-telas-restantes/tela-vipclass.md`: a API do aluno e a tela "VIP Class"; as respostas 49 (a), 144 (a), 213 (a) e 119 (a) do dono. **Para quem usa:** quem tem "VIP Class - Edu" abre a VIP Class na tela nova, pelo link do menu: os cartões dos cursos da unidade e do grupo dele, com o "N de M concluídos" no alto (a tela antiga não dizia o que a pessoa já tinha feito) e o selo de cada curso -- concluído (a data, a nota e o certificado), disponível, trancado (o curso que falta e o caminho para ele) e sem checkpoint; o curso, com as aulas num vídeo 16:9 pelo `youtube-nocookie.com`, uma coluna a 375 px, os materiais e o botão em ouro ("Fazer o checkpoint" ou "Certificado"); o checkpoint em grupos de rádio com setas, o "Concluir" que diz quais perguntas faltam e leva o foco à primeira, o aprovado com a nota e o certificado, e o não aprovado com a nota, o mínimo de 80 e "nada foi gravado", com o "refazer". Quem só tem o `ler` vê as aulas e o certificado, e a tela diz por que o checkpoint não é dele. A administração (Configurações › VIP Class) e o relatório continuam na tela antiga, e a tela antiga do aluno também continua no ERP: o desenho não manda tirá-la.

**O que entrou:** `MeusCursosController` na `/adm/api/meus-cursos` (os cartões, o curso, o checkpoint sem a resposta correta, a conclusão com 201, 200 sem gravar, 409 e 422, o certificado em PDF e o material pela API com sessão, como a entrega da fatia 27), as formas em `MeusCursosJson`, a fábrica, as rotas e o `Module.php`; na regra, `RegraDoCursoVipClass::selo`, `conferirRespostas` e `notaEmTexto`; `VipClassCheckpointUsuarioRepository::primeirasDoAluno` (a primeira conclusão, pela 213 (a)); o serviço da 79 devolve também a nota, e a tela antiga a tira da resposta. A regra de quem abre o quê é a da fatia 79, chamada (`listagemIndexEdu` e `cursoDoAluno`), com a unidade e o grupo da sessão. No front, `api/cursos.ts`, `telas/MeusCursos.tsx`, `MeusCursosLigado.tsx`, a regra pura `checkpointDoCurso.ts`, o Console (`CAMINHO_DOS_MEUS_CURSOS`), as regras `.cursos*` no `App.css`, e a prévia do design (`.design-sync/previews/MeusCursos.tsx`, o dado em `_meusCursos.ts`, a entrada no `componentSrcMap` e o viewport `1280x820`, medido no Chromium do Playwright: o conteúdo mais alto termina em 752,3 px a 1.280, e nenhuma das dez células rola de lado a 1.280 nem a 375; a célula "Personificando" do Console passou aos Assistentes VIP, que ainda caem na tela não migrada). O `vitest.config.ts` desliga o carregamento das páginas de iframe no happy-dom: o vídeo ia à rede de verdade a cada caso.

**O provado:** `MeusCursosApiTest` (31 casos) e quatro novos no `RegraDoCursoVipClassTest`; com os vizinhos (`VipClass`, `D373`, `D203TextoDigitado`, as guardas de login e de origem, as dez rotas do aplicativo, o mapeamento, os portões da família dos relatórios, o `D199AtivarRemover`, o `D237`), 555 casos em cada banco, saída 0 (4 pulados: três do `D199AtivarRemoverPorPostTest` -- os dois "vipclass" e o "mensagens" -- e o `VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador`). O front: os 46 casos novos (`MeusCursosLigado` 32, o `.contrato` 2, `checkpointDoCurso` 12) e o do Console; o front inteiro 4.294 de 4.294, saída 0; amplificado (`AMPLIFICAR_EFEITOS_MS=50`), os da fatia verdes e os mesmos 6 vermelhos do `Console.teste.tsx` que o HEAD, pelo nome; tsc (e o das prévias, por um tsconfig temporário, apagado), lint (os 8 avisos de antes) e build (385,47 KB em gzip). O ensaio novo `scripts/ensaiar-vipclass-adm-api.php` (20 recusas e 8 controles, cada um contra um SELECT) nos dois bancos de contrato, 28 de 28, saída 0; o de permissão, com as seis rotas novas, 194 de 194 nos dois; o ensaio está no CI, no `conferir-lacuna-do-ci.sh` e no `medir-telas-no-postgres.sh`. As recusas ficaram de início só no ensaio próprio; a correção as pôs também no de recusas (abaixo). Os portões: o `conferir-acl-da-api` (244 ações conferem privilégio), o `conferir-recorte-por-unidade` com três entradas novas (454 métodos), o `conferir-rotas-sem-login` com a família e as seis rotas, o `conferir-cobertura-por-par` de 439 para 440 pares; o `conferir-tudo.sh` inteiro, sozinho, saída 0.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1: no servidor, M1 a M27 (a resposta certa no JSON; o curso pelo `find`, sem a regra; o cadeado ignorado; o checkpoint de quem concluiu; a corrida perdida virando 200; o corte de 80 trocado; o vídeo pelo link gravado; o certificado de qualquer conclusão e o certificado pela regra do curso; o checkpoint e a conclusão com o `ler`; a lista sem o `ler`; o grupo nulo; a conclusão mais recente; o cadeado vencendo o concluído; a resposta de outra pergunta e a pergunta de fora aceitas; o curso sem checkpoint aberto; o material de outro curso; sem o endereço direto, o `nosniff`, o `no-store` e o nome limpo; o 201 virando 200; a nota sem as casas; o cadeado sem o requisito; a lista de outra unidade; o `podeFazerOCheckpoint` sempre; a nota na resposta da tela antiga), 29 vermelhos pela asserção nos dois bancos. No front, F1 a F22: 20 vermelhos sozinhos; as duas travas do duplo clique (a por referência e a do clique) verdes cada uma sozinha, porque uma cobre a outra, e vermelhas juntas -- como na fatia 78. Dois casos nasceram de mutantes que sobreviveram na primeira rodada (o certificado só no cartão concluído; a resposta atrasada do checkpoint pedido de novo).

**O oráculo** pelo HTTP nos dois bancos de contrato, como o gerente e o colaborador de teste (o privilégio por `privilegios_usuarios`, desfeito no fim; as sessões apagadas no contêiner): a lista e o selo de cada curso, cada curso aberto ou recusado, o cadeado com o requisito, o checkpoint sem a correta, o reprovado com a nota da conta do banco e nada gravado, o aprovado com uma linha, o 409 de quem já concluiu e o certificado, contra o SELECT: 41 conferências, 0 divergências em cada banco; falsificado (o restrito aberto ao gerente), 2.

**Débitos:** o D359 CORRIGIDO (a API pela mesma regra); o D372 com a nota da API, PARCIAL (o índice e a limpeza na 85); o D714 registrado, com a pergunta no lote: o certificado de quem concluiu um curso que saiu da lista dela não tem caminho na tela. De pé, 195 para 195 (196 depois da correção, com o D715). **O navegador e o ciclo inteiro são do coordenador.** Nada da produção mudou; na `franquiabv_dev`, nada; o banco de contrato foi remontado; o `local.php` de volta ao `franquiabv_dev`.

**A correção depois da verificação** (três lentes, 12 achados confirmados, um médio e onze baixos; dois eram o mesmo). **O médio:** a resposta da conclusão chegava depois de a pessoa sair do checkpoint e aparecia no checkpoint de OUTRO curso ("Aprovado, com nota 100." com o certificado do primeiro, ou o 409 dele); agora sair do checkpoint (voltar ao curso, à lista, abrir outro, refazer) desliga o envio no ar e a trava do duplo clique dele (`envioVigente` no `MeusCursosLigado.tsx`), e a aprovação que chega depois relê a lista sem o "carregando" -- a conclusão foi gravada. **Os baixos:** o curso concluído com o requisito pendente (as 3 conclusões do M9) tinha o cartão "concluído" com o nome clicável, e abrir dava o cadeado do servidor: o cartão ganhou `abre` (`MeusCursosJson::cartao`, a mesma situação do `cursoDoAluno`), e a tela só oferece abrir o que abre, com "As aulas ficam trancadas até você fazer o curso X" e o certificado no cartão; a concordância ("Falta responder às perguntas 1 e 3.", "Falta responder à pergunta 2."); o comentário que dizia que a "Minha equipe" espera a pergunta 4 (é a fatia 81, que a segue pela decisão 3); as recusas no `ensaiar-recusas-adm-api.php`, que as notas pediam (os casos 204 a 210: sem sessão, sem o privilégio, a conclusão do curso fora, com o requisito pendente, de quem já concluiu, com a resposta de outra pergunta, e o controle aprovado, cada um contra um SELECT; o ensaio inteiro leva segundos no Mac -- os 300 s eram do Windows); a tela antiga do aluno sem a pergunta ao dono (o D715, no lote: "VIP Class: a tela antiga do aluno sai do ERP agora, ou fica até o corte?", recomendada a (b), como no D710); e, sem caso, a falha e o "tentar de novo" do curso e do checkpoint, os textos de carga e o 409 `sem_checkpoint` do checkpoint (sete casos novos no front), o material pelo endereço direto sem o arquivo e o `Cache-Control` do 302, a sessão solta antes do PDF e do material (a conta existia e nenhum caso a lia), o restrito aberto pelo id a quem é do grupo (`testORestritoAbreAQuemEDoGrupoPeloId`), e o desenho falando em "seis rotas da tabela" (são cinco, e uma a mais). **O provado depois:** `MeusCursosApiTest` 32 casos; os da fatia (75) e, com os vizinhos e os que citam o ensaio de recusas, 1.316 casos em cada banco, saída 0 (7 pulados: os 4 de antes e três dos relatórios fora da fatia -- `ComboboxDeAnosNoRelatorioTest`, `DescontosNoRelatorioTest`, `VendasNoRelatorioTest`); o front da fatia 55 casos (com `AMPLIFICAR_EFEITOS_MS=50` também), o front inteiro 4.303 de 4.303, tsc (e o das prévias), lint (os mesmos 8 avisos), build (385,53 KB em gzip); os ensaios de recusas (260 de 260), da VIP Class (28 de 28) e de permissão nos dois bancos de contrato, saída 0; o de recusas falsificado (a resposta de outra pergunta aceita), 6 de 7, o 209 vermelho; o `conferir-tudo.sh` sozinho, 74 de 74, saída 0. **Os mutantes da correção**, um por vez, a reserva num diretório novo e vazio, devolvidos pelo sha1: N1 a N8 no servidor (o `abre` sempre, o `abre` pelo selo, o grupo nulo e a unidade zero no curso pelo id, o endereço direto sem conferir o arquivo, o 302 sem o `no-store`, o certificado e o material sem soltar a sessão), vermelhos pela asserção nos dois bancos; G1 a G17 no front (a conclusão sem vigência, o erro e o resultado da conclusão velha, sem reler a lista, a trava que fica ao sair, a geração que não muda, o "tentar de novo" sem o curso e sem o checkpoint, o cartão que abre pelo selo, o concluído mandando "faça antes", os três textos de carga, o curso sem a frase, o 409 como "não veio", o plural e a crase), todos vermelhos.

**Conferido por mim, depois da correção** (05/11/2026): os 12 achados das três lentes (um médio, onze baixos, dois o mesmo) lidos um a um, e o relato da correção contra cada um. O ciclo inteiro depois da correção: caracterização 6.423 em cada banco, saída 0 (11 e 16 pulados); contrato 74 e 74; os ensaios do MySQL e do PostgreSQL, os portões (74) e o front 4.303 de 4.303, saída 0 (cada `EXIT=` dos ensaios lido, nenhum diferente de 0); o medidor dos relatórios no PostgreSQL, saída 0; em cerca de 10 min. **No navegador**, contra o banco de contrato, como o Gerente Teste com "VIP Class - Edu" (o ler e o escrever, a linha só no banco de contrato, remontado no fim), a 1280 e a 375 px (sem rolar de lado): o "VIP Class" do menu abre a tela nova com "1 de 3 cursos concluídos", os três cartões (sem checkpoint, concluído em 05/01/2026 com a nota e o certificado, disponível); o curso com requisito com o vídeo 16:9 pelo `www.youtube-nocookie.com/embed/...` e o foco no título; o "Concluir" sem resposta com "Falta responder à pergunta 1." e o foco na primeira opção; a resposta errada com "Nota 0: o mínimo é 80." e "Nada foi gravado" (no banco, nenhuma linha nova); o refazer com a certa, "Aprovado, com nota 100.", uma linha no banco, o certificado em PDF (200, `application/pdf`, o nome limpo no `Content-Disposition`) e a lista com "2 de 3". **Achado por mim e consertado:** as capas dos cartões saíam quebradas no Vite -- no disco local a capa vem como `/vip-class/capas/<nome>` (`Armazenamento::url()`), que o nginx do ERP serve, e o `server.proxy` do `frontend/vite.config.ts` só levava os banners; a linha `/vip-class` entrou, no molde da dos banners, com o caso novo `ProxyDasImagensNoViteTest` (lê o proxy sem os comentários; os dois mutantes -- a linha tirada e a linha comentada -- vermelhos pela asserção, devolvidos pelo sha1), 1 de 1 nos dois bancos; na produção a tela nova e o ERP têm a mesma origem, e nada muda. Depois do conserto: build (385,53 KB em gzip), lint (os avisos de antes) e o `conferir-tudo.sh` sozinho, saída 0. As sessões dos usuários de teste apagadas no contêiner (152), o `local.php` de volta pelo sha1. **As perguntas novas foram à página do lote:** a 308 (o D714, os certificados dos cursos que saíram da lista da pessoa; recomendada a (a)) e a 309 (o D715, a tela antiga do aluno sai agora ou fica até o corte; recomendada a (b)). Débitos: o D359 CORRIGIDO, o D372 PARCIAL, o D714 e o D715 registrados; de pé 195 para 196 (`contar-debitos`, CONTINUAM DE PÉ). Nada da produção mudou; na `franquiabv_dev`, nada.

## 05/11/2026 -- VIPCLASS-QUEM-FEZ (a fatia 81 da fila das telas que faltam)

A F2 de `docs/f3-telas-restantes/tela-vipclass.md`: quem fez os cursos da VIP Class (o S21 e o DV-13); as respostas 213 (a) e 3 do dono. **A pergunta 4 do desenho (onde fica o relatório) não foi respondida, e segui a (A)**, como o desenho manda sem resposta: a vista "Minha equipe" no destino VIP Class para quem tem "VIP Class - Relatório", e o "Quem fez" da administração (a fatia 83) sobre a mesma rota.

**Para quem usa:** quem tem "VIP Class - Relatório" (na cópia, 63: 62 "Gerente unidade" e 1 "Colaborador"; e os administradores, pelo grupo) vê "Minha equipe ›" no alto dos cartões da VIP Class na tela nova: por curso, "1 pessoa fez, de 3 que podem fazer.", e, aberto o curso, cada pessoa com o grupo e "fez em 05/01/2026, nota 100" ou "não fez". O gerente vê a unidade dele; o administrador escolhe a unidade, ou todas; quem tem o relatório e não é gerente vê só a si mesmo, e a tela diz por quê (a resposta 3). Conta quem pode fazer o curso -- o grupo E o privilégio de aluno (a decisão 10) --, uma vez cada, com a data e a nota da PRIMEIRA conclusão (a 213 (a)). As duas telas antigas do relatório (a do menu Relatórios e a "Relatório" da administração) mostram o mesmo de antes pela mesma consulta, com cinco diferenças: pedem o `ler` (pediam o `escrever`); saem da lista 5 pessoas sem o privilégio de aluno (63 pares de curso e pessoa, 9 com conclusão -- a pergunta no lote, o D717); quem não é gerente vê só a si; o curso inativo (pelo `?status=0` ou pelo id) só aparece ao administrador, e os filtros da lista dos cursos com letra (a situação, o grupo, a unidade, a página) são ignorados em vez de dar erro no PostgreSQL (a linha da VIP Class do D361); e **a data e a nota de cada pessoa passam a ser as da PRIMEIRA conclusão** (eram as da mais recente, o D72): na cópia, nos 167 pares repetidos a linha passa a mostrar a primeira tentativa, e em 3 deles a nota também muda (a 213 (a)). E mais depressa: a rede inteira numa consulta (cerca de 24 ms na cópia) em vez de uma por pessoa. Nenhuma tela antiga sai nesta fatia: a do menu Relatórios fica (o D306), e a da administração sai com a administração nova. **O que o relatório antigo faz e a "Minha equipe" não faz, de propósito:** a busca por nome, o filtro por grupo, a situação com os inativos (do administrador), a paginação de 20 e a capa dos cursos. A F2 do desenho descreve a vista só como "por curso, quem da unidade fez ... e quem não fez", sobre a lista dos cursos ativos publicados para a unidade (a mesma dos cartões do aluno, uma dezena na rede); o administrador abre o curso inativo pelo id (a rota da fatia 83), e nada se perde porque o relatório do menu Relatórios fica, com todos os filtros (o D306).

**O que entrou:** `GET /adm/api/cursos/conclusoes?unidade=` e `GET /adm/api/cursos/:id/conclusoes?unidade=` em `Adm\ConclusoesDosCursosController` (ISConfiguracao), as formas em `ConclusoesDosCursosJson`, a fábrica, as rotas, as famílias em `rotas-publicas.php`; `VipClassRepository::equipe` (as pessoas e as conclusões numa consulta, a regra em PHP) e `::cursosComVinculos`; na regra, `escopoDaEquipe`, `cursoNaLista`, `cursoDaEquipe`, `elegivel` (o administrador passa pelo grupo, como na sessão, sem linha em `privilegios_usuarios`), `primeira` e `situacaoDaLista`; `relatorioUsuarios` delega a `equipe`; o `podeVerAEquipe` na lista do aluno. No front, `api/cursos.ts`, as vistas `equipe` e `equipeDoCurso` em `MeusCursos.tsx`, o invólucro (a resposta velha da unidade trocada é descartada; a volta à equipe não relê), as frases puras em `equipeDoCurso.ts` e a prévia do design com cinco células novas (os tipos conferidos por um tsconfig temporário, apagado).

**O provado:** `ConclusoesDoCursoApiTest` (16 casos: a unidade, o administrador que escolhe, a outra unidade com 422, quem não é gerente vendo só a si, o elegível pelo grupo E pelo privilégio, o administrador pelo grupo, o par repetido contado uma vez com a primeira, o curso de outra unidade e o inativo com 404, as guardas, uma consulta para todas as pessoas pelo `SQLLogger`, as duas telas antigas listando o mesmo que a API, o recorte novo nelas e o repositório), seis casos novos no `RegraDoCursoVipClassTest`, um no `MeusCursosApiTest` e o `VipClassNoRelatorioTest` com o privilégio dado na transação; com os vizinhos (as telas e a API do aluno, o D237, o D199, os portões da família dos relatórios, as rotas sem login, a barreira de origem e o mapeamento), 787 casos em cada banco, saída 0 (5 pulados, os de sempre: três do `D199AtivarRemoverPorPostTest`, o `VendasNoRelatorioTest::testODescontoNaoSomaODeOutraUnidade` e o `VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador`, que pedem a cópia). No front, 20 casos novos (`MeusCursosLigado.equipe.teste.tsx`, 14; `equipeDoCurso.teste.ts`, 6); o front inteiro 4.323 de 4.323, saída 0; com `AMPLIFICAR_EFEITOS_MS=50`, os da VIP Class verdes, e os 6 vermelhos do `Console.teste.tsx` (a comanda e o caixa) são os mesmos do HEAD, medido num `git archive`; tsc, build e lint (8 avisos, nenhum dos arquivos da fatia), saída 0. O ensaio HTTP do aluno com os casos 29 a 37 (37 de 37), os 211 a 216 do de recusas (266 de 266) e as duas rotas no de permissão (196 de 196), nos dois bancos de contrato. Os portões: o `conferir-tudo.sh` sozinho, 74 de 74; o `conferir-cobertura-por-par` de 440 para 442 pares; o `conferir-injecao-dql` de 15 para 13; o `conferir-grupos-por-id` sem o "LEGÍTIMO" da VIP Class; o `conferir-recorte-por-unidade` com oito entradas (nove depois da correção); o `conferir-rotas-sem-login` com as duas rotas.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio a cada um, devolvidos pelo sha1. No servidor, S1 a S19, todos vermelhos nos dois bancos pela asserção (o gerente escolhendo a unidade; o elegível sem o privilégio; o administrador sem o grupo; a última conclusão em vez da primeira; o par repetido contado duas vezes; o inativo na lista; o curso de outra unidade a todos; o `escrever` de volta na API e na tela antiga; a consulta por pessoa de volta; quem não é gerente vendo a unidade, na API e na tela antiga; o `?status=` cru de volta no relatório; o "0" a quem não é administrador; o inativo pelo id na tela antiga; o caminho da equipe a todos; as pessoas sem o recorte da unidade) -- o S19, o `?status=` cru na lista da administração, vermelho no MySQL pela asserção e no PostgreSQL pelo próprio defeito do D361 (o 22P02). Arrancado o `soAPessoa` do controller, o `conferir-recorte-por-unidade` fica vermelho. No front, uma amostra, F1 a F12, todos vermelhos (as frases no plural com 1, quem vê só a si nunca fez, a nota crua, o caminho a todos, a resposta velha valendo, as pessoas sem a unidade escolhida, a volta relendo, o foco saltando a cada releitura, a coluna da unidade na rede, o foco na volta da equipe, o porquê de uma linha só).

**O oráculo** pelo HTTP nos dois bancos de contrato, como o gerente de teste (com "VIP Class - Relatório") e como um administrador de ensaio (um grupo administrador e uma pessoa sem linha de privilégio), com os pares repetidos gravados no cenário (uma tentativa depois, com outra nota) e tudo desfeito no fim (as sessões apagadas no contêiner): o resumo e as pessoas de cada curso contra um SELECT, a outra unidade recusada, a rede e a outra unidade do administrador, e a tela antiga do relatório com os mesmos nomes. 23 conferências, 0 divergências em cada banco; falsificado (a mais recente no lugar da primeira), 4 em cada.

**Débitos:** o D716 registrado (o DV-13), PARCIAL -- resta a página da administração, que sai com a administração nova; o D361 CORRIGIDO (a linha da VIP Class); o D372 com a nota do relatório, PARCIAL (o índice e a limpeza na 85); o D124 com a nota do "LEGÍTIMO" que saiu; o D717 registrado, com a pergunta no lote: quem fez o curso e hoje não pode fazê-lo aparece em "Quem fez"? De pé, 196 para 197. **O navegador e o ciclo inteiro são do coordenador.** Nada da produção mudou; na `franquiabv_dev`, só leitura (as contagens: SELECT, e uma tabela TEMPORARY da própria sessão numa medição, que some com ela); os bancos de contrato foram remontados.

**A correção depois da verificação em três lentes** (9 achados; 8 confirmados no código e tratados, 1 recusado):
- **A linha da VIP Class do D361 inteira** (o médio): a primeira passada conferiu só o `?status=`; o `?grupo=` e o `?unidade=` das duas listas antigas dos cursos ainda iam crus às colunas inteiras (22P02 no PostgreSQL, a lista vazia e calada no MySQL), e o link da paginação era montado sem codificar. Agora `RegraDoCursoVipClass::filtrosDaLista` e `::noLink` (o molde da linha dos Arquivos): fora da lista, ignorado; o link por `http_build_query`, com a unidade vazia quando são todas (sem ela, a página 2 voltava à da sessão). A busca por nome da lista da administração nunca filtrou (o formulário manda `nome`, o controller lia `filtro`) e fica como estava, escrito no D716 para a 83.
- **A página "Relatório" da administração para quem não é administrador** (o médio, dois achados): o `ler`, o inativo e o de outra unidade com 404, o "só a si" e o `?status=0` da lista dela ganharam caso (`testORelatorioDaAdministracaoParaQuemNaoEAdministrador`), e o "só a si" uma entrada no `conferir-recorte-por-unidade` (de 461 para 462 métodos vigiados).
- **O `pri.ler` do privilégio do aluno** ganhou caso (`testOPrivilegioDoAlunoSemOLerNaoConta`).
- **O front:** a vista das pessoas de um curso ganhou a carga, a falha com "tentar de novo" (que repete a unidade escolhida), o curso sem ninguém, a volta a "a unidade em que entrei" e a resposta do curso deixado descartada -- quatro casos novos no `MeusCursosLigado.equipe.teste.tsx` (18).
- **Este parágrafo** dizia "quatro diferenças" e esquecia a primeira conclusão; e não dizia o que o relatório antigo faz e a vista nova não faz. Corrigido acima.
- **Recusado:** "a tela antiga oferece 'Inativos' a quem não é administrador e ignora calada". A caixa da situação está escondida a todos (`col-md-2 display-none`, sem condição, desde antes da fatia; `.display-none { display: none }`): ninguém a escolhe na tela, só digitando a URL, e o D361 aceita o filtro ignorado.

Os testes depois da correção: a caracterização inteira, 6.452 em cada banco, saída 0 (11 e 16 pulados, os de sempre); o front inteiro, 4.327 de 4.327, saída 0; os da VIP Class com `AMPLIFICAR_EFEITOS_MS=50`, 63 de 63; tsc, lint (8 avisos, nenhum da fatia) e build, saída 0; o `conferir-tudo.sh` sozinho, 74 de 74, saída 0; `conferir-segredos`, `conferir-referencias-docs`, `contar-debitos` e `conferir-debitos-repetidos`, saída 0. Os mutantes novos, um por vez, a reserva num diretório novo e vazio, devolvidos pelo sha1: no servidor, C1 a C10, vermelhos nos dois bancos (o `grupo` e a `unidade` crus -- no PostgreSQL pelo 22P02, o próprio defeito --, o link sem codificar, o `escrever` de volta, o inativo e a outra unidade pelo id, o "só a si" e o "0" na administração, o `pri.ler`, o gerente escolhendo a unidade); no front, H1 a H7, vermelhos (o "tentar de novo" do curso sem pedido e sem a unidade, a resposta do curso deixado valendo, a tabela vazia, as duas frases de carga, a unidade em que entrei relida como todas). Os ensaios HTTP não rodaram de novo: a correção não toca rota da API nem a consulta das pessoas, e nenhum ensaio pede as telas antigas.

**Conferido por mim, depois da correção** (05/11/2026): os 9 achados das três lentes (dois médios: a página "Relatório" da administração sem caso para quem não é administrador, e o D361 fechado com as duas listas da VIP Class ainda mandando parâmetros crus à consulta) lidos um a um, e o relato da correção contra cada um; o que ela recusou (o "Inativos" oferecido a quem não é administrador) conferido no `vip-class-relatorio/index.phtml`: a caixa está escondida para todos, desde antes. O ciclo inteiro depois da correção: caracterização 6.452 em cada banco, saída 0 (11 e 16 pulados); contrato 74 no MySQL e, no PostgreSQL, **um vermelho fora da fatia, o mesmo da 76d**: `ApiV4Test::testRegistrarERetirarAparelhoAutenticado` ("o registrado acima sai"). Os ensaios, os portões (74) e o front 4.327 de 4.327, saída 0; o medidor dos relatórios no PostgreSQL, saída 0. **A rodada de causa** (a segunda vez do mesmo caso, pela regra de que o que passa sozinho tem causa): o caso sozinho no banco de contrato do PostgreSQL, 40 de 40; a suíte inteira em laço caiu em outra coisa, o 429 do limite do nginx nas rotas públicas (a repetição seguida, não a suíte), com a tabela dos aparelhos vazia. O que o ciclo tem e o laço não: o contrato do PostgreSQL troca o `local.php` cerca de 2 s depois de o do MySQL terminar, e o `ApiV4Test` é o primeiro arquivo da suíte. **Medido** com uma sonda descartável (um arquivo PHP escrito no Mac e lido pelo php-fpm logo depois, apagada no fim): o fpm serviu o conteúdo VELHO em 8 de 8 trocas, em até 69 de 100 pedidos em 4 s, o último aos 2,72 s (o `revalidate_freq` = 2 do opcache, contado em segundos inteiros); com 3 s e com 4 s de espera depois da escrita, nenhum pedido velho em 8 trocas de 60 pedidos. Um dos dois pedidos do caso ia ao `franquiabv_contrato` do MySQL pelo worker que acabara de servi-lo, onde o aparelho não estava. **O conserto:** `scripts/esperar-o-opcache.sh` (4 s, sem reiniciar nada) depois da troca do `local.php` no `rodar-contrato.sh`, no `rodar-contrato-postgres.sh` e no `medir-telas-no-postgres.sh` (os ensaios, com a mesma janela), com o caso novo `TrocaDoLocalEsperaOOpcacheTest` (a última escrita do `local.php` antes da espera, e a espera antes do primeiro pedido, lido sem os comentários; os seis mutantes -- a espera tirada e a espera depois do primeiro pedido, nos três roteiros -- vermelhos pela asserção, devolvidos pelo sha1), 3 de 3 nos dois bancos; o par do ciclo de novo (contrato do MySQL e logo o do PostgreSQL), 74 e 74. O que não provei: a falha voltar sem a espera numa rodada do par (ela é de 2 em cerca de 16 ciclos; a prova é a da sonda). **No navegador**, contra o banco de contrato, como o Gerente Teste com "VIP Class - Edu" e "VIP Class - Relatório" (as linhas só no banco de contrato, remontado no fim), a 1280 e a 375 px: o "Minha equipe ›" no alto dos cartões; os 4 cursos com "1 pessoa fez, de 1 que pode fazer.", "Ninguém fez: 1 pessoa pode fazer." e, no restrito (o grupo do Colaborador Teste, que não tem o privilégio de aluno), "Ninguém da equipe pode fazer este curso." -- conferido contra o banco: na rede inteira, só o gerente tem o privilégio de aluno; o curso com o gerente, "fez em 05/01/2026, nota 100", o foco no título e, na volta, no curso de onde saiu; como administrador (o grupo marcado só no contrato), a escolha da unidade com "todas as unidades" e a coluna da unidade. **Achado por mim e consertado:** a 375 px a página não rolava de lado, mas o `main` sim -- as duas tabelas da equipe herdavam o mínimo de 30rem do catálogo, sem embrulho que rolasse (a das pessoas com 450 px); a lição das etiquetas e dos banners, sem o mínimo e com o texto quebrando na célula (`overflow-wrap: break-word`: o `anywhere`, a primeira tentativa, partia o "Gerente Teste" em "Gerent / e Teste", medido); a tabela com 345 px, nenhuma célula transbordando, o `main` com 375. O caso no `recuosDosRelatorios.teste.ts` (a regra e a tela pintando a classe nas duas tabelas) e os dois mutantes (o mínimo de volta, o `anywhere` de volta) vermelhos, devolvidos pelo sha1. Depois dos consertos: o front inteiro 4.328 de 4.328, build (386,99 KB em gzip), lint (os 8 avisos de antes) e o `conferir-tudo.sh` sozinho, saída 0. As sessões dos usuários de teste apagadas no contêiner, o `local.php` de volta pelo sha1. **A pergunta nova foi à página do lote:** a 310 (o D717, quem fez o curso e hoje não pode fazê-lo; recomendada a (a)). Débitos: o D361 CORRIGIDO, o D716 PARCIAL, o D372 com a nota, o D717 registrado; de pé 196 para 197 (`contar-debitos`, CONTINUAM DE PÉ). Nada da produção mudou; na `franquiabv_dev`, só SELECT.

## 05/11/2026 -- VIPCLASS-ADMIN-SERVIDOR (a fatia 82 da fila das telas que faltam)

A F3 de `docs/f3-telas-restantes/tela-vipclass.md`, só o servidor: a administração da VIP Class ganhou a API, e a tela antiga dela,
que segue em uso até a fatia 83, foi consertada. A tela nova "Configurações › VIP Class" é da 83; a API existe e ninguém a chama
ainda. **A pergunta 5 do desenho (o curso 17 como a chave do visagismo) não foi respondida, e segui a (A)**: o 17 aparece marcado
"libera o Visagismo" e não se desativa; a pergunta foi ao lote (o D721).

**O que muda para quem usa (a administração antiga, só os administradores: 10 ativos na cópia):** o lápis e o "Excluir" da
pergunta funcionam (davam 404 desde a importação), e excluir a pergunta a desativa; a resposta correta é uma por pergunta --
marcar uma desmarca a outra, e desmarcar ou remover a correta é recusado com a frase; o curso não aceita a si mesmo nem um ciclo
de pré-requisito, e o curso que é pré-requisito de um curso ativo (11 dos 21 na cópia) e o 17 não se desativam -- nem pelo X da
lista, nem pelo select do editar; o curso grava tudo ou nada, e a unidade ou o grupo inativos ligados ficam ao salvar (aparecem
marcados no formulário); o link da aula só aceita vídeo do YouTube (as 68 de hoje são), o Vimeo saiu, e a lista das aulas toca o
vídeo pelo `youtube-nocookie`; a pergunta e a resposta saem escapadas, e as capas e os fundos pelo `Armazenamento`.

**A API** (`/adm/api/cursos`, 16 ações, `vipclass/admin` E administrador -- 0 pessoas perdem acesso, medido na cópia): a lista com
as contas e os avisos, as opções, o curso, o cadastro, o PUT que nunca toca a situação, a ativação e a desativação com o 409, as
aulas, as perguntas (a exclusão desativa) e as respostas. Os detalhes estão no desenho, na F3.

**O provado:** `CursoApiTest` (25 casos) e `TelasDoVipClassDoErpTest` (10 casos), novos, e os da regra; a fatia e os vizinhos (VipClass, CursoApiTest, D202, D199, D203, RecorteNosServicos, MeusCursosApi, D373, D490, D237, TrocaDoLocal, Mensage, Convenio e Pesquisa): 825 de 825 no MySQL e 825 de 825 no PostgreSQL, saída 0 (4 pulados, os de sempre: dois casos da VIP Class no `D199AtivarRemoverPorPostTest` (ela so desativa pela lista), o caso das mensagens no mesmo arquivo e o `VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador`, que pede a copia). O front: o caso novo do
`vipClassDaTelaAntiga.teste.ts` e o front inteiro, 4.329 de 4.329, saída 0; build e lint, saída 0. **Os mutantes:** 73 no MySQL (71 do servidor -- R1 a R23 na regra, S1 a S11 no servico, C1 a C14 na API, P1 a P5 no repositorio, O1 a O18 na tela antiga e nas views -- e F1 e F2 no JavaScript, pelo vitest) e os 71 do servidor no PostgreSQL, um por vez, a reserva num diretorio novo e vazio a cada um, devolvidos pelo sha1: todos vermelhos (o P5, a resposta inativa nas contas, nasceu verde, ganhou o caso no `CursoApiTest` e ficou vermelho). **Os
ensaios**, nos dois bancos de contrato: o novo `ensaiar-cursos-adm-api.php` (32 de 32: 19 recusas e 13 controles, cada controle
contra um SELECT, e a pergunta da tela antiga pelo POST de formulário), o de recusas (com os 217 a 220), o de permissão (as 16
rotas) e o do aluno, saída 0. **O oráculo** pelo HTTP, como o administrador do ensaio (o grupo administrador só no contrato,
desfeito no fim) e como o gerente com o privilégio (recusado): o ensaio novo e o oraculo (o cadastro com a aula, a pergunta e as respostas, a alteracao, o desativar e o ativar, a recusa do ciclo e da correta removida, contra um SELECT a cada passo; e o editar e o excluir da pergunta na tela antiga), 32 conferencias, 0 divergencias em cada banco; falsificado (marcar a correta sem desmarcar a outra, o S7), 1 divergencia (o caso 27). **Os portões:** o `conferir-remocao-por-post.php` passou a ler o
JavaScript servido (o D362: o endereço de ação que não existe é vermelho; falsificado com o controller do HEAD, vermelho nas duas
chamadas da pergunta), o `conferir-escape-nas-views.php` com as duas telas da administração, o `conferir-cobertura-por-par.php` de
442 para 452 pares (e o "sem menção" de 113 para 111), o `conferir-rotas-sem-login.php` com as 16 rotas, o inventário regerado;
o `conferir-tudo.sh` sozinho, saida 0 (na primeira rodada, dois vermelhos consertados: a frase do YouTube com o endereco inteiro, no `conferir-provedores-no-loopback.php`, e o `$pergunta` e o `$resposta` do servico, declarados no `conferir-escritores-do-horario.php`). Débitos: o D712 e o D713 CORRIGIDOS, o D342 e o D362 PARCIAIS, o D718, o D719 e o D720 registrados e corrigidos, o
D721 registrado com a pergunta no lote; de pe 197 para 196 (`contar-debitos`, CONTINUAM DE PE); o `conferir-debitos-repetidos.php` com os onze pares novos declarados. O navegador e o ciclo inteiro são do coordenador. Nada da produção mudou; na
`franquiabv_dev`, só SELECT.

**A correção depois da verificação (05/11/2026, a fatia 82):** a verificação em três lentes confirmou 8 achados, dois médios e seis
baixos, e todos foram tratados. **Os dois médios:** o formulário antigo com o grupo inativo ligado e com o requisito inativo
gravado não tinha caso -- o POST do caso lia o valor cru, e não o que o navegador manda, então o formulário que deixasse de oferecer
a opção ficava verde (agora o POST do caso é o do navegador, e os dois ramos têm caso); e o `novoAction` reescrito não tinha caso
(agora a unidade que não existe, o requisito inativo e o que não existe voltam no campo, e o cadastro que serve nasce ativo com o
requisito e a unidade). **Os baixos:** (1) as TRAVAS: cada gravação travava só a linha do próprio curso, e duas abas fechavam o
ciclo A↔B, ou desativavam o requisito que a outra punha, sem esperar uma pela outra; agora o cadastro, a alteração e o estado
leem o grafo inteiro travado (todos os cursos, na ordem do id), e cinco casos provam, por outra conexão no instante da escrita,
o grafo preso e a pergunta presa nas duas escritas da resposta (o D720 e o D719 afirmavam a trava sem caso); (2) as opções da API
trazem o requisito gravado mesmo inativo, com `ativo: false`, como o formulário antigo -- o `<select>` da 83 tem o valor gravado;
(3) o `VisagismoController` da API do aplicativo NÃO pede o curso 17, só a página do ERP: a regra, o D721 e a pergunta do lote
diziam "as duas portas", e o caso pulava o arquivo calado; (4) o D719 volta a PARCIAL: perder a correta é recusado, mas a pergunta
nova nasce sem a correta e só avisa -- como fazer valer a decisão 7 foi ao lote ("VIP Class: a pergunta pode ficar sem a resposta
correta enquanto é montada?", D719, recomendando criar a pergunta já com as respostas na tela nova); (5) o D718 diz que as três
remoções da tela antiga acham a linha só pelo id (o JavaScript manda só o id); (6) o `RegraDoCursoVipClassTest` ganhou oito casos,
e não sete. **O provado:** `CursoApiTest` 30, `TelasDoVipClassDoErpTest` 12, `RegraDoCursoVipClassTest` 32 (93 com o
`ConclusoesDoCursoApiTest`, nos dois bancos, saída 0); a fatia e os vizinhos 867 de 867 em cada banco, saída 0 (4 pulados, os de
sempre); os mutantes K1 a K15, um por vez, nos dois bancos: 14 vermelhos, e o K8 equivalente (o `insert` do serviço nunca lê a
situação). De pé: de 196 para 197 (o D719 PARCIAL). O navegador e o ciclo inteiro seguem com o coordenador.

**Conferido por mim, depois da correção** (05/11/2026): os 11 achados das três lentes (8 confirmados, dois médios: os ramos do formulário antigo com o grupo e o requisito inativos sem caso, e o "novo" da tela antiga reescrito sem caso) lidos um a um, e o relato da correção contra cada um; os três não confirmados conferidos (a tela antiga pede administrador pelo menu da administração; o flash sai pelo partial de sempre; o 409 da correta, e não o 422, é a forma do D256). O ciclo inteiro depois da correção: caracterização 6.505 em cada banco, saída 0 (11 e 16 pulados); contrato 74 e 74 (o PostgreSQL já com a espera do opcache da 81); os ensaios, os portões (74) e o front 4.329 de 4.329, saída 0 (cada `EXIT=` dos ensaios lido); o medidor dos relatórios no PostgreSQL, saída 0. **No navegador**, contra o banco de contrato, como o Gerente Teste no grupo administrador e com "VIP Class - Admin" (só no banco de contrato, remontado no fim), a administração antiga (`/mod-configuracoes/vipClassAdmin/index`): desativar o "Curso VIP Aberto (teste)", pré-requisito do curso ativo "Com Requisito", recusado com a frase e o curso ativo no banco; o editar da pergunta, que dava 404, abrindo com o texto e a ordem e salvando ("Pergunta salva com sucesso.", o texto novo na lista); o link da aula fora do YouTube recusado com a frase, e o do YouTube salvo, tocado pelo `youtube-nocookie.com`. **Achados por mim e consertados:** (1) **uma regressão da fatia: editar uma resposta existente deixou de funcionar na tela antiga** -- o servidor passou a conferir a resposta contra a pergunta do `id_pergunta` (o DV-11), e o `vipClassAdminCheckpointRespostaSalvar` só preenchia o campo na "Nova resposta"; editar ouvia "ID inválido." e o modal ficava aberto (os testes do PHP mandavam o campo direto). O JS preenche o campo ao abrir a resposta (`mod-configuracoes.js?cache=172`), com o caso novo no `vipClassDaTelaAntiga.teste.ts` (roda a função do arquivo servido) e o mutante vermelho; no navegador, desmarcar a correta recusado com "A pergunta ficaria sem resposta correta: marque outra como correta antes." e o banco intacto, e editar uma resposta errada salvo ("Resposta salva com sucesso."). (2) a concordância da recusa do desativar: com um curso só, "Troque o pré-requisito deles, ou desative-os antes." -- agora "dele" e "desative-o" com um (`RegraDoCursoVipClass::fraseDosDependentes`), com o caso no `RegraDoCursoVipClassTest` e o mutante (o plural sempre) vermelho. Anotado, sem conserto: o botão de remover a aula se chama "Remover arquivo" desde antes desta fatia (`aulas-tabela.phtml`); a tela antiga sai com a 83 e a 84. Depois dos consertos: a fatia e os vizinhos (`RegraDoCursoVipClass`, `TelasDoVipClass`, `CursoApiTest`, `D203`, `VipClass`) 154 em cada banco, saída 0 (3 pulados, os de sempre); o front inteiro 4.330 de 4.330, build, lint (os 8 avisos de antes) e o `conferir-tudo.sh` sozinho, saída 0. As sessões dos usuários de teste apagadas no contêiner, o `local.php` de volta pelo sha1. **As perguntas novas foram à página do lote:** a 311 (o D721, o curso 17 e o visagismo; recomendada a (a)) e a 312 (o D719, a pergunta sem a correta enquanto é montada; recomendada a (c)). Débitos: o D712 e o D713 CORRIGIDOS, o D718 e o D720 registrados e corrigidos, o D719 PARCIAL com a pergunta, o D342 e o D362 PARCIAIS, o D721 registrado; de pé 197 para 197 (`contar-debitos`, CONTINUAM DE PÉ: o D712 e o D713 saem, o D719 e o D721 entram -- o "196" do relato do implementador é de antes de a correção voltar o D719 a PARCIAL). Nada da produção mudou; na `franquiabv_dev`, só SELECT.

## 05/11/2026 -- VIPCLASS-ADMIN-TELA (a fatia 83 da fila das telas que faltam)

A F3 de `docs/f3-telas-restantes/tela-vipclass.md`, a tela, sem os arquivos (que são da 84): "Configurações › VIP Class" abre a
tela nova no Console, para o administrador com "VIP Class - Admin" (a resposta 144 (a) do dono). A tela chama a API da fatia 82
(`/adm/api/cursos`) e a rota de conclusões da 81; **o servidor não mudou** (na correção, saiu a página "Relatório" da administração
antiga, abaixo), e nenhuma regra dele foi reescrita no front.

**O que muda para quem usa (os administradores; 8 ativos na cópia):** a VIP Class se administra na tela nova. A lista tem a
busca pelo nome -- que na tela antiga nunca filtrou --, a situação, "vale na unidade" e "para o grupo", e cada linha diz onde o
curso vale, para quem, quantas aulas, perguntas e conclusões, e os avisos (sem checkpoint, pergunta com uma resposta só, pergunta
sem a correta, pré-requisito de N cursos ativos, libera o Visagismo). O curso abre em vistas -- Dados, Aulas, Checkpoint, Quem fez
e Arquivos. Em relação à antiga (que, desde a 82, também edita e remove a pergunta e a resposta), ganha: ativar como ação própria
(na antiga, só pelo select do editar), os avisos, o alcance antes de desativar ("N pessoas que podem fazer o curso deixam de vê-lo", as conclusões que ficam, e o
curso de que ele é pré-requisito -- que o sistema recusa, dito antes de confirmar) e o "Quem fez" no mesmo destino. A unidade e o
grupo inativos ligados aparecem marcados e continuam ligados ao salvar. **A tela antiga da administração continua no ERP**: os
arquivos (a capa, o fundo do certificado e os materiais) se trocam lá até a 84, e a vista "Arquivos" da tela nova leva até eles,
em aba nova. Quando ela sai, o desenho não diz -- a não ser a página "Relatório" dela, o relatório duplicado (o S21), que o desenho
manda sair com a administração nova e saiu na correção, abaixo --: a pergunta foi ao lote ("VIP Class: a administração antiga sai do ERP depois dos
arquivos (a fatia 84), ou fica até o corte?", o D722; recomendada a saída na 84). **A pergunta 312 (o D719) segue sem resposta**, e
a tela segue a (a): a pergunta nova nasce sem respostas, a tela diz isso antes de incluir, e o aviso do servidor fica no curso até a
correta entrar. **O que a (c) pediria**, quando o dono responder, para virar fatia: na API, o `POST .../perguntas` aceitando as
respostas junto (uma e só uma correta), gravadas numa transação, com o 422 por campo; na tela, o formulário da pergunta com as
respostas (incluir e tirar linhas, a correta num grupo de rádio), no lugar dos dois passos de hoje.

**Os arquivos:** `CursosDaVipClass.tsx` (a apresentação), `CursosDaVipClassLigado.tsx` (o invólucro: cada pedido com número, a
escrita inclusive; a trava por referência, o envio barrado recebendo a mesma promessa; o 409 relendo o curso e segurando os
botões até chegar; o 422 de volta ao campo), `formularioDoCurso.ts` (a regra pura: os campos, a conferência, o corpo de cada
pedido -- o PUT só do que mudou --, o YouTube como o servidor o lê, e as frases com número, cada uma concordando); os tipos e as
chamadas em `api/cursos.ts`; a entrada no `Console.tsx` (escrever pede o `escrever` e ser administrador; a prancheta, tudo); o CSS
das vistas no `App.css`; a prévia `CursosDaVipClass` (dez células, o dado em `_cursosDaVipClass.ts`, o viewport 1280x1040 medido
com o `playwright` num roteiro temporário, e nada rola de lado a 375 px), e a célula `CadastroDaConfiguracao` da `TelaNaoMigrada`
passando aos Assistentes VIP, o último destino que cai nela.

**O provado:** o front da fatia, 131 casos (`CursosDaVipClassLigado.teste.tsx` 73, `.contrato.teste.tsx` 10 -- a vigência, a trava
e as chaves dos casos contra o JSON de verdade da API, capturado no banco de contrato pelo oráculo e guardado em
`src/testes/cursosDaAdministracao.real.json` --, `formularioDoCurso.teste.ts` 43, `ConsoleAbreVipClassAdmin.teste.tsx` 5), e o
`Console.teste.tsx` (168) com a tela não migrada em 1 destino; os da fatia com `AMPLIFICAR_EFEITOS_MS=50`, verdes (os 6 vermelhos
da comanda no `Console.teste.tsx` com o amplificador são do HEAD também, medidos numa cópia do HEAD: nada desta fatia); o front
inteiro, 4.462 de 4.462 (era 4.330), saída 0; tsc, build e lint, saída 0; a prévia pelo tsc com o `paths` do `frontend`, saída 0.
**Os mutantes:** 40 do front, um por vez, a reserva num diretório novo e vazio, devolvidos pelo sha1, todos vermelhos -- dois (o
número do desativar trocado pela unidade de Quem fez, e a releitura do 409 sem segurar os botões) estavam verdes na primeira
rodada e ganharam caso. Não houve mutante de servidor: o servidor não mudou. **O oráculo** pelo HTTP no banco de contrato MySQL,
como um administrador de teste (o grupo e a pessoa só no contrato, desfeitos no fim; a sessão apagada no contêiner), nas rotas que
a tela chama, na ordem de um uso -- listar, as opções, cadastrar, abrir, quem fez, editar, a aula (e a fora do YouTube recusada), a
pergunta, a resposta correta, desativar, ativar e a recusa do pré-requisito de curso ativo --, cada passo contra um SELECT: 36
conferências, 0 divergências; falsificado (a carga esperada trocada), 1. Os portões: o `conferir-tudo.sh` sozinho, no fim, 74 de 74,
saída 0 (o `conferir-acl-da-api`, o `conferir-rotas-sem-login`, o `conferir-cobertura-por-par`, o `conferir-links-mortos` e o
`conferir-referencias-docs` também sozinhos, saída 0); `contar-debitos` e `conferir-debitos-repetidos` (nove pares novos
declarados), saída 0; o `conferir-segredos.sh`, saída 0. **Os débitos:** o D716 PARCIAL (o Quem
fez da administração na tela nova; a página antiga sai com a administração antiga), o D719 com o que a tela faz e o que a (c)
pediria, o D722 registrado com a pergunta no lote; de pé 197 para 198. **O navegador e o ciclo inteiro são do coordenador.** Nada da
produção mudou; na `franquiabv_dev`, nada; o banco de contrato foi remontado antes do oráculo.

**A correção depois da verificação (05/11/2026).** Sete achados confirmados, um médio e seis baixos, todos tratados:
(1) **a página "Relatório" da administração antiga saiu** -- o desenho já mandava o relatório duplicado (o S21) sair com a
administração nova, e a fatia o tinha posto na pergunta do D722: `VipClassAdminController::relatorioAction`, a view
`relatorio.phtml` e a aba das seis páginas do curso; o endereço cai no 404 do controller, para todos; o relatório do menu
Relatórios fica (o D306). O `ConclusoesDoCursoApiTest::testORelatorioDaAdministracaoSaiu` no lugar do caso dela; as duas entradas no
`conferir-recorte-por-unidade.php`, a linha do inventário (345 para 344 ações) e uma chamada da base do ISConfiguracao no
`conferir-cobertura-por-par.php` (228 para 227) saíram com ela, cada uma com o motivo; o D716 CORRIGIDO, e o D722 e a pergunta do
lote ajustados. (2) **O link da aula pelo `youtube-nocookie.com`** (era o `youtube.com/watch`), como o aluno o vê; a prévia do
vídeo na tabela e a miniatura da capa na lista, que a antiga tem, ficaram de fora e agora estão ditas no desenho -- a capa entra com
os arquivos (a 84; a API da 82 não a traz). (3) **A vigência de quem fez** (o médio: o número do desativar de um curso trocado
pelo de outro), (4) **a releitura do 409 que falha** (os botões presos) e **o voltar com a releitura no ar**, e (5) **a vigência
das opções**, todas com caso no `.contrato.teste.tsx`, com o `fetch` segurado por inteiro (as opções e quem fez inclusive). (6)
**Os links do YouTube dos dois lados**: `formularioDoCurso.casos.json` (35 links), lido pelo `formularioDoCurso.teste.ts` e pelo
`RegraDoCursoVipClassTest::testOsLinksDaTelaDaoOMesmoNaRegra`. (7) **O parágrafo acima dizia** que a tela nova ganhava "editar e
remover pergunta e resposta" -- a antiga já faz isso desde a 82; corrigido aqui e na pergunta do lote. **O provado:** o front da
fatia, 138 casos (`CursosDaVipClassLigado.teste.tsx` 74, `.contrato.teste.tsx` 15, `formularioDoCurso.teste.ts` 44,
`ConsoleAbreVipClassAdmin.teste.tsx` 5), verdes também com `AMPLIFICAR_EFEITOS_MS=50`; o front inteiro 4.469 de 4.469, saída 0;
build e lint, saída 0; os testes PHP da VIP Class e vizinhos (`RegraDoCursoVipClass`, `ConclusoesDoCurso`, `TelasDoVipClassDoErp`,
`VipClass*`, `CursoApi`, `MeusCursosApi`, D199, D202), 242 em cada banco, saída 0. **Os mutantes**, um por vez, a reserva num
diretório novo e vazio, devolvidos pelo sha1, todos vermelhos: C1 a C8 do front (a guarda de quem fez, o `.catch` da releitura, o
voltar sem invalidar a escrita, as duas guardas das opções, o link de volta ao youtube.com, e o `http` e os shorts aceitos pela tela)
e S1 a S4 do servidor nos dois bancos (a ação do relatório de volta, a aba de volta numa view, e os shorts e o `http` aceitos pela
regra). Os portões: o `conferir-tudo.sh` sozinho, 74 de 74, saída 0; `contar-debitos` e `conferir-debitos-repetidos`, saída 0; o
`conferir-segredos.sh`, saída 0; de pé 198 para 197. Não rodou pelo HTTP a página que saiu (a prova é o despacho em processo, nos
dois bancos). **O navegador e o ciclo inteiro são do coordenador.** Nada da produção mudou; na `franquiabv_dev`, nada.


**Conferido por mim, depois da correção** (05/11/2026): os 12 achados das três lentes (9 confirmados, um médio: a vigência de "quem fez", de onde sai o número da pergunta do desativar, sem caso) lidos um a um, e o relato da correção contra cada um; a terceira lente (privilégio e dados) não achou defeito, e os dois não confirmados conferidos (a 49 (a) do dono é sobre a ordem, não sobre a saída da tela antiga; o registro do amplificador, refeito pela correção). O ciclo inteiro depois da correção: caracterização 6.506 em cada banco, saída 0 (11 e 16 pulados); contrato 74 e 74; os ensaios, os portões (74) e o front 4.469 de 4.469, saída 0 (cada `EXIT=` dos ensaios lido); o medidor dos relatórios no PostgreSQL, saída 0. **No navegador**, contra o banco de contrato, como o Gerente Teste no grupo administrador e com "VIP Class - Admin" (só no banco de contrato, remontado no fim), a 1280 e a 375 px: "Configurações › VIP Class" no Console abre a tela nova com os 4 cursos, os avisos curtos ("sem checkpoint", "pré-requisito de 1 curso ativo") e as contas; o curso com o foco no título, as vistas, os dados com "onde vale" e "para quem" por extenso e o "é pré-requisito de"; o desativar com a pergunta no botão e o alcance pelos números do servidor ("1 pessoa que pode fazer o curso deixa de vê-lo [...] o sistema recusa desativá-lo enquanto ele o pedir"), e, confirmado, o 409 do servidor na região de status com a frase consertada na 82 ("dele", "desative-o") e o curso ativo; o checkpoint com a correta marcada, e marcar outra como correta desmarcando a de antes ("Resposta salva.", o foco de volta ao botão que abriu o formulário); a 375 px, a página e o `main` sem rolar de lado, a tabela de 7 colunas rolando dentro do quadro dela, e a volta à lista com o foco na linha do curso. **Achado por mim e consertado:** a região de status dizia "Curso Curso VIP Aberto (teste) aberto" -- 7 dos 21 nomes da cópia (só contagem) começam por "Curso"; agora "«nome» aberto: ...", com a asserção no `CursosDaVipClassLigado.teste.tsx` e o mutante (o "Curso" de volta) vermelho, devolvido pelo sha1. Visto e deixado: o código pequeno " (conflito)" ao lado da frase do 409 é o padrão da casa (o `Erro.tsx`, o D322: a frase primeiro, o código pequeno). Depois do conserto: o front inteiro 4.469 de 4.469, build (398,89 KB em gzip), lint (os 8 avisos de antes) e o `conferir-tudo.sh` sozinho, saída 0. As sessões dos usuários de teste apagadas no contêiner, o `local.php` de volta pelo sha1. **A pergunta nova foi à página do lote:** a 313 (o D722, a administração antiga sai na 84 ou fica até o corte; recomendada a (a)). Débitos: o D716 CORRIGIDO (a página "Relatório" da administração antiga saiu), o D722 registrado; de pé 197 (`contar-debitos`, CONTINUAM DE PÉ). Nada da produção mudou; na `franquiabv_dev`, só SELECT de contagem.


## 05/11/2026 -- VIPCLASS-ARQUIVOS (a fatia 84 da fila das telas que faltam)

A F4 de `docs/f3-telas-restantes/tela-vipclass.md`, os arquivos (o DV-9, registrado como D723): a capa, o fundo do certificado e
os materiais dos cursos da VIP Class passam pela tela nova, e a tela antiga, que fica no ERP, foi consertada nos mesmos envios. A
resposta 144 (a) do dono põe a administração na tela nova; **a pergunta 313 (o D722, a saída da administração antiga) não foi
respondida**, e a antiga FICA -- a vista "Arquivos" da tela nova só deixou de levar a ela.

**O que muda para quem usa (os administradores; 8 ativos na cópia):** em "Configurações › VIP Class", a vista "Arquivos" do curso
mostra a capa e o fundo do certificado de agora (a imagem padrão marcada como padrão) e troca cada um ali mesmo; lista os materiais
com "abrir" (o link público, em aba nova), "copiar o link" e "remover" (a pergunta no botão), marca o material cujo arquivo falta no
servidor ("sem o arquivo", com o aviso no alto -- na cópia, 1 dos 14), e inclui um material novo (a descrição e o PDF). A lista dos
cursos ganhou a miniatura da capa. A imagem só entra se for JPG de verdade e até 5 MB; o material, se for PDF de verdade e até o
limite do servidor (64 MB aqui) -- o arquivo que diz ser e não é ouve "Só imagem JPG: o conteúdo deste arquivo não é de um JPEG."
ou "Só PDF: o conteúdo deste arquivo não é de um PDF." no campo, e o grande demais ouve a frase do tamanho, também quando o
servidor o barra antes do sistema (o 413 do nginx). **Na tela antiga**, que segue no ERP: a capa e o fundo deixam de aceitar o
PNG com nome .jpg e o teto é o que a frase diz (5 MB; antes, 20 MB com a frase "limite 5mb"); o material deixa de aceitar o HTML com
nome .pdf e ganha o teto do servidor; o nome comprido é cortado (antes, o banco o recusava depois de guardar o arquivo); vários
materiais de uma vez entram todos ou nenhum; e "Salvo com sucesso." só aparece quando salvou. Para todos: trocar a imagem nunca
deixa o curso sem ela (a nova é guardada e gravada antes de a antiga sair), e o link público do material manda o nome do arquivo
limpo. **O material de 87 MB contra os 64 MB** (o "Cuidado" da fila): o que existe continua saindo pelo link; um novo desse tamanho
não entra neste ambiente (a tela recusa antes, e o nginx também); o teto de produção não está no repositório.

**Os arquivos:** no servidor, a seção "OS ARQUIVOS DO CURSO" da `RegraDoCursoVipClass` (o JPEG por `getimagesize`, o PDF por
`%PDF-`, os tetos, o nome cortado em 100, o nome físico aleatório, a imagem padrão que nunca sai, as recusas da tela antiga), o
`VipClassService` (`trocarImagem`, `incluirMateriais`, `excluirMaterial`, na ordem "guarda, grava, e só então apaga"; o
`uploadMateriais` morto saiu), a `/adm/api/cursos` com as quatro rotas novas (`POST .../capa`, `.../certificado`, `.../materiais`,
`.../materiais/:material/exclusao`, em multipart, a mesma guarda) e o curso com `capa`, `certificado`, `materiais` e `limites`
(`CursoJson`), e o `VipClassAdminController` (os três envios e o `Content-Disposition` do link público). No front, a vista em
`CursosDaVipClass.tsx`, as escritas no `CursosDaVipClassLigado.tsx` (o 413 do nginx vai ao campo), a regra pura em
`formularioDoCurso.ts`, as chamadas em `api/cursos.ts`, o CSS e a célula "Os arquivos" da prévia. Nos roteiros, os portões
`conferir-rotas-sem-login.php` (as quatro rotas; a pública do material continua uma só) e `conferir-cobertura-por-par.php` (227 para
228), e o caminho do `Armazenamento` em literal no ponto da chamada (o `conferir-protecao-do-webroot.php` o resolve).

**O provado:** no PHP, `ArquivosDoCursoApiTest` (18), `ArquivosDaVipClassNaTelaAntigaTest` (8), cinco casos no
`RegraDoCursoVipClassTest` (um lê as frases da tela), um no `VipClassArquivosTest`; a VIP Class e os vizinhos (os banners, os
arquivos, o recorte), 451 em cada banco, saída 0 (4 pulados: três do `D199AtivarRemoverPorPostTest` e o
`VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador`). No front, `CursosDaVipClassLigado.arquivos.teste.tsx` (28) e cinco
casos no `formularioDoCurso.teste.ts`; os da fatia, 170, verdes também com `AMPLIFICAR_EFEITOS_MS=50`; o front inteiro 4.501 de 4.501;
tsc, build e lint (os 8 avisos de antes), saída 0; a prévia pelo tsc com o `paths` do `frontend`, saída 0. **Os mutantes**, um por vez,
a reserva num diretório novo e vazio, devolvidos pelo sha1, todos vermelhos: S1 a S22 do servidor nos dois bancos (o conteúdo trocado
pela extensão na imagem e no material, o teto de 20 MB e o `>=`, o nome sem corte, apagar antes de gravar, a nova que fica quando a
gravação falha, a padrão apagada, a guarda sem o administrador, o `Content-Disposition` cru, a recusa do `post_max_size` calada, o
material apagado antes da linha, o material de outro curso, as três da tela antiga, a recusa só do primeiro material, a miniatura, o
`guardado` sem olhar o armazenamento, o recorte arrancado, a frase da tela trocada e a guarda que falha seguindo) -- o S3 (o teto)
estava verde na primeira rodada, porque o caso lia o teto da própria constante, e ganhou o literal; F1 a F16 do front; e os portões
(uma segunda rota pública, e uma ação sem guarda), vermelhos. **O ensaio** `ensaiar-cursos-adm-api.php` com os casos 33 a 48 (a capa,
o fundo e o material em multipart, contra o SELECT e o armazenamento; o 413 do nginx no material acima do limite), 48 de 48 nos dois
bancos de contrato; o de recusas (221 e 222) 272 de 272 e o de permissão 216 de 216, nos dois. **O oráculo** pelo HTTP como um
administrador de teste (o grupo e a pessoa só no contrato, desfeitos; a sessão apagada no contêiner) e como o gerente: cadastrar,
trocar a capa duas vezes, o fundo, as três recusas, o material com o nome comprido, o link público sem sessão, a miniatura na
lista e remover, cada passo contra um SELECT e contra o armazenamento: 19 conferências, 0 divergências no MySQL e 0 no PostgreSQL;
falsificado (o nome cortado em 101), 1; o armazenamento da cópia como estava no fim (24 capas, 22 fundos, 14 materiais). Os portões:
o `conferir-tudo.sh` sozinho, no fim; `contar-debitos` e `conferir-debitos-repetidos`, saída 0. **Os débitos:** o D723 (o DV-9)
registrado e CORRIGIDO; o D722 com a nota da 84; de pé 197. **O navegador e o ciclo inteiro são do coordenador.** Nada da produção
mudou; na `franquiabv_dev`, nada; os bancos de contrato foram remontados no fim.

**A correção depois da verificação em três lentes (05/11/2026, a mesma fatia):** onze achados (dois deles o mesmo), dois médios e
os outros baixos; nenhum recusado. **O que muda para quem usa:** na tela nova, trocar a capa ou o fundo que era a imagem padrão diz
"Capa trocada. A imagem padrão fica no servidor, para os outros cursos." (antes dizia que a de antes tinha saído do servidor, o que
era falso: a padrão nunca sai), e a dica do material diz "Só PDF, até 64 MB." -- o mesmo número da recusa, e não "64,0 MB"; na tela
antiga, o material ou a imagem grande demais para o servidor ouve a frase do tamanho, e não "Erro ao enviar." (o
`mod-configuracoes.js?cache=173`). **No código:** o `refresh` depois do `find` travado do `trocarImagem` saiu (no Doctrine 2.20 o
`find` com `PESSIMISTIC_WRITE` já relê o curso carregado; arrancar o `refresh` deixava o caso verde); a frase do tamanho do material
passou a ser a dos arquivos da franqueadora (`fraseDoTamanho` e o novo `limiteEmTexto` do `formularioDoArquivo.ts`), e não uma
cópia; o formulário da tela antiga traz a frase do 413 (`data-frase-do-tamanho`). **Os casos novos:** no
`ArquivosDoCursoApiTest`, a releitura (a capa gravada por outra troca no meio é a que sai), a trava (a releitura por `FOR UPDATE` e a
gravação com a linha presa, sondadas por outra conexão com `NOWAIT`), "todos ou nenhum" com a guarda que falha no meio, e o nome
gravado que não é de arquivo (22 casos); no `ArquivosDaVipClassNaTelaAntigaTest`, o fundo na coluna dele, os materiais que não se
guardam sem o "Salvo" e a frase do 413 nos formulários (11); no front, a troca da capa padrão, a frase pela imagem de antes, a dica
com o número da recusa, e o 413 da tela antiga no `vipClassDaTelaAntiga.teste.ts`. **O que rodou:** as duas classes, 33 em cada
banco, saída 0; a VIP Class e os vizinhos, 674 em cada banco, saída 0 (5 pulados: três do `D199AtivarRemoverPorPostTest`, o
`RecorteDeUnidadeNaAgendaTest::testComOPrivilegioGeralAListaAlcancaAsOutrasUnidades` e o
`VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador`); o front inteiro 4.505 de 4.505, os da fatia também com
`AMPLIFICAR_EFEITOS_MS=50`; build, lint (os 8 avisos de antes) e a prévia pelo tsc, saída 0. **Os mutantes**, um por vez, a reserva
num diretório novo e vazio, devolvidos pelo sha1: o S18 de novo contra o texto de agora do `CursoJson` (vermelho nos dois bancos);
T2 (o `find` sem a trava), T3 (a trava depois, sem releitura), T3b (a releitura solta antes da trava, que só o caso da trava pega),
T4 (a limpeza dos já guardados arrancada), T5 (o "Salvo" sempre nos materiais da tela antiga), T6 (o fundo despachado como capa),
T7 (o nome gravado sem conferir no remover), T8 (o endereço do nome inválido) e T9 (a frase do 413 fora do formulário), vermelhos
nos dois bancos; o T1 (arrancar o `refresh`) ficou verde por ser equivalente, e o `refresh` saiu; no front, G1 a G8 (o 413 da tela
antiga nos três envios e o `status` trocado, a frase sem olhar a padrão, a imagem de antes lida da capa ou do curso relido, a dica e
a recusa com o `pesoDoArquivo`), vermelhos. O texto da pergunta 313 no lote ganhou a atualização (a (a) vira uma etapa própria).
**O navegador e o ciclo inteiro são do coordenador.** Nada da produção mudou; na `franquiabv_dev`, só SELECT de contagem.

**Conferido por mim, depois da correção** (05/11/2026): os 12 achados das três lentes (10 confirmados, dois médios: a trava e a releitura da troca da imagem sem caso, e a limpeza do "todos ou nenhum" dos materiais sem caso) lidos um a um, e o relato da correção contra cada um, com a premissa que ela corrigiu (o `find` com a trava já relê o curso no Doctrine 2.20, e o `refresh` redundante saiu); o não confirmado (o nome físico previsível dos materiais no bucket com `url_publica`) conferido: é o desenho do endereço direto da 79, que só existe com o bucket público. O ciclo inteiro depois da correção: caracterização 6.545 em cada banco, saída 0 (11 e 16 pulados); contrato 74 e 74; os ensaios, os portões (74) e o front 4.505 de 4.505, saída 0 (cada `EXIT=` dos ensaios lido); o medidor dos relatórios no PostgreSQL, saída 0. **No navegador**, contra o banco de contrato, como o Gerente Teste no grupo administrador e com "VIP Class - Admin" (só no banco de contrato, remontado no fim): a lista com as miniaturas das capas (decorativas, `alt` vazio); a vista "Arquivos" do curso restrito com a capa e o fundo padrão marcados e os três campos com os tetos ("Só imagem JPG, até 5 MB.", "Só PDF, até 64 MB."); um PNG com nome `.jpg` recusado no campo pelo conteúdo, com o foco nele; um JPEG feito no navegador trocando a capa, com "Capa trocada. A imagem padrão fica no servidor, para os outros cursos." (o conserto da correção); um PDF incluído como material ("O curso tem 1 material."), o link público com 200, `application/pdf`, o nome limpo no `Content-Disposition` e `nosniff`; e o remover com a pergunta no botão, "O curso não tem material." e o foco no título da vista. O armazenamento local terminou como estava (24 capas, 22 fundos, 14 materiais): a capa de teste apagada pela comparação com a lista de antes, o material pelo próprio remover. As sessões dos usuários de teste apagadas no contêiner, o `local.php` de volta pelo sha1. **A pergunta 313 foi atualizada na página do lote** (a etapa dos arquivos foi feita sem a resposta; a (a) vira uma etapa curta à parte). Débitos: o D723 (o DV-9) registrado e CORRIGIDO, a nota no D722; de pé 197 (`contar-debitos`, CONTINUAM DE PÉ). Nada da produção mudou; na `franquiabv_dev`, só SELECT de contagem.

## 05/11/2026 -- O ritmo, quanto falta e quanto tempo, depois da fatia 84 (pedido do dono)

O dono pediu: "Atualize o roadmap, o ritmo, quanto falta, quanto tempo e % que falta". Medido pela mesma régua de 16/10 a 04/11 (as inserções do `git show --numstat` de cada commit, sem `docs/`, `*.md`, os dumps, a carga, as fixtures, os lockfiles, o inventário gerado e o ds-bundle; os roteiros `medir-falta-2610.py` e `medir-ritmo-0511.py` do rascunho), e publicado no Artifact do roadmap (versões 222 a 224).

**Quanto falta: cerca de 87% feito, entre 84% e 92% (falta cerca de 13%, entre 8% e 16%).** Era 81% em 04/11, 70% em 01/11, 62% em 26/10 e 50% em 16/10; desta vez o salto é trabalho feito, não remedida. Feito desde a importação: cerca de 665 mil linhas (616 mil em 04/11). Falta: a fila das telas que faltam, 8 fatias, cerca de 9 a 12 mil (a 85, a 86 e a 90, que eu faço sozinho, cerca de 3 mil; a 87 a 89, a 91 e a 92g, que esperam o dono, cerca de 6,5 a 9 mil); o código que as perguntas abertas 306 a 313 pediriam pelas recomendações, cerca de 2 a 6 mil; os débitos de pé fora da fila, 40 a 90 mil (o custo remedido em 04/11, não remedido de novo: os de pé passaram de 190 a 197, e os novos são quase todos perguntas ao dono); e o preparo do corte, 10 a 20 mil. No total, cerca de 61 a 128 mil linhas. Na régua dos débitos, 524 dos 721 não estão mais de pé (72,7%).

**As estimativas:** as fatias desde 04/11 custaram 1,48 vez o previsto (17 com estimativa); as seis da VIP Class, 1,40; todas as da fila desde 14/10, 1,52 (278.825 linhas reais contra 183.440 previstas).

**O ritmo:** de 04/11 às 07h51 (o commit da 76k, o fim da medida anterior) a 05/11 às 08h58 (o commit da 84), 25,1 h de relógio, 22 commits, todos de fatia, 49.339 linhas: **1.964 linhas por hora de relógio**, nenhuma parada acima de 4 h, mediana de 1,1 h entre commits, 2.243 linhas por commit. Contra 1.082 do Mac de 02 a 04/11 (1,8 vez), 519 da fila inteira no Windows (3,8 vezes) e 794 do melhor trecho do Windows (2,5 vezes). O Mac inteiro, de 02/11 às 23h28 até a 84: 57,5 h, 51 commits, 83.908 linhas, 1.459 por hora. A fatia grande da VIP Class levou de 63 a 86 min no fluxo de agentes e cerca de 1 h 30 com o ciclo, o navegador e o commit; o ciclo inteiro subiu para 10 min 4 s a 10 min 22 s (a suíte cresceu para 6.545 casos em cada banco e 4.505 no front). O que pesa contra: o que falta é de outro tipo (dois terços são débitos pequenos, que ainda não rodaram no fluxo do Mac, onde o custo fixo de cada item pesa mais), e as 25 h não tiveram pausa nenhuma.

**Quanto tempo:** cerca de 35 a 95 horas de trabalho -- a fila que eu faço sozinho, 4 a 5 h; a que espera o dono, 5 a 8 h quando destravar; o código das respostas 306 a 313, 2 a 4 h; os débitos, 20 a 65 h (a 1.400 a 1.964 linhas por hora); o preparo do corte, 5 a 14 h. Sem pausas, de 1,5 a 4 dias (perto de 7 a 9/11, retomando em 05/11); com metade do relógio parado, de 3 a 8 dias (perto de 8 a 13/11). Era de 5,5 a 16 dias em 04/11. Fora da conta: as janelas do corte na produção, que dependem do dono. O trabalho está em pausa, por ordem do dono, depois da fatia 84.

## 06/11/2026 -- ENTRADA-NO-NOVO prevista: a fatia 94, a tela de login do `/novo` (pedido do dono)

O dono, testando no simulador do iPhone, perguntou se a versão nova não tinha tela de login, e pediu: *"Sim, preveja como uma
fatia nova"*. Hoje o `/novo` não tem entrada própria: sem sessão, o `GET /adm/api/sessao` dá 401 e a tela mostra "A sessão do ERP
caiu" com o link para a página antiga. E não tem "Sair". O desenho está em `docs/f3-telas-restantes/tela-entrada.md`: o formulário
de entrada dentro do `/novo`, com a **mesma sessão do ERP** (o POST vai para o `/autenticacao/entrar` que já responde JSON; nada de
token, e a premissa do `vite.config.ts` continua valendo), a frase de recusa do servidor sem reescrita (as do D349 e do D351 só
saem com a senha certa), os atributos do celular, e a volta ao destino depois de entrar (F1, só front); a entrada automática pelo
cookie numa rota `POST /adm/api/sessao/retomar` e o "Sair" na barra do topo (F2). Três perguntas ao dono, P1 a P3 (a entrada
automática, onde o "Sair" deixa, a tela de "esqueci a senha"), cada uma com recomendação que vale até a resposta; vão para a página
do lote a partir da 314. Achado da leitura, a conferir no passo 0: o login não conta tentativas, e nenhum débito tem isso no título
(DE-1). Tamanho: 2.000 a 3.100 linhas, 2 a 3 h. Entra na fila depois da 85, da 86 e da 90, e antes das que esperam o dono. Nada foi
implementado: o trabalho continua em pausa depois da fatia 84, por ordem do dono. Só documento. *Corrigido no mesmo dia: o número
previsto era 93, mas a fila do levantamento já tem uma 93 (a Carteira, feita nas fatias 76); a fatia nova é a 94, a primeira livre.*

## 06/11/2026 -- VIPCLASS-CONCLUSAO-UNICA (a fatia 85 da fila das telas que faltam)

O dono mandou retomar ("continue") depois da pausa da fatia 84. Esta é a F5 da VIP Class (`docs/f3-telas-restantes/tela-vipclass.md`),
pela resposta 213 (a): **uma conclusão por pessoa e curso, vale a primeira**. **O que entrou:** o roteiro do corte
`scripts/migracao-vipclass-conclusao-unica.sql`, no **passo 3** de `docs/procedimento-de-corte.md` (depois da limpeza e antes do
dump; no passo 0 só a linha "Roda no passo 3 da janela, e não aqui" -- apagar linha não vai ao laço, que a etapa 1 copia, a 192
(a)): conta antes, lista o que fica e o que sai, tira toda conclusão que tem outra mais antiga no mesmo par (fica a de menor data e,
no mesmo segundo, a de menor id), cria a chave única (vipclass, usuario) e confere depois; idempotente. O irmão do PostgreSQL da
cópia em `postgres/`. A chave no `mysql.sql` e no `postgres/schema.sql` (pela lista `$unicidades` do gerador, com o `inquilino_id`
na frente), e os portões de unicidade sabendo dela. O envio da conclusão passou a gravar pela conexão e a tratar a recusa da chave
como "Você já concluiu este curso" (409 na API), e não como erro.

**O que muda para quem usa:** nada visível hoje -- as telas já mostram a primeira conclusão desde a fatia 80. Depois do corte, o
banco recusa a segunda por si. **O que some na produção** (medido na cópia, só contagem): 191 linhas de 167 pares (119 pessoas, 17
cursos), todas com a data diferente da primeira, 4 com a nota diferente (em 3 a que sai tem nota maior); **nada some de quem
concluiu** -- os 1.940 pares continuam, cada um com a primeira.

**O que rodou:** a caracterização inteira nos dois bancos (6.552 casos em cada; 12 pulados no MySQL e 16 no PostgreSQL -- os nomes
estão no parágrafo da verificação, abaixo), o contrato (74 e 74), o
`ensaiar-sql-do-passo-3.php` com o cenário da VIP Class (o par de três, o empate no mesmo segundo, o id fora da ordem, o que não é
par, a segunda vez), e os portões. **Os mutantes**, 18, cada um sozinho e devolvido pelo sha1, todos vermelhos: no roteiro do MySQL
a última no lugar da primeira, o empate pelo id maior, a chave de uma coluna, o par inteiro apagado, a chave sem a conferência de
existência, o roteiro sem o `DELETE`; no do PostgreSQL a última, o empate, a não idempotência, a chave sem o inquilino; no código a
recusa sem o `catch` (o 500), sem o `rollback` e pelo `flush` (o EntityManager fechado); no esquema o `mysql.sql` sem a chave, o
gerador sem ela e com as colunas trocadas; no procedimento o comando do passo 3 comentado e a linha do passo 0 sem o arquivo.
**O oráculo:** o roteiro num banco descartável de cada lado, contra a contagem antes e depois; e, na cópia, só a contagem do que
ele apagaria (os mesmos números nos dois bancos).

**Fica para depois:** as cópias locais NÃO receberam a limpeza. A 213 (a) já a autoriza nelas (o texto da opção na página: "as 191
linhas a mais saem, nas duas cópias locais já e na produção na etapa 2"), mas a ordem da fatia era só `SELECT` na `franquiabv_dev`:
rodá-la é do coordenador, e até lá o ensaio geral do corte sobre a cópia para na carga das conclusões (o D728). Na produção, o
passo 3 da janela, do dono. O D372 fica PREPARADO. O ciclo inteiro e o commit são do coordenador.

**A verificação (06/11/2026), e o que ela consertou.** (1) O caso 35 do `ensaiar-vipclass-adm-api.php` gravava a segunda
conclusão do gerente; com a chave, o `INSERT` dava 1062 (23505 no PostgreSQL) e o ensaio saía 2 nos dois bancos, sem rodar o 36
e o 37 (o recorte do "Quem fez"); medido antes do conserto, no MySQL e no PostgreSQL. Agora o caso grava quando o banco deixa e
confere a recusa quando ele tem a chave, lida do catálogo: 37 de 37 nos dois. (2) A chave na suíte escondia o caso que prova a
releitura DEPOIS da trava -- o mutante "releitura antes do `FOR UPDATE`" ficava verde --: o caso de fora da transação confere
agora que o envio não chega ao `INSERT`, e o irmão na temporária sem a chave (`testSemOIndiceAConclusaoERelidaDepoisDaTrava`)
prova a ordem no mundo de antes do corte. (3) Só a recusa da chave é "já concluiu": `testOutroErroDoBancoNoEnvioNaoEJaConcluiu`
faz o `INSERT` falhar pela chave estrangeira da pessoa, e o erro sobe com a transação desfeita. (4) As temporárias sem a chave --
as pessoas, os cupons e as conclusões -- copiam agora o recorte do inquilino da de verdade no PostgreSQL (o `LIKE` não o copiava),
conferido pelo `TemporariaSemAChaveComORecorteTest`. (5) A pergunta que tinha ido ao lote sobre a cópia local saiu dele (o dono
já respondeu na 213 (a)), e o procedimento de corte cita agora o texto da opção da página, e não o do desenho. **Os mutantes da
verificação**, cada um sozinho e devolvido pelo sha1, todos vermelhos pela asserção: a releitura antes do `FOR UPDATE` (dois
casos, nos dois bancos), o `catch` do DBAL inteiro e o de `Throwable` (nos dois), a temporária das conclusões sem o recorte e as
três sem o `ENABLE` (no PostgreSQL), o caso 35 de antes no PostgreSQL (saída 2, 23505) e a chave lida como ausente no MySQL (o 35
ERRADA). **A caracterização inteira de novo**, nos dois bancos: 6.557 casos cada, 0 falhas; **15 pulados no MySQL**: os 11 que
pulam nos dois (`ComboboxDeAnosNoRelatorioTest::testOComboDoPainelDeduplicaOsAnos`, três do `D199AtivarRemoverPorPostTest`, dois
do `DashboardFaturamentoAnosTest`, `DescontosNoRelatorioTest::testOsTotaisNaoSomamOsDeOutraFranquia`,
`RecorteDeUnidadeNaAgendaTest::testComOPrivilegioGeralAListaAlcancaAsOutrasUnidades`, `RegrasDosAtendimentosTest::testBancadaDoAno`,
`VendasNoRelatorioTest::testODescontoNaoSomaODeOutraUnidade` e `VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador`),
o `VipClassConclusaoUnicaTest::testORoteiroDoPostgresDeixaAPrimeira` e os três do `TemporariaSemAChaveComORecorteTest` (o RLS é
só do PostgreSQL); **16 no PostgreSQL**: os mesmos 11 e os cinco do `ConsumoDoClienteTest` (o roteiro dele é do MySQL). Os 12 do
MySQL da primeira rodada eram os 11 e o caso do roteiro. Os arquivos dos 11 não mudaram nesta fatia, mas não há lista de pulados
de antes dela para comparar: "os de sempre", no texto de cima, era afirmação sem prova, e saiu. Depois da suíte inteira, o caso da chave
estrangeira passou a ir pela tela antiga (a sessão de uma pessoa que não existe), e não pelo serviço com `selecionarPerguntas` --
homônimo do da Pesquisa, que o portão de cobertura por par acusou --, e rodaram de novo os casos da VIP Class, dos cupons, dos
sorteios e dos privilégios: 640 em cada banco, 0 falhas; os mutantes do `catch` de novo vermelhos nos dois. **Fica como estava:** a violação
da `PRIMARY KEY (id)` no `INSERT` é da mesma classe do DBAL e também vira "já concluiu" -- só com a sequência atrás do maior id,
que a carga reponta.

## 06/11/2026 -- VIPCLASS-RESPOSTAS (a fatia 95 da fila das telas que faltam)

O código das respostas 308 a 313 do dono, todas da VIP Class, que voltaram da página do lote em 05/11/2026 (a ordem é o
"continue" de 06/11). **Resposta a resposta:**

- **313 (a), a administração antiga sai do ERP (o D722, CORRIGIDO, e PARCIAL na correção: o item do menu).** No molde da 76e, derivado do `module.config.php`, do
  `Module.php` e do grep: saíram o `VipClassAdminController` (as 19 ações), a fábrica, o invokable e o alias `vipClassAdmin`, as
  dez views de `vip-class-admin/`, os oito formulários que só ela usava, as funções `vipClassAdmin*` do `mod-configuracoes.js`
  (`?cache=` de 173 para 174), o item "VIP Class" de Configurações do `layout.phtml` e a linha do `SessaoAcl::menuAtivo`; no
  `VipClassService`, os seis métodos que só ela chamava; na `RegraDoCursoVipClass`, o formulário do curso do ERP, a marca de
  correta do `<select>` e as recusas em HTML dos envios. **Ficam:** o LINK PÚBLICO DOS MATERIAIS (`/vipclass/material-download`),
  cuja ação foi, sem mudar uma linha, para um controller só dela e sem alias (`VipClassMaterialController`); o relatório do menu
  Relatórios (o D306); e a tela antiga do aluno (a 309). **O item do menu do ERP não "leva à tela nova", como a letra da (a)
  diz: ele saiu do menu** (o ERP não tem endereço que abra uma tela dentro do `/novo`), como na 76e -- e isso voltou ao dono
  na correção (a pergunta nova no lote, D722).
- **312 (c), a pergunta nasce com as respostas (o D719, CORRIGIDO).** O `POST /adm/api/cursos/:id/perguntas` exige
  `respostas` (pelo menos uma, sem teto, exatamente uma correta) e grava a pergunta e as respostas numa transação; o que não serve é 422 no
  campo e nada grava. O mínimo é uma (a decisão 7: a resposta só é aviso), e não há máximo, como na tela antiga (o teto de dez da primeira escrita saiu na correção). **Na cópia (só
  SELECT de contagem):** das 66 perguntas ativas, nenhuma seria recusada (de 1 a 4 respostas, todas com uma correta, 3 com uma
  resposta só); a regra vale só para a criação. Não há outra porta que crie pergunta (grep): a antiga saiu, e o `salvarPergunta`
  do serviço só altera a que existe. Incluir resposta numa pergunta existente segue como a 82 deixou, e o aviso
  `pergunta_sem_correta` fica para o dado velho.
- **308 (a), "Outros certificados" (o D714, CORRIGIDO).** A `GET /adm/api/meus-cursos` traz `outrosCertificados`: os cursos que
  a pessoa da sessão concluiu e que não estão nos cartões dela, com a data da primeira conclusão e sem a nota (`RegraDoCursoVipClass::
  outrosCertificados`, `VipClassCheckpointUsuarioRepository::conclusoesDoAluno`).
- **309 (b), a tela antiga do aluno fica até o corte de cada unidade (o D715, PREPARADO):** nada no código; a saída, com a lista
  do que sai e do que fica, está no novo item 6 do "Depois" de `docs/procedimento-de-corte.md` (o procedimento não tinha esse
  passo).
- **310 (a), "Quem fez" como está (o D717, DECIDIDO)** e **311 (a), o curso 17 continua a chave do visagismo (o D721,
  DECIDIDO):** só as marcas.

**Para quem usa:** os administradores deixam de ver "VIP Class" em Configurações no ERP antigo (o item não leva à tela
nova, como a sua escolha dizia: sumiu do menu, e a pergunta voltou ao lote) -- os endereços
`/mod-configuracoes/vipClassAdmin/...` respondem 404 --, e administram tudo em "Configurações › VIP Class" da tela nova, que no
menu dela virou botão. Ao incluir uma pergunta no checkpoint, o formulário pede o texto e as respostas juntos (duas linhas para
começar, "incluir outra resposta" sem teto, "tirar" em cada uma), com a correta num grupo de rádio, e só inclui com uma correta
marcada; não existe mais pergunta "em montagem" sem a correta. Quem estuda e concluiu um curso que saiu da lista dele (mudou de
grupo, curso despublicado ou desativado) vê "Outros certificados" no pé da lista, com o nome, a data e o certificado --
sem abrir o curso. O link de compartilhar um material continua funcionando. A tela antiga do aluno continua no ERP.

**O que rodou:** os testes da VIP Class e os vizinhos (`CursoApiTest`, `MeusCursosApiTest`, `ConclusoesDoCursoApiTest`,
`ArquivosDoCursoApiTest`, `RegraDoCursoVipClassTest`, `VipClassArquivosTest`, `VipClassAlunoPeloErpTest`,
`VipClassConclusaoUnicaTest`, `VipClassNoRelatorioTest`, o novo `AdministracaoAntigaDaVipClassSaiuTest`, os D199, D202 e D203, o
`RecorteNosServicosTest`, o `ClientesAntigoSaiuTest`, o `RelatoriosNoMenuTest`, o `SessaoApiTest` e outros pelo grep), **508 em
cada banco, saída 0** -- pulados pelo nome: no MySQL 6 (o `D199...::testOAtivarDaListaEFormularioComOEstado` "mensagens", o
`VipClassNoRelatorioTest::testORelatorioBifurcaPorAdministrador`, o `VipClassConclusaoUnicaTest::testORoteiroDoPostgresDeixaAPrimeira`
e os três do `TemporariaSemAChaveComORecorteTest`), no PostgreSQL 2 (os dois primeiros). Saíram, com a tela que provavam, o
`TelasDoVipClassDoErpTest` e o `ArquivosDaVipClassNaTelaAntigaTest` (o que provavam da regra e do serviço continua provado pela
API), a VIP Class do D199 e do D202 e as listas antigas do `ConclusoesDoCursoApiTest`. Os ensaios HTTP: o
`ensaiar-cursos-adm-api` 48 de 48 (o 25 e o 26 da pergunta com as respostas; o 30, 31, 32, 47 e 48 com 404 e nada gravado; o 44, o
link público com 200 e `nosniff`), o `ensaiar-vipclass-adm-api` 37 de 37, o `ensaiar-recusas-adm-api` 272 de 272 e o
`ensaiar-permissao-adm-api` saída 0, **nos dois bancos**. O front: 4.513 de 4.513, e os arquivos tocados com
`AMPLIFICAR_EFEITOS_MS=50` (273 de 273); tsc, lint (saída 0) e build. As prévias: "Outros certificados" no `MeusCursos` e a
pergunta nova com as respostas no `CursosDaVipClass` (prop `perguntaNovaAberta`); o envio ao Claude Design é do dono. Os portões:
o `conferir-tudo.sh` (74), saída 0 -- com o `conferir-guarda-do-erp` (a CAPACIDADE no controller novo), o `conferir-escape-nas-views`
sem as duas views, o `conferir-links-mortos` com o piso de 380 para 320 (de 391 para 343 chamadas, com o motivo), o inventário
regerado (344 para 326 ações) e o `conferir-cobertura-por-par` com o par novo (452 para 453; sem ele, o sha1 é o de antes) e o
ISConfiguracao de 228 para 220 chamadas; o `conferir-segredos`, o `contar-debitos` e o `conferir-debitos-repetidos` (treze pares
da VIP Class saíram de `$conhecidos`, abaixo do limiar com as marcas novas), saída 0.

**Os mutantes**, um por vez, a reserva num diretório novo e vazio, devolvidos pelo sha1, os de servidor nos dois bancos, todos
vermelhos: S1 a rota antiga viva (o invokable `VipClassAdmin` de volta), S2 o link público morre junto, S3 a pergunta sem a
correta aceita, S4 duas corretas aceitas, S5 a pergunta gravada sem a transação (a segunda resposta que falha deixava a pergunta),
S6 a pergunta gravada sem as respostas, S7 "Outros certificados" com o curso que está nos cartões, S8 com a conclusão de outra
pessoa, S9 com a mais recente no lugar da primeira, S10 o link público sem o `nosniff`, S11 o item do menu do ERP de volta; F1 o
corpo sem as respostas, F2 sem conferir a correta, F3 a marca que não acompanha a linha tirada, F4 a seção sempre, F5 a linha sem
a data, F6 a administração com link no ERP. **A primeira escrita do S5 era inválida** (a função devolvida, e não chamada: vermelho
por `TypeError`); refeita com a função chamada sem a transação, vermelha pelo caso certo.

**O que não foi provado:** a trava da pergunta nova -- a linha recém-criada já nasce presa pelo `INSERT` da própria transação, e
nenhum caso distingue a trava explícita; o navegador (é do coordenador). **A 95 é ímpar:** só os testes do que ela toca; o ciclo
inteiro fica para a 96. Nada da produção mudou; na `franquiabv_dev`, só SELECT de contagem. Débitos: o
D714, o D719 e o D722 CORRIGIDOS, o D715 PREPARADO, o D717 e o D721 DECIDIDOS; de pé 201 para 195 (196 depois da correção,
abaixo).

**A verificação em três lentes confirmou 6 achados (dois médios), tratados na correção (06/11/2026):**
- **O item do menu do ERP (médio).** A opção (a) da 313 diz que "o item do menu do ERP leva à tela nova"; a fatia o tirou do
  `layout.phtml` (como a 76e) e só escreveu o desvio no desenho. Escolher entre tirar o item, apontá-lo para a página inicial
  da tela nova ou fazer um endereço que abra a VIP Class dentro do `/novo` é do dono: **a pergunta foi ao lote** ("VIP Class: o
  item do menu do ERP que devia 'levar à tela nova' saiu do menu", D722; recomendei a (c), o endereço que abre a tela). Até a
  resposta fica como está, o que não perde nada; o D722 ganhou a marca PARCIAL, com o que resta escrito.
- **Os dois casos da fatia 82 que tinham saído do `CursoApiTest` (médio).** O `testMarcarACorretaDesmarcaAOutra` e o
  `testAPerguntaNuncaFicaSemACorreta` (a troca da correta, o 409 de perdê-la, a resposta de outra pergunta com 404 e a marca
  booleana) saíram sem motivo, junto com os da pergunta nova, e o texto acima dizia que tudo continuava provado pela API.
  Voltaram como estavam no HEAD; os mutantes C1 (o `respostaDa` sem o recorte da pergunta, pelo `find`) e C2 (a marca não
  booleana aceita), que ficavam verdes sem eles, ficaram vermelhos nos dois bancos.
- **A nota nos "Outros certificados" (baixo).** A letra da 308 (a) é "só o nome do curso, a data da conclusão e o botão do
  certificado": a API passou a mandar `concluidoEm` (só a data) no lugar da `conclusao` com a nota, e a linha diz só
  "concluído em dd/mm/aaaa". Mutantes C4 (a nota de volta na API) e FE (de volta na linha), vermelhos.
- **O teto de dez respostas (baixo).** Sem base no desenho nem na tela antiga, e incoerente com o `/respostas` sem teto: saiu
  dos dois lados (a frase agora é "pelo menos uma"); caso novo das doze respostas no `CursoApiTest`, no
  `RegraDoCursoVipClassTest`, no `formularioDoCurso.teste.ts` e no `CursosDaVipClassLigado.teste.tsx` (onze linhas e o botão
  ainda lá). Mutantes C3 (na regra), FF (na regra da tela) e FD (o botão escondido em dez), vermelhos.
- **O comentário do `criarPergunta` em `api/cursos.ts`** dizia que a pergunta nasce sem respostas (reescrito), e a regra
  `.vipclass-montagem` do `App.css` ficou sem uso (saiu).
- **O caso do front "sem a correta marcada"** prometia um envio que não fazia: agora marca a única, inclui e confere o POST com
  uma resposta só; e entraram o foco depois de incluir e de tirar uma linha e a recusa `respostas` do servidor (duas corretas)
  chegando ao grupo de rádio (o foco no primeiro rádio, `aria-invalid` nos dois). Mutantes FA (a recusa da marca no texto da
  pergunta), FB (sem o foco ao tirar) e FC (sem o foco ao incluir), vermelhos.

**O que rodou na correção:** o `CursoApiTest`, o `ArquivosDoCursoApiTest`, o `MeusCursosApiTest` e o
`RegraDoCursoVipClassTest`, 147 casos em cada banco, saída 0; os mutantes de servidor (C1 a C4, e o C3 também pela API), um por
vez, nos dois bancos, devolvidos pelo sha1; o front inteiro 4.514 de 4.514, os arquivos tocados com `AMPLIFICAR_EFEITOS_MS=50`
(179 de 179), tsc, lint e build; o `contar-debitos.php` e o `conferir-debitos-repetidos.php` (seis pares do D722 saíram de
`$conhecidos`, abaixo do limiar com a marca nova), saída 0. Débitos: o D722 PARCIAL (o item do menu, no lote); de pé 196.
Uma pergunta nova ao dono (a do item do menu).

## 06/11/2026 -- SAIDAS-RESPOSTAS (a fatia 96 da fila das telas que faltam)

O código das respostas 306 e 307 do dono, de Contas a Pagar (gravadas na página do lote em 05/11/2026).

**307 (b): o extrato mostra quem estornou só a quem pode escrever em Contas a Pagar** (o D711 CORRIGIDO). Para quem usa: o
gerente que lança e paga vê, ao lado do pagamento estornado, "estornado em dd/mm/aaaa por <nome>"; quem só lê Contas a Pagar
continua vendo só o dia e o motivo, e o servidor nem manda o nome a ele. O nome é o de quem ENTROU: quando um administrador da
rede assume um funcionário da unidade e estorna, aparece o administrador. Os estornos antigos, sem autor gravado, ficam só com
o dia. No servidor: `SaidaParcelaPagamentoRepository::autoresDosEstornos` (o `usuarios.nome` de `estornado_por`), lido pelo
`SaidasController::motivosDe` só com o `escrever` de `financeiro/saidas` na sessão; o `SaidaJson::parcela` põe `estornadoPor`
só quando o recebe (nulo no ativo e no estorno sem autor). Na tela, `notaDoEstorno` (`regraDasContas.ts`), sem "por" sem nome.

**306 (b): a tela antiga de Contas a Pagar fica até o corte de cada unidade** (o D710 PREPARADO). Nada no código: a lista do que
sai (o `SaidasController` do ERP, as views, o `mod-financeiro.js`, os itens "Nova" e "Gerenciar" do menu) e do que fica (a
`/adm/api/saidas`, o serviço, a despesa da sangria) entrou no item 6 do "Depois" de `docs/procedimento-de-corte.md`, ao lado da
VIP Class do aluno.

**Provado.** Os testes de Contas a Pagar e vizinhos (`ContasAPagar*`, `Saida*`, `D375`, a barreira de origem, as colunas fora
do mapeamento, a sessão da API), 601 casos em cada banco, saída 0; o caso novo `ContasAPagarQuemEstornouTest` (quem só lê não
recebe a chave nem o nome em parte nenhuma do corpo; quem escreve recebe o nome; o colaborador que assumiu o gerente aparece, e
a mesma sessão com o assumido só lendo não recebe a chave; o estorno sem autor sai com a chave nula; o repositório pelo nome). O
front: os arquivos tocados também com `AMPLIFICAR_EFEITOS_MS=50` (102 de 102), tsc, lint e build. **O ciclo inteiro** (a 96 é
par, e cobre a 85 e a 95): caracterização 6.541 em cada banco, saída 0 (13 pulados no MySQL, 14 no PostgreSQL); contrato 74 e
74; os ensaios do MySQL e do PostgreSQL, saída 0; o front 4.519 de 4.519; os portões, 73 e uma falha -- o
`conferir-cobertura-por-par.php`, que pedia o par novo declarado (de 453 para 454, o `ISFinanceiro` de 11 para 12, o par citado
num caso): consertado, e a etapa repetida sozinha deu 74, "Nenhuma falha inesperada"; o medidor dos relatórios no PostgreSQL
(`p48-medir-relatorios-pg.sh`), saída 0. Nada vermelho veio da 85 nem da 95. **Os mutantes**, um por vez, devolvidos pelo
sha1: o nome para quem só lê (S1), a chave nula para quem só lê (S2), o nome do assumido no lugar de quem entrou (S3), quem
escreve sem o nome (S4), o estorno sem autor sem o `?? null` (S5, o aviso do PHPUnit), a tela que diz "por" sem nome (F1), a
nota sem o nome (F2) e o extrato sem passar o nome à regra (F3): todos vermelhos; o filtro do nome em branco no repositório
(S6) sobreviveu na primeira rodada e ganhou caso na verificação (abaixo). Débitos: o D711 CORRIGIDO, o D710 PREPARADO; de
pé 196 para 194. O `conferir-debitos-repetidos.php` ganhou o par D710|D715 (as marcas iguais de "fica até o corte") e perdeu
cinco que deixaram de aparecer. Nada da produção mudou; na franquiabv_dev, nada; o navegador fica com o coordenador.

**A verificação em três lentes** confirmou cinco achados (dois deles o mesmo), todos de prova ou de documento, e não de
comportamento; consertados com caso: (1) o contrato da API em `docs/f2-fatia-saidas.md` (o `GET /adm/api/saidas/:id`) ainda
dizia que quem estornou não é publicado -- reescrito com a 307 (b); (2) a guarda das colunas no `autoresDosEstornos` não tinha
caso (arrancada, tudo verde; na base do teste do BVIP sem as colunas, quem escreve levaria um 500): o
`ContasAPagarSemAsColunasTest` pede os autores com o `estornado_por` escrito à mão e espera nenhum; (3) o S6 não era
equivalente: o `ContasAPagarQuemEstornouTest::testONomeEmBrancoSaiComoNulo` apaga o nome no cadastro ('' e só espaços) e espera a
chave nula, no repositório e na despesa; (4) a caracterização do ciclo tinha rodado antes da versão final do caso novo: rodou de
novo inteira, com os arquivos finais, 6.543 casos em cada banco, saída 0 (13 pulados no MySQL, 14 no PostgreSQL). Os mutantes
da verificação, um por vez, devolvidos pelo sha1: a guarda das colunas arrancada (G1), o filtro do nome em branco arrancado
(S6) e o filtro sem o `trim` (S7), vermelhos. O `conferir-tudo.sh`, 74 ok, saída 0; contar-debitos (de pé 194),
conferir-debitos-repetidos, conferir-segredos e conferir-cobertura-por-par, saída 0.

## 06/11/2026 -- ASSISTENTES-API-DE-USO (a fatia 86 da fila das telas que faltam)

A F1b e a F2 de `docs/f3-telas-restantes/tela-assistentes.md` (a resposta 144 (a) do dono: dos Assistentes, só a tela de uso),
**sem a conversa do Zapier**: em 03/11/2026 o dono decidiu que o assistente deixa o Zapier, e as fatias 87, 88 e 89 esperam a
decisão dele. A outra resposta que a fila citava, a **28 (b)** (o código do Zapier com outro tamanho, remontado no de hoje),
ficou sem efeito aqui: ela já vale na administração antiga desde 07/10/2026 (o D341), e na API só tocaria a conversa; volta com
a decisão sobre o assistente novo.

**Para quem usa, na tela antiga: o item do menu e a tela pedem o mesmo privilégio** (o D729, o Dβ do desenho, CORRIGIDO). A lista
"Assistentes VIP" e o abrir pediam o privilégio da agenda ("Agendas visualizar"; o abrir, o escrever dela), enquanto o menu pede
"Assistente IA - Usuários". Agora os dois pedem o `ler` de "Assistente IA - Usuários": na cópia (o desenho, Q12), as 60 pessoas
que viam o cartão e levavam 404 passam a abrir, e as 51 que abriam pela URL sem ter o menu deixam de abrir. O recorte de qual
assistente cada um vê (o grupo, a unidade, a situação; o D340) não mudou. A P3 do desenho não teve resposta própria: seguiu a
recomendação A, como a tabela "O que ficou para depois" do `telas-consolidado.md` manda; a volta é uma linha. Como ela nunca
tinha chegado ao dono, foi ao lote como "feito; quer voltar?" ("Assistentes: quem pode usar um assistente", o D729).

**A API nova, para a tela que vier** (a 87): `GET /adm/api/assistentes` (os que eu posso usar) e `GET /adm/api/assistentes/:id`,
com `assistentes/usuarios/ler`, o recorte da tela antiga chamado (o `listagemIndexUsuarios` e o `disponivelPara`, com a unidade e
o grupo da sessão), o mesmo 404 ("Este assistente não está disponível para você.") para o inexistente, o inativo, o de outro
grupo, o de outra unidade e o id que não serve, e o 401 antes do 403. Cada assistente sai com o id, o nome, a descrição (com as
quebras de linha) e o endereço da capa -- **e nada do Zapier**: nem o HTML gravado, nem o id do chatbot. O `conversa` do desenho
fica de fora até a decisão do dono sobre o assistente novo. A família entrou na `rotas-publicas.php` (sem ela, o pedido sem
sessão recebia a página de login com 200, o D539). Nada no front: a tela é a 87, reservada.

**Provado.** O `AssistenteApiTest`, 12 casos, e os dois novos do `AssistentesPelosControllersTest` (que agora monta o controller
com o módulo e a funcionalidade do construtor de verdade), com os vizinhos dos assistentes: 145 casos em cada banco, saída 0. Os
casos 223 a 234 do `ensaiar-recusas-adm-api.php` (9 recusas e 3 controles) e as 2 rotas no `ensaiar-permissao-adm-api.php`, nos
dois bancos. **O ciclo inteiro** (a 86 é par): a caracterização, 6.557 casos em cada banco, saída 0 (13 pulados no MySQL, 14 no
PostgreSQL); o contrato 74 e 74; os ensaios do MySQL e do PostgreSQL, saída 0 (as recusas 284 de 284); o front 4.519 de 4.519;
os portões, 73 e uma falha -- o `conferir-protecao-do-webroot.php`, que lê o caminho da capa no ponto da chamada e não o achou
numa constante: o caminho passou a literal (`'assistentes/' . $capa`), os testes dos assistentes rodaram de novo nos dois bancos
(145 e 145) e o `conferir-tudo.sh` deu 74 ok, saída 0; o medidor dos relatórios no PostgreSQL (`p48-medir-relatorios-pg.sh`),
saída 0. **Os mutantes**, um por vez, devolvidos pelo sha1, nos dois bancos: o 404 virar 403, o ver sem o grupo, sem a unidade e
pelo `find`, a lista sem o grupo e sem a unidade, a funcionalidade da agenda no construtor, o ver sem o privilégio, a lista sem o
401, o grupo lido da query, a conversa saindo no ver, o construtor da tela antiga de volta à agenda e o abrir pedindo o escrever:
todos vermelhos; o id sem o teto do `int4`, vermelho só no PostgreSQL (no MySQL o id grande não acha nada e dá o mesmo 404). Por
HTTP, a família tirada da `rotas-publicas.php`: o portão das rotas sem login saiu 1, e os casos 223 e 224 receberam 200. Os
portões que a fatia toca ganharam o que pediam: as duas rotas no bloco congelado das rotas sem login, três entradas no recorte por
unidade, o `ISCadastro` de 148 para 150 chamadas na cobertura por par (os mesmos dois pares da tela antiga), e os pares D340|D729
e D291|D729 no portão de repetidos. Débitos: o D729 nasce e fecha; de pé 194. Nada da produção mudou; na franquiabv_dev, nada; o
navegador fica com o coordenador.

**Na verificação (06/11/2026)**, quatro achados, todos baixos, consertados: a P3 foi ao lote como "feito; quer voltar?"; o D340
ganhou a nota datada que aponta para o D729 (ele ainda dizia que o abrir pedia a agenda), e a linha "ver os cartões" do desenho
(1.3) e o item 3 da seção 5 ganharam a marca de feito; a 28 (b) ficou escrita como sem efeito nesta fatia (no roadmap, na F2 do
desenho e no `AssistenteJson`); e o caso novo `testAQueryNaoMudaORecorteNoVer` prova que a query não muda o recorte também no
ver (o caso da query só pedia a lista). Os dois mutantes novos -- a unidade e o grupo lidos da query só no `verAction` --,
vermelhos nos dois bancos só nesse caso. Os testes dos assistentes e vizinhos (`Assistente|RecorteNosServicos`): 155 de 155 em
cada banco, saída 0; os portões que a fatia toca, o `contar-debitos` (de pé 194), o `conferir-debitos-repetidos`, o
`conferir-referencias-docs` e o `conferir-segredos`, saída 0.

## 06/11/2026 -- EXPORTAR-PACOTES (a fatia 90 da fila das telas que faltam)

**O que mudou para quem usa.** O botão "Exportar" da lista de pacotes do ERP estava escondido desde sempre e apontava para
uma ação que nunca existiu (o D105). Pela resposta 120 do dono ("crie-as para que exista um destino"), ele aparece e baixa
a lista em planilha do Excel (XLSX): Nome, Valor e Status, as colunas da lista, com o valor em número (a soma da coluna
funciona) e o nome do arquivo dizendo o estado, o dia e a unidade (`pacotes-ativos-2026-11-06-unidade-3.xlsx`). A tela
nova de Pacotes ganhou o mesmo arquivo, no pé da lista: "baixar a planilha (XLSX)", o botão dos relatórios, também para
quem só lê. As duas portas -- `/mod-configuracoes/pacotes/exportar` e `GET /adm/api/pacotes/exportacao` -- dão a mesma
planilha (`PlanilhaDosPacotes`), com o mesmo recorte e a mesma busca da lista: a unidade da sessão (só o administrador
escolhe outra, e o zero é a rede), o estado e o nome, sem a página. A guarda é a da lista, o `ler` dos pacotes. A entrada
`pacotes|exportar` saiu de `MORTOS_CONHECIDOS`; resta o `usuarios|contrato`, da fatia 91. A pergunta das colunas (só as da
lista, ou também a validade e as sessões) foi ao lote, com a recomendação de ficar como está.

**Provado.** O `PlanilhaDosPacotesTest` (8 casos) e o `TelasDosPacotesDoErpTest`, com os vizinhos dos pacotes (127 casos),
saída 0 em cada banco. Seis casos novos no `PacotesLigado.teste.tsx` (o botão para quem lê, o endereço da busca que produziu
a lista, a recusa, o 200 em HTML, a lista vazia sem botão, o desligado enquanto relê), também com
`AMPLIFICAR_EFEITOS_MS=50`. Os casos 235 a 237 do `ensaiar-recusas-adm-api.php` (o 401, o controle de quem só lê e o 422
do estado) e a rota no `ensaiar-permissao-adm-api.php` (o 403), nos dois bancos. **O ciclo inteiro** (a 90 é par): a
caracterização 6.566 casos em cada banco, saída 0 (13 pulados no MySQL, 14 no PostgreSQL); o contrato 74 e 74; os ensaios
do MySQL e do PostgreSQL, saída 0 (as recusas 287 de 287, a permissão 219 de 219); os portões 74 ok; o front 4.525 de
4.525; e o `p48-medir-relatorios-pg.sh`, saída 0. **Os mutantes**, um por vez, devolvidos pelo sha1, nos dois bancos, todos
vermelhos: a planilha sem o recorte da unidade, sem o filtro do nome, sem o do estado, sem o privilégio; o botão de volta ao
`display-none`; a coluna do valor lida do preço de compra; o nome gravado sem o tipo texto (vira fórmula); a ação antiga sem
o `ler`, com a unidade lida da query e com o estado aberto; e o link antigo com a busca crua da paginação. Nos portões: a
ação antiga sumida deixa o `conferir-links-mortos.php` com um morto novo (saída 1), e o `filtrosDaLista` sem o
`usuarioAdmin` deixa o `conferir-recorte-por-unidade.php` vermelho. No front: o endereço sem o nome, o botão escondido, o
desligado ignorando a releitura e o arquivo sem o `salvarArquivo` -- vermelhos; a lista lida do pedido (e não da lista que
voltou) ficou verde, e é equivalente: os dois só diferem enquanto a lista relê, e aí o botão está desligado. Os portões que
a fatia toca ganharam o que pediam: a rota no bloco congelado das rotas sem login, o `filtrosDaLista` no recorte por
unidade, o `ISConfiguracao` de 220 para 222 chamadas na cobertura por par (o mesmo par da lista), o inventário regerado (de
326 para 327 ações). O piso do `conferir-links-mortos.php` não mudou (343 chamadas, piso 320). Débitos: o D105 segue PARCIAL;
de pé 194. Nada da produção mudou; na franquiabv_dev, nada; o navegador fica com o coordenador.

**A verificação (três lentes) confirmou seis achados, todos de prova, e nenhum de comportamento; consertados com caso.** (1)
**O link da rede pela tela antiga** não tinha caso: o administrador que lista "Todas" manda a unidade vazia, e tanto recusá-la
(o 404, o link morto de volta) quanto lê-la como a unidade dele passavam verdes; o `testOLinkDaRedeLevaAPlanilhaDaRede` segue o
`href` que a lista desenha e confere a planilha da rede. (2) **A ordem 403 antes do 422** só valia por leitura: com
`estado=todos`, o 401 e o 403 em processo, e a linha `/adm/api/pacotes/exportacao?estado=todos` no ensaio de permissão. (3)
**Os cabeçalhos da porta antiga** (`no-store`, `nosniff`, o tamanho) ganharam asserção. (4 e 6) **O controle 236 aceitava
qualquer 200 que não fosse JSON** -- o fatal do PHP, que sai com 200 no local, passava: o `pedir()` do ensaio de recusas
devolve agora o corpo cru e os cabeçalhos ao `conferir`, e o `$aPlanilhaDosPacotes` abre o arquivo (o tipo, o anexo com o
nome, o `nosniff`, o `no-store`, o "PK" e a planilha pelo PhpSpreadsheet); o caso 238 pede a porta antiga por HTTP, que
nenhum ensaio pedia. (5) **Um segundo caso antigo do `PacotesLigado` cai com `AMPLIFICAR_EFEITOS_MS=50`**, além do
"desativar com o formulário do item aberto": o "o aviso da gravação não passa ao próximo pacote aberto" -- 2 de 69 com a
fatia e os mesmos 2 de 63 numa cópia do `git archive HEAD frontend`, em duas rodadas; registrado no D471, que é a forma dele.
**Os mutantes novos**, um por vez, devolvidos pelo sha1, todos vermelhos: a unidade vazia recusada e a unidade vazia lida
como a do administrador (o caso do link da rede, nos dois bancos); a busca lida antes do `ler` na API (o caso das recusas,
nos dois bancos, e por HTTP o ensaio de permissão: 219 de 220, a linha nova com 422); o `Cache-Control` e o `nosniff`
arrancados da porta antiga (o caso dela, nos dois bancos; o `nosniff`, também o 238 por HTTP); e a planilha trocada por um
HTML de fatal com 200 (o 236 e o 238 por HTTP). **Os números depois do conserto:** os casos dos pacotes (`--filter Pacote`)
171 de 171 em cada banco, saída 0; as recusas 288 de 288 e a permissão 220 de 220, no MySQL e no PostgreSQL; os portões 74
ok (`conferir-tudo.sh`, saída 0); `contar-debitos` (de pé 194), `conferir-debitos-repetidos`, `conferir-referencias-docs` e
`conferir-segredos`, saída 0. O front não mudou na verificação.

## 06/11/2026 -- ENTRADA-NO-NOVO (a fatia 94 da fila das telas que faltam)

**O pedido do dono**, ao abrir o `/novo` no simulador do iPhone: *"A versão nova ainda não possui uma tela de login?"* -- e
*"Sim, preveja como uma fatia nova"*. O desenho é `docs/f3-telas-restantes/tela-entrada.md`; a seção 7 diz o que foi feito e
o que ele dizia de errado (o `/novo` não tem endereço por tela, e não havia "destino da URL" a preservar).

**O que muda para quem usa.** Quem abre a tela nova sem estar logado vê o formulário de entrada dela, e não mais o aviso "A
sessão do ERP caiu" com um link para a página antiga. É a MESMA sessão do ERP: o formulário vai à porta de sempre, e a frase
da recusa é a do servidor. No celular, o gerenciador de senhas oferece a senha guardada, o teclado não põe maiúscula no login,
o "entrar" do teclado envia, e o campo não faz o Safari ampliar a página. Quem tem a entrada automática do ERP (o cookie de 12
horas) entra sozinho, sem ver o formulário. **Se a sessão cai no meio do trabalho**, o formulário abre por cima da tela, que
fica intacta embaixo: entrando a mesma pessoa, o que ela fazia é refeito e a tela continua de onde estava, com o que estava
digitado; entrando outra pessoa, a tela é refeita para ela. **A barra do topo ganhou o "sair"**, o mesmo do ERP, que volta à
entrada da tela nova. "Esqueci minha senha" leva às páginas antigas. P1 a P3 seguiram a recomendação, e foram ao lote como
"feito; quer outra?".

**O que entrou.** No servidor, `POST /adm/api/sessao/retomar` (`SessaoController::retomarAction`), com a regra do cookie
extraída para um dono só (`Permissions\EntradaPeloCookie`, que o `entrarCookies` do ERP também chama): o mesmo 401, byte a
byte, para o token ausente, velho (D223), fora do prazo (D77), de inativo, de unidade desativada e que não é texto; nada
refeito com sessão existente (a personificação continua, D315); só POST, com a barreira de origem (D290). O cookie que não é
texto (`token_web[]=...`) entrava pela página antiga, e agora não entra. No front, a peça `EntradaNoNovo`, o invólucro, a
regra pura, `api/entrada.ts`, o **porteiro** do 401 no cliente da API (a sessão que cai no meio do trabalho, sem tocar tela
por tela) e o "sair" no `ConsoleLigado`; a prévia `EntradaNoNovo` e a célula `ComASaida` da barra. O limite de tentativas do
login, que não tinha débito, é o **D730** (registrado, sem conserto).

**O provado.** Os casos do servidor (`SessaoRetomadaPeloCookieTest`, 9, e os vizinhos da sessão, do cookie e da
personificação) 172 de 172 em cada banco; o ensaio da sessão pelo nginx com o caso 25 (o retomar sem nada, com o token velho,
de outra origem, por GET e com o token de agora), 25 de 25 no MySQL e no PostgreSQL. O front: os casos novos (o formulário,
a carga, a queda no meio do trabalho com a mesma pessoa e com outra, o "sair", o porteiro, a regra) também com
`AMPLIFICAR_EFEITOS_MS=50`; tsc, lint e build saída 0. Os mutantes, um por vez, devolvidos pelo sha1: F1 a F17 vermelhos,
também com o amplificador (o F11, o foco por efeito de layout, só depois de o caso esperar a grade chegar; o F9, o login de
outra pessoa tratado como o da mesma, só depois de o caso contar que o pedido não é refeito); S1 a S8 vermelhos nos dois
bancos; G1 a G3, o portão do registro de acesso arrancando a regra de cada uma das três pontas. Os portões: o
`conferir-registro-de-acesso.php` passou a conferir a regra e as duas portas (pelos tokens, sem ler comentário); o
`conferir-cobertura-por-par.php` desceu de 454 para 453 pares (o `selecionarPorTokenWeb` saiu de controller para a regra;
conferido pelo sha1); o inventário regenerado. **A 94 é par: o ciclo inteiro** -- a caracterização 6.576 em cada banco
(13 pulados no MySQL, 14 no PostgreSQL), o contrato 74 e 74, os ensaios saída 0 nos dois bancos, o front 4.567 de 4.567, os
portões 74 ok (depois do conserto dos três acima, rodados de novo), e o medidor dos relatórios no PostgreSQL, saída 0.
**Não provado aqui:** o navegador e o simulador do iPhone (o coordenador). Nada da produção mudou; na `franquiabv_dev`, nada
além de leitura. De pé 195 (o D730).

**A verificação (06/11/2026), e o que ela consertou.** Três lentes confirmaram dez achados. **O defeito de verdade:** com a
janela modal da agenda aberta (a remarcação, o fechamento -- `showModal()`), a sessão que caía deixava o formulário de entrada
inerte e por baixo dela (o navegador deixa inerte tudo o que está fora do modal aberto), e a tela só saía recarregando. O
formulário por cima do Console virou um `<dialog>` aberto com `showModal()` (`JanelaDaEntrada`, em `ConsoleLigado.tsx`): o modal
de cima, com o Esc recusado e reabrindo se o navegador o fechar. **A chave que decide se o pedido é refeito** (`chaveDaPessoa`)
leva também os papéis, os privilégios e as guardas do menu: a mesma pessoa noutra unidade, ou sem um privilégio, não tem o pedido
refeito e ganha o Console refeito (antes, a tela que o menu passava a esconder ficava aberta). **Provas que faltavam, com caso:** o
"sair" pelo `opaqueredirect` de status 0 -- o dublê da API responde agora o redirecionamento como o navegador, pelo `redirect` do
pedido (`redireciona()`), e o "voltar a ser eu", irmão do "sair", também ganhou o caso --; o foco que volta ao elemento de antes da
queda; os dois 401 juntos esperando a mesma queda; o Console desmontado com a janela aberta soltando o pedido com o 401; e o "mesmo
401, byte a byte" também para o cookie ausente e o fora do prazo. **Documento:** o desenho deixou de prometer "o destino" na lista
do navegador e no "Em seis linhas", e diz por que a rota não foi ao ensaio de permissão (ela não exige privilégio: não há recusa a
medir; o `conferir-acl-da-api.php` a conta entre as "só sessão", e o caso 25 prova o 401 pelo nginx). **Os números:** o front 4.581
de 4.581 (eram 4.567), os tocados também com `AMPLIFICAR_EFEITOS_MS=50`; tsc, lint e build saída 0; os casos do servidor
(`SessaoRetomadaPeloCookieTest` e os vizinhos da sessão, do cookie e da personificação) 428 de 428 em cada banco. **Os mutantes**,
um por vez, devolvidos pelo sha1: N1 a N11 e N13 (o `opaqueredirect`, o `redirect: 'manual'`, a volta do foco, o foco guardado, a
mesma queda, a limpeza, a chave só pelo id, sem os privilégios, sem as guardas, o `show()` no lugar do `showModal()`, o Esc aceito,
a janela que não reabre) vermelhos com e sem o amplificador, e o N14 (o `opaqueredirect` do "voltar a ser eu"); o N12 (o foco
posto no campo depois do `showModal()`) sobrevive no happy-dom, que foca o campo da janela fechada -- é do navegador; P1 e P2 (o
ausente e o fora do prazo com outra frase) vermelhos nos dois bancos, e verdes com o laço de antes. Fora da fatia: dois casos das
Pesquisas caem com o amplificador também com o dublê do `HEAD` -- relidos no D471. De pé 195.

## 06/11/2026 -- No navegador, a fatia 94 contra o banco de contrato (o coordenador)

**O que o navegador mostrou**, como `gerente.teste` no banco de contrato, no Chrome do painel (largura de computador e de
celular, 375 px):
- **Na carga sem sessão:** um `retomar` só (401) e o formulário, sem rolagem de lado no celular.
- **A senha errada:** "Dados inválidos.", do servidor; a senha limpa, o login mantido e o foco no campo da senha.
- **A senha certa:** o Console na tela inicial (o Painel), com o "sair" na barra.
- **A sessão apagada no servidor com o cookie do ERP valendo:** o "atualizar" voltou 401, o `retomar` entrou sozinho (200) e o
  pedido foi refeito, sem formulário.
- **Sem o cookie também** (o `token_web` anulado no banco de contrato):
  - o formulário abriu num `<dialog>` modal de verdade (`:modal`), com o véu, o Console inerte embaixo e o foco no Login (o N12
    que o happy-dom não provava);
  - o Esc foi recusado;
  - entrando a mesma pessoa, o painel foi refeito e o foco voltou ao "atualizar".
- **O "sair":** "Você saiu do sistema.", e a página recarregada não entra sozinha (o `retomar` volta 401: o cookie foi apagado).

**O defeito achado e consertado:** depois do "sair", a aba seguia com o título da última tela ("Painel da unidade ·
Barbearia VIP") com o formulário na tela. O `ConsoleLigado` passa a pôr "Entrar · Barbearia VIP" enquanto a entrada está de
pé; entrando, o Console põe o da tela dele.
- **O caso:** no "sair" de `ConsoleLigadoEntrada.teste.tsx`, esperado por `waitFor`, porque o título vem de um efeito que o
  `findBy` não espera. Com o amplificador, a primeira versão do caso caiu por isso.
- **O mutante sem o título:** vermelho com e sem `AMPLIFICAR_EFEITOS_MS=50`.
- **As provas:** os dois arquivos do `ConsoleLigado` 52 de 52 com e sem o amplificador; tsc e lint saída 0. Conferido na tela
  depois do conserto.

**O laço do começo, que não é defeito:** antes desta conferência, a aba do painel tinha milhares de pares `sessao`/`retomar`
seguidos. Eram as recargas do Vite a cada mutante que a fatia aplicava com a aba aberta no formulário. Na carga limpa, e depois
de 5 s parada, foi um `retomar` só.

**O que não foi visto:** o simulador do iPhone (o gerenciador de senhas oferecendo salvar e o teclado sobre o botão).

**A volta:** o `local.php` de volta ao `franquiabv_dev`, conferido pelo sha1; os caches limpos; o banco de contrato remontado
(o `token_web` anulado voltou com ele); nenhuma sessão de teste ficou. Nada da produção mudou.

## 06/11/2026 -- PACOTES-PLANILHA-COLUNAS (a fatia 97 da fila das telas que faltam)

*A resposta 316 (b) do dono, gravada na página do lote em 07/11/2026 01:15 UTC, sem observação: "Acrescentar a validade e o
total de sessões: duas colunas a mais, uma linha por pacote". Rodada com a ordem "perguntas respondidas, continue".*

**O que muda para quem usa:** o "Exportar" da lista de pacotes -- na tela antiga e no "baixar a planilha (XLSX)" da tela
nova -- baixa a planilha com duas colunas a mais, depois de Status: **"Validade (dias)"** e **"Total de sessões"**, em número
(a soma e o filtro do Excel funcionam). A validade é a gravada no cadastro: o zero sai zero, e a validade em branco fica em branco (o que o branco quer
dizer -- "sem prazo", como a tela nova mostra, ou um defeito de cadastro, como a venda o trata -- é a pergunta do D731). O
total é a soma das sessões dos serviços que o cadastro do pacote mostra -- os mesmos que a venda entrega ao cliente; o serviço
desligado da lista do pacote não conta, e o serviço inativo que continua no pacote conta (a venda ainda cria as sessões
dele). Uma linha por pacote, os mesmos pacotes de antes (a mesma unidade, o mesmo estado, a mesma busca), o mesmo nome do
arquivo e as mesmas recusas (401, 403, 422).

**Como:** a soma sai na mesma consulta da lista (`ProdutoRepository::listagemIndexPacotes`, uma subconsulta correlata
acrescentada só quando ela é chamada sem a página, o que só a planilha faz): nenhuma consulta por pacote, e a lista
paginada não mudou. A `PlanilhaDosPacotes` escreve as duas colunas como número (a validade vazia fica sem célula), e as duas
portas -- `GET /adm/api/pacotes/exportacao` e `/mod-configuracoes/pacotes/exportar` -- não mudaram. O controle
`$aPlanilhaDosPacotes` do ensaio de recusas passou a conferir, por HTTP, o total em número e a validade que o caso espera
(o 236 grava 30 dias no pacote do contrato e espera o 30 em número; o 238 espera a célula em branco).
O D105 recebeu a marca PARCIAL (esta parte está feita; resta só o contrato, a 91).

**As outras respostas (gravadas em 07/11 UTC, lidas em 06/11), só documento:** a 315 (a) -- os assistentes ficam como estão, só "Assistente IA -
Usuários" -- fechou o D729 como DECIDIDO; a 317 (a) -- a entrada automática na tela nova fica como está -- entrou no D77 (que
segue PARCIAL pelo `ALTER` e pelas rotações, da janela e da produção); a 318 (a) -- o "sair" fica na entrada da tela nova --
entrou no corpo do D222, sem mudar o estado; a 314 (c) e a 319 (b) viram as fatias 98 e 99 (escrito no D722 e no D527). A
seção 7 de `docs/f3-telas-restantes/tela-entrada.md` ganhou as respostas 317 a 319; `docs/f2-fatia-pacotes.md`, a seção
da fatia. O `conferir-debitos-repetidos.php` perdeu os pares D340|D729 e D291|D729, que deixaram de aparecer com o D729
fechado.

**Os testes (a 97 é ímpar: só os do que ela toca):** os casos dos pacotes (`--filter Pacote`) 172 de 172 em cada banco,
saída 0 -- um caso novo, `testAsColunasNovasSaemDeUmaConsultaSo`, e os da 90 com as colunas novas (as células, o tipo
numérico, o cabeçalho, as duas portas, a validade vazia e a zero, o item tirado e o do serviço inativo); o ensaio de
recusas 288 de 288 no MySQL e no PostgreSQL (o banco de contrato remontado antes). A rota não mudou: o ensaio de permissão
não rodou. O front não mudou: nem vitest, nem build. Os portões 74 ok (`conferir-tudo.sh`, saída 0), `contar-debitos` (de
pé 195), `conferir-debitos-repetidos` e `conferir-segredos`, saída 0.

**Os mutantes**, um por vez, devolvidos pelo sha1, vermelhos no MySQL e no PostgreSQL: (M1) as duas colunas trocadas (3
casos); (M2) a soma contando o item tirado (2); (M3) o total como texto (3, e por HTTP os controles 236 e 238 nos dois
bancos: "o total de sessoes nao e numero"); (M4) a porta antiga sem as colunas (2); (M5) o total pacote a pacote, uma
consulta por pacote, com os números certos (1: `testAsColunasNovasSaemDeUmaConsultaSo`, quatro consultas em `pacotes_itens`
em vez de uma); (M6) a validade vazia virando zero (3); (M7) a validade zero virando vazia (3); (M8) o serviço inativo
deixando de contar (2).

**O que não foi visto:** a planilha aberta num Excel de verdade e o botão clicado no navegador (o navegador é do
coordenador). O ciclo inteiro fica para a 98. Nada da produção mudou; o `local.php` de volta ao `franquiabv_dev`, conferido
pelo sha1.

**A verificação em três lentes** confirmou quatro achados baixos, consertados. (1) O texto dizia que a validade em branco
saía "como o cadastro a mostra", e o comentário do código, que o vazio não era o zero; mas a tela nova mostra o vazio como
"sem prazo", e a venda o lê como a data 31/12/1969 -- o pacote sem validade sai vendido já vencido (1 dos 239 pacotes da
cópia, nenhuma venda dele assim ainda). O comentário e os textos passaram a dizer que a planilha não escolhe, e o defeito
da venda virou o D731, com a pergunta ao lote ([vendas] "Pacote sem validade", recomendando pedir a validade antes de
vender). (2) A justificativa da guarda ("a planilha não leva nada que a lista não mostre") ficou falsa com as colunas
novas: no `exportacaoAction`, em `docs/f2-fatia-pacotes.md` e em `docs/convencao-api.md` ela diz agora que a planilha não
leva nada que o `ler` já não abra (o `GET /adm/api/pacotes/:id` entrega a validade e os itens). (3) Os comentários do
código datavam a fatia de 07/11 (o dia UTC da resposta): passaram a 06/11. (4) O controle HTTP da validade passava
sempre pelo ramo vazio, porque o pacote do contrato nasce sem validade: o 236 passou a gravar 30 dias antes do pedido (a
foto do catálogo os devolve) e a esperar o 30 em número, e o 238 espera a célula em branco. Os testes, de novo, em série:
`--filter Pacote` 172 de 172 em cada banco; o ensaio de recusas 288 de 288 no MySQL (o banco de contrato remontado antes) e
no PostgreSQL. Os mutantes novos, por HTTP nos dois bancos, vermelhos: (MV1) a validade escrita como texto (o 236: `a
validade nao e numero nem vazia; veio "30"`; antes do conserto passaria verde, pela leitura: o pacote não tinha validade); (MV2) a validade vazia virando zero (o
238: "esperava a validade null; veio 0"); (EV1) o 236 sem os 30 dias gravados (o controle cai: "esperava a validade 30;
veio null"), a prova de que o ramo do número é exercido. Débitos: o D731 registrado; de pé 195 para 196 (`contar-debitos`, CONTINUAM DE PÉ).

## 06/11/2026 -- ENDERECO-POR-TELA (a fatia 98 da fila das telas que faltam)

*A resposta 314 (c) do dono, gravada na página do lote em 07/11/2026 01:12 UTC, sem observação: "O item volta e abre direto
a VIP Class na tela nova: a tela nova passa a aceitar um endereço que abre uma tela dela, e o item do ERP usa esse
endereço." Rodada com a ordem "perguntas respondidas, continue". O desenho: `docs/f3-telas-restantes/endereco-por-tela.md`.*

**O que muda para quem usa:** a tela nova abre direto numa tela pelo endereço -- `/novo/?tela=...` --, e recarregar a página
fica na tela em que se estava (até aqui voltava sempre à inicial). Quem abre um endereço de uma tela que não está no menu
dele (ou que não existe) cai na tela inicial de sempre, com o aviso "Essa tela não está no seu menu."; quem chega sem sessão
entra (sozinho, pelo cookie do ERP, ou pelo formulário) e cai na tela pedida. No ERP antigo, o item **"VIP Class"** volta ao
menu Configurações e abre a administração da VIP Class na tela nova, para quem tem o acesso a ela -- **mas só onde a tela nova
está publicada**: no teste do BVIP, depois do próximo `publicar` (o `docker-compose.teste.yml` liga); na produção, na
homologação e na cópia local o item continua escondido, para não apontar para uma página que não existe ali. O passo de
ligá-lo na produção está no `docs/procedimento-de-corte.md` ("Depois", o item 7). O "voltar" do navegador continua saindo da
tela nova (o endereço é trocado, e não empilhado): é o próximo passo do desenho.

**Como:** no front, a regra pura em `menu-do-erp.ts` (`PARAMETRO_DA_TELA`, `podeAbrirPeloEndereco` -- o mesmo `podeVer` do
menu, achando também as telas de primeiro nível e as dos subgrupos, sem mudar o `podeAbrirNoMenu` dos atalhos --,
`aberturaPeloEndereco`, que devolve a chave da LISTA e nunca o texto do endereço, e `buscaComATela`); o `ConsoleLigado` lê o
endereço ao montar o Console e o acompanha por `history.replaceState`, sem a tela inicial e sem as telas que o endereço não
abriria de volta, e o "sair" o limpa; o `Console` ganhou o aviso (`telaRecusada`, até a primeira navegação) e o `aoMudarDeTela`
-- ele mesmo não toca no `history`, porque as pranchetas montam vários lado a lado. Nem o nginx nem biblioteca de roteador
mudaram (o comentário do `App.tsx` diz por quê). No servidor, `ISBase\Util\EnderecoDaTelaNova`: o endereço da tela nova é a
chave `telaNova.endereco` do `local.php` (a forma da `busca` do passo 7c) ou, sem ela, a variável `TELA_NOVA_ENDERECO`;
desligado por padrão; o valor é conferido (um `javascript:` ou um `//outro-host` desligam); o item do `layout.phtml` aparece a
quem tem `vipclass/admin/ler`, o `ler` da `/adm/api/cursos`. O `preparo.ts` dos testes devolve o endereço da página à raiz
entre os casos. O D722 fechou CORRIGIDO. **A Carteira de clientes não voltou ao menu do ERP** -- a resposta não a pediu --, e
foi ao lote como pergunta (o D732, recomendando que volte pelo mesmo caminho).

**O provado:** o `EnderecoDaTelaNovaTest` desenha o layout do ERP de verdade, nos dois bancos (desligado, o item não aparece
nem a quem tem o privilégio, também com a chave nula no `local.php` por cima da variável; ligado, aparece a quem tem e não a
quem não tem, com o `href` `/novo/?tela=%2Fisconfiguracao%2FvipClassAdmin%2Findex`; o valor configurado conferido; a chave e o
privilégio contra o `menu-do-erp.ts` e o `CursosController`, e o compose do teste). No front, o `ConsoleLigadoEndereco.teste.tsx`
(15 casos: a tela pedida com sessão, sem o privilégio, cinco chaves que não abrem -- desconhecida, com marcação, de fora, a
de outra aba e vazia --, a agenda de primeiro nível, o aviso que sai na navegação, pelo retomar, pelo formulário, outra
pessoa sem o privilégio, o `replaceState` sem `pushState` nem histórico a mais, o recarregar e o "sair") e o "endereço por
tela" do `menu-do-erp.teste.ts` (7 casos), também com `AMPLIFICAR_EFEITOS_MS=50`. Os mutantes, um por vez e devolvidos pelo
sha1, todos vermelhos: a guarda de privilégio arrancada do endereço (M1), a chave aceitando qualquer texto (M2), a inicial
depois da entrada pelo formulário (M3), o `pushState` no lugar do `replaceState` (M6), o primeiro nível fora da lista (M7), o
"sair" sem limpar (M8), a inicial escrita no endereço (M9), o aviso que não sai (M10), o link externo e o de outra aba na lista
(M11); no servidor, o item sem a guarda (M4), a chave desligada ignorada (M5) e o `if` do layout arrancado (M13), nos dois
bancos, e o valor sem conferência (M12), no MySQL. **A 98 é par: o ciclo inteiro rodou** -- caracterização 6.581 em cada banco,
contrato 74 e 74, os ensaios nos dois bancos, os portões 74 ok, o front 4.603 de 4.603, tsc, lint e build saída 0, e o
medidor dos relatórios no PostgreSQL, saída 0. Com o amplificador, o `Console.teste.tsx` dá 6 vermelhos da comanda e do caixa
que a HEAD dá iguais, pelo nome (relido no D471). Prévia nova: a célula `ColaboradorComATelaRecusada` do `Console`. Não visto
no navegador (é do coordenador), nem o item ligado num ERP de verdade. Nada da produção mudou; na `franquiabv_dev`, nada.
De pé 196 (o D722 fechou, o D732 abriu).

**A verificação em três lentes confirmou 8 achados (sete baixos e um médio; o médio e um baixo são o mesmo defeito), consertados:** pelo porteiro da 94,
OUTRA pessoa que entrava pela janela herdava a `tela` do endereço de quem caiu (abria na tela da outra, ou via o aviso de uma
tela que não pediu) -- agora a `tela` sai do endereço antes de o Console ser refeito, e ela começa na inicial dela, como na
94, com três casos (a mesma pessoa fica na tela; outra que abriria a tela e outra que não abriria vão à inicial, sem aviso);
a exclusão dos devedores no acompanhamento do endereço e a inicial do Painel não tinham caso (um caso cada: o "receber fiado"
do caixa, e o gerente que vai do Painel à agenda e recarrega); o `'telaNova' => null` do procedimento de corte não desligava
por cima da variável do ambiente -- a chave `telaNova` presente passou a decidir em qualquer forma
(`ConfiguracaoLocal::tem`, novo), com o caso das três formas; a célula da prévia punha o aviso sobre os Assistentes, um
estado que o aplicativo não produz -- passou à agenda, a inicial do colaborador, com a grade e o mural respondidos na célula;
o D732 prometia as outras três telas "se o dono quiser" e a pergunta não as citava -- a pergunta ganhou a opção (c), com as
Datas da equipe, a Capacidade × realizado e o Profissional Parceiro; e a pergunta dizia que o item já valia "hoje" no teste,
quando ele só aparece depois da próxima atualização dele. Os testes: o `ConsoleLigadoEndereco.teste.tsx` 20 de 20, e com os
vizinhos do `ConsoleLigado` e o `menu-do-erp.teste.ts` 104 de 104 com `AMPLIFICAR_EFEITOS_MS=50`, duas vezes; o front
inteiro 4.608 de 4.608; tsc, lint e build saída 0; o `EnderecoDaTelaNovaTest` e vizinhos 103 de 103 em cada banco. Os
mutantes novos, vermelhos e devolvidos pelo sha1: a `tela` que fica na troca de pessoa (M14, dois casos), a exclusão dos
devedores arrancada (M15), a inicial trocada por `'/'` (M16), os três também com o amplificador, e a chave `telaNova` nula
caindo no ambiente (M17, nos dois bancos).

## 06/11/2026 -- REDEFINIR-SENHA-NO-NOVO (a fatia 99 da fila das telas que faltam)

*A resposta 319 (b) do dono (gravada na página do lote em 07/11/2026 01:12 UTC): "Fazer a tela nova da redefinição agora: as
duas páginas (pedir o e-mail, trocar a senha) dentro da tela nova". Desenho: a seção 8 de
`docs/f3-telas-restantes/tela-entrada.md`. Última da leva: o ciclo inteiro rodou.*

**O que muda para quem usa:** no formulário de entrada da tela nova, "Esqueci minha senha" deixa de levar à página antiga e
abre, ali mesmo (na entrada e na janela que aparece quando a sessão cai), a página de pedir o link: o e-mail do cadastro, com
o teclado de e-mail do celular, e a frase do servidor como ele a escreve. O link que chega por e-mail abre a troca da senha
**na tela nova onde ela está publicada** (a mesma chave do item "VIP Class" da fatia 98: hoje só o teste do BVIP, a partir
do próximo publicar); nos outros lugares -- a produção, a homologação, a cópia local -- o link continua o antigo, igual. Na
troca, o link é conferido ao abrir e sai da barra do navegador na hora; a senha nova e a repetição pedem "de 6 a 20
caracteres", dito antes de enviar; a recusa aparece linha a linha; trocada, "Sua senha foi redefinida." e a entrada com o
login vazio. O link que não vale (usado, trocado por um mais novo, cortado) tem uma frase só, com "pedir outro link".

**O servidor mudou pouco, e as portas são as mesmas:** o link do e-mail sai de `EnderecoDaTelaNova::linkDaRedefinicao` (a
chave da 98; o relativo `/novo/` ganha a origem da `sistema.url`), e a frase do sucesso das duas portas passou a ir também no
`mensagem` da resposta JSON, com o texto do flash (`AutenticacaoController::fraseDoLinkEnviado` e `::FRASE_DA_SENHA_REDEFINIDA`,
um dono só). A conferência do link usa o status do GET antigo (200 ou o mesmo 404 para todos): sem endpoint novo, sem
oráculo novo. O D347 não muda (a pessoa logada como ela mesma segue; o resto cai), provado pelo POST da tela nova.

**O que foi ao lote:** a frase "O e-mail informado não está cadastrado." diz a quem não entrou se o e-mail tem conta -- é da
porta, e as duas telas a usam. Não mudou: virou o **D733**, com a pergunta `TELAS-RESTANTES [seguranca] (06/11/2026) --
"Esqueci minha senha" diz se o e-mail tem conta (D733)`, recomendando a frase única "Se o e-mail estiver cadastrado, o link
chega em instantes." nas duas telas. O limite de pedidos, como o do login, segue no D730. O D527 ganhou a marca da 319 (b).

**O provado:** no front, `RedefinicaoDaSenha.teste.tsx` (30 casos pelo `ConsoleLigado` inteiro) e
`formularioDaRedefinicao.teste.ts` (8), com os vizinhos do `ConsoleLigado`, também com `AMPLIFICAR_EFEITOS_MS=50` (110 de
110); no servidor, `RedefinicaoDaSenhaNaTelaNovaTest` (7: o link com a chave desligada, byte a byte, e ligada pelo
`local.php` e pelo ambiente; a frase na resposta e no flash; a recusa de hoje presa; o 200 só do token que vale; as portas e o
corpo do front iguais aos daqui) e `SenhaTrocadaTest::testATrocaPelaTelaNovaDizAFraseNaRespostaEOD347Segue`; os casos da fatia
e os vizinhos (`SenhaTrocada`, `EmailsDoSistema`, `EnderecoDaTelaNova`, `LoginSemCaixa`, `SessaoRetomadaPeloCookie`) 59 de 59
em cada banco. Os mutantes, um por vez e devolvidos pelo sha1, todos vermelhos: o token que fica na barra (F1), a recusa em
`innerHTML` (F2), dois envios na troca e no pedido (F3, F4), a frase da recusa e a do sucesso reescritas (F5, F6), o oráculo
do token por corpo do 404 (F7), a senha que fica no campo depois da recusa (F8), a rede caída limpando os campos (F9), a
conferência seguindo o redirecionamento (F10), "Esqueci" de volta à página antiga (F11), o e-mail sem o teclado de e-mail
(F12), o link perguntando a sessão antes (F13), o tamanho não dito (F14); no servidor (MySQL), o link novo com a chave
desligada (S1), o antigo com ela ligada (S2), o caminho da `sistema.url` no link (S3), o pedido e a troca sem a frase na
resposta (S4, S5), o token sem codificar (S6) e o D347 desligado (S7). **O ciclo inteiro:** caracterização 6.590 em cada
banco, contrato 74 e 74, os ensaios HTTP nos dois bancos saída 0, o front 4.646 de 4.646, tsc, lint e build saída 0, os
portões 74 ok (o de segredos caiu na primeira rodada pelo nome `TrocarASenha` como chave do `config.json` das prévias -- um
falso positivo do padrão `senha": "..."`; o componente virou `TrocaDeSenhaPeloLink` e o portão voltou a 0), e o medidor dos
relatórios no PostgreSQL, saída 0. Prévias novas: `PedirOLink` (o vazio, a recusa, o pedido aceito) e `TrocaDeSenhaPeloLink`
(o vazio, a recusa, o link que não vale, o conferindo). **Não visto:** o navegador e o simulador do iPhone (é do
coordenador); nenhum e-mail de verdade saiu (o transporte foi dublado). Nada da produção mudou; na `franquiabv_dev`, nada.
De pé 197 (o D733 abriu).

**A verificação em três lentes (07/11/2026) confirmou 10 achados, todos consertados com caso:**

- **O token ia ao log de acesso do nginx** (médio; as notas da fatia: "o token nunca em log"): o link era
  `/novo/?redefinir=<token>`, e a conferência e a troca iam a `?token=` -- ao menos três linhas de log por link aberto, sem
  débito nem pergunta. Agora o link traz o token no FRAGMENTO (`/novo/#redefinir=<token>`, que o navegador não manda ao
  servidor nem põe no `Referer`), a conferência é um POST à porta da troca com o token e o `conferir` no corpo
  (`{vale: true}` ou o mesmo 404 de sempre: o oráculo do GET, sem oráculo novo), e a troca manda o token só no corpo -- o
  `senhaConfirmarAction` o lê do corpo quando a consulta não o traz (a página antiga manda nos dois, e a consulta vale
  primeiro). Pela tela nova, nenhuma URL leva o token. O link antigo (a chave desligada) segue como sempre foi.
- **O flash com o e-mail de quem pediu o link sobrava para outra pessoa:** pela tela nova a porta gravava o flash, que a tela
  nunca lê; parado na sessão anônima, ele passava pelo login seguinte e aparecia na página do ERP antigo de quem entrasse
  depois no mesmo computador. Os três POST da tela nova levam `X-Tela-Nova: 1`, e com ele a porta não grava o flash.
- **Sair da troca sem trocar caía no formulário** mesmo com a sessão ou o cookie da entrada automática valendo: o "voltar à
  entrada" (e o "pedir outro link" seguido do "voltar") passa pela carga de sempre -- a sessão, a entrada automática uma vez
  (a 317 (a)), o formulário só sem as duas.
- **O "primeiro quadro" não tinha prova:** um caso lê a barra no instante em que a conferência sai.
- **O e-mail mal formado ia ao servidor:** a conferência da página antiga ("Informe um e-mail válido.") voltou, antes do envio.
- **O `EmailsDoSistemaTest` dependia do ambiente** (vermelho num contêiner com a `TELA_NOVA_ENDERECO`): ele desliga a tela
  nova em todo caso, como o teste da fatia.
- **Sem caso:** o escape do link no template do e-mail (agora um `href` cru com `&amp;`, `&quot;` e `&lt;`), a lista
  `http`/`https` da origem (`ftp://`, `javascript://`, `file://` ficam com o link antigo), e o `redirect: 'manual'` dos POST.
- **A pergunta do D733 ao lote não mostrava o par:** ganhou o caso de quem tem o e-mail em dois cadastros (com a frase
  única, lê "o link chega" e nada chega) e a opção (c), a frase única menos para o par; a recomendação segue a (a), com o
  custo escrito.

Os testes depois: o PHP da fatia e os vizinhos 63 de 63 em cada banco; o front 4.658 de 4.658 (`RedefinicaoDaSenha.teste.tsx`
41, `formularioDaRedefinicao.teste.ts` 9), os tocados e os vizinhos do `ConsoleLigado` com `AMPLIFICAR_EFEITOS_MS=50` 122 de
122; lint e build saída 0; os portões 74 ok (o de segredos caiu uma vez por um `'token' => '...'` literal num caso novo, que
virou constante). Os mutantes novos, um por vez e devolvidos pelo sha1, todos vermelhos: no servidor (MySQL) o template sem
escape (P1), sem a lista `http`/`https` (P2), o token de volta à consulta do link (P3), o flash gravado pela tela nova (P4),
o token do corpo ignorado (P5), sem o ramo da conferência (P6), o corpo antes da consulta (P7), o `EmailsDoSistemaTest` sem
desligar a tela nova, rodado com `TELA_NOVA_ENDERECO=/novo/` (P8); no front, o token tirado num efeito passivo (F15), o
"voltar" da troca e o do "pedir outro" sem a carga (F16, F17), os POST sem `redirect: 'manual'` (F18, F19), sem a forma do
e-mail (F20), sem o cabeçalho da tela nova (F21), a conferência e a troca com o token na URL (F22, F23), qualquer 200
valendo (F24) e o token lido da consulta (F25). Nenhuma rota nova (a porta da troca já estava na lista das rotas sem
login); nenhum ensaio HTTP passa por estas portas. Nada da produção mudou; na `franquiabv_dev`, nada. De pé 197.

## 07/11/2026 -- ENTRADA-DESENHO-NOVO (a fatia 100 da fila das telas que faltam)

*O pedido do dono, em 06/11/2026: "como enviar as novas telas para que eu possa manipular, quero ajustar a tela de login".
O desenho que ele ajustou no canvas do Claude está copiado em `docs/f3-telas-restantes/desenho-entrada/` (a8b62dd3); o README
de lá ganhou a seção "O que o código seguiu e o que não", com nove decisões para ele conferir. Par: o ciclo inteiro rodou.*

**O que muda para quem usa:** a entrada da tela nova (`/novo/`) passa a ser a do desenho: a tela inteira escura, com
estrelas e um brilho dourado embaixo, o formulário sem caixa no centro, "Entrar" grande, os campos de vidro de 52 px, um olho
dentro do campo da senha (no lugar do botão "mostrar"), "Esqueci minha senha" à direita acima do botão, e o "entrar" em ouro
com um giro enquanto entra. As páginas de pedir o link e de trocar a senha pelo link (a fatia 99) vão na mesma cena, com um
olho em cada senha; a janela que aparece quando a sessão cai tem o formulário novo por dentro, por cima do Console como antes.
Na barra do topo, as palavras "computador | toque" viram um ícone só (o monitor ou o celular), e o botão do tema vira a lua
ou o sol; clicar leva ao outro, e a escolha continua lembrada como antes. No modo toque os dois ícones têm 44 px. **A imagem
de fundo** que o dono vai escolher entra mudando uma variável CSS (`--entrada-fundo`, no começo da regra `.entrada-cena` do
`frontend/src/App.css`, com o como trocar escrito ali); o lugar da imagem flutuante à esquerda existe e fica vazio. Os
atributos do celular, o envio único, a senha limpa na recusa e o foco não mudaram; o servidor não mudou.

**Decisões que não estão nos quadros** (o README do desenho as lista): a cena é escura também no tema claro (ela leva a
paleta do escuro, pelo mesmo seletor do `index.css`); a janela da sessão caída segue o tema; nada se mexe no fundo; cada olho
da troca mostra só o seu campo (até a 99, um botão mostrava as duas); o `title` do tema sem escolha diz também que o tema
segue o sistema. O contraste, com a paleta do escuro: o texto 16,3:1, a explicação 8,2:1, o "Esqueci" 11,0:1, a recusa
6,0:1, o botão 9,3:1; a borda do campo fica em 1,9:1 (o token de borda da casa).

**O provado:** o ciclo inteiro -- a caracterização 6.594 em cada banco (saída 0; cobre a correção da 99, que mexeu no
`AutenticacaoController` depois do ciclo dela), o contrato 74 e 74, os ensaios HTTP nos dois bancos saída 0, os portões 74
ok, o front 4.660 de 4.660 -- e o medidor dos relatórios no PostgreSQL, saída 0. Os três arquivos de teste tocados também com
`AMPLIFICAR_EFEITOS_MS=50`: os 6 vermelhos do `Console.teste.tsx` são os mesmos do HEAD (rodado o HEAD do arquivo, D471).
`tsc`, lint e build saída 0. Casos novos ou reescritos: o olho com o nome fixo e o `aria-pressed`, cada olho da troca só no
seu campo, a cena com a camada do fundo fora da leitura, o "Esqueci" entre o olho e o "entrar", o giro fora do nome, a
janela da sessão caída sem cena, o modo e o tema em ícones que alternam, dizem o estado no nome e guardam a escolha. Os
mutantes M1 a M16, vermelhos: o olho sem `aria-pressed`, o nome do olho mudando, o modo que não alterna, o tema fora da
raiz, o nome do modo trocado, o `autocapitalize` do login perdido, o tema e o modo que deixam de ser lembrados, a cena sem
a classe, o "Esqueci" depois do "entrar", o olho da repetição mostrando a senha nova, o giro e o fundo lidos pelo leitor,
os ícones do tema e do modo invertidos, a janela da sessão caída dentro da cena. O que é só aparência (as medidas, o vidro,
os 44 px do toque, os 6rem da barra) não tem caso -- o CSS chega vazio no vitest -- e é do coordenador no navegador, contra
os quadros. Prévias atualizadas: `EntradaNoNovo` (com a cena, e a célula nova `Saiu`), `PedirOLink`, `TrocaDeSenhaPeloLink`
e o texto do `Console`. Nenhum débito aberto ou fechado. Nada da produção mudou; na `franquiabv_dev`, nada. De pé 197.

**A verificação em três lentes** confirmou 5 achados, todos baixos, consertados: o "entrar" da janela da sessão caída
estava sem a sombra que os quadros SessaoCaida-* desenham (a regra ficava presa a `.entrada-cena`, e a janela não fica
dentro da cena; agora vale para `.entrada > button.primaria`); o brilho do horizonte tinha uma camada só, e os quadros
desenham duas -- entrou o realce (a segunda elipse, mais clara, em `screen`, a 40%, desfocada a 48 px, na variável nova
`--entrada-realce`, ao lado do `--entrada-brilho`; as duas encolhem juntas no celular); o giro do "enviando…" e do
"gravando…" (as páginas da 99) não tinha caso; e dois documentos descreviam o código de antes: o README do desenho ainda
mandava ao `.console-segmentado` e ao `.console-disco`, apagados nesta fatia, e o desenho da 99
(`docs/f3-telas-restantes/tela-entrada.md`, seção 8) ainda falava no "mostrar" das duas senhas. O que é só CSS ganhou caso
que lê o `App.css` do disco sem os comentários, como no `recuosDosRelatorios.teste.ts` (prova o seletor e o valor dos
quadros, não a geometria). Com o realce, a recusa sob o trecho mais forte do brilho cairia de 4,3:1 para cerca de 3,5:1
(nenhum texto fica ali nos quadros; anotado no README). Os mutantes N1 a N7, vermelhos (a sombra presa à cena, o realce
sem regra, sem `screen` ou sem a variável, o celular encolhendo só uma camada, o "enviando…" e o "gravando…" sem o giro).
Depois da correção: o front 4.661 de 4.661, os dois arquivos tocados também com `AMPLIFICAR_EFEITOS_MS=50`, `tsc`, lint e
build saída 0. Nenhum PHP mudou na correção. De pé 197.

## 07/11/2026 -- No navegador, as fatias 98, 99 e 100 contra o banco de contrato (o coordenador)

**O que o navegador mostrou**, como `gerente.teste` no banco de contrato. A chave `telaNova` estava ligada só no `local.php`
trocado, apontando para o Vite. Foram vistos o computador (1024 px) e o celular (375 px), nos dois temas.
- **A entrada no desenho novo:** a cena com estrelas e o brilho dourado, os campos de vidro, o olho dentro da senha
  (com `aria-pressed`), "Esqueci minha senha" acima do botão e a recusa "Dados inválidos.".
- **A barra do topo:** o ícone do modo (monitor e celular) e o do tema (lua e sol) alternam; o tema vai à raiz
  (`data-tema`), e os dois ficam lembrados depois de sair e entrar.
- **O endereço por tela:** `?tela=%2Fisconfiguracao%2FvipClassAdmin%2Findex` abre a VIP Class; a chave desconhecida
  cai na Agenda com "Essa tela não está no seu menu.".
- **O item "VIP Class" no ERP:** está no menu Configurações do ERP, com o `href` da tela nova.
- **O "Esqueci minha senha":**
  - o pedido mostra a frase do servidor, e o e-mail chegou ao mailpit local;
  - o link do e-mail tem o token depois do `#`, o token sai da barra no primeiro quadro, e a conferência é um POST;
  - a repetição diferente é recusada linha a linha, como texto;
  - a certa grava e volta à entrada com "Sua senha foi redefinida.", e a senha nova entra.
- **A janela da sessão caída**, no celular e no tema claro: modal de verdade, com o foco no Login, o campo de 52 px, o
  olho de 44 px e a sombra do botão.

**Os três defeitos achados e consertados:**

1. **A cena rolava com o foco.** O brilho, mais largo que a tela, fazia da `.entrada-cena` (com `overflow: hidden`)
   uma caixa rolável de 1403 x 1008 numa tela de 1024 x 768. O primeiro clique num campo a rolava, e o formulário pulava
   173 px para a esquerda e 66 px para cima.
   - **O conserto:** a camada do fundo recorta o próprio brilho, e a cena usa `overflow: clip`, com o `hidden` antes,
     para o navegador que não conhece o `clip`. Na tela, a cena ficou com a rolagem 0 e a caixa do tamanho da tela.
2. **As medidas em px saíam menores.** A raiz da casa tem 15 px, e a 100 converteu as medidas do desenho supondo 16:
   - o campo de 52 px saía com 48,75;
   - o olho e os ícones do toque, de 44 px, saíam com 41,25, abaixo do alvo de toque;
   - os ícones do computador, de 28 px, saíam com 26,25.

   **O conserto:** `max(<rem>, <px do desenho>)`. Na tela, 52 e 44.
3. **O link do rodapé do e-mail de redefinição estava quebrado:** `https://www.https://barbeariavip.com.br/`, desde a
   importação do ERP. Virou `https://barbeariavip.com.br/`, e o `testOTemplateDoEmailEscapaOLink` o confere.

**Os testes:**
- dois casos novos em `ConsoleLigadoEntrada.teste.tsx`, que leem o `App.css` sem os comentários;
- os três arquivos de tela tocados, 239 de 239;
- lint e build com saída 0;
- os casos do e-mail e da senha (`RedefinicaoDaSenhaNaTelaNova|EmailsDoSistema|SenhaTrocada`), 34 de 34 no MySQL e no
  PostgreSQL;
- os mutantes, vermelhos e devolvidos pelo sha1: o `clip` de volta a `hidden`, o campo em rem, os ícones do toque em
  rem e o rodapé quebrado.

**O que não foi visto:** o simulador do iPhone (o teclado de e-mail, o gerenciador de senhas, o link aberto pelo
aplicativo de e-mail).

**A volta:** o `local.php` de volta ao `franquiabv_dev`, conferido pelo sha1; os caches limpos; o banco de contrato
remontado (a senha trocada e o `token_web` anulado voltaram com ele). O e-mail de teste ficou só no mailpit local. Nada
da produção mudou.

## 07/11/2026 -- FINALIZAR-E-CANCELAR-SEM-DINHEIRO-EM-DOBRO (a fatia 101, débitos)

A primeira fatia de débitos da fila (D228, D321, D383 e D694, autônomos pela triagem de 07/11). O coração do PDV --
finalizar e cancelar --, nas duas telas (o ERP e a tela nova), sem dinheiro saindo duas vezes.

**O que muda para quem usa:**

- **O caixa fechado no meio não leva mais o crédito.** Se o operador fecha o caixa (outra aba, a tela de fechamento)
  entre abrir a venda e finalizar, o finalizar recusa antes de gravar qualquer coisa -- a tela nova com o 409 "Não há
  caixa aberto para este operador.", o ERP com a mesma frase. Até aqui a venda morria no meio (erro 500) com o crédito do
  cliente já debitado, e o finalizar seguinte debitava de novo (D228).
- **O erro no meio do finalizar devolve o que já tinha saído.** O crédito do cliente e o do programa, o troco que virou
  crédito e o estoque voltam, por conta no banco, quando o finalizar cai antes de gravar a venda fechada (D321).
- **Dois cancelamentos da mesma venda: só um vale.** O segundo recebe "Esta venda já está cancelada." (ou "Esta venda
  mudou de situação...", se a comanda foi finalizada no meio), e nada volta duas vezes -- nem o caixa, nem o crédito, nem o
  estoque. O caixa e o crédito do cancelamento passaram a ser conta no banco: a venda, a retirada ou a devolução de outra
  sessão no meio não somem mais da coluna. A venda de crédito confere o saldo de novo, travada, antes de cancelar (D383).
- **Dois resgates dos mesmos pontos: só um vale.** O segundo recebe "A pontuação do cliente mudou enquanto o resgate era
  pedido."; o crédito do programa soma por conta no banco (D383).
- **Cancelar a venda devolve a "última visita" do cliente** à venda finalizada anterior (ou a deixa em branco), nas duas
  telas: o "Sentimos sua falta", os 31 dias da Agenda, a fidelização e os filtros deixam de ver a visita que não houve
  (D694). Os 435 cadastros já errados na cópia são dado da produção: a pergunta foi ao lote.
- **Os débitos de fiado em dobro (a 86 (a) do dono):** `scripts/medir-debitos-em-dobro.sql` (só lê) e
  `scripts/baixar-debitos-em-dobro.sql` (fecha só a cópia aberta, sem venda nem caixa), com os irmãos em `postgres/`, para
  o dono rodar na produção. Na cópia local, só a consulta: 52 vendas, 56 cópias, 20 abertas (R$ 1.109,50), 36 fechadas
  (R$ 2.326,30), e 3 primeiros ainda abertos com a cópia já paga (R$ 149,50), que a baixa não toca: a 86 (a) não fala
  deles, e a pergunta foi ao lote.

**Os testes:** `D383CancelarUmaVezSoTest` (21 casos, novo), `DebitosEmDobroTest` (4, novo) e casos novos no
`D321FinalizarUmaVezSoTest` (30), nos dois bancos, também com OUTRO PROCESSO segurando a linha (dois cancelamentos, a
desistência, dois resgates); os vizinhos ajustados ao comportamento novo (`D452SessaoDoPacoteForaDaMesmaVendaTest`,
`PacoteEncerradoNoCancelamentoTest` e o portão do `CancelamentoGravaQuemEQuandoTest`). O ciclo inteiro: caracterização
6.621 em cada banco, contrato 74 e 74, os ensaios HTTP saída 0, o front 4.663 (não tocado); os portões caíram em quatro
(o agrupamento e os booleanos lidos em texto nas consultas novas, a cobertura por par e o inventário), consertados -- as
consultas com o estado por parâmetro e as contagens em métodos à parte, a linha de base de 453 para 455 pares com os dois
novos declarados, o inventário regerado --, e de novo a caracterização (6.622 em cada banco, saída 0) e os portões (74 ok);
o medidor dos relatórios no PostgreSQL, saída 0.

**Os mutantes, um por vez, devolvidos pelo sha1** (MySQL; os marcados com * também no PostgreSQL):

| mudança | caso vermelho |
|---|---|
| a busca do caixa de volta para depois do débito * | `testSemCaixaOFinalizarRecusaAntesDeQualquerEscrita` e os dois do caixa que fecha no meio |
| a API sem a frase do D164 depois da recusa | `testOCaixaQueFechaNoMeioDaApiRecusaComOD164ENaoDebitaOCredito` |
| o ERP sem a frase | `testOCaixaQueFechaNoMeioDoErpDizAFraseDoD164ENaoDebitaOCredito` |
| o erro do meio sem devolver nada * / sem o troco / sem o estoque | `testOErroAntesDaVendaGravadaFechadaDevolveOCreditoOProgramaOTrocoEOEstoque` |
| o cancelamento sem o `UPDATE` condicional * | cinco, entre eles `testQuemChegaDepoisQueOOutroCancelouNaoGravaNada` e `testDoisCancelamentosAoMesmoTempoSoUmVence` |
| o caixa do cancelamento regravado pelo número lido | `testOCaixaSaiNoBancoEAVendaDoMeioFica` |
| o crédito do estorno regravado | `testOCreditoEOProgramaVoltamNoBancoEODoMeioFica` |
| o programa do estorno regravado * | `testOProgramaSozinhoVoltaNoBancoEODoMeioFica` |
| a venda de crédito sem conferir o saldo sob a trava | `testAVendaDeCreditoConfereOSaldoSobATrava` |
| a API sem a frase do cancelamento que perdeu | `testAsDuasPortasDizemAFraseDaVendaJaCancelada` |
| o cancelamento sem refazer a última visita * | quatro do D694 |
| sem a guarda da finalizada mais nova | `testCancelarUmaVendaAntigaNaoMudaAUltimaVisita` e o caso pelo repositório |
| o barbeiro sem a guarda do serviço | `testCancelarAUltimaSemServicoNaoMexeNoBarbeiro` |
| o resgate sem a trava e a releitura * / só sem a releitura | `testOResgateDosPontosQueOutroJaResgatouERecusado` e `testDoisResgatesAoMesmoTempoSoUmVence` |
| a baixa sem a guarda de cliente, unidade e valor / (PG) sem a do primeiro * | `testABaixaFechaSoAsCopiasAbertasComAMesmaMarca` |

**O que não foi provado:** o 409 do resgate pela API não tem caso em processo (a recusa do serviço tem); nada foi aberto
num navegador; os roteiros dos débitos em dobro não rodaram na produção (é do dono). Nada da produção mudou.

**A verificação (07/11/2026, três lentes; oito achados confirmados, todos tratados):**

- **Os 3 primeiros débitos ainda abertos com a cópia já paga** (D321): a fatia escreveu que a 86 (a) os deixava para depois,
  e a 86 (a) não fala deles -- era uma escolha de dinheiro do cliente feita no lugar do dono. Os cabeçalhos dos roteiros e o
  D321 corrigidos, e a pergunta foi ao lote (TELAS-RESTANTES [vendas]); contado na cópia: as 3 vendas finalizadas, com 5
  cópias já quitadas por venda de quitação. A baixa não mudou.
- **O troco no caixa** (o D734, novo): a verificação leu *"o cancelamento tira o troco a mais"*; o código diz que o saldo da
  casa conta o recebido inteiro (o recálculo, o `ContasDoCaixa`, o S16), e o cancelamento o segue -- quem destoa é o finalizar,
  que soma o recebido menos o troco desde a importação, e a coluna `valor_final` fica abaixo do saldo pelo troco até o
  próximo recálculo. Registrado com a contagem da cópia (2.358 finalizadas com troco sem destino, R$ 48.074,19), sem
  conserto nesta fatia: muda o número do caixa entre recálculos em todas as lojas, e pede fatia própria.
- **A fidelização e a restrição de agendamento** que o finalizar grava e o cancelamento não desfaz (D694): escritas como fora,
  e a pergunta foi ao lote (TELAS-RESTANTES [cadastro]).
- **As provas feitas de outro jeito:** o "caixa que fecha no meio" do D228 é na mesma conexão, e o D228 agora diz por que
  outro processo não acrescenta caminho; os roteiros dos débitos em dobro rodaram também pela linha de comando, como o dono
  os roda (`mysql` e `psql`, inteiros), em dois bancos descartáveis com os débitos semeados -- o MySQL montado pelo
  `montar-banco-contrato.sh` --, com o resumo e a lista esperados, a baixa das cinco cópias no mesmo instante, a segunda
  rodada sem mudança e as vendas intactas; os dois bancos apagados.
- **Casos novos**, nos dois bancos: as duas portas na recusa do saldo do cancelamento; a trava do caixa no cancelamento e a do
  cliente no resgate, cada uma com outro processo; a trava do cliente no cancelamento contra o finalizar de outra venda do
  cliente (D694); o barbeiro da última visita pelo último serviço da finalizada mais nova que tem serviço; e a lista do que se
  devolve no erro do meio esvaziada a cada finalizar (D321).

**Os testes da verificação:** os arquivos da fatia e os vizinhos (`D383CancelarUmaVezSoTest`, agora 26 casos;
`D321FinalizarUmaVezSoTest`, 31; `DebitosEmDobroTest`, `D452SessaoDoPacoteForaDaMesmaVendaTest`,
`PacoteEncerradoNoCancelamentoTest`, `CancelamentoGravaQuemEQuandoTest`) 107 de 107 em cada banco; a caracterização inteira
6.628 em cada banco, saída 0; os portões 74 ok; o `contar-debitos.php` (de pé 196, com o D734), o
`conferir-debitos-repetidos.php`, o `conferir-referencias-docs.php` e o `conferir-segredos.sh`, saída 0. O servidor não mudou
(só testes, documentos e comentários dos roteiros); o front não foi tocado.

**Os mutantes da verificação, um por vez, devolvidos pelo sha1:**

| mudança | banco | caso vermelho |
|---|---|---|
| a API sem o ramo `CANCELAMENTO_SEM_SALDO` | MySQL | `testAsDuasPortasDizemAFraseDoSaldoQueBaixouNoMeio` (200 com a venda concluída) |
| o ERP sem o ramo | MySQL | o mesmo (o "cancelada com sucesso" sem nada cancelado) |
| o cancelamento sem a trava do caixa | os dois | `testOCancelamentoTravaOCaixaAntesDaPrimeiraEscrita` (*deadlock*) |
| o resgate sem a trava do cliente | os dois | `testOResgateEsperaADevolucaoNaOrdemDoCliente` (*deadlock*) |
| o cancelamento sem a trava do cliente | PostgreSQL | `testOCancelamentoEsperaOFinalizarDeOutraVendaDoClienteENaoApagaAVisitaNova` (a visita antiga por cima da nova) |
| o barbeiro sem `p.tipo = 'ser'` | os dois | `testOBarbeiroVemDoUltimoServicoDaFinalizadaMaisNovaQueTemServico` |
| o barbeiro com `vp.id ASC` | MySQL | o mesmo |
| o finalizar sem esvaziar `desfazerSeMorrer` | os dois | `testOErroDoMeioNaoDevolveOQueOFinalizarAnteriorDoMesmoServicoGravou` |

**Um mutante que fica verde, e por quê:** o cancelamento sem a trava do cliente, no MySQL. O `UPDATE` condicional do `status`
muda a entrada do índice da chave estrangeira do cliente (`FK_VENDAS_cliente`: `cliente, status, data_criacao`), e o InnoDB
confere a chave com uma trava compartilhada na linha do cliente: a primeira escrita já espera o finalizar do outro (1,5 s,
medido no registro de SQL), com ou sem a trava explícita. A trava explícita fica -- é ela que segura no PostgreSQL, onde o
mutante é vermelho.

**O que segue sem prova:** o 409 do resgate pela API em processo; o erro do meio do finalizar vindo de um `flush` que fecha o
gerenciador; nada aberto num navegador; os roteiros não rodaram na produção (é do dono).

## 07/11/2026 -- O ritmo, quanto falta e quanto tempo, depois da fatia 100 (pedido do dono)

O dono pediu "Atualizei roadmap e depois Continue de forma autônoma". Medido pela régua de 05/11 (as inserções do `git show --numstat`, sem `docs/`, `*.md`, os dumps, a carga, as fixtures, os lockfiles, o inventário gerado e o ds-bundle), no HEAD da conferência no navegador da 98 a 100 (696791ae), e publicado no Artifact do roadmap pela sessão das perguntas.

**Quanto falta: cerca de 88% feito, entre 84% e 92%.** Era 87% em 05/11. Feito desde a importação: cerca de 677,6 mil linhas, em 936 commits; 272 ações na API do `/novo` e 74 portões. A fila das telas que faltam esgotou-se no que eu faço sozinho: da 85 à 100, todas no main, menos a 87 a 89 (o assistente sem Zapier), a 91 (o modelo de contrato) e a 92g (a medição na produção), que esperam o dono. Na régua dos débitos, 197 de pé dos 731 depois da 100 (196 de 732 depois da 101, com o D734 novo).

**A triagem dos débitos de pé (07/11):** cada um dos 197 lido e classificado -- 94 eu faço sozinho, 50 são do corte (dados da produção, rodam na janela), 33 pedem decisão do dono e 19 ficam fora (OneSignal, SMS, visagismo, produção). Os 94 viraram a fila das fatias **101 a 130**, uma por assunto (o finalizar e o cancelar sem dinheiro em dobro, o CI verde no runner, a idempotência das escritas de dinheiro, o crédito do cliente com permissão, os Royalties e as travas do cron, ..., o deploy de teste, o preparo do PHP e do armazenamento). Os 33 do dono viraram 27 perguntas novas na página do lote (324 a 350), além das 320 a 323 já lá; a 101 acrescentou três (351 a 353).

**O ritmo:** as fatias 85 a 100, a 1.393 linhas por hora de relógio em 9,3 h medidas (menos que as 1.964 de 04-05/11: telas pequenas e a conferência no navegador pesam mais que linhas). A primeira fatia de débitos, a 101, levou 94 min no fluxo de agentes (implementador, três lentes, correção) e cerca de 1 h 45 com o commit.

**Quanto tempo:** a fila 102 a 130, 29 fatias de 1,5 a 2,5 h cada, cerca de 45 a 70 h; a fila que espera o dono, 5 a 8 h quando destravar; o código que as respostas 320 a 353 pedirem, 3 a 10 h; o preparo do corte, 5 a 14 h. No total, cerca de 60 a 100 horas; sem pausas, 3 a 4 dias e meio; com metade do relógio parado, 5 a 8 dias. Fora da conta: as janelas do corte na produção, que dependem do dono.

## 07/11/2026 -- CI-VERDE-NO-RUNNER (a fatia 102, débitos)

**O que entrou.** O CI voltou a rodar desde 10/10/2026 (a cobrança da conta foi resolvida), mas nenhuma das 200 últimas
execuções saiu verde. Lidas as execuções 42338443751 e 42347466612: cinco casos caíam só no runner, e nenhum era defeito da
aplicação. **D187 PARCIAL:** o job do MySQL ganhou o fuso de São Paulo no PHP (`ini-values`) e -03:00 no banco
(`SET GLOBAL time_zone`), como o job do PostgreSQL, o `php.ini` e o `docker-compose.yml` já tinham, com a afirmação de que
os dois pegaram; e o `TemporariaSemAChaveComORecorteTest` passou a medir o recorte por um papel que não o ignora quando a
conexão é superusuário (no CI ela é). **D441 PARCIAL:** no runner, o job `verificacao` inteiro (64 passos), os 2 roteiros
do PostgreSQL e os testes do front (4.663) já saíram verdes; faltam os 3 do `contrato`, que vêm depois da caracterização
do MySQL. **D440 PARCIAL:** o portão do relógio nos testes passou a ver `new DateTime`/`DateTimeImmutable` e
`date_create` sem data absoluta, e `gmdate`, `idate`, `microtime`, `hrtime`, `gmmktime` e `gettimeofday`; o `amanha()` do
`CampanhaAudienciaTest` passou ao relógio único da suíte, e as 22 leituras que ficam estão declaradas com o motivo.

**O que muda para quem usa:** nada na tela nem no servidor. Muda para quem empurra: o sinal do CI volta a significar
alguma coisa -- vermelho passa a ser defeito, e não ambiente.

**Provado:** com o PHP em UTC, os dois do MySQL caem aqui com os mesmos números do runner; em São Paulo, 57 de 57. O
PostgreSQL lido como superusuário reproduz os três vermelhos do runner com o caso antigo, e o novo passa (3 de 3). Os
mutantes, vermelhos: o caso antigo como superusuário; sem o `SET LOCAL ROLE`; e no portão, um `new \DateTime('now')` novo e
mais 8 formas de leitura, o `amanha()` de volta, a exceção sem uso, e cinco mutantes do código do portão (cada um deixa de
acusar a leitura plantada); a data absoluta, o `agora()` e o comentário saem 0. O ciclo inteiro: caracterização 6.628 em cada banco,
o contrato 74 e 74, os ensaios HTTP saída 0, os portões 74 ok, o front 4.663 (não tocado), o medidor dos relatórios no
PostgreSQL saída 0. **O que só o runner prova:** a primeira execução verde, com os 3 do `contrato` -- o commit desta fatia a dispara.

**A verificação (três lentes) confirmou 6 achados, e o maior mudou o que a fatia dizia.** Os cinco casos não eram tudo:
atrás da caracterização do PostgreSQL, o passo "Telas do ERP" cai no runner desde 15/10/2026 -- primeiro pelo piso da
cobertura (116 contra 117), e desde 17/10 pela concessão de privilégios ao gerente de fixture, que batia na unicidade nova
de `privilegios_usuarios` (788c1e79) --, e os 27 passos seguintes não rodam lá desde então. Repetindo o job do PostgreSQL
aqui na ordem do runner, num banco descartável lido como superusuário, apareceram mais dois vermelhos atrás dele: o piso
(98 de 227) e 15 ensaios que caíam porque a concessão vinha antes deles. **Consertado:** a concessão é repetível (`ON
CONFLICT` no `ci.yml` e no `medir-telas-no-postgres.sh`, `NOT EXISTS` no ramo do MySQL, onde o `|| true` engolia o erro);
o passo das telas retira o que concedeu depois de medir, e o dos relatórios concede de novo (e o mesmo no roteiro irmão,
`medir-telas-no-postgres.sh`, rodado inteiro uma vez antes da retirada: 98 nos dois bancos); o piso desceu para 98 com o
motivo escrito (a administração antiga da VIP Class e quatro relatórios antigos saíram, telas fecharam ao gerente). A
repetição: **31 passos de 31 com saída 0** (fora o cron e o fechamento da agenda, pelas regras da casa, e a caracterização,
que roda no ciclo). O D441 passou a nomear os 28 pulados do PostgreSQL. **D440 voltou a PARCIAL** (o corpo dizia que restava
parte): o `RegrasDoMarketingTest` relia o relógio a cada chamada -- o D87 -- e agora lê uma vez no `setUp`; o portão vê o
nome com a barra (`\microtime(`) e ganhou ensaio próprio no corredor e no CI (`ensaiar-portao-do-relogio.sh`, 21 casos); o
que resta é trocar por `agora()`, caso a caso, as leituras dos 12 arquivos do grupo "relógio da aplicação". Casos novos:
`ConcessaoDoUsuarioDeFixtureTest` (dois casos, nos dois bancos) e `testOInstanteDoCasoEUmSo`. Mutantes vermelhos: as quatro
concessões sem o desvio, o passo sem a retirada, a retirada do roteiro larga demais (nos dois ramos), a retirada antes da medição, o `agora()` relido, o `as()` relido (o portão),
e sete do código do portão (dois equivalentes sobrevivem, escritos no D440). De pé 196.

## 07/11/2026 -- IDEMPOTENCIA-DAS-ESCRITAS-DE-DINHEIRO (a fatia 103, débitos)

**O que entrou:** a resposta 87 (a) do dono, "um número por confirmação" (o **D162** e o **D285**, os dois PREPARADO). O
pagamento da comanda, a compra e a devolução de crédito pela API aceitam o número da confirmação no cabeçalho
`Idempotency-Key`, e a tela nova o manda: o "Receber" da comanda e a fidelidade do cliente geram um número ao confirmar, o
repetem na nova tentativa do mesmo pedido e o liberam quando dá certo. O servidor grava o número na tabela nova
`confirmacoes`, antes da escrita e na mesma transação, e ao número repetido devolve a primeira resposta sem gravar de novo
(com outro pedido, `422`; o que não serve, `400`); só o que deu certo fica. Dois pedidos com o mesmo número ao mesmo tempo
se enfileiram no índice único, e o segundo devolve a resposta do primeiro. A regra 12 de `docs/convencao-api.md` diz o
resto. A tabela só acrescenta (a resposta 83 (a)): no `mysql.sql`, no `schema.sql` com o RLS, na carga, em
`scripts/migracao-confirmacoes.sql` no laço do passo 0 e no do ensaio geral, e nas duas cópias locais (o PostgreSQL por
`postgres/migracao-confirmacoes.sql`). A quitação do fiado entrou na correção, como a quarta escrita (ver abaixo).

**O que muda para quem usa:** na tela nova, o pagamento, a compra ou a devolução que falham pela rede e são confirmados de
novo não entram duas vezes -- a comanda não ganha troco fantasma, o caixa não sobe em dobro, o dinheiro do cliente não sai
duas vezes. Dois pagamentos iguais de verdade (o cliente e o acompanhante) continuam passando. O ERP antigo não muda
nesta fatia: o recorte na tela nova é da triagem e das notas da fatia, e não da resposta 87 do dono, que não falava em
"tela nova" -- a pergunta foi ao lote. Em produção vale depois do passo 0 do corte; até lá, sem a tabela, tudo grava como
antes.

**Para o dono:** quanto tempo guardar os números (o **D735**, a pergunta nova do lote, "Quanto tempo guardar o número de
cada confirmação"); até a resposta, nada apaga. E duas da correção: o número também no ERP antigo ("O número da
confirmação também no ERP antigo?", o D162 e o D285), e a cópia anonimizada, que levaria os nomes guardados nas respostas
("A cópia anonimizada levaria os nomes guardados nas confirmações", o **D736** novo).

**Provado:** `UmNumeroPorConfirmacaoTest` (15 casos, com a corrida por outro processo) e `RegraDaConfirmacaoTest` (4), nos
dois bancos; os casos 239 a 242 do `ensaiar-recusas-adm-api.php` nos dois bancos de contrato (292 de 292); 9 casos novos na
tela. O ciclo inteiro: caracterização 6.650 em cada banco, o contrato 74 e 74, os ensaios HTTP saída 0, o front 4.672, os
portões 75 ok (o da unicidade do esquema ganhou a do número, com o porquê), o medidor dos relatórios no PostgreSQL saída 0;
tsc, lint e build; os tocados com `AMPLIFICAR_EFEITOS_MS=50` (o único vermelho, "as pílulas do catálogo andam pelas setas",
é o do HEAD, o D471). Mutantes, cada um sozinho e devolvido pelo sha1: sete no servidor, vermelhos nos dois bancos (as três
portas sem a conferência, o serviço guardando a recusa, sem a chave repetida, sem conferir o pedido, a senha no pedido), e
sete na tela. Nada da produção mudou; no `franquiabv_dev`, só a tabela nova e SELECT de contagem. De pé 195.

**A correção da verificação (07/11/2026).** Cinco achados confirmados, os cinco tratados. (1) O texto do D162, do D285,
deste parágrafo e do comentário do `ClienteService` punha na boca do dono o recorte na tela nova; reescrito (o recorte é da
triagem e das notas da fatia), e a pergunta foi ao lote: "O número da confirmação também no ERP antigo? (D162, D285)". (2)
A quitação do fiado entrou como a quarta escrita com o número (`Adm\DevedoresController::quitarAction` e a tela de
devedores): as notas a mandavam entrar por ser escrita de dinheiro, e ela é; a nova tentativa recebe o `200` da primeira
vez, e não o `ja_quitado`. (3) A ordem "resposta repetida antes das recusas" ganhou caso na devolução (o saldo inteiro) e na
compra (o caixa fechado), além da quitação. (4) Na tela, a devolução que deu certo libera o número (caso novo); no
servidor, duas devoluções iguais de verdade passam. (5) As respostas guardadas trazem o nome do cliente e do operador, e a
anonimização da cópia não limpa a tabela: o conserto é nos arquivos selados da cópia do teste, então virou o **D736** e a
pergunta "A cópia anonimizada levaria os nomes guardados nas confirmações"; hoje nada vazou (0 linhas no `franquiabv_dev`,
e a produção só ganha a tabela no corte). Provado: `UmNumeroPorConfirmacaoTest` com 20 casos (eram 15) e
`RegraDaConfirmacaoTest` 4, e os vizinhos da comanda, da fidelidade, dos devedores e do caixa, 716 em cada banco; o ensaio
de recusas 292 de 292 nos dois bancos de contrato; o front 4.674 (2 casos novos), tsc, lint e build; com
`AMPLIFICAR_EFEITOS_MS=50`, os tocados verdes, fora três casos antigos do `DevedoresLigado.teste.tsx` que caem igual com
o `DevedoresLigado.tsx` do HEAD; os portões 75 ok. Mutantes, cada um sozinho e devolvido pelo sha1: no servidor, a
resposta repetida descida para depois das recusas na devolução, na compra e na quitação, e a quitação sem o número --
vermelhos nos dois bancos; na tela, a devolução que não libera o número, a quitação sem o número, com número novo a cada
confirmação, e o `quitarDebito` sem o cabeçalho. De pé 196 (o D736 novo).

## 07/11/2026 -- CREDITO-DO-CLIENTE-PEDE-PERMISSAO (a fatia 104, debitos)

**O que entrou.** O **D123** pela resposta 99 (a) do dono ("Pedir 'Clientes crédito' também no ERP e no servidor"):
comprar e devolver crédito pedem agora o cadastro E "Clientes crédito" nas quatro portas que gravam (as páginas
`clientes/creditoComprar` e `clientes/creditoDevolucao` do ERP, e `POST /adm/api/clientes/:id/creditos/compras` e
`/devolucoes`), derivadas dos chamadores de `comprarCreditoTravado` e `creditoDevolucao`. Sem a permissão, o ERP dá o 404
das outras recusas e a API o 403 `sem_permissao`, antes de buscar o cliente; a lista antiga só mostra o "Crédito" a quem
tem as duas. O resgate de pontos ficou como estava (o cadastro, e no PDV o código do cliente ou a senha do gerente): a
pergunta e a tela nova falam de comprar e devolver, e a escolha foi ao lote ("O resgate de pontos também passa a pedir a
permissão 'Clientes crédito'?", o **D737** novo). O **D439**: a página `editar` do ERP recusa o avulso e o "sem
cadastro" com o 404 que já dava ao 2, o `salvar` do modal os recusa antes de gravar, com a frase da API, e a lista não
desenha o "Editar" nem o nome como link no avulso.

**O que muda para quem usa.** Quem não tem "Clientes crédito" deixa de comprar e devolver crédito também pelo endereço
das páginas antigas e pela API (a tela nova já não lhe mostrava os botões). Na cópia, nenhum não administrador ativo sem a
permissão comprou crédito alguma vez; o aviso de distribuí-la antes de o código subir está no item 13 de "O que tem de
estar pronto antes de a janela começar" da etapa 1 e no item 6 da preparação do `procedimento-de-corte.md`. Nenhuma
permissão foi dada a ninguém na cópia; o gerente das fixtures ganhou a 11ª linha, para os ensaios que compram crédito.
O avulso e o "sem cadastro" não se renomeiam mais pelo ERP.

**Provado:** `D123CreditoPedePermissaoTest` (8 casos) e `D439SentinelasNaoSeEditamNoErpTest` (5) nos dois bancos, com o
`PrivilegioApiTest` (agora onze linhas do gerente). Mutantes, cada um sozinho e devolvido pelo sha1, vermelhos nos dois
bancos: o `getAcesso2` arrancado de cada página, a conferência de `clienteParaEscrever`, o argumento de cada ação da API, a
condição da lista do "Crédito", a condição antiga `!= 2` no `editar`, a recusa arrancada do `salvar`, a condição do
"Editar" na lista e a linha da fixture. O `conferir-autorizacao-privilegiada.php` vigia as cinco guardas novas, e ficou
vermelho com cada uma arrancada (código, não comentário). O ciclo inteiro: caracterização 6.668 em cada banco, o
contrato 74 e 74, os ensaios HTTP saída 0 nos dois bancos (o passo 13 do fluxo, a compra de crédito como o gerente, 201;
o da fidelidade 24 de 24), o front 4.674 com build e lint (não tocado), os portões 75 ok (a marca do `fixtures.sql`
regravada: a primeira rodada caiu nela, e foi a única), o medidor dos relatórios no PostgreSQL saída 0. Nada da produção
mudou; no `franquiabv_dev`, só SELECT de contagem. De pé 195 (o D123 e o D439 saem, o D737 entra).

**A verificação (07/11/2026)** confirmou 4 achados, consertados com caso. O mais sério (médio): o POST da página `editar`
conferia o cliente da rota e gravava no `id` escondido do corpo, e o `editar/<comum>` com `id=1` reescrevia o avulso por
baixo da guarda; agora grava no id da rota. Os outros links para a página `editar` (a aba "Dados" do histórico de
consumo, do de pontuação e dos pacotes, o relatório de descontos, as estatísticas do cupom, os conveniados, a lista e os
detalhes da venda) mostram o nome sem o link nos dois marcadores, pela `RegraDoCadastroDeCliente::eMarcadorDoSistema()`.
O PDV mostra a recusa do avulso sem o "Campos Obrigatórios:" (o `salvar` manda `sentinela: true`; `mod-vendas.js?cache=168`).
O item 13 da etapa 1 ganhou a segunda consulta, só leitura, de quem tem o cadastro sem o crédito (6 na cópia): a
primeira só via quem comprou, e a devolução de antes do corte não grava quem a operou. Casos novos: quatro no
`D439SentinelasNaoSeEditamNoErpTest` (agora 9) e o `sentinelaNoPdvDaTelaAntiga.teste.ts` (3). Mutantes, cada um sozinho
e devolvido pelo sha1, vermelhos nos dois bancos no caso esperado: o id do corpo de volta, a condição de cada aba
"Dados" (três), a do relatório de descontos, a do cupom, a dos conveniados, a dos detalhes e a da lista de vendas de
volta ao `!= 1`, a marca `sentinela` arrancada do `salvar`; e o ramo do PDV arrancado, vermelho no vitest. Depois: a
caracterização inteira 6.672 em cada banco (13 e 14 pulados), 0 falhas, o front 4.677 com build e lint, os portões 75 ok (`conferir-tudo.sh` saída 0), e o `conferir-autorizacao-privilegiada.php`, o dos links mortos, o dos scripts servidos, o das referências dos docs e o dos segredos saída 0. Não visto no
navegador.

## 07/11/2026 -- ROYALTIES-E-TRAVAS-DO-CRON (a fatia 105, débitos)

Três débitos das rotinas de royalties e da trava do cron: **D640 CORRIGIDO**, **D44 PREPARADO**, **D364 PARCIAL**.

**O que muda para quem usa.** A rotina das 12:30 deixa de perguntar ao Asaas, todo dia, pelas faturas de royalties já
pagas em dinheiro (`RECEIVED_IN_CASH`, 8 na cópia): a consulta das "não quitadas" deixa de fora as duas situações que
quitam. A apuração das 05:25 avisa quando o retrato de ontem soma zero com unidade ativa, e quando um dos sete dias antes
de ontem ficou, no retrato gravado, com base zero e sem a rodada do dia seguinte para refazê-lo (ou sem linha nenhuma) --
a forma dos dois dias zerados de 05/09/2025 e de 10/01/2026, R$ 7.438,12 nunca faturados: a linha de aviso no log das
rotinas e o `aviso` na resposta da rota; o retrato é gravado como antes. Falta a linha do crontab levar esse `aviso` ao log do sistema (a resposta 128 (b); o D331,
a fatia 113); cobrar os dois dias continua sendo o P-R3, do dono. No PostgreSQL, as seis rotinas de cron com trava
(apuração e faturas dos royalties, painel de faturamento, NPS, campanhas e fila do push) passam a travar também no banco
(`pg_try_advisory_lock` pela conexão do EntityManager, depois do arquivo): a segunda execução vinda de outra máquina
desiste, como a desta. E a tabela `royalties_faturas` ganha, no `postgres/schema.sql`, a restrição de uma fatura por
unidade e vencimento (na cópia, 890 faturas e zero pares repetidos, contados só com SELECT; no MySQL ela não entra, como a
de `royalties`). No MySQL de produção nada muda até o corte.

**Os portões tocados:** o `conferir-unicidade-do-schema.php` declara a 11ª unicidade (e ficou vermelho sem ela); o
`conferir-rastro-das-rotinas.php` segue vendo a apuração como muda (a decisão de 31/08 do D68 vale), sem lista de exceção:
o aviso sai por um método dois níveis abaixo da ação (ver a correção, abaixo); o `conferir-cobertura-por-par.php` desceu
de 111 para 110 sem menção, por cobertura (o `selecionarPorTodasNaoQuitadas`, citado pela primeira vez num teste), e subiu
de 455 para 456 pares com o `RoyaltiesRepository::retratoDosDias` da correção.

**Provado:** os casos novos `D44TravaNoBancoTest` (16), `D640FaturaPagaEmDinheiroTest` (1), três no
`FaturasDeRoyaltiesTravaTest`, três no `RoyaltiesTest` e um no `RegrasDosRoyaltiesTest`, nos dois bancos (no MySQL, os 8
casos da "outra máquina" e da transação abortada pulam dizendo que lá a trava é de arquivo). O `FaturasDeRoyaltiesTravaTest`
e o `RelatorioDeRoyaltiesTest` deixaram de semear duas faturas no mesmo par no PostgreSQL. Mutantes, cada um sozinho e
devolvido pelo sha1: a trava do banco arrancada (9 vermelhos), o desbloqueio arrancado, a conexão tirada de cada uma das
seis rotinas (um vermelho cada), a restrição tirada do `schema.sql`, a consulta de volta ao `!= 'RECEIVED'` (vermelho nos
dois bancos), o aviso arrancado, a linha do log arrancada, o aviso no dia de hoje em vez de ontem, a rota sem o `aviso` e
a regra sem a pergunta das ativas; um equivalente (soltar o arquivo ao desistir: o PHP o solta ao sair do escopo). O
ciclo inteiro: a caracterização 6.696 em cada banco (21 e 14 pulados), 0 falhas; o contrato 74 e 74; os ensaios HTTP saída
0 nos dois bancos; o front 4.677 (não tocado); os portões 75 ok (a primeira rodada caiu no rastro e na cobertura por par,
consertados acima); o medidor dos relatórios no PostgreSQL saída 0. As rotinas que cobram e falam com o Asaas não rodaram:
a das faturas só com o Asaas dublado, a apuração só no banco da suite. Não provado: duas máquinas de verdade (a outra
sessão do banco faz o papel); o PgBouncer em modo transação, que quebraria a trava de sessão. Nada da produção mudou; no
`franquiabv_dev`, só SELECT de contagem. De pé 193.

**A correção da verificação** (três lentes; seis achados confirmados, um alto, todos consertados com caso):

- **O aviso não via os dois dias do débito** (o alto). Lido na cópia: as linhas de 05/09/2025 e de 10/01/2026 foram
  gravadas, com base zero, só pela rodada da madrugada do **próprio** dia, e a rodada do dia seguinte não rodou -- a conta
  de "ontem" nunca as veria. A rotina passou a conferir também o retrato **gravado** dos sete dias antes de ontem
  (`RoyaltiesRepository::retratoDosDias`, `RegraDosRoyalties::avisoDosDiasQueFicaramZerados`): avisa o dia com base zero
  cuja última escrita é do próprio dia, e o dia sem linha. Medido de 01/01/2025 a 16/08/2026: só os dois dias do débito e o
  dia da cópia; os feriados zerados foram refeitos e ficam com o aviso de "ontem", uma vez.
- **O aviso volta a nulo quando a rotina desiste pela trava**: agora com caso.
- **Os comentários que diziam "sem unicidade em banco nenhum"** (o do `relatoriosRoyaltiesFaturasGerar`, o do
  `royaltiesAtualizar` sobre `royalties` e a linha 212 da especificação) dizem o que vale: a chave no PostgreSQL, o MySQL
  sem ela até o corte.
- **A lista `$soAvisam` do portão do rastro o cegava** para a apuração: saiu. O aviso foi para
  `FranquiaService::escreverNoLogDasRotinas`, e uma linha de "rodou" escrita direto na rotina deixa o portão vermelho
  (falsificado e devolvido pelo sha1).
- **A suíte escrevia avisos falsos no log da cópia** (44 linhas "AVISO" na manhã de hoje, de todo caso que rodava a
  apuração): os casos usam a apuração de verdade com a linha guardada (o trait `ApuracaoQueGuardaOLog`), e o método de
  verdade é provado num diretório descartável. Depois das duas suítes inteiras, nenhuma linha nova.
- **A chave das faturas faltava na cópia local do PostgreSQL**: `postgres/migracao-royalties-faturas-unica.sql` (só
  acrescenta; idempotente) a criou na `franquiabv_dev` do contêiner, rodado duas vezes (890 faturas, zero pares), com caso
  que o roda sobre uma tabela sem a chave. A de `royalties` continua fora dela: os 87 pares do D110 pedem apagar linha.

Os testes da correção: cinco casos novos (três no `RoyaltiesTest`, um no `RegrasDosRoyaltiesTest`, um no
`FaturasDeRoyaltiesTravaTest`, só no PostgreSQL); a caracterização inteira 6.701 em cada banco, 0 falhas (22 pulados no MySQL
e 14 no PostgreSQL); os portões 75 ok. Mutantes da correção, um por vez, devolvidos pelo sha1, todos vermelhos: a segunda
conta tirada, a regra sem "foi refeito?", a regra sem o dia sem linha, a janela sem a véspera, o aviso sem voltar a nulo, a
chamada da linha do log arrancada, o corpo do método do log vazio, o resumo sem a hora da alteração (nos dois bancos, menos
o do corpo do log, no MySQL), o `ALTER` arrancado do roteiro da cópia (no PostgreSQL), e a linha de "rodou" direto na rotina
(o portão do rastro sai 1). O contrato, os ensaios HTTP e o medidor dos relatórios não rodaram de novo: a correção não
tocou rota, tela nem relatório além da apuração das 05:25, que nenhum deles chama. De pé 193.

## 07/11/2026 -- RECALCULO-DA-PONTUACAO-NO-CORTE (a fatia 106, débitos)

Dois débitos da pontuação da fidelidade, os dois no passo do corte: **D284 PREPARADO** e **D312 PREPARADO**. Nada muda
hoje para quem usa: é um roteiro que roda uma vez, na janela.

**O que muda para quem usa, depois da janela.** Todo saldo de pontos passa a bater com o histórico, de uma vez, sem esperar
a próxima visita de cada cliente (a resposta 123 (a) do dono). Antes do recálculo, quem tem pontos ainda válidos numa unidade
onde não tinha saldo ganha o saldo lá (a 230 (a)): é o caso de quem indicou um amigo que comprou em outra unidade -- os 10
pontos saem da unidade do cadastro dele e aparecem na da compra, e ninguém perde ponto. Quem tinha pontos a mais na coluna
(o cancelamento antigo tirava reais, e não pontos) perde o excesso, que o resgate já não pagava. O crédito em dinheiro não é
tocado, também o dos pontos de venda cancelada que já viraram crédito (pela lógica da 68).

**O que entrou:** `scripts/recalcular-pontuacao-no-corte.sql` (o passo 7 da etapa 1, logo depois do consumo dos clientes,
pelo caminho da árvore no lugar e com o `PIPESTATUS` do `mysql`) e o irmão `postgres/recalcular-pontuacao-no-corte.sql` (o
7d da etapa 2, que, com a etapa 1 feita, só acerta o que o intervalo deixou; começa por `ANALYZE`, porque sem as
estatísticas a contagem levou 61 s num clone recém-carregado). Os dois contam antes (os pares sem saldo; das linhas de
saldo, as que diferem, acima, abaixo, nulas, e o líquido), listam o que nasce e o que muda com o número de antes (o caminho
de volta, guardado pelo `tee`), criam o saldo zerado, recalculam pela soma das linhas `ativa` (a conta do
`ClienteService::atualizarPontuacao`) e conferem no fim com a mesma contagem de antes (as linhas de saldo, e todo o resto
zero). Idempotentes. Na etapa 1, o item 18 de "O que falta", as
linhas 123 e 230 das decisões, a linha da janela, a da linha do tempo e a da volta ("fica").

**Medido na cópia, só SELECT:** 1 par sem saldo, de 10 pontos (o do D312); das 256.987 linhas de saldo, 28 diferem no
PostgreSQL da cópia (22 acima, 6 abaixo, +5.092) e 7.793 no MySQL de desenvolvimento (quase todas expiradas no próprio
ambiente). **Num clone descartável das duas tabelas em cada banco** (apagado no fim), o roteiro de verdade, duas vezes: no
MySQL cerca de 3 s, 1 linha criada e 7.794 recalculadas; no PostgreSQL 1 e 29, cerca de 1,3 s; a segunda rodada não mudou nada.

**Provado:** o `RecalculoDaPontuacaoNoCorteTest` (5 casos, nos dois bancos): o roteiro de cada banco comando por comando
dentro da transação do caso, com o indicador de outra unidade, o cancelamento antigo, o abaixo, os pontos resgatados com o
crédito que fica, o par gêmeo, a pontuação nula, o que já bate e o que só tem linha expirada; o `atualizarPontuacao` que,
depois do roteiro, não muda nada; a segunda rodada com zero; os dois roteiros só com o `INSERT` zerado e o `UPDATE` da
pontuação; e o lugar no passo 7 e no 7d, fora do passo 0. O `PermissoesComONomeDaTelaTest` passou a exigi-lo entre os
roteiros do passo 7. O ciclo inteiro: a caracterização 6.706 em cada banco, 0 falhas (22 pulados no MySQL e 14 no
PostgreSQL); o contrato 74 e 74; os ensaios HTTP saída 0 nos dois bancos; o front 4.677 (não tocado). Os portões: o ciclo
achou um vermelho, o `conferir-ancora-de-medicao.php` -- o maior id de antes, que separa as linhas criadas, lido como âncora
de janela porque o cabeçalho citava a cópia; o roteiro não roda nela, o cabeçalho passou a dizer "a cópia local", e o
comentário diz por que não é âncora --; depois, os portões inteiros de novo, 75 ok e saída 0, e o medidor dos relatórios no PostgreSQL, saída 0. Mutantes, um por vez, devolvidos pelo
sha1, todos vermelhos: sem o saldo zerado (no MySQL e no PostgreSQL), o saldo criado também no par que já tem linha, a soma
com todo status (nos dois), a pontuação nula deixada, o `valor` zerado junto, sem o recálculo, e as linhas do 7d e do passo 7
tiradas; o par sem o inquilino no PostgreSQL ficou verde, equivalente com um inquilino só. O `D312|D466` saiu dos pares
conhecidos do `conferir-debitos-repetidos.php` (o título do D312 mudou). Nada da produção mudou; na cópia, só SELECT. De pé 189.

**A verificação** (três lentes) confirmou quatro achados, consertados com caso. A conferência do fim passou a repetir a
contagem de antes, coluna por coluna (se der diferente de zero, quem opera lê ali quantas ficaram acima, abaixo e o líquido).
O indicador do cenário passou a ter indicado dois amigos na mesma outra unidade, e o caso exige UMA linha de saldo para as
duas linhas do histórico -- sem o `GROUP BY` do `INSERT`, o roteiro criaria um par gêmeo, e a suíte ficava verde. A chave do
inquilino em cada consulta do irmão do PostgreSQL ganhou caso, pela leitura do roteiro sem comentários; o cenário do achado
(dois inquilinos com os mesmos ids) não existe no esquema, porque `clientes.id` e `unidades.id` são chave primária sozinhos, e o
cabeçalho diz que a chave guarda só a linha incoerente. E o arquivo inteiro rodou pelo `mysql` e pelo `psql`, duas vezes,
num banco descartável montado pela receita do contrato em cada banco, com o indicador de outra unidade e os pontos resgatados
da 68: saída 0, a primeira criou 1 e recalculou 6, a conferência deu zero, a segunda não mudou nada; apagados no fim (o ensaio
fica no rascunho da sessão, `ensaio-106/`). O `RecalculoDaPontuacaoNoCorteTest` tem 6 casos, verdes nos dois bancos. Mutantes
novos, um por vez, devolvidos pelo sha1, vermelhos: sem o `GROUP BY` do `INSERT` (nos dois), a conferência só com pares e
divergentes (nos dois), e o inquilino tirado do `NOT EXISTS` (o que ficava verde), do `JOIN` do recálculo e do da contagem.

## 07/11/2026 -- AGENDA-ORIGEM-E-GRADE (a fatia 107, débitos)

Dois débitos da agenda, pelas respostas do dono de 12/10/2026: **D216 CORRIGIDO** (a 156 (b)) e **D324 CORRIGIDO** (a 157 (a)).

**O que muda para quem usa.** O agendamento que o cliente fez pelo aplicativo continua dizendo "aplicativo" depois de
remarcado -- pelas três portas: a API (a rota que a tela nova chama), o modal e o arrastar da tela antiga --, e os
relatórios que separam o que veio do aplicativo deixam de perdê-lo na remarcação (492 assim no arquivo da cópia). O marcar continua "web", e remarcar para o
passado e para outro colaborador continua como estava. E no horário que tem um bloqueio e um agendamento no mesmo instante, as
duas grades mostram o cliente que vai chegar, e não "fechado" com ele por baixo: eram 38 horários no `franquiabv_dev`, e a
ocupação da agenda do dia passa a contá-los como o relatório do Profissional Parceiro conta.

**O que entrou.** `AgendaService::agendar` ganhou o argumento `$origem` (padrão "web"), e a `RemarcacaoDeAgendamento` passa a
da raiz velha -- argumento, e não chave do pedido, porque o marcar da tela antiga entrega o POST cru e um `origem=app` escrito à
mão gravaria o balcão como aplicativo. `AgendaRepository::selecionarPorColaboradorData` ordena, no mesmo instante, o agendamento
antes do bloqueio (`ORDER BY age.data, ehBloqueio, age.id`, o `ehBloqueio` HIDDEN); as duas grades leem essa consulta. O front
não mudou.

**Provado:** `RemarcacaoDeAgendamentoTest` (+3: a origem do aplicativo pelo serviço que a API usa; a mesma pela rota da tela
nova, `POST /adm/api/agendamentos/:id/remarcacao` despachada em processo, com a resposta e o banco; a vazia levando o atendimento),
`AgendaAntigaComoANovaTest` (+4: o modal e o arrastar do aplicativo; o marcar com `origem` no POST continua "web"; a grade
antiga desenha o agendamento por cima do bloqueio mais antigo) e `GradeDaAgendaApiTest` (o caso de 12/10 invertido, e o
controle do agendamento mais antigo): 98 casos, verdes no MySQL e no PostgreSQL. Mutantes, um por vez, nos dois bancos,
devolvidos pelo sha1, todos vermelhos: a remarcação sem a origem (4 casos), o `agendar` com "web" fixo (4), o `agendar` lendo a
origem do pedido (1), a origem vazia virando "web" (1), a ordem só pelo id, a de 12/10 (2), o id ao contrário (1) e o bloqueio
antes do agendamento (3). A verificação em três lentes confirmou 1 achado, baixo: o caso dito "pela API" chamava o serviço
direto, sem o despacho da rota, e os documentos contavam três portas num lugar e quatro no outro. Consertado com o caso da rota
e a redação (o comentário do `remarcacaoAction`, que ainda dizia que a origem voltava a "web", também); os mutantes de depois,
nos dois bancos e devolvidos pelo sha1: a remarcação sem a origem (5 vermelhos, o da rota entre eles) e a resposta da rota com
"web" fixo (só o da rota). Os 98 casos da fatia, verdes nos dois bancos depois.

**O ciclo inteiro, depois do conserto e antes da verificação:** a caracterização 6.714 em cada banco, 0 falhas (22 pulados no MySQL e 14 no
PostgreSQL); o contrato 74 e 74; os ensaios HTTP saída 0 nos dois bancos; os portões 75 ok, nenhuma falha inesperada; o
front 4.677 (não tocado); o medidor dos relatórios no PostgreSQL, saída 0. Nada da produção mudou; no `franquiabv_dev`, só
SELECT de contagem. De pé 187.

## 07/11/2026 -- WHATSAPP-DO-BALCAO (a fatia 108, débitos)

Os débitos D351 e D594, pela triagem dos débitos de pé. **D351 CORRIGIDO** (pela resposta 178 (c) do dono): as mensagens que
saem do balcão -- o "cliente chegou" do check-in, o código e o "resgate realizado" da fidelidade, o crédito do programa usado
na venda e o cupom gerado -- só saem pelo WhatsApp da unidade que manda (ligado **e** ativa, `Unidade::mandaWhatsApp`), como as
rotinas, o NPS, a campanha e a indicação já faziam desde outubro. Numa das 4 unidades fechadas com o WhatsApp ligado, o código,
o resgate, o crédito usado e o cupom vão pelo aplicativo, como numa unidade sem WhatsApp; o "cliente chegou", que vai ao
colaborador e não tem canal no aplicativo, não sai. Quem chega a cada uma: o código e o resgate seguem a unidade da sessão, e o
"cliente chegou", a do colaborador da agenda -- o administrador que troca a sessão para a fechada chega aos três. A venda, não:
ela é da unidade em que é lotado quem abriu o caixa, e o PDV (o antigo e a comanda nova) recusa a venda de outra unidade que não
a da sessão; o crédito usado e o cupom de uma fechada só saem de quem é lotado nela, isto é, pela personificação (na cópia,
nenhum administrador ativo é lotado nas 4, contado só por SELECT). **D594 CORRIGIDO**: a tela antiga
do WhatsApp (`/mod-configuracoes/whatsapp/instancia`) leva nos dois formulários -- "Gerar código" e "Desconectar" -- o id da
unidade que a página mostra; trocada a unidade noutra aba, o botão da página de antes não age na unidade nova: volta à tela com a
frase da recusa da API nova, sem falar com o servidor do WhatsApp e sem rastro. Nada muda na tela nova nem no front.

**Provado:** `D351MensagensDoBalcaoTest` (5 casos novos, um por porta: ativa sai pelo WhatsApp, fechada com ele ligado sai pelo
aplicativo ou não sai) e `WhatsAppTelaAntigaTest::testATelaQueMostraOutraUnidadeNaoEscreveNaTelaAntiga` (os dois formulários com
o campo; outro id, o texto torto e o vazio recusam sem chamar o provedor nem gravar rastro; o id certo conecta e desconecta): 29
casos dos dois arquivos, verdes no MySQL e no PostgreSQL. Mutantes, um por vez, devolvidos pelo sha1, todos vermelhos: cada uma
das quatro escritas de volta ao interruptor (o check-in, o ajudante da fidelidade -- 2 casos --, o crédito usado, o cupom) e a
conferência arrancada do código e do desconectar, nos dois bancos; o campo arrancado de cada formulário e a variável da tela
arrancada do controller, no MySQL. Nenhuma mensagem saiu: as portas só enfileiram, e o provedor do WhatsApp é o de mentira.

**O ciclo inteiro:** a caracterização 6.721 em cada banco, 0 falhas (22 pulados no MySQL e 14 no PostgreSQL); o contrato 74 e 74;
os ensaios HTTP saída 0 nos dois bancos; os portões 75 ok, nenhuma falha inesperada (o `conferir-segredos.sh`, saída 0); o front
4.677 (não tocado); o medidor dos relatórios no PostgreSQL, saída 0. Não visto: a tela antiga num navegador com duas abas de
verdade. Nada da produção mudou; no `franquiabv_dev`, nada foi lido nem escrito. De pé 185.

## 07/11/2026 -- SESSAO-LE-O-BANCO (a fatia 109, débitos)

Os débitos D350, D514, D702 e D546, pela triagem dos débitos de pé. **D350 PARCIAL** (resta a fila): o login não copia mais na
sessão a configuração da unidade -- os dias de convênio, o prazo do cadastro, o "só o primeiro nome na agenda", o WhatsApp ligado
e a chave, a instância e o provedor dele; a sessão guarda só o id, o tipo, o país, a moeda e o DDI. Quem usava a cópia lê a
unidade no banco a cada pedido: o item "WhatsApp" do menu (no ERP antigo e na tela nova), o aviso do primeiro nome, a grade e a
lista de agendamentos (novas e a antiga), a busca de clientes antiga do PDV e da agenda (o "atualize o cadastro") e o convênio do
dia no PDV antigo, nos clientes antigos e na comanda nova. Para quem usa: mudar essas coisas em Unidades vale no pedido seguinte de
quem já entrou, e não no próximo login -- e a tela nova de Unidades passou a dizer isso ("vale na hora, também para quem já está no
sistema"). O que resta é a fila do WhatsApp, que guarda a chave do momento em que a mensagem entrou.
**D514 CORRIGIDO**: o administrador que assumiu alguém ("Autenticar como") volta a ser ele mesmo, com a mensagem "O acesso de quem
você assumiu mudou...", no pedido seguinte a uma mudança no que a pessoa pode -- o efetivo das linhas de privilégio (a linha
"nada" não conta; na pessoa do grupo de administrador, as linhas não contam), o grupo, as marcas do grupo ou a do painel --, pela
102 (b); o login dela noutro lugar, a saída dela, a edição do cadastro e a desativação (a decisão (f2) do D315) não o tiram. O
pedido que provoca a volta não segue (foi montado na tela da pessoa, e gravaria na unidade do administrador): a página do ERP vai à
lista de usuários com a mensagem; a tela nova recebe `401 personificacao_encerrada`, refaz o Console para ele (sem a faixa) e
mostra a frase no topo. E ninguém assume a si mesmo pelo "voltar a ser eu" que chega depois. Custa duas consultas por pedido, só
na sessão personificada: 0,165 ms no MySQL e 0,281 ms no PostgreSQL da cópia (a mediana). **D702
CORRIGIDO**: o grupo Relatórios do menu antigo abre também pelo Atendimentos, pelo NPS, pela Retirada de Produtos, pelo Consultor
Visagista e pelo VIP Class, com a condição de cada item (o colaborador abre pelo Atendimentos, e não pelo NPS nem pela Retirada,
que a política lhe recusa). **D546 CORRIGIDO**: o "Aplicativo" do menu antigo só mostra a Versão e o Push a quem tem o escrever,
que é o que as duas telas pedem; o Push do menu novo também. Ninguém da cópia muda de menu pelo D702 nem pelo D546.

**Provado:** `SessaoLeOBancoTest` (8 casos novos; os seis dos leitores são "a sessão diz X, o banco diz Y", nos dois sentidos),
`D514PersonificacaoSegueOAcessoTest` (15 casos novos), o caso do D702 no `AniversariantesSaiuTest` (tomou o lugar do que dizia o
efeito antigo), os casos da grade antiga do `D203TextoDigitadoNasTelasDoErpTest` (a cópia velha ao contrário) e a guarda do Push no
`menu-do-erp.teste.ts`; os arquivos da fatia e os vizinhos (as personificações, as guardas de login, os convênios, as grades, os
menus: 532 casos) verdes no MySQL e no PostgreSQL. Mutantes, um por vez, devolvidos pelo sha1, todos vermelhos: M1 a M9 (cada
leitor de volta à sessão, o ouvinte do menu sem o privilégio, a chave de volta ao login), P1 a P6 (a guarda arrancada, o sinal não
guardado, as linhas, a falta de sinal como "igual", as duas marcas fora do sinal), R1 a R3 e A1 no MySQL; M1, M4, M8, P1, P3, R1 e
A1 também no PostgreSQL; A2 (o Push de volta ao ler) no vitest.

**O ciclo inteiro:** a caracterização 6.740 em cada banco, 0 falhas (22 pulados no MySQL e 14 no PostgreSQL); o contrato 74 e 74; os ensaios HTTP saída 0 nos dois bancos (22 e 22); os portões 75 ok, nenhuma falha inesperada (na primeira rodada, o `conferir-cobertura-por-par.php` acusou o `convenioAtivo` saindo da lista de falsos positivos, agora citado com a classe -- tirado dela, e anotado no D177 --, e o front, o caso do Push do Console com só o ler -- mudado junto); o front 4.678 com o build (tsc e vite) e o lint; o medidor dos relatórios no PostgreSQL, saída 0; o `conferir-segredos.sh`, o `contar-debitos.php` e o `conferir-debitos-repetidos.php` (nove pares que sumiram com as marcas novas do D514 e do D546, tirados da lista), saída 0. Não visto: o menu e a personificação num navegador. Nada da produção mudou; no `franquiabv_dev`, só SELECT (a medida do custo). De pé 182.

**A verificação (07/11/2026, dez achados confirmados, todos consertados).** (1) A tela nova de Unidades dizia "vale no próximo
login" nos dias de convênio e no "só o primeiro nome" -- agora "vale na hora, também para quem já está no sistema", e o desenho
anota a U13 como resolvida. (2) e (10) O pedido que provocava a volta da personificação seguia como o administrador, e gravaria na
unidade dele o que foi digitado na tela da pessoa assumida; e a tela nova não mostrava nada, com a faixa "vendo como Fulano" ainda
na tela: agora a guarda responde no lugar do pedido -- a página do ERP vai à lista de usuários com a mensagem, a `/adm/api`
responde `401 personificacao_encerrada` --, e o `/novo` refaz o Console para o administrador e mostra a frase no topo. (3) O sinal
usava as linhas cruas: agora é o efetivo, o critério da 102 (b) (a linha "nada" não conta; na pessoa do grupo de administrador, as
linhas não contam). (4) O custo medido também no PostgreSQL: 0,281 ms por sinal (0,165 ms no MySQL). (5) Nenhum caso conferia
que a aplicação liga o ouvinte do item WhatsApp no `render`: o caso agora usa o ouvinte achado no `EventManager` da aplicação. (6)
O `sort` do sinal não tinha caso: um gerenciador de mentira escolhe a ordem da leitura. (7) A busca antiga só provava um sentido:
quatro combinações agora. (8) O D514 dizia "6 pelo provedor": são 5. (9) O "voltar a ser eu" que chega depois da mudança fazia o
administrador assumir a si mesmo, com uma linha "assumida por" ele mesmo no registro de acessos: a guarda o recusa, e a ação
também recusa assumir o próprio id. Testes: o `D514PersonificacaoSegueOAcessoTest` com 15 casos e o `SessaoLeOBancoTest` com 8,
e os arquivos da fatia e os vizinhos (536 casos) verdes no MySQL e no PostgreSQL; o front 4.682 com `tsc -b`, o build e o lint, e
os tocados também com `AMPLIFICAR_EFEITOS_MS=50` (um caso da fidelidade do `UnidadesLigado.escrita.teste.tsx` cai amplificado
igual com os três arquivos de Unidades do HEAD: não é desta fatia). Mutantes novos, todos vermelhos e devolvidos pelo sha1: N1 a N9
(no MySQL; N1, N2, N4, N5, N6 e N8 também no PostgreSQL) e F1 a F5 no vitest. Depois da verificação: o ensaio HTTP da sessão (`ensaiar-sessao-do-erp`, que personifica no caso 22)
saída 0 nos dois bancos; os portões 75 ok; a cobertura por par, o `contar-debitos.php`, o `conferir-debitos-repetidos.php`, o
`conferir-referencias-docs.php` e o `conferir-segredos.sh` saída 0. O ciclo inteiro não foi repetido depois da verificação. Não visto num navegador: a volta da personificação e a frase no topo do `/novo`.

## 07/11/2026 -- O caso 236 do runner (o seguimento da fatia 108)

**O que caiu.** A execução 42374193312 do CI (o commit 8d1457bf, a fatia 108) caiu nas duas tentativas no mesmo lugar: o job
do PostgreSQL, o passo "As recusas de escrita da comanda (D170)", o caso 236 (a planilha dos pacotes, `GET
/adm/api/pacotes/exportacao`, pelo colaborador que só lê) recebia HTTP 0 entre 1,3 e 1,8 s depois do 235, e o trabalhador do `php
-S` que aceitou o pedido nunca registrou `Closing` nem erro. O diff da 108 não passa pela planilha; a execução seguinte (04adbf58,
42380435126) passou o mesmo caso. **A causa:** o `shivammathur/setup-php` liga o JIT do opcache em todo PHP 8
(`opcache.jit=1235` e `opcache.jit_buffer_size=256M`, no `99-pecl.ini`), e o `php -S` -- a SAPI `cli-server`, que o
`opcache.enable_cli` não cobre -- roda com ele; o JIT do PHP 8.1 derruba o trabalhador com SIGSEGV durante a geração da
planilha, dependendo do que aquele trabalhador compilou antes. O PHP 8.1 nasce sem JIT (o buffer padrão é 0), e é assim no
`docker/php/php.ini` -- por isso o ciclo local, pelo nginx e o PHP-FPM, nunca o viu. **A prova:** o job repetido no contêiner
na ordem do runner, contra um `php -S` de 4 trabalhadores com as opções do `ci.yml`, num banco PostgreSQL descartável montado
pela receita dele (servidor novo e banco novo a cada rodada): com o ini do `setup-php`, 4 quedas em 12 rodadas, todas numa
planilha (duas no 236, duas no 238, a da tela antiga), e nas quatro o trabalhador zumbi pelo sinal 11, sem uma linha no log;
sem o JIT, nenhuma em 13 (292 de 292 em todas), e mais 1.200 planilhas seguidas. Um servidor sem histórico serviu 300
planilhas sem cair. **O conserto, no `ci.yml`:** `opcache.jit=disable` no `ini-values` dos quatro `setup-php` (ele vem depois
do JIT no mesmo arquivo, e vale), e, antes de cada `php -S` da aplicação (o do MySQL e o do PostgreSQL), o PHP do runner
perguntado se o JIT ficou desligado (sai 1 com ele ligado). O caso `D115JitDesligadoNoCiTest` (2 casos) exige os dois no
`ci.yml` lido sem comentários; os mutantes M1 a M10 (cada `ini-values` sem o valor, o JIT de volta, a pergunta arrancada, depois
do servidor, sem o opcache na linha, só imprimindo, invertida, e o valor só no comentário), vermelhos e devolvidos pelo sha1. É
o D115 (as planilhas cortadas de 06/09, a mesma cara), PARCIAL: a revisão lembrou que o contêiner é `aarch64` e o runner x86_64, então a causa no runner é inferência; fecha com o runner verde. **A produção:** roda PHP-FPM, e o JIT só
existiria nela com `opcache.jit_buffer_size` posto à mão -- a configuração de opcache dela segue "A CONFIRMAR"
(`docs/operacao-atual.md`); se estiver lá, o mesmo trabalhador cai (o pedido vira 502, e o PHP-FPM sobe outro processo). Os
testes: o caso novo e o `ConcessaoDoUsuarioDeFixtureTest` (que também lê o `ci.yml`) nos dois bancos; o ensaio de recusas
inteiro pelo `php -S` (saída 0 nas 13 rodadas sem JIT) e pelo nginx no banco de contrato (292 de 292, saída 0); o
`conferir-lacuna-do-ci.sh`, o `contar-debitos.php` e o `conferir-debitos-repetidos.php`, saída 0. Não visto: o CI verde com o
conserto (nada foi empurrado). Não explicado: numa rodada à parte com o JIT (o ensaio de recusas sozinho, repetido no mesmo
servidor), as sessões passaram a responder 401 do caso 192 em diante, sem trabalhador morto; essa rodada não foi
repetida, e não entrou na conta. Nada da produção mudou; no `franquiabv_dev`, nada. De pé 181.

**A revisão do caso 236 (07/11/2026).** Um segundo agente tentou derrubar a investigação e confirmou o conserto (o `opcache.jit=disable` nos quatro `setup-php`, a pergunta antes dos dois `php -S`, o caso vermelho sem ele, nenhum caso tirado nem tempo aumentado), mas não a prova: a queda foi reproduzida num contêiner ARM, e o runner é x86; o incidente de 06/09 não foi repetido; e a rodada dos 401 com JIT, do caso 192 ao 238, ficou sem explicação nem controle. Por isso o D115 ficou PARCIAL, e não CORRIGIDO. Também: o CI da fatia 109 (04adbf58, execução 42380435126) já tinha passado o caso 236 com o JIT ligado -- a queda depende do que cada trabalhador compilou antes, e vai e volta entre execuções.

## 07/11/2026 -- TEXTO-NAS-REGRAS (a fatia 110, debitos)

**O que entrou.** Os débitos D495 (PARCIAL), D421 e D393 (CORRIGIDOS). **O texto que o sistema grava passa pela mesma régua
em mais seis lugares** (o D495): a descrição da despesa (Contas a Pagar, na tela nova e no ERP), a observação do agendamento
(marcar e remarcar, nas duas agendas), o nome do amigo indicado na página da fidelidade, os textos do cadastro do cliente na
tela nova, o motivo do desconto da comanda (na tela nova e no PDV antigo) e o motivo do fechamento da agenda. Cada um passou a
recusar, com a frase no campo, o `<` e o `>` (o texto sai cru em telas do ERP) e a tabulação ou outro caractere de controle
(a observação aceita a quebra de linha: é um campo de várias linhas); o emoji, que ainda dava erro 500 no MySQL no nome do
cliente e no motivo do desconto, também. **O que já está gravado não trava ninguém:** quem edita uma despesa, remarca um
agendamento, altera um cliente ou ajusta uma linha da comanda e manda o texto de volta como estava, passa; só o texto novo
ou mudado é conferido. Na cópia, 1 descrição de despesa, 15 motivos de fechamento e 4 motivos de desconto têm o sinal (quase
sempre o "->"); daqui em diante, o texto novo com ele é recusado com a frase. O formulário de despesa do ERP deixou de cortar
o `<` e o `>` calado (o `StripTags`): "x > y" virava "x  y" a cada edição. **O fechamento da agenda com emoji no motivo** (o
D421) deixou de dar 500 no MySQL depois do "pode fechar": as duas telas o recusam antes ("- Motivo sem emoji" na antiga, 422
na nova). **A retirada do caixa com motivo de mais de 100 caracteres ou com emoji** (o D393) deixou de dar 500: a API responde
422 no campo, e a tela antiga do caixa -- cujo campo não tinha limite -- diz "- Motivo com até 100 caracteres" ou "- Motivo
sem emoji". **Falta** do D495: o cliente na tela antiga dos clientes e nas etapas da fidelidade -- nenhuma fatia da fila o
tem ainda: fica no D495 para a próxima triagem dos débitos de pé --, e a sangria, que leva o motivo da retirada à descrição da
despesa sem a recusa do sinal.

**O provado.** O `TextoNasRegrasTest` (novo, 15 casos com os do provedor de dados) e casos novos no `FechamentoDaTelaAntigaTest`,
`D206D207SaidasPelaRotaNoErpTest`, `D304EtapaSeisDaAdesaoTest`, `D477DescontoAteOValorDoItemTest`, `AgendaAntigaComoANovaTest`,
`RemarcacaoDeAgendamentoTest` e `D208RetiradaNoFinanceiroTest`; esses e os vizinhos (38 arquivos, 768 casos) verdes nos dois
bancos. Os mutantes M1 a M21, um por vez, vermelhos nos dois bancos e devolvidos pelo sha1 -- no MySQL, o M17 (a API do
fechamento como antes) caiu no erro 1366 e o M18 (a retirada sem o teto) no 1406: os dois defeitos, reproduzidos.
O ciclo inteiro: caracterização 6.772 em cada banco (eram 6.744), contrato 74 e 74, os ensaios HTTP saída 0 nos dois bancos
(22 de 22 em cada), os portões 75 ok, o front 4.682 não tocado; o medidor dos relatórios no PostgreSQL, saída 0;
`contar-debitos`, `conferir-debitos-repetidos` e `conferir-segredos`, saída 0.
Nada da produção mudou; no `franquiabv_dev`, só SELECT de contagem. De pé 180.

**A verificação da fatia 110 (07/11/2026).** Um segundo agente tentou derrubar a fatia e confirmou dez achados (um médio),
consertados com caso. O médio: **a observação de várias linhas travava a remarcação pelo modal antigo** -- ela é gravada com a
quebra `\n` e o modal a devolve com `\r\n`, então a mesma observação parecia mudada, e a com o "->" era recusada; agora as duas
se comparam com a quebra numa forma só. Junto, a comparação deixou de usar o `trim` do PHP, que escondia a tabulação e o
controle colados nas pontas de uma observação já gravada. Os baixos: o motivo do desconto que não é UTF-8 pulava a conferência
e o PDV antigo dava 500 (agora é recusado); o cupom com `>` no nome deixava de se aplicar no PDV antigo (o motivo com a marca
do cupom aceita o sinal lá; o emoji e o controle, não); a tela antiga do caixa media o motivo da retirada sem a quebra de linha
do fim, que o banco recusa (cem letras e uma quebra davam 500); o nome do amigo só de espaços, que passou a ser recusado, ganhou
caso; o `!== 0` do emoji na descrição da despesa ganhou o caso que o distingue (conta os avisos do PHP); e o texto: o "Falta" do
D495 diz que nenhuma fatia da fila tem o cliente da tela antiga (o relatório da fatia apontava a 117, que é a ficha do usuário) e
passou a ter a sangria, e um comentário do `SaidasDoErpTest` foi corrigido. Os testes: os tocados e os vizinhos achados por grep
(31 arquivos, 737 casos) verdes nos dois bancos; os mutantes N1 a N7, um por vez, vermelhos nos dois bancos e devolvidos pelo
sha1; os portões 75 ok, `contar-debitos`, `conferir-debitos-repetidos`, `conferir-referencias-docs` e `conferir-segredos`
saída 0. De pé 180.

## 07/11/2026 -- PDV-ANTIGO-ABAS-E-CONVENIO (a fatia 111, débitos)

**O que muda para quem usa.** **Duas abas do PDV antigo com o mesmo usuário** (o D429, PARCIAL): abrir o `/vendas/novo`
descarta a venda em andamento de quem abre, também a da outra aba (ou do mesmo login em outro balcão). Até aqui a aba antiga
que lançava mais um item criava, calada, outra venda só com esse item -- os de antes tinham ido com a venda apagada, e, com a
venda da aba nova já aberta, o mesmo usuário ficava com duas vendas em andamento. Agora a aba antiga é recusada em todas as
portas -- lançar e tirar item, lançar pagamento, "Quitar" e finalizar -- com a frase *"Esta venda não existe mais: o PDV foi
aberto de novo com o seu usuário (em outra aba, janela ou computador), e cada abertura descarta a venda em andamento, com os
itens desta tela. Continue onde o PDV foi aberto por último. Atualizar esta página descarta também a venda em andamento de
lá."*, e nada é gravado (o precedente da aba antiga do D321). A venda de outra unidade segue com a frase de antes. O que o
título diz continua -- abrir o PDV apaga a venda da outra aba --, e parar de apagar (ou perguntar antes) foi ao lote: "Abrir o
PDV continua descartando a venda em andamento de outra aba? (D429)", recomendada a (a), como está. **O convênio da venda** (o
D632, MEDIDO): a causa foi achada e contada, sem mudar código -- a venda guarda o convênio do cliente de quando nasce, e o
item o de quando é lançado. O cliente que ganha o convênio depois de a comanda abrir leva o desconto nos itens, e a venda
segue "sem convênio" (na cópia, 2.338 itens em 1.708 vendas desde 2018; em 1.642 o primeiro item já é do convênio, a comanda
nascida vazia); e, ao contrário, a comanda que troca de cliente fica com o convênio do anterior (53 vendas com itens só de
outro convênio, o teto). O conserto foi ao lote: "A venda passa a guardar o convênio que o item recebeu? (D632)", recomendada
a (b), a venda segue o convênio do item, daqui para a frente.

**A verificação** (três lentes) confirmou 7 achados, consertados: a frase mandava "atualize esta página", o que apagava a
venda da aba nova, e falava só de "aba ou janela" (o `DELETE` é por usuário); as outras portas da aba antiga recusavam sem
dizer por quê; o D429 estava CORRIGIDO com o comportamento do título de pé (agora PARCIAL, e a escolha no lote); o "confirmado
pelo mutante M1" não tinha contado o banco (os casos agora conferem o banco antes da resposta, e o M1b o mostra); a pergunta
do D632 não citava a venda com o convênio do cliente anterior (reescrita, com a contagem); e a linha do D-d no desenho dos
Descontos e na tabela do `README.md` dizia "causa não lida".

**O provado.** `D429DuasAbasNoPdvAntigoTest` (novo, 4 casos, as duas abas despachadas em processo na ordem do navegador)
verde nos dois bancos; o caso do D320 no `D317A320RestosDoD316Test` passou a esperar a frase nova. Os tocados e os vizinhos
achados por grep (950 casos) verdes no MySQL e no PostgreSQL. Os mutantes, um por vez, nos dois bancos, devolvidos pelo
sha1: M1b (a recusa do lançamento arrancada) derruba os casos 1 e 2 mostrando no banco a venda nova e as duas em andamento;
M2 (a abertura sem o `apagarVendaTemporaria`, da primeira rodada) os três de então; M3, M4a, M4b e M4c (a recusa de tirar o
item, do pagamento, do "Quitar" e do finalizar) cada um a sua porta; M5 (o `descartadaPelaOutraAba` que nunca acha) o caso
3 e o do D320; M6 (a frase também para a venda de outra unidade) o caso 4. Os portões 75 ok; `contar-debitos`,
`conferir-debitos-repetidos` (o par D214|D632 caiu abaixo do limiar com a marca nova e saiu dos conhecidos),
`conferir-referencias-docs` e `conferir-segredos`, saída 0. O D632 foi contado no `franquiabv_dev` só com SELECT de
contagem. Não visto num navegador: a frase no aviso do PDV com as duas abas. O front não foi tocado. Nada da produção mudou.
De pé 180.

## 07/11/2026 -- MARKETING-CAMPANHAS-E-SORTEIOS (a fatia 112, débitos)

**A lista antiga dos sorteios não grava mais** (o D334, CORRIGIDO). Abrir a lista passava a "Aguardando Sorteio" todo
sorteio vencido da rede, dentro do GET. A escrita saiu (com o `validarVencimento`, o `passarAAguardando` e a disputa do D559),
e o filtro de "Status", a coluna e o "Realizar Sorteio" da lista antiga passaram a ler a `RegraDoSorteio`, como a tela nova
e o aplicativo: o sorteio vencido aparece "Aguardando Sorteio", com o botão, e fica `ativo` no banco até ser sorteado ou
cancelado. Para quem usa, a lista mostra o que mostrava.

**A busca das campanhas sem o acento** (o D618, PREPARADO E DESLIGADO). No PostgreSQL quem procura "promocao" não acha
"Promoção" (no MySQL acha, pela colação). Entrou o caminho do D119 para coluna natural: o termo sem o acento e a coluna sem o
acento pelo mesmo mapa (a função `SEM_ACENTO()`, o `TRANSLATE` no PostgreSQL), na lista antiga e na `/adm/api/notificacoes`,
atrás da chave `busca.campanhas_sem_acento` -- desligada, liga na janela no 7c do procedimento do corte, junto com a do D119.
Sem a chave, nada muda.

**A retomada da campanha entre dois nós** (o D419, PREPARADO). Conferido antes de consertar: a causa do débito (a trava por
máquina) foi consertada na fatia 105 -- no PostgreSQL, o único banco com mais de um nó, a trava das campanhas é também do banco,
e só uma rodada da rede roda de cada vez. A trava por linha que a triagem propunha não entrou (pediria duas colunas novas, e
o `ultimo_envio` como prazo quebraria a "parada" da 258 (a)). Ficou provado com a rotina de verdade e o WhatsApp de mentira.

**O provado.** Casos novos: `TelasDosSorteiosDoErpTest` (3: nenhum GET grava, o filtro e a coluna com o botão),
`D618BuscaDasCampanhasSemAcentoTest` (3) e `CampanhaDoDiaTest::testARetomadaSoSaiDeUmaRodada` (2 conjuntos; o da outra máquina
pula no MySQL); saíram os quatro que mediam a troca do vencido. O ciclo inteiro depois do conserto: caracterização 6.785 em
cada banco, contrato 74 e 74, ensaios HTTP saída 0 nos dois bancos, portões 75 ok, front 4.682 (não tocado), o medidor dos
relatórios no PostgreSQL saída 0. Os mutantes, um por vez e devolvidos pelo sha1: M1 (a escrita de volta no GET), M2 e M3 (o
filtro), M4 (a coluna), M5 (o botão), N1 a N5 (o `TRANSLATE`, o termo, a chave ligada, a API e a lista antiga sem a função),
P1 (a trava sem a conexão) e P2 (a rodada sem a trava) -- todos vermelhos. O portão da cobertura por par desceu de 456 para
455 pares (saiu `SorteioRepository::validarVencimento`) e o `NotificacaoRepository::listagemIndex` saiu dos falsos positivos;
o inventário de ações foi regerado (o `index` dos sorteios deixou de mutar). Nenhum envio real: só a rotina com o WhatsApp de
mentira, na suíte. Não visto num navegador. Nada da produção mudou. De pé 177.

**A verificação (07/11/2026)** confirmou cinco achados baixos, consertados. (1) No MySQL a `SEM_ACENTO()` devolvia a coluna
como estava, e o texto gravado com a marca do acento solta (a letra seguida da marca) não era achado -- o `LIKE` conta a marca
como uma letra a mais, e o PostgreSQL o achava: agora é `REGEXP_REPLACE` da faixa das marcas, com o caso
`testOTextoGravadoComAMarcaSoltaAchaNosDoisBancos`. (2) A paginação da lista antiga dos sorteios, reescrita em PHP, não tinha
caso: `testAPaginacaoDaListaAntigaEDepoisDoFiltro` (25 sorteios, as duas páginas, com e sem filtro). (3) A etapa 1 do corte
passou a citar a chave `campanhas_sem_acento`. (4) O D559 ganhou a anotação de que o método e os dois casos que ele citava
saíram com a escrita. (5) O D419 continua PREPARADO -- o argumento da trava da rotina foi conferido no código --, e o corpo
nomeia o que fica com o D44 (o PgBouncer em modo transação e a conexão que cai) e quando a trava por linha volta a ser o
conserto. Os testes da fatia e os vizinhos nos dois bancos, e os portões; mutantes novos, um por vez e devolvidos pelo sha1:
Q1 (a função do MySQL sem o `REGEXP_REPLACE`) e Q2 (a faixa só com o agudo), vermelhos no MySQL; Q3 (a página pedida
ignorada), vermelho nos dois bancos; Q4 (a página de 10), vermelho no MySQL.

## 07/11/2026 -- ROTINAS-NOVAS-E-CRONTAB (a fatia 113, débitos)

**O que entrou.** As duas limpezas que hoje moram dentro do MySQL da produção viraram rotinas do código (o **D26**; as
respostas 127 (a), 184 (a), 185 (a) e 195 (a)): `/api/cronjobs/pushAppLimpar` apaga da fila de mensagens a que espera há mais
de 24 horas e **poupa a que falhou** (ela sai em 7 dias, contados do pedido, pela rotina da própria fila), e
`/api/cronjobs/agendasExclusoesLimpar` apara o arquivo dos horários desmarcados aos 60 dias -- no molde das outras (o token e
o 401, a trava, a linha no log das rotinas), em lotes de 5.000 por causa das três réplicas. O crontab novo do corte ficou
**sem e-mail** (o **D331**; a resposta 128 (b)): cada linha termina em `|| logger -t rotinas "falhou (curl $?): <a rota>"`,
no domínio raiz, e a da apuração dos royalties manda também o `aviso` do retrato zerado ao log do sistema (o resto do
**D364**); a conferência do passo 9 deixou de exigir um endereço que nunca veio, e o 8 de "O que hoje bloqueia" passou a
testar o `logger`. E a rotina que cancela comandas abandonadas parou de chamar de "duplicada" a comanda do avulso e do "SEM
CADASTRO" (o **D635**): ela sai como abandonada.

**O que muda para quem usa:** nada hoje. As duas rotinas e o crontab novo só rodam na produção pela janela da etapa 1 (o
passo 12: as linhas de `docs/crontab-a-aplicar.md` às 35 de cada hora e às 02:40, e o evento de hora em hora desligado). Depois
dela, a mensagem de WhatsApp que falhou fica 7 dias registrada em vez de sumir em 24 horas, a rotina que falha deixa uma linha
`falhou` no log do sistema (`journalctl -t rotinas`), e as comandas do balcão canceladas pela rotina aparecem como abandonadas
na contagem das Vendas Canceladas por motivo.

**Medido num banco descartável** (o MySQL 8.4.13 local, apagado no fim): o `ALTER EVENT ... DISABLE` troca o `DEFINER` do
evento pela conta que o roda; com `ALTER DEFINER=<o de antes> EVENT ...`, ele fica. O passo 12 e a volta da etapa 1 passaram
a essa forma. E, sem o privilégio (o `SET_ANY_DEFINER` no 8.4; o `SET_USER_ID` no 8.0), a cláusula recusa e nada muda, e a
volta pela mesma conta também recusa: o passo 12 manda conferir o privilégio antes da janela e, recusada a cláusula na hora,
parar a linha com o evento ligado, como hoje -- não mais desligar sem ela, trocando o dono do evento na produção. O que fazer
nesse caso foi ao lote (TELAS-RESTANTES [corte], o **D26**).

**Provado:** `D26LimpezasNoCrontabTest` (9: a fronteira dos dois prazos dos dois lados, a falhada poupada, 5.001 linhas
saindo todas, a trava, a linha do log de verdade numa pasta descartável, e a resposta das duas rotas com um serviço de
mentira -- `sucesso: false` com a trava detida, o 401 sem o token) e
`ComandaFantasmaTest::testAComandaDoAvulsoNaoViraDuplicada`; os da fatia e os vizinhos, 1.111 em cada banco; o ciclo
inteiro sem o front (não tocado): caracterização 6.795 no PostgreSQL e, no MySQL, 6.795 com dois vermelhos em arquivos que a
fatia não toca (`D321FinalizarUmaVezSoTest::testOCreditoEsperaADevolucaoESaiPorCima` e `D351UnidadeDesativadaTest::
testARegraDaUnidade`), verdes sozinhos e na suíte inteira rodada de novo (6.795, saída 0); contrato 74 e 74, ensaios HTTP saída
0 nos dois bancos, o medidor dos relatórios no PostgreSQL saída 0;
`SegredosForaDoCodigoTest` cobre o token e a página das duas rotas novas sem lista. O `scripts/ensaiar-crontab-do-passo-9.sh`
reescrito, verde no Mac (bash e gawk) e no contêiner (dash e mawk): o caso certo, os recusados de antes e sete correções à mão
recusadas (sem o `logger`, outra marca, `www`, `%`, `MAILTO` com endereço, royalties sem o aviso e duas vezes), e as linhas
instaladas rodadas com um `curl` e um `logger` de mentira. Os portões 75 ok. Mutantes do servidor, um por vez nos dois
bancos e devolvidos pelo sha1, vermelhos: a falhada não poupada, o `<=` nos dois prazos, um lote só nos dois, a fila sem a
trava, o arquivo aos 59 dias, a linha do log do arquivo só com algo apagado, a fila sem a linha do log, a guarda do token
arrancada, o retorno do D635 arrancado e a rota do arquivo fora do roteador; e, nos portões, a rota fora da
`rotas-publicas.php` e a guarda arrancada no `conferir-resposta-cron.php`. **Sobrevivente:** a falhada poupada em qualquer
posição do nome da rotina (`LOCATE(...) = 0` no lugar de `<> 1`) -- equivalente na prática, porque nenhuma fila tem
`falhou:` no meio do nome. Mutantes do procedimento, numa cópia, vermelhos: cada condição nova da conferência, cada troca nova
do primeiro bloco e o 8 com `grep` no lugar do `zgrep`. O portão da cobertura por par subiu de 455 para 456 pares (o
`PushAppRepository::apagarPendentesAntigas`). **Não provado:** as rotinas e o crontab novo na produção ou no BVIP, e que o
log do sistema de lá guarda o que o `logger` escreve (o 8, no servidor). Nada da produção mudou. De pé 173.

**A verificação em três lentes** confirmou 7 achados (um médio), consertados. O médio: o teste do 8 contava o texto fixo, e a
linha do teste de antes da etapa 1 seguia no log quando ele rodasse de novo antes da etapa 2 -- com o caminho do `logger`
quebrado no meio, a contagem dava "chegou"; agora a linha leva a hora e o número do processo e só ela conta, e o ensaio roda
o 8 duas vezes no mesmo log, a segunda com um `logger` que sai 0 sem escrever (zero, e não mais o "conta zero" que o
documento dizia e o ensaio não media). Os baixos: a resposta `sucesso: false` das duas rotas sem caso (o
`testAsRotasDizemQueDesistiram`, mutantes R1 a R5 vermelhos nos dois bancos); o `ALTER EVENT` sem a cláusula como recurso do
roteiro (acima); a leitura 20 da etapa 1 e o `dia0-remediacao-producao.md` ainda com o `No MTA installed` e o `MAILTO`; e dois
comentários dizendo que a contagem dos 7 dias "foi ao lote", que a 185 (a) decidiu. Mutantes do 8, numa cópia do
procedimento, vermelhos: o texto fixo na contagem, a marca sem a hora e o número, o `logger` escrevendo o texto fixo e o
`grep` no lugar do `zgrep` (agora lido sem os comentários, porque a linha de agora nunca está num `.gz`).

## 07/11/2026 -- PDV-PERGUNTA-ANTES-DE-DESCARTAR (a fatia 131, a resposta 359 (b))

**Para quem usa o PDV antigo (Vendas > Nova):** abrir o PDV não apaga mais, calado, a venda que o mesmo usuário deixou em
andamento em outra aba, janela ou balcão. Se ela tem item, a tela pergunta, com a frase que o dono escolheu: "Você tem uma
venda em andamento com N itens, aberta em outra aba ou computador. Retomar ou descartar?" (com um só, "1 item"), mostrando o
código, o cliente e o total. **Retomar** abre a mesma venda, com o cliente, os itens, os pagamentos já lançados e o
"Quitar" como estava -- as duas abas passam a mexer nela. **Descartar** é o descarte de antes, agora por um botão de
formulário (POST), e o "Sim" do "Limpar" também. A venda sem item continua descartada sem perguntar. A aba que ficou com a
venda descartada continua recusada, com a frase nova ("ela foi descartada no PDV aberto com o seu usuário... Se atualizar
esta página, o PDV pergunta antes de descartar a venda em andamento de lá."). As frases do "Quitar" e da comanda que
mandavam recarregar o PDV dizem agora "recarregue o PDV e escolha descartar a venda em andamento". A tela nova não tem
esse fluxo e não chama o PDV antigo. O débito D429 fecha (CORRIGIDO). De pé 172.

**Provado:** `D429DuasAbasNoPdvAntigoTest` (12 casos, em processo, na ordem do navegador: a pergunta sem apagar nada, com o
banco conferido e a página desenhada; o retomar na mesma venda, sem apagar o pagamento, com as duas abas lançando nela até
uma descartar e a outra ser recusada; o retomar de outra venda perguntando de novo; o descartar por GET em 404 sem apagar;
a vazia descartada sem perguntar; as duas consultas novas pelo repositório; o N e a frase pelo texto), o caso do D380 que recarrega o PDV (agora com o "Descartar") e
os do D384 com a frase nova; os da fatia e os vizinhos achados por grep, 1.363 em cada banco; os portões 75 ok (a cobertura por par de 456 para 458). Mutantes, um por vez,
devolvidos pelo sha1, vermelhos nos dois bancos: o descarte de volta na abertura, o `NOT EXISTS` arrancado, o retomar
abrindo o PDV vazio, o descartar aceitando GET, a vazia que fica, o N fixo, a retomada apagando os pagamentos e o retomar
sem conferir o id; no MySQL, a regra perguntando na vazia e a frase velha; nos portões, o "Limpar" e o "Descartar" de volta
a link (remoção por POST) e o nome do cliente sem escape (a pergunta entrou nas views vigiadas). O inventário de ações
regerado (330 ações; o `descartar` novo). **Não visto num navegador** (é do coordenador): a pergunta, o "Retomar" com a venda
montada pelo servidor e o "Limpar" por POST. Nada da produção mudou.

**A verificação da fatia (o mesmo dia)** achou que o "Descartar" de uma pergunta velha apagava a venda que ninguém viu (o
mesmo login em dois balcões: um finaliza a venda da pergunta e lança outra; o outro, ainda na pergunta, descarta a nova).
Agora o botão leva o código da venda mostrada, e o "Limpar" o da venda da aba; se a venda em andamento mudou, nada é
apagado, e o PDV pergunta de novo, com o aviso "Nada foi descartado: a venda em andamento não é mais a que esta tela
mostrava...". Entraram os casos que faltavam: o PDV vazio de todo dia desenhado, a venda retomada na tela (os botões do
modal, o bloco do "Quitar" antes e depois, o aviso de comanda aberta), o caixa de ontem sem apagar a venda e a venda sem
cliente só com o "Descartar". Os comentários que ainda diziam "recarregar apaga" foram corrigidos, um deles no front (só
texto). Com duas vendas em andamento do mesmo usuário, o "Descartar" ainda leva as duas, como a letra da resposta; se deve
levar só a mostrada foi ao lote. O caso do D429 foi de 12 para 20, verdes nos dois bancos; 14 mutantes novos vermelhos nos
dois bancos; os vizinhos (77 arquivos) 1.197 em cada banco; os portões 75 ok. Nada da produção mudou.

## 07/11/2026 -- Onde a fila parou (pausa pedida pelo dono)

O dono pediu: "finalize esta fatia, atualize o roadmap e faça uma pausa". A fila dos débitos de pé, que a triagem de 07/11 montou (94 débitos autônomos em 30 fatias, a 101 à 130), andou assim desde o balanço depois da fatia 100: no main, as fatias **101 a 113** e a **131**, mais os seguimentos do CI (o fuso e o papel do PostgreSQL na 102; o ensaio do D111 nos dois bancos; o `padrao.jpg` do ensaio da VIP Class; o inventário de ações lido em ordem de caminho; o JIT do opcache desligado no CI, o D115). São 20 commits desde 696791ae, 956 no total. **O CI do GitHub está verde inteiro** desde a8324f15, a primeira execução verde desde 13/09/2026, e seguiu verde fatia a fatia, com duas quedas tratadas no caminho (o inventário que mudava de máquina para máquina e o `php -S` derrubado pelo JIT).

**Na régua dos débitos:** 172 de pé de 735 (eram 197 de 731 depois da 100); 563 não estão mais de pé (76,6%).

**As respostas do dono que viraram fatia:** a 359 (b), o PDV que pergunta "Retomar ou descartar?" antes de descartar a venda em andamento, é a fatia 131, feita. A 358, (c) com a (b) -- a escolha da (b) dada no chat em 07/11 --, a venda que segue o convênio do item e o roteiro que acerta as vendas antigas pelo nome do convênio no motivo, é a fatia **132**, escrita e ainda não rodada.

**O que espera:** a 132 e as fatias **114 a 130** da triagem, uma a uma, quando o dono mandar retomar; as perguntas ao lote até a 361 (a 361, nova: com duas vendas em andamento do mesmo usuário, o "Descartar" leva as duas?); a 87 a 89, a 91 e a 92g, que esperam o dono desde antes. **Não visto num navegador:** a pergunta do PDV e a retomada (131), a aba antiga do PDV (111), as frases novas dos campos de texto (110), a volta da personificação e a frase no topo do `/novo` (109), as grades da agenda (107) e a lista antiga de sorteios (112). **A vigiar:** dois casos do MySQL caíram uma vez na suíte inteira do ciclo da 113 e passaram repetidos (`D321FinalizarUmaVezSoTest::testOCreditoEsperaADevolucaoESaiPorCima` e `D351UnidadeDesativadaTest::testARegraDaUnidade`); se caírem de novo, rodada de causa.

## 07/11/2026 -- CONVENIO-SEGUE-O-ITEM (a fatia 132, a resposta 358 (c) com a (b))

**Para quem usa:** a venda passa a guardar o convênio que de fato deu o desconto. Quando o caixa lança um serviço e o item
recebe o desconto de um convênio, a venda grava esse convênio -- também se estava sem nenhum (o cliente que ganhou o
convênio depois de a comanda abrir) ou com outro (a comanda trocada de cliente, que ficava com o convênio do anterior). Vale
nas três portas: o PDV antigo, a comanda da tela nova (o "+" e o "-" também, que relançam a linha) e a comanda aberta pela
agenda. Com itens de dois convênios, vale o do último lançado; o item sem o desconto do convênio (produto, cliente sem
convênio, dia sem convênio, a linha em que o cupom venceu) não mexe. Nenhum valor muda. Com isso a venda entra na lista de
vendas do convênio certo, a "última venda" dele é atualizada no fechamento, e o relatório de Descontos deixa de pôr as vendas
novas na linha "sem o convênio na venda" (o verbete do glossário diz que ela fica só com as de antes). **O passado** é um
roteiro que o dono roda na produção quando quiser, fora da janela (`scripts/acertar-convenio-das-vendas.sql`, e o irmão no
PostgreSQL): grava nas vendas antigas o convênio pelo nome do motivo do último item do convênio, só quando o nome bate com
exatamente um convênio, com a contagem antes, a lista com o convênio de antes (o caminho de volta) e a conferência; rodado de
novo, não muda nada. Na cópia, mudaria 1.181 vendas sem convênio e 46 com outro; o roteiro não toca 38 que citam um nome que
não existe mais e 127 um nome que dois convênios têm -- das 127, 120 já guardam um dos dois, e só 7 estão sem convênio. Na
linha "sem o convênio na venda", que só lê as concluídas, ele tira 1.152 vendas e deixa 41. Os procedimentos o citam na parte
de depois da janela.

**O provado:** `D632VendaSegueOConvenioDoItemTest` (18 casos, as três portas, o PDV e a API despachados em processo, a agenda
também num dia sem convênio) e `AcertoDoConvenioDasVendasTest` (3 casos, o roteiro inteiro sobre treze vendas, duas rodadas),
nos dois bancos; o roteiro rodado inteiro pelo `mysql` e pelo `psql`, duas vezes, em dois bancos descartáveis montados pela
receita do contrato, saída 0, o dinheiro igual antes e depois, os bancos apagados; 15 mutantes (três deles também no
PostgreSQL) vermelhos e devolvidos pelo sha1. O ciclo inteiro: a caracterização 6.830 em cada banco, o contrato 74 e 74, os
ensaios HTTP 22 e 22 com saída 0, os portões 75 ok, o front 4.682 com o build e o lint, o medidor dos relatórios no
PostgreSQL saída 0. Na cópia, só SELECT de contagem. D632 PREPARADO; o **D738** novo (a venda que guarda um convênio sem
nenhum item com o desconto dele: a comanda trocada de cliente, que a (b) da 358 prometia resolver, e o item do convênio que
sai da venda, caminho que a fatia abriu) e a "última venda" dos convênios no acerto do passado foram ao lote. **A
verificação em três lentes** confirmou 6 achados (um médio: a guarda do `convenioAtivo` na comanda da agenda sem caso, o
mutante M6), consertados com caso ou levados ao lote; depois do conserto, a caracterização inteira de novo, 6.834 em cada
banco, e os portões. Não visto num navegador. Nada da produção mudou. De pé 172.

## 07/11/2026 -- ESQUEMA-QUE-SO-ACRESCENTA (a fatia 114, débitos)

**Para quem usa:** nada muda na tela hoje; muda o que a janela do corte leva, e o que o relatório de NPS conta daqui em
diante. Três migrações novas no laço do passo 0 (`docs/procedimento-de-corte.md`, com a linha `| 0 | **Aplicar**`, o laço do
ensaio geral e o `mysql.sql` juntos; o `scripts/conferir-passo-zero-do-corte.php` saiu 0): o **índice da grade da agenda**
(`scripts/migracao-agendas-indice-colaborador-data.sql`, o **D189**, as respostas 126 (a) e 196 (b)) troca o
`FK_AGENDAS_colaborador` de uma coluna pelo de (`colaborador`, `data`) com o mesmo nome, no molde da das vendas por cliente --
com que a grade de um dia passou de 17,5 s a 0,72 ms na cópia; o **índice das saídas do estoque pela data**
(`scripts/migracao-produtos-estoque-indice-data.sql`, o **D630**): o mês da Retirada, no MySQL, de 5,2-6,5 ms a 0,58-0,64 ms
na rede (no PostgreSQL o planejador segue varrendo, com o `random_page_cost` de fábrica, e com o de NVMe entra pelo índice);
e a **coluna do envio do NPS** (`scripts/migracao-nps-envio.sql`, o **D648**): cada envio do formulário de satisfação grava
o mesmo número em todas as linhas dele, e o relatório de NPS junta as pesquisas por esse número -- a cadeia de 60 s fica só
para as respostas de antes. Esta é condição de subir (a entidade a mapeia). E o **gerador do PostgreSQL** (o **D622**) põe o
inquilino na frente da chave primária que não é o `id` (hoje só a de `colunas_gravadas`): um segundo inquilino grava a data
da mesma coluna. As duas cópias locais receberam o índice das saídas e a coluna às 18:56 de hoje, duas vezes cada (a segunda
sem efeito; o PostgreSQL pelos irmãos em `postgres/`); o da grade elas já tinham desde 02/10/2026. A chave nova do D622 ficou
fora da cópia PostgreSQL (trocar chave não é só acrescentar): a produção nasce do `schema.sql` no corte. O `postgres/schema.sql`
e o `scripts/migrar-dados.sh` regerados.

**O provado:** `PassoZeroDoEsquemaQueSoAcrescentaTest` (4), `EsquemaQueSoAcrescentaTest` (6, um só no PostgreSQL: dois
inquilinos gravam a mesma coluna), `D648UmEnvioPorFormularioTest` (3) e o caso novo do `RegrasDoNpsTest`, nos dois bancos; 10
mutantes (o `setEnvio` do serviço, o `envios` sem o número, a consulta sem o `nps.envio`, o gerador sem o inquilino, o
`schema.sql` com a chave de antes, o `KEY` da grade de volta a uma coluna, a migração da grade com uma coluna, o índice das
saídas fora do `mysql.sql`, a migração fora do laço, a coluna `int`), um por vez, todos vermelhos e devolvidos pelo sha1:
o `setEnvio` e a consulta nos dois bancos, o `envios` sem o número de começo só no MySQL (e no PostgreSQL na correção, abaixo),
o `schema.sql` só no PostgreSQL e os do gerador, do `mysql.sql` e das migrações só no MySQL. O ciclo inteiro: a caracterização 6.848 em cada banco, o contrato 74 e 74, os ensaios HTTP
22 e 22 com saída 0, os portões 75 ok, o front 4.682 (não tocado), o medidor dos relatórios no PostgreSQL saída 0. Na cópia,
só SELECT e as migrações que só acrescentam. D189, D630, D648 e D622 PREPARADOS (falta a janela). Não visto num navegador.
Nada da produção mudou. De pé 168.

**A correção da verificação** (6 achados confirmados, 4 distintos, todos consertados com caso): (1) as migrações de índice
tinham rodado só no ramo "já existe" (nas cópias), e nenhum caso executava o `.sql` -- agora ensaiadas num banco descartável,
apagado no fim, com as tabelas da cópia na forma da produção: a troca da grade em 3,6 s nas 4.000.085 linhas, com a chave
estrangeira de pé, a segunda vez sem efeito, e a parada com o `mysql` saindo 1 e o `|| break` do laço parando nela; e o
`EsquemaQueSoAcrescentaTest` ganhou três casos que rodam os roteiros da grade, das saídas e do envio inteiros, em tabelas de
ensaio, a cada suíte do MySQL; (2) o glossário do NPS (`nps.envio`) dizia só a cadeia de 60 s: agora diz que as pesquisas de
um mesmo envio contam como um só, e que a cadeia é das antigas (e o mesmo na decisão E1 do `spec-nps.md`); (3) a frase dos
mutantes acima, que dizia "os de servidor nos dois bancos" e o do `envios` só tinha rodado no MySQL -- rodado no PostgreSQL,
vermelho; (4) a linha do `ContasDoNps::envios` que faz da pesquisa juntada pelo número a anterior não tinha caso --
`RegrasDoNpsTest::testADeAntesDepoisDaQueEntrouPeloNumeroComecaEnvioNovo`. Os mutantes novos, um por vez, vermelhos e
devolvidos pelo sha1: a linha tirada (nos dois bancos), o ramo da troca da grade com outro literal, a parada da grade e a das
saídas trocadas por um `SELECT`, o ramo "sem o índice" das duas migrações, o `IF` da coluna invertido e a frase de antes no
glossário. Depois: os casos da fatia nos dois bancos, o front inteiro (4.682, o build e o lint) e os portões.

## 07/11/2026 -- ROTAS-404-E-401 (a fatia 115, débitos)

**O que entrou:** os débitos D424, D14 (o resto dos apelidos), D621 e D146 (o resto do JSON). (1) O `CrudController` declara o
`indexAction`, e ele é o 404: os 12 `/<módulo>/<apelido>/index` que as seis curingas alcançam (`/mod-vendas/caixas/index`,
`/mod-configuracoes/whatsapp/index` e os outros dez) deixam de responder o 500 de "Unable to render template" do `index` de
marcador do Laminas, sem redirecionar e antes de qualquer pergunta de privilégio. (2) O `conferir-rotas-orfas.php` desce a
config mesclada, como o `conferir-rotas-sem-login.php`: confere a ação de cada rota-filha da `/adm/api` contra a classe de
verdade -- eram 87 pares lidos, agora 398, 312 em rotas-filhas --; no CI o passo foi para depois do `composer install`. (3)
Sem sessão, o pedido que espera JSON (o `$.ajax` das telas antigas, pelo `X-Requested-With`, ou o `Accept` que só pede JSON)
recebe 401 `nao_autenticado` em JSON, a convenção da `/adm/api`, e não mais a tela de entrar com 200
(`Permissions\RecusaSemSessao`, chamada pela guarda de login); o `geral.js` ganhou um `ajaxError` que, nesse 401, tenta a
entrada pelo cookie (`POST /adm/api/sessao/retomar`) e só sem ela leva ao entrar (o `?cache=` 165).

**O que muda para quem usa:** quem estava numa tela antiga quando a sessão do ERP expirou, com o "lembrar" do navegador ainda
valendo (até 12 horas, o D77), e clica em algo que faz pedido em segundo plano, continua na mesma tela: a sessão volta sozinha,
como antes, e o clique seguinte funciona. Quando não há volta (outro aparelho entrou, o privilégio mudou, a unidade foi
desativada, o "lembrar" venceu), a pessoa vai para a tela de entrar; com a mensagem do motivo no outro aparelho e no privilégio
mudado, e sem mensagem na unidade desativada e no "lembrar" vencido, como já era na navegação. Antes, esse clique não fazia
nada, ou mostrava "Erro", ou desenhava o formulário de entrar dentro de um modal. Um endereço `/<módulo>/<apelido>/index`
digitado mostra "não encontrado" em vez de erro. A navegação comum não muda.

**O provado:** os casos novos -- `D14RaizDoModuloDeVendasTest::testNenhumApelidoCaiNoIndexDeMarcadorETodoIndexSemTelaE404`
(deriva do `ControllerManager` todo apelido, e despacha os doze pelo `index`: 404) e dois no `GuardaDeLoginTest` (o 401 pelo
ouvinte de verdade, com sete formas de pedido, e a regra sozinha em 12) --, nos dois bancos; por HTTP, o caso 6 novo do
`ensaiar-links-consertados-do-erp.php` (quatro apelidos, 404; o caso 5 exige agora o 404 do link morto) e o caso 26 novo do
`ensaiar-sessao-do-erp.php` (o ajax sem sessão, 401 `application/json`; a navegação, 200 com o formulário), no MySQL e no
PostgreSQL; no front, `sessaoCaidaNaTelaAntiga.teste.ts` (5) roda o `geral.js` servido. Os mutantes, um por vez, vermelhos e
devolvidos pelo sha1: sem o `index` do `CrudController` (o caso nos dois bancos; o ensaio mede os quatro em **500**); a guarda
sem o ramo do JSON (os casos nos dois bancos; o ensaio mede o ajax em **200 `text/html`**); a regra larga e a regra sem o
`X-Requested-With` (nos dois bancos); a ação `verAction` dos cupons renomeada (o portão novo sai 1; o do HEAD sai **0** sobre o
mesmo mutante, que era o D621); a rota apontada para o `index` sem tela (saída 1, e 0 sem a regra no portão); e, no `geral.js`,
o ouvinte sem o desvio, o que leva qualquer 401 e o cache de volta ao 164. O ciclo inteiro: a caracterização 6.854 em cada
banco, o contrato 74 e 74, os ensaios HTTP 22 e 22 com saída 0, os portões 75 ok (o `conferir-guarda-do-erp.php` com o `index`
do `CrudController` na lista como COTO, e o inventário de ações regerado: 331, com ele), o front 4.687 com o build e o lint, o
medidor dos relatórios no PostgreSQL saída 0. D424, D621 e D146 CORRIGIDOS; o D14 PARCIAL (fica a V1 de agendamento, que espera
os logs da produção). Não visto num navegador: o `ajaxError` com o jQuery de verdade, numa tela antiga com a sessão derrubada.
Nada da produção mudou. De pé 165.

**A correção da verificação:** nove achados confirmados, consertados (o detalhe no corpo do D146). (1, médio) Com o 401 e o
ouvinte indo direto ao entrar, quem tinha a sessão do PHP vencida e o `token_web` valendo entrava pelo cookie no `GET` do
entrar e caía na tela inicial, fora da agenda ou do PDV: o ouvinte agora pede antes o `retomar` (uma vez, `global: false`) e,
com a sessão de volta, deixa a pessoa na tela. (2) O expulso pelo ajax: a mensagem gravada no pedido do 401 chega ao entrar
dois pedidos depois -- casos 27 e 28 novos no `ensaiar-sessao-do-erp.php`. (3) A `RecusaSemSessao` lia os cabeçalhos pelo
Laminas, que lança exceção num byte inválido (`Accept: \xff`), e o anônimo levava 500: agora lê cru, por
`OrigemDoPedido::cabecalho`. (4) As duas regras de "quem pede JSON" (a da barreira de origem, larga, e a da guarda, estreita)
ficam diferentes de propósito, escritas nos dois comentários e fixadas por um caso. (5) O detector do D539 por ensaio ficou em
parte cego (o ensaio só JSON recebe o 401 da guarda); o portão `conferir-rotas-sem-login.php` o pega (medido: sai 1 com a
família `adm-api-notificacoes` arrancada), escrito no D539 e nos desenhos `tela-entrada.md` e `tela-assistentes.md`. (6-9)
Frases corrigidas no D146, no `ci.yml` (três portões do job precisam do `vendor/`), no caso 6 do
`ensaiar-links-consertados-do-erp.php` e na `RecusaSemSessao`. **Provado:** o `GuardaDeLoginTest` com 11 casos nos dois bancos,
o `ensaiar-sessao-do-erp.php` 28 de 28 no MySQL e no PostgreSQL, o `sessaoCaidaNaTelaAntiga.teste.ts` com 8. **Mutantes**, um
por vez, vermelhos e devolvidos pelo sha1: a leitura pelo Laminas de volta; a regra da barreira trocada pela da guarda; o
`retomar` que gasta o flash (o caso 28 vermelho e o 9 verde); e, no `geral.js`, direto ao entrar, ao entrar mesmo com a sessão
de volta, sem o `global: false` e sem a trava do retomar em andamento.

## 07/11/2026 -- API-ADM-IDS-E-PDV (a fatia 116, debitos)

**O que entrou.** D516 (PARCIAL): o número que a coluna não guarda é o que não existe em toda a `/adm/api`. Um ajudante só
no `ApiAdmController` (`idDaRotaQueCabe`, `acharPelaRota`, `acharPeloId`, pela `ISBase\Util\IdDaColuna`) tomou o lugar das 43
chamadas `find((int)` de 16 controllers, dos outros `(int)` do número da rota que iam ao banco (o cliente, o caixa, o item e o
pagamento da comanda, o relatório de caixa) e das três cópias da guarda; e, no PDV antigo, do `venda_id` e do `id` do item e do
pagamento. O portão novo `scripts/conferir-id-que-cabe.php` (no `conferir-tudo.sh` e no `ci.yml`) acusa o `find((int)` cru que
voltar. Fica no débito a busca (`fromQuery`, como o `?grupo=` da lista de usuários) e o ERP antigo fora do PDV. D517
(CORRIGIDO): a comanda nova oferece como quem atendeu só quem tem "Aparece no PDV: sim", como o PDV antigo -- a lista de
`GET /adm/api/usuarios` ganhou a porta do PDV (`?porta=pdv`), e o lançamento do item recusa com 422 quem o cadastro tira do
PDV; a comanda aberta pela agenda continua no nome de quem atendeu.

**O que muda para quem usa.** No PostgreSQL, o endereço com um número grande demais responde "não encontrado" em vez do erro
do servidor (no MySQL já era assim). Na comanda nova, a lista de quem atendeu e a de quem pediu a retirada do estoque deixam de
mostrar quem tem "Aparece no PDV: não" (na cópia, 103 ativos em 48 unidades). A gorjeta do troco não muda: segue com a lista
de antes, até o dono dizer quem a recebe (o D739, novo).

**O provado.** `D516IdQueNaoCabeNaColunaTest` (183 casos: as 159 rotas com número derivadas da configuração, 12 números de
dentro, 10 ids do corpo com o controle, os dois da busca da agenda) -- antes do conserto, 84 com erro no PostgreSQL e todos
verdes no MySQL --, `D517QuemAtendeNoPdvTest` (6) e o caso novo do `D429DuasAbasNoPdvAntigoTest` (2), nos dois bancos; no front,
`catalogo.teste.ts` (2) e as listas de pedidos do `ComandasLigada.teste.tsx`. O ciclo inteiro: a caracterização 7.047 em cada
banco, o contrato 74 e 74, os ensaios HTTP com saída 0 nos dois bancos, os portões 76 ok, o front 4.692 com o build e o lint, o
medidor dos relatórios no PostgreSQL saída 0. Os mutantes, um por vez, devolvidos pelo sha1: o ajudante e a `IdDaColuna` sem o
teto (97 e 102 erros no PostgreSQL, e o portão), a forma do débito, os serviços da agenda e o caixa de volta ao `find` cru (no
PostgreSQL), o `comandaDaUnidade` de volta (só o portão: a `vendas.id` é `bigint` no PostgreSQL), as três portas do PDV antigo
(no PostgreSQL), a recusa do D517 arrancada (nos dois bancos), o filtro da consulta arrancado (nos dois), a porta ignorada, a
recusa sem a conferência da unidade e a porta tirada do front (três casos no vitest). Com `AMPLIFICAR_EFEITOS_MS=50`, o caso
"as pílulas do catálogo andam pelas setas" do `ComandasLigada.teste.tsx` cai -- e cai igual no HEAD, sem esta fatia: é o D471,
da fatia 124. Não visto num navegador. Nada da produção mudou. De pé 164.

**A verificação (07/11/2026).** Três lentes confirmaram sete achados, todos tratados. No D517: a gorjeta do troco tinha mudado
calada (a lista do PDV ia também ao destino do troco) -- agora a porta do PDV só vale com `?porta=pdv`, e a comanda nova pede
uma lista à parte para a gorjeta, a de antes; quem recebe gorjeta foi ao lote como o D739 (o PDV antigo oferece só o grupo
Colaborador; a recomendação é a (a), igual ao antigo); a retirada do estoque e o PDV antigo, que seguem com o filtro só na tela,
ficaram escritos no D517. No D516: a forma do pagamento da comanda e o serviço e o cliente da marcação iam crus a um `find` por
uma variável (500 no PostgreSQL), e passaram ao `acharPeloId`; três cópias do teto que sobravam em `Controller/Adm/` (a retirada
do `ProdutosController`, o `CotaParteController`, o `ProfissionalParceiroController`) passaram ao ajudante, e o portão acusa
agora o `const MAIOR_ID` e o 2147483647 antes de um `find`; os ids do corpo são pedidos também com 2147483648 (o caso do
fechamento da agenda ficava verde sem o conserto); e a remoção do item no PDV antigo, que já era segura, deixou de ser contada
como conserto. O `destinoTroco` da finalização vai cru a um `getReference` (lido, não medido) e espera o D739. Os testes: o
`D516IdQueNaoCabeNaColunaTest` com 186 casos, o `D517QuemAtendeNoPdvTest` (6) e o `D429DuasAbasNoPdvAntigoTest` verdes nos dois
bancos; no front, o caso novo da gorjeta no `ComandasLigada.teste.tsx` e o de `catalogo.teste.ts`. Os mutantes, um por vez,
devolvidos pelo sha1: V1 (a porta de volta para quem só entra pelo PDV: vermelho nos dois bancos), V2 (o `Finalizar` com a lista
do PDV: vitest), V3, V4 e V5 (a forma, o serviço e o cliente com o `find` cru: erro no PostgreSQL, verde no MySQL), V6 (o
fechamento da agenda com a guarda do `HEAD`: erro no PostgreSQL com 2147483648, e o portão), V7 e V8 (as cópias do teto de volta:
o portão). O ciclo inteiro depois da correção: a caracterização 7.050 em cada banco, o contrato 74 e 74, os ensaios HTTP com
saída 0 nos dois bancos, os portões 76 ok, o front 4.694 com o build e o lint, o medidor dos relatórios no PostgreSQL saída 0. Com
`AMPLIFICAR_EFEITOS_MS=50`, caem o caso das pílulas (o D471) e seis do `Console.teste.tsx` -- os seis caem igual com os arquivos
do `HEAD`. De pé 165 (o D739 novo).

## 07/11/2026 -- USUARIOS-FICHA-ANTIGA (a fatia 117, debitos)

**O que entrou.** Três débitos fechados (CORRIGIDO) e o D530 PARCIAL. D522: a ficha antiga do usuário mostra o telefone pelas duas partes da
tela nova (`RegraDosDadosDoUsuario::partesDoTelefone`) e só o confere e regrava quando o DDI ou os dígitos do número mudam
(`doFormularioDoErp`); mudado, passa pela regra do `PUT`, com a frase no campo. D528: o login trocado na ficha antiga não termina
no sufixo de quem está inativo nem passa do texto do cadastro (`RegraDoUsuario::fraseDoLoginTrocado`), e só quando muda. D476: o "Meus dados" grava o que o
formulário filtrou, e não o POST cru. D530: `GET /adm/api/usuarios/unidades` devolve o nome do cadastro da unidade e o lugar, e o
filtro e o cadastro da tela nova mostram "nome (lugar)"; as listas suspensas do ERP não mudaram. O D530 fica PARCIAL: os filtros
dos relatórios novos, o do Parceiro e as outras listas novas de unidades seguem só com o lugar (escrito no corpo dele).

**O que muda para quem usa.** Na ficha antiga, quem tem o telefone gravado sem o código do país (na cópia, 366 dos 371 ativos) vê o
DDI vazio, e não mais o DDD nele; salvar sem tocar no telefone o deixa como estava (antes virava "+(47) ..."), e os 5 ativos sem
telefone salvam a ficha sem precisar inventar um. Trocar o login para um terminado em "_old" é recusado no campo. No "Meus dados", o
nome digitado com marcas de HTML grava sem elas. Na tela nova, as duas unidades do mesmo bairro (duas duplas na cópia) se distinguem
pelo nome no filtro e no cadastro de usuários.

**O provado.** Quatro casos novos no `UsuarioDadosPelaApiTest` (25 casos, 482 asserções) verdes nos dois bancos, e os vizinhos -- os
63 arquivos que citam os controllers, as regras e o filtro de usuários, mais o `D516IdQueNaoCabeNaColunaTest`: 1.441 casos, 3
pulados, verdes no MySQL e no PostgreSQL. No front, `api/usuarios.teste.ts` (novo) e os casos do filtro e do cadastro: os
tocados e os vizinhos, 616 casos, verdes, e os tocados também com `AMPLIFICAR_EFEITOS_MS=50`; o build e o lint saída 0. Os ensaios
HTTP dos usuários e das permissões, saída 0 nos dois bancos. Os portões 76 ok (o de cobertura por par subiu a base do
ISConfiguracao de 221 para 222: a lista de unidades chama o `nomesDoCadastro`). Os mutantes, um por vez, devolvidos pelo sha1: M1 o
POST cru no "Meus dados", M2 a ficha pelo `getDdi`, M3 a regravação do telefone de volta, M4 o telefone fora da regra, M5 a
comparação pelo texto e não pelos dígitos, M6 a recusa do sufixo arrancada, M7 a recusa sem a pergunta "mudou?", M8 o servidor sem o
nome, M9 o DDI obrigatório no filtro da ficha -- todos vermelhos nos dois bancos; F1 o rótulo sem o lugar, F2 a lista sem o rótulo,
F3 o nome vazio, vermelhos no vitest. Não visto num navegador (a máscara do telefone foi lida, não exercitada). Nada da produção
mudou. De pé 161.

**A verificação em três lentes** confirmou 8 achados, consertados com caso ou levados ao texto. O D530 voltou de CORRIGIDO a
PARCIAL: o D617 diz que o rótulo repetido dos filtros dos relatórios é ele, e esses filtros (e o do Parceiro, e as outras listas
novas de unidades) seguem só com o lugar; o comentário do `ProfissionalParceiroController` dizia que a lista dele tinha a forma da
dos usuários. Na ficha antiga, o "*" de obrigatório saiu do DDI e do telefone (fica no cadastro). Casos novos no
`UsuarioDadosPelaApiTest`: só o DDI mudado grava o telefone; o telefone vazio só é recusado a quem o muda (quem não tem telefone
salva, quem apaga o gravado ouve a frase no campo), com o rótulo sem o asterisco; e o login trocado passa pelo texto do cadastro
(tabulação, emoji e 101 caracteres recusados no campo -- antes o de 101 dava 500). No `UsuarioCadastroESenhaPelaApiTest`, o espelho
da ficha (`postDaFichaAntiga`) manda o telefone como o GET o mostra, com caso. Os dois arquivos: 55 casos, 1.026 asserções, verdes
nos dois bancos; os vizinhos (os 54 arquivos que citam o formulário, as regras e os controllers de usuários, o Parceiro e o D516):
1.296 casos, 1 pulado, verdes nos dois. Os mutantes novos, um por vez, devolvidos pelo sha1, vermelhos nos dois bancos: N1 a pergunta
pelo DDI arrancada, N2 o telefone obrigatório de volta no filtro da ficha, N3 o asterisco de volta na ficha, N4 o texto do login
arrancado, N5 o espelho de volta ao `getDdi`, N6 a pergunta "mudou?" do login arrancada. De pé 162 (o D530 PARCIAL).

## 07/11/2026 -- UNIDADES-TELA-ANTIGA (a fatia 118, debitos)

**O que entrou.** Três débitos fechados (CORRIGIDO) e o D377 PARCIAL, todos na tela antiga de unidades. D506: os combos de país,
UF, cidade e bairro da lista antiga são os da situação pedida (`UnidadeRepository::opcoesDosFiltros`, o mesmo da API), a UF com o
nome da tabela de estados, e o escolhido continua no combo; as duas consultas só das ativas saíram. D509: o formulário do sócio
desenha o token e o `socioSalvar` o confere, sem prazo e com sal próprio. D448: o nascimento do sócio e o do usuário (no cadastro e
na ficha antigos) passam pelo `dataValida`, e a inauguração da unidade também; a contratação já era da regra desde 18/10. D377, o
U18: o NPS nulo diz "não apurado" e o zero "0"; o cadastro grava a unidade e a cópia dos textos numa transação só (a falha na cópia desfaz a unidade; sem a linha ativa da unidade 1 a cópia ainda não acontece, e de onde copiar foi ao lote); a chave do
WhatsApp num campo de senha que a página devolve como leu; o `tipo` mapeado com 30; a aba "Mensageria"; e o modal da lista antiga
de mensagens diz "desativar". Seguem no D377 o DS-8, o M10, o resto do Dη, o link de boas-vindas e a origem dos textos da unidade nova.

**O que muda para quem usa.** Na lista antiga de unidades, buscar as inativas oferece as cidades, os bairros e os países delas (na
cópia, 14 das 28 inativas ficavam de fora), e o NPS ainda não apurado não se confunde com zero. Salvar um sócio com a página aberta há
muito tempo continua funcionando; o pedido forjado sem o token é recusado. A data de nascimento digitada errada no sócio, na ficha
ou no cadastro de usuário, e a inauguração da unidade, voltam com a frase no campo, e não com a tela de erro. A chave do WhatsApp
não aparece mais em claro na tela.

**O provado.** Casos novos no `TelasDasUnidadesDoErpTest` (18 casos, 196 asserções), no `UsuarioDadosPelaApiTest`, no
`UsuarioCadastroESenhaPelaApiTest` e o das mensagens no `D199AtivarRemoverPorPostTest`, verdes nos dois bancos. O ciclo inteiro: a
caracterização 7.069 em cada banco, o contrato 74 e 74, os ensaios HTTP com saída 0 nos dois bancos, os portões 76 ok (o de
cobertura por par desceu os pares de 458 para 456, o "sem menção" de 110 para 108 e o ISConfiguracao de 222 para 220: as duas
consultas saíram), o front 4.697 com o build e o lint, e o medidor dos relatórios no PostgreSQL saída 0. Os mutantes, um por vez,
devolvidos pelo sha1, vermelhos nos dois bancos: M1 a chave pelo campo de senha do Laminas (o valor apagado), M2 e M18 o campo de
texto de volta no editar e no novo, M3 e M4 o "Menasageria", M5 o `length=20`, M6 o NPS pelo `!empty`, M7 a cópia dos textos depois
do `commit`, M8 os filtros só das ativas, M9 o escolhido fora do combo, M10 a UF sem o nome, M11 o token não conferido, M12 o prazo
de fábrica, M13 o sal de fábrica, M14 o token fora da página, M15 o `dataValida` do sócio arrancado (o 500 de volta), M16 o do
usuário, M17 o "remover" no modal das mensagens, M19 o da inauguração. Não visto num navegador. Nada da produção mudou.

**A verificação em três lentes** confirmou 9 achados (dois médios), consertados com caso ou no texto. Os combos da lista antiga voltaram à ordem do que aparece (a UF pelo nome, e não pela sigla; o escolhido de fora no lugar dele), e a situação é uma só para a lista e os combos (o `?status=` vazio, escrito à mão, dava as inativas na lista do MySQL e o 500 do booleano no PostgreSQL); o valor da chave do WhatsApp ganhou caso com aspas e `&`, no editar e no novo. Os médios: o D448 tinha sido fechado sem o levantamento que o corpo deixava ao item que o consertasse -- ele foi ao D740 novo, com a medida do validador `Date` (o do convênio aceita `10/01/90`, que vira o ano 90); e a transação da mensageria só cobre a falha na cópia: sem a linha ativa da unidade 1 (4 das 64 unidades na cópia estão sem a delas), a unidade nova ainda nasce sem os textos -- de onde copiar é do dono, e foi ao lote ("De onde a unidade nova copia os textos das mensagens", D377). Os itens consertados do D377 foram riscados no lugar, e o desenho das mensagens ganhou a nota do modal; os comentários da `RegraDoCadastroDoUsuario` dizem que a ficha antiga confere a data. Os testes: `TelasDasUnidadesDoErpTest` com 21 casos e 227 asserções, verdes nos dois bancos; os mutantes novos, vermelhos nos dois e devolvidos pelo sha1: N1 as UFs pela sigla (`ksort`), N2 sem ordem, N3 o status cru de volta na lista (no PostgreSQL, o erro do booleano), N4 e N5 o valor da chave sem o `escapeHtmlAttr` no editar e no novo. De pé 160 (o D740 novo).

## 07/11/2026 -- CATALOGO-CADASTRO-SEM-POST-CRU (a fatia 119, debitos)

**O que entrou.** O D679 e o D537, os dois CORRIGIDO. O cadastro novo de produto, de serviço e de pacote do ERP antigo grava só os
campos que a tela de cada um mostra (`CAMPOS_DO_CADASTRO` no `insert` do `ProdutoService`, do `ServicoService` e do
`PacoteService`, o molde da edição do D202): antes ia o formulário inteiro ao hidratador, e uma chave posta à mão -- a quantidade
do estoque, a linha VIP, o insumo, os períodos, a categoria do pacote -- chegava ao banco, em qualquer caixa. E o formulário
antigo de serviço trata a área de atuação como já tratava a etiqueta (o D536): o editar oferece as ativas e a que o serviço já
tem, com "(inativa)" (`RegraDoCatalogo::opcoesDaArea`), o novo só as ativas, e o formulário aceita só essa lista, estrita.

**O que muda para quem usa.** Quem edita um serviço cuja área de atuação foi desativada (na cópia, 193 serviços ativos em
19/10/2026) salva a ficha sem ser obrigado a trocar a área; a área de outra lista, a desativada que o serviço não tem e a que
não existe são recusadas no campo com uma frase, em vez de gravar ou dar a tela de erro. O cadastro pelas três telas grava o
mesmo de antes para quem usa a tela; só o pedido montado à mão perde o que a tela não manda.

**O provado.** `D679CadastroComOsCamposDaTelaTest` (7 casos) e `D537AreaDoServicoNaTelaAntigaTest` (5), verdes nos dois bancos; o
D679 conferido vermelho no código de antes (as três telas gravavam a chave a mais). Os vizinhos, derivados por grep dos serviços,
dos controllers, do formulário e da regra tocados (48 arquivos, 974 casos), verdes nos dois bancos, com o pulado conhecido do
D199 (as mensagens). Os portões, 76 ok, saída 0. Os mutantes, um por vez, devolvidos pelo sha1, vermelhos nos dois bancos: M1 a
M3 o POST cru de volta no serviço, no produto e no pacote; M4 a quantidade fora da lista do produto; M5 o "visível no app" fora
da do serviço; M6 o `InArray` da área arrancado; M7 ele não estrito; M8 o editar sem a área atual; M9 o vazio sem a quebra; M10 a
regra sem a atual. O `disable_inarray_validator` da área ficou, contra a triagem: sem ele, o "Selecione" dizia também a frase em
inglês do `Select` (medido). Fatia ímpar: sem o ciclo inteiro. O CI do GitHub está parado por cobrança. Não visto num navegador.
Nada da produção mudou.

**A verificação em três lentes** confirmou 2 achados, baixos. O corpo do D679 dizia "medido nos dois bancos" que o `id` posto à
mão não tocava a linha para a qual apontava, mas essa conferência vinha depois da comparação das colunas, que caía antes dela no
código de antes: o caso a pôs antes, e os mutantes M1 a M3 (o POST cru, o código de antes) rodados de novo nos dois bancos a
passam e caem nas colunas -- agora medido; o texto diz que ela é guarda de regressão, sem mutante que a derrube. E a lista do
D679 filtra as chaves, não os valores: o ERP antigo ainda grava o valor cru do POST na categoria (fora da lista, com marcação),
nos valores, nos booleanos e na quantidade, sem a regra que a API aplica -- já era assim antes da fatia, sem saída sem escape;
foi ao D741 novo, com o caminho (o `getData()` e a `RegraDoCatalogo::conferir`, poupando o valor antigo) e o aviso no D679. Os
12 casos verdes de novo nos dois bancos. De pé 159 (o D741 novo).

## 08/11/2026 -- ERP-ANTIGO-ESCAPE-E-MENSAGENS (a fatia 120, debitos)

**O que entrou.** O D203 (o texto do banco impresso sem escape) nos itens que o corpo dele ainda nomeava, e o D521 (a recusa da
tela antiga que aparecia de novo na página seguinte) no ERP inteiro, com um portão.

**O que muda para quem usa.** No PDV, o seletor "Troco como gorjeta ou deixar na conta do cliente?" mostra o nome de cada
colaborador como texto, e o relatório de caixas, o nome de cada forma de pagamento: um nome com `<` ou aspas não vira mais
marcação na página. Nas telas antigas que voltam a mesma página depois de um pedido -- a recusa do cadastro e da edição de
clientes (o e-mail ou o telefone de outro), a devolução de crédito (o valor, o saldo, a senha), o cadastro e a edição de
pesquisas, o molde novo de privilégio, a saída do estoque acima do saldo, a versão e o Push do aplicativo, os textos da unidade,
o aviso dos clientes novos da agenda (sete dias) e o de Vendas Realizadas (o colaborador e a forma juntos) --, a frase aparece
nessa página e não aparece mais de novo na próxima que a pessoa abrir. A frase de Vendas Realizadas dizia "possóvel", e agora
diz "possível". O resto do D203 que o corpo apontava já estava resolvido: o extrato do plano de carreira saiu com a tela em
13/10/2026, e o link das notificações só aceita `http(s)` desde o D605; o PDF do histórico de consumo foi lido, e o FPDF já
escreve o texto como texto.

**O provado.** Três casos do D203 (o seletor do troco desenhado pelo serviço de verdade, os detalhes do caixa, e o PDF lido com
um nome hostil) e a tabela dos pagamentos nas views vigiadas do `conferir-escape-nas-views.php` (12 views, 185 impressões). Do
D521: o molde `dizerNaPagina` / `mensagensDaPagina` no `CrudController`, o `D521MensagemSoNaPaginaQueVoltaTest` novo (3 casos) e
a afirmação "nada fica no `flashMessenger`" nos arquivos que já despachavam as outras ações (com casos novos do cadastro de
clientes, da credencial da devolução e de Vendas Realizadas); e o portão novo `conferir-mensagem-sem-redirecionar.php`, no
`conferir-tudo.sh` e no `ci.yml` (154 mensagens, saída 0; as que respondem JSON só na lista das que o script recarrega).
Mutantes, um de cada vez, devolvidos pelo sha1, todos vermelhos no MySQL: M1 e M2 o escape do colaborador e o da forma no
seletor do troco (caso e portão), M3 o `(int)` do id (portão), M4 o escape da forma nos detalhes do caixa; N1 o molde mandando ao
`flashMessenger`, N2 o molde sem o escape, N3 a N18 e N20 cada ação (ou a vista dela) de volta ao `setMensagem*` (caso e portão),
N19 o ajudante dos cupons de volta ao `addErrorMessage` (portão); o portão, com a chamada só num comentário, fica verde (G1), e
fica vermelho com uma entrada tirada da lista dos que recarregam (G2) e com uma inventada (G3). Os mutantes do servidor (M1, M2,
M4, N1 a N18 e N20) rodaram também no PostgreSQL. O ciclo inteiro (fatia par): caracterização 7.093 em cada banco, contrato 74 e
74, ensaios HTTP saída 0 nos dois, front 4.697 (não mudou); os portões deram um vermelho, a catraca da cobertura por par que
desceu de 108 para 107 -- o caso do PDF chama `VendaRepository::historicoVendasPorCliente`, conferido pela lista do HEAD contra a
de agora --, e a linha de base desceu junto, com o motivo; de novo, 77 ok, saída 0. O medidor dos relatórios no PostgreSQL, saída
0. O `UsuariosController` segue com o seu `recusaNaPagina` privado (o mesmo molde). Não visto num navegador. O CI do GitHub está
parado por cobrança. Nada da produção mudou. De pé 158.

**A verificação da fatia (três lentes) confirmou quatro achados, consertados.** O arnês dos testes lia a frase da página no
controller, e não na variável que a view imprime: a ação que passasse `getCurrentMessages()` à view perdia a recusa da tela
com os casos e o portão verdes. O `ControllerEmProcesso::mensagens()` lê agora a variável `mensagens` da `ViewModel`, e o
`conferir-mensagem-sem-redirecionar.php` acusa a ação que diz a frase e não a passa à view, o ajudante que nenhuma ação chama
e o `dizerNaPagina` antes de um redirecionamento ou de um JSON (21 frases, saída 0; falsificado com as quatro formas, e com a
chamada só num comentário). Ganharam caso o caixa que fecha no meio da devolução, a recusa de quem não é gerente e o e-mail
recusado no cadastro e na edição de clientes; o caso do PDF do histórico confere os ids das vendas contra o banco (a catraca
da cobertura por par tinha descido por ele com "lista não vazia"). Testes: os 56 arquivos que leem as mensagens pelo arnês,
1.189 casos, verdes nos dois bancos. Mutantes, vermelhos nos dois bancos e devolvidos pelo sha1: V1 a V9 a view de cada ação
com `getCurrentMessages()`, E1 a E4 os quatro ramos de volta ao `setMensagem*`, P1 o histórico sem o filtro do cliente.

## 08/11/2026 -- DATAS-NOS-FILTROS (a fatia 121, debitos)

**O que muda para quem usa.** Nada pelo seletor de datas. Quem monta o endereço à mão com a data fora da forma (`1/02/02026`,
`001/2/2026`, `01-02-2026`) recebe a tela com o padrão no filtro -- hoje nos descontos e nas vendas canceladas, o período
dos royalties, sem filtro no programa de indicação, nos bloqueios da agenda, na lista de campanhas e nos cupons emitidos --
no lugar do 500 do PostgreSQL (e do erro ou da lista de outro dia no MySQL). A pesquisa de audiência da campanha antiga com a
data torta recebe "Informe as datas dos filtros no formato dd/mm/aaaa." em vez de alargar a lista. O "ver as vendas" de um
mês em Vendas Realizadas responde em centésimos de segundo (na cópia, de cerca de 1,1 s para 0,01 s; a mesma resposta). A
página de documentação da API do aplicativo diz a data que cada rota lê: o cadastro em `dd/mm/aaaa`, a agenda do
colaborador em `aaaa-mm-dd`, o marcar em `dd/mm/aaaa`, a v1 do marcar como 404.

**O que entrou.** D447 PARCIAL: `DataHora::dataDoFiltro` (a data ruim vira o padrão; o vazio continua sem filtro) em sete
telas do ERP antigo, derivadas do grep do `fromQuery` -- os cupons emitidos não estavam na lista do débito --, e a recusa
na audiência da campanha antiga; fica o consultor visagista, fora do escopo. D449 CORRIGIDO: o cadastro, a agenda (v1 e
v2), o marcar (v1 a v3) e duas respostas da página do aplicativo conferidos contra o código que os lê. D637 CORRIGIDO:
`VendaRepository::filtroDoPeriodo`, a faixa meio-aberta na lista, nos totais e nas três abas da exportação. O D740
(escritas de data) foi lido e não coube: são escritas, cada uma com a sua frase.

**O provado.** `D447DataNosFiltrosTest` (42 casos), `D449DatasDaDocumentacaoDaApiTest` (5) e quatro casos novos no
`ListagemDeVendasTest` (as fronteiras do dia, a virada do ano, nenhuma função na data), verdes no MySQL e no PostgreSQL; o
D447 escrito antes do conserto e vermelho contra o código de então. Os vizinhos -- 50 arquivos dos relatórios, das
campanhas, da agenda antiga, do aplicativo e da lista de vendas, mais os dos cupons; 1.292 casos -- verdes nos dois bancos
(no PostgreSQL, uma rodada deu vermelho o `CampanhaDoDiaTest::testARetomadaSoSaiDeUmaRodada` -- a trava de outra conexão
ainda presa; o arquivo não foi tocado --, verde sozinho e na rodada inteira repetida). Mutantes, um
de cada vez, devolvidos pelo sha1, vermelhos nos dois bancos: M1 a M7, M17 e M18 cada tela de volta à leitura crua e a
recusa arrancada; M8 e M9 o ajudante; M10 a M13 a faixa (`<=`, sem o dia seguinte, `>`, o `DATE()` de volta -- o M13
pediu que o caso reconhecesse o `CAST(... AS date)` do PostgreSQL); M14 a M16 a página de volta. Fatia ímpar: sem o ciclo
inteiro; o front não mudou. O CI do GitHub está parado por cobrança. Não visto num navegador. Nada da produção mudou. O par D381|D637 saiu do `conferir-debitos-repetidos` (a marca
nova do D637 os afastou). De pé 156.

**A verificação (08/11/2026, três lentes; cinco achados confirmados, todos tratados).** Dois na faixa do D637, consertados
com caso: o fim em 31/12/9999 virava `10000-01-01`, que o MySQL recusa com 1525 (a lista, os totais e a exportação davam
500; agora esse fim fica no `DATE()`), e o ramo da data fora da forma não tinha caso -- `ListagemDeVendasTest` ganhou
`testADataForaDaFormaSegueOCaminhoAntigo` e `testOFimEm31De12De9999Responde` (41 casos, verdes nos dois bancos); mutantes,
devolvidos pelo sha1: sem o teto do ano (vermelho no MySQL, verde no PostgreSQL, que aceita o ano 10000), a faixa forçada
na data fora da forma e os dois ramos antigos apagados (vermelhos nos dois). Três no texto: o D447 dizia que as consultas do
`CronJobService` recebiam datas montadas pelo código, e as três exportações `/api/unidade/vendas` leem a data da URL -- a v1
manda a barra que a página promete crua ao `DATE()` (1525 no MySQL; no PostgreSQL, `01/02/2026` é 2 de janeiro), a v2 e a
v3 dão 500 com a data torta --, registrado no **D742** novo; o título do D447 e este parágrafo diziam oito telas, e são
sete; a linha do D637 no índice `docs/f3-relatorios/README.md` ganhou a correção. De pé 157 (o D742).

## 08/11/2026 -- RELATORIOS-ARQUIVO-FRASE-E-MEMORIA (a fatia 122, debitos)

**O que muda para quem usa.** As planilhas das telas antigas -- o XLS do Atendimentos, a Vendas Realizadas, o relatório de
vendas (versões 1 e 2) e os participantes do sorteio -- chegam com o tamanho na resposta: um download cortado no caminho passa a
ser percebido pelo navegador, em vez de chegar como arquivo que não abre. Quem é recusado no Caixa e no Programa de Indicação lê
a regra inteira: "é do gerente da unidade, do caixa e do administrador" (antes, o caixa não aparecia, e ele vê); a Cota-parte não
recusa ninguém pelo papel (o colaborador vê a própria linha) e não mudou. O
Atendimentos de 2025 da maior franquia -- os números, os clientes novos e o PDF e o XLS -- cabe em 128 MB de memória (de 103 a 111
MB, antes 131 a 139 MB e o erro). A Cota-parte não mudou.

**O que entrou.** D115 PARCIAL: `ISBase\Util\PlanilhaParaBaixar` (os bytes em `php://temp` e a resposta com o `Content-Length`);
os cinco geradores antigos devolvem os bytes, sem `header()`, `ob_clean()` nem `exit`; falta o runner do CI verde sem o JIT (o CI
está parado por cobrança) e a configuração da produção. D662 CORRIGIDO: `RecorteDoRelatorio::fraseDoPapel` e a prop
`tambemDoCaixa` da `RecusaDoRelatorio`, no Caixa e na Indicação. D667 CORRIGIDO: `ContasDosAtendimentos::paraOAno` (o item com 16
chaves, e não 17: a tabela do arranjo do PHP não dobra), chamado num lugar só (`AtendimentosController::itensDoMes`), e as
leituras leves dos clientes antes do ano (o cache das consultas do Doctrine, o D646);
o mesmo sha1 do corpo antes e depois, e entre os dois bancos. D675 PARCIAL: medido com a máquina calma, o quente da Cota-parte
27% acima do Parceiro no MySQL na mediana (26% com o buffer pool em 1 GB: a razão não se move com ele) e 30% no PostgreSQL; a
lista sem as linhas por produto foi feita, medida (1% do pedido) e desfeita; a diferença é a leitura de 31 colunas, e a meta e o
conserto previsto foram ao lote do dono. O portão do buffer de saída confere a leitura
numa amostra (não há mais `ob_clean()` no código); o do recorte segue as compras para `leiturasLevesDosClientes`; a base do
ISRelatorio no `conferir-cobertura-por-par` foi a 194; saíram três pares do D662 do `conferir-debitos-repetidos`.

**O provado.** Casos novos: `D115PlanilhaComOTamanhoTest` (3), um em cada vizinho das planilhas (`ListagemDeVendasTest`,
`PedidoDaCotaParteAntigaTest`, `TelasDosSorteiosDoErpTest`), `RegrasDoRecorteDoRelatorioTest::testAFraseDoPapelSegueAPolitica`,
`RegrasDosAtendimentosTest::testOAnoGuardadoSemAChaveQueNinguemLe`, `AtendimentosTest::testAsLeiturasLevesDosClientesVemAntesDoAno`,
verdes nos dois bancos; no front, a frase nova no `componentesDoRelatorio`, no `RelatorioDeCaixa` (o detalhe), no
`RelatorioDeCaixaLigado`, no `ProgramaDeIndicacaoLigado` e no `CotaParteLigado`. Mutantes, um de cada vez, devolvidos pelo
sha1: M1 a M9 (a frase sem o caixa, o controller com a frase fixa, sem o `Content-Length`, o tamanho em caracteres, o cabeçalho
de volta no gerador, a Vendas Realizadas sem a resposta, o ano com 17 chaves, a estreia sem a pergunta de antes, as compras
depois do ano) vermelhos, os de servidor nos dois bancos; F1 a F5 no vitest vermelhos; o `unset` no lugar do `array_diff_key`
verde na suíte e vermelho no medidor (os números de 2025 caem com 128M). O medidor do Atendimentos e o da Cota-parte, em
processo e só leitura, com a carga anotada (`p122-atendimentos.txt` e `p122-cota.txt` do rascunho). Fatia par: o ciclo inteiro
(caracterização 7.158 em cada banco, saída 0; contrato 74 e 74; os ensaios HTTP nos dois bancos, saída 0; portões 77 ok; front
4.699 com build) e o medidor dos relatórios no PostgreSQL, saída 0; os casos do front tocados também com
`AMPLIFICAR_EFEITOS_MS=50`; tsc e lint limpos. O CI do GitHub está parado por cobrança.
Não visto num navegador. Nada da produção mudou. De pé 155 (o D662 e o D667).

**A correção depois da verificação (08/11/2026).** A verificação em três lentes confirmou 9 achados (um médio), consertados:
**(médio) o D667 sem caso no controller** -- trocar as duas chamadas ao `paraOAno` de volta pelo `normalizar` puro deixava a suíte
verde; a leitura do mês foi para um lugar só (`AtendimentosController::itensDoMes`), com
`AtendimentosTest::testCadaMesGuardadoSemAChaveQueNinguemLe` (nos números, nos clientes e no arquivo, tantas consultas dos itens
quantos meses passaram por ele, e cada item sem a `unidade`) e o `conferir-recorte-por-unidade.php` vigiando o `painelItens` nele
e a chamada nos dois. **A Cota-parte v1 dizia o caixa numa recusa que não acontece** -- a frase do servidor só diz o caixa onde os
outros são recusados (`outros` nulo), a tela da Cota-parte e do extrato não passa mais o `tambemDoCaixa`, e o caso das regras prova
que no Atendimentos e na Cota-parte v1 ninguém que entrou é recusado (o "se, e só se" vale onde há recusa); casos no
`CotaParteLigado` e no `ExtratoDaCotaParteLigado` (que não tinha nenhum). **O "só do administrador vence"** agora com as duas props
ligadas. **A prévia** da `RecusaDoRelatorio` ganhou a história com o caixa. **Os desenhos** do Caixa, da Indicação e da parte
comum dizem a frase nova. **O D675:** o buffer pool do MySQL variado (1 GB: 26%; de volta a 128 MB: 30%), as faixas do débito e do
desenho iguais (as medianas), e o conserto previsto pelas notas (feito, medido em 1% e desfeito pela fatia) foi ao dono como a
opção (d) da pergunta. Mutantes novos, um de cada vez e devolvidos pelo sha1: N1 e N2 (o mês lido sem o `itensDoMes` nos clientes
e nos números), N3 (o `itensDoMes` sem o `paraOAno`) e N4 (a frase com o caixa também na Cota-parte), vermelhos nos dois bancos;
F6 e F7 (a Cota-parte e o extrato passando o `tambemDoCaixa`) e F8 (o ternário com o caixa antes do administrador), vermelhos no
vitest. O ciclo inteiro de novo depois da correção (caracterização 7.159 em cada banco, saída 0; contrato 74 e 74; os ensaios HTTP nos
dois bancos, saída 0; front 4.700 com build e lint); o portão da cobertura por par ficou vermelho no ciclo (o ISRelatorio de 194
para 193: as duas chamadas ao `painelItens` viraram uma) e, com a base acertada, os portões 77 ok; o medidor dos relatórios no
PostgreSQL, saída 0; os casos tocados do front com `AMPLIFICAR_EFEITOS_MS=50`, 102 de 102. Nada da produção mudou. De pé 155.

## 08/11/2026 -- ARQUIVOS-NA-TELA-NOVA (a fatia 123, debitos)

**O que muda para quem usa.** Na biblioteca de arquivos da tela nova, o "abrir" em aba nova com a sessão do ERP caída mostra uma
página curta -- "A sessão do ERP caiu", com o link "Entrar no ERP" -- em vez do texto técnico da API; o arquivo que sumiu ou que a
pessoa não pode abrir mostra a frase, com "feche esta aba e volte à biblioteca". Quem desativa o último arquivo de uma página
volta à última página que existe, e não a "nenhum arquivo" com o total acima de zero. O item "Configurações › Cadastro de
arquivos" só aparece a quem também pode ler a biblioteca (antes, quem tivesse só o privilégio do cadastro veria o item e uma
recusa; hoje ninguém fica nesse meio). Na tela antiga, trocar o arquivo pelo "editar" não faz mais quem abre o mesmo arquivo
esperar o envio (o efeito só aparecia com o armazenamento S3).

**O que entrou.** D574 CORRIGIDO: a `GET /adm/api/arquivos/:id/conteudo` responde ao pedido de navegação (`Accept` com
`text/html`, sem o `X-Requested-With`) com a página da recusa e o mesmo código (`RegraDoArquivo::pedeAPagina` e
`paginaDaRecusa`); quem pede JSON segue com o JSON; e, pela correção da verificação, o 401 da volta da personificação, dado
antes do controller, também sai em página nessa rota (`PersonificacaoComOAcessoDeAgora::respostaDaVolta`). D585 CORRIGIDO: o editar antigo (`ArquivoService::trocar`) guarda o novo antes
da trava da linha, e as recusas e as falhas depois de guardar o tiram. D603 CORRIGIDO: `ArquivosLigado.tsx` pede a última página
que existe pelo total do envelope (o molde dos avisos). D577 CORRIGIDO: a guarda `{ todos: [...] }` no `menu-do-erp.ts`, e o item
do cadastro pede `arquivosCadastrar/ler` e `arquivosExibir/ler`; quem lê a API não mudou (a resposta 135 (a)).

**O provado.** Os vizinhos (`Arquivo|QuemAbriu|D199AtivarRemoverPorPost|TelasDosBanners|GuardaDeLogin|RecusaSemSessao`, 288
casos) nos dois bancos, saída 0, com os casos novos (`ArquivoApiTest`, `RegraDoArquivoTest`, `ArquivosPelaApiTest`,
`ArquivosTravessiaTest`). O front inteiro, 4.708 de 4.708, com build e lint; os tocados também com `AMPLIFICAR_EFEITOS_MS=50`,
com os seis vermelhos conhecidos do D471 no `Console.teste.tsx` (os mesmos com o `menu-do-erp.ts` do HEAD). Os portões, 77 ok,
saída 0. Mutantes, um de cada vez e devolvidos pelo sha1: M1 a M8 (a página, o `X-Requested-With`, o tipo do JSON que ficava, o
escape; o envio dentro da transação e as três retiradas do guardado) vermelhos nos dois bancos; F1 a F4 (sem o recuo, o recuo
direto ao total, o item com um privilégio só, o "todos" como "qualquer um") vermelhos no vitest. A verificação em três lentes
confirmou 3 achados baixos: a volta da personificação ainda dava o JSON cru na aba (consertada, com o caso
`D514PersonificacaoSegueOAcessoTest::testOAbrirEmAbaNovaRecebeAPaginaDaVolta`), a página do 422 e do 404 do que não existe sem
caso (agora no `testARecusaAoPedidoDeNavegacaoEAPaginaCurta`) e a data deste título (08/11, a do Mac, como as fatias 120 a 122;
fica). Depois da correção, os vizinhos com os testes da guarda de login e da personificação, 446 casos, nos dois bancos, saída 0;
os mutantes P1 a P3 (a volta sem a página, a página em toda a `/adm/api`, sem olhar o `X-Requested-With`) e A1 a A3 (sem o 422,
a frase do 422 lida do campo, sem o 404 do que não existe) vermelhos nos dois bancos e devolvidos pelo sha1. Fatia ímpar: sem o
ciclo inteiro. O CI do GitHub está parado por cobrança. Não visto: nada num navegador; o S3 de verdade. Nada da produção mudou. De pé 151.

## 08/11/2026 -- TELAS-NOVAS-ACABAMENTO-E-TESTES (a fatia 124, debitos)

**O que muda para quem usa.** Na tela nova, quando o servidor falha e responde a página de erro do PHP (um 500), a mensagem
passa a dizer "o servidor falhou ao montar a resposta", e não mais "a sessão do ERP caiu" -- entrar de novo não resolvia, e foi o
que o dono leu no servidor de teste em 05/11. Nos quinze relatórios com a escolha da unidade, o administrador que pede "todas as
unidades" vê "Todas as unidades" no cabeçalho também enquanto os números carregam ou quando eles falham (antes aparecia o nome
da unidade em que entrou). O "tentar de novo" sai dos quinze lugares, dentro de cartões e formulários, em que a recusa era de
privilégio (o 403): repetir dava a mesma recusa. Os campos que saíam com a cara do navegador -- as escritas da Contas a Pagar, o
"Quem fez" da VIP Class, o campo de arquivo -- ganham o campo da casa, e o "Vale na unidade" da busca de cursos não corta mais o
"qualquer unidade"; a regra nova vale para todo campo que nenhuma tela estiliza, então o próximo não sai cru.

**O que entrou.** D727 CORRIGIDO: `fraseDoQueNaoVeio` no `cliente.ts`, no `pedir` e no download (o 5xx diz o servidor; o resto, a
sessão). D725 CORRIGIDO: `deOndeSemOsDados` (`deOndeDoRelatorio.ts`) nas quinze telas com `filtroDeUnidade`. D472 CORRIGIDO: os
quinze botões com `erro.semPermissao ? null : ...` (Agenda 2, Catálogo 2, Clientes 2, a peça `TodasOuAsMarcadas` dos Convênios, das
Mensagens e da VIP Class, Cupons 3, Fidelidade 3, Painel 1, Usuários 1). D724 CORRIGIDO: o campo da casa global com peso zero
(`:where`) no `App.css`, o campo de arquivo, a trilha da busca de cursos e a largura das escritas da Contas a Pagar. D471
CORRIGIDO: só testes -- cada caso espera o sinal do que precisa (o foco, o conteúdo depois do título, a linha que volta a abrir
depois da releitura) nos oito arquivos da triagem e em mais três achados pela suíte inteira com o amplificador (Acl, Unidades,
WhatsApp, iguais na HEAD).

**O provado.** Só front. O vitest inteiro, 4.744 de 4.744 (eram 4.708), sem o amplificador e com `AMPLIFICAR_EFEITOS_MS` a 50 e a
10 ms (`--maxWorkers=2`); antes, os oito arquivos do D471 davam 23 vermelhos em 762 a 50 ms, e depois 762 de 762 em oito rodadas a
50 e a 10 ms. Build e lint, saída 0. Mutantes, um de cada vez e devolvidos pelo sha1: C1 a C3 (D727), U1 a U6 (D725), S1 a S5
(D724), A1 a A15 (D472, um por botão) e W1 a W8 (as esperas do D471, a 50 ms) -- todos vermelhos, menos o W5 (a espera do
`abrirReceber` do Devedores, que as esperas de dentro dos casos já cobrem). Fatia par: o ciclo inteiro (ciclo-124), as seis etapas com saída 0 -- caracterização 7.166 em cada banco, contrato 74 e 74, ensaios HTTP saída 0 nos dois, portões 77 ok, front 4.760 de 4.760 com build e lint (o total conta o guarda derivado das quinze telas, posto depois das rodadas com o amplificador). Só front: o p48 dos relatórios não roda (nenhum PHP mudou). O CI do
GitHub está parado por cobrança. Não visto: nada num navegador -- a regra global do campo muda todo campo que hoje sai cru, e
quais são só o navegador diz; a re-sincronização do /design-sync é do dono. Nada da produção mudou. De pé 146.

**A correção depois da verificação em três lentes (08/11/2026).** Três achados confirmados, os três consertados. (1) O D472 tinha
irmãos fora da tabela de 11/10: mais quinze "tentar ... de novo" sem a guarda do 403, de telas que nasceram depois (as campanhas
-- a nova, "Quem recebe", a edição e a hora nova --, o NPS, a Carteira, os Descontos, o Programa de Indicação, os Atendimentos, o
Histórico do parceiro e o Profissional Parceiro). Para quem usa: nesses lugares, a recusa por falta de privilégio também deixa de
oferecer o "tentar de novo". Um caso por botão (15) e os mutantes B1 a B15, vermelhos; e o caso `tentarDeNovoNo403.teste.ts`, que
deriva os botões do disco e pede a guarda em cada um, para a próxima tela não esquecer (mutantes G1 a G5, vermelhos, um deles
a guarda trocada por um comentário). (2) O `frontend/.design-sync/NOTES.md` seguia dizendo que o D724 e o D725 estavam adiados:
registra o conserto e a recaptura que falta (do dono). (3) As esperas do D471 que os mutantes W não arrancaram foram arrancadas
uma a uma (os mutantes X, até 6 rodadas a 50 ms e 6 a 10 ms): nove seguram o verde; quatro não (a do pedido da releitura no
Convênios, a do "voltar" do Devedores -- também junto com a do `abrirReceber` --, e a do "abrindo" da comanda encerrada), e o
comentário de cada uma diz isso. Os números da correção: o vitest inteiro 4.779 de 4.779 (os 15 casos novos e os 4 do caso
derivado), os arquivos tocados 633 de 633 com o amplificador a 50 e a 10 ms, build e lint saída 0; o ciclo inteiro de novo
(ciclo-124c), as seis etapas com saída 0 -- caracterização 7.166 em cada banco, contrato 74 e 74, ensaios HTTP saída 0 nos dois,
portões 77 ok, front 4.779. Só front de novo: nenhum PHP mudou. Não visto num navegador. Nada da produção mudou. De pé 146.

## 08/11/2026 -- CONSULTAS-MORTAS-E-PORTOES-DE-DQL (a fatia 125, debitos)

**O que muda para quem usa.** Nada que se veja numa tela. A rotina das 02:10, que monta o relatório de atendimentos antigo, passa a
perguntar ao banco cada mês e cada ano pela faixa de datas, e não pela função em volta da data, o que deixa o índice da data servir;
os números que ela grava são os mesmos (conferido mês a mês na cópia, nos dois bancos). As rotinas do parabéns, da Yungas e dos
atendimentos, e o combo de colaboradores do NPS antigo, deixam de colar valores no texto da consulta. Saíram do código dez consultas
e dois parâmetros que nada mais chamava; os privilégios das telas que saíram continuam na lista, sem efeito: os três da 182 (a), como
o dono decidiu, e os da Fidelização e da Evasão pelo mesmo padrão, com a pergunta no lote. O combo de convênios (o cadastro de
clientes, os Descontos) também deixa de pôr a unidade no texto da consulta.

**O que entrou.** D485 PARCIAL: saíram as consultas sem chamador da tabela de 13/10 (`totalServicosVendidosPorMesAnoColaborador`,
`totalProdutosVendidosPorMesAnoColaborador`, `selecionarUnidadesComVendasAnoMes`, `totalVendaPorAnoMesDiaUnidade`,
`totalAgendamentoPorMesAnoColaborador`, `selecionarColaboradoresUnidadeDashboard`, `DataHora::totalDiaSemanaMes`), as sobras de
04/11 (`DataHora::removerUmAno`, o grupo de `selecionarUsuariosComVendas`), o `$dataDashCola` de `selecionarUsuariosRelatorio`, e duas
achadas fora da lista (`selecionarTotalUnidadesComVendas`, `DataHora::totalDiasEntreDatas`); o `ColaboradoresNoRelatorioTest` e o
`VendasNoRelatorioTest` perderam os casos do que saiu. D427 CORRIGIDO: o sítio saiu com o D485; as cinco consultas da rotina dos
atendimentos (quatro por colaborador e mês, uma por ano) passaram a `DataHora::faixaDoMes` e `::faixaDoAno`. D72 CORRIGIDO: as três
buscas que restavam têm escritor único e travado (o caixa, a pesquisa da adesão, a extra da cota-parte), a dispensa cita o escritor e
os casos que ficam vermelhos sem a trava, e o `conferir-linha-sorteada.php` está em zero. D73 CORRIGIDO: os valores internos viraram
parâmetro (o parabéns, a data de corte, a Yungas, a unidade do combo de colaboradores e a do combo de convênios, que o portão não
via); o `conferir-injecao-dql.php` ficou mais largo (o literal que começa por `AND `/`OR `, e um contador novo, em zero, da variável
interpolada depois de um operador) e desceu de 13 para 6, nenhum com valor; a prova por HTTP perdeu o objeto (o
`rel-clientes` é 404 desde a 76e). O `conferir-cobertura-por-par.php` desceu o "sem menção" de 107 para 103, por cobertura.

**O provado.** Os casos novos (`D427FaixaDoMesNosAtendimentosTest` 7, `D73ValorPorParametroTest` 6, `D485ConsultasSemChamadorSairamTest`
3) com os tocados e os vizinhos, 735 casos, nos dois bancos, saída 0 (3 pulados, os de sempre). A equivalência da faixa na cópia, só
`SELECT`: 136 meses de vendas e 135 de clientes, nenhum diferente, nos dois bancos. Mutantes, um por vez e devolvidos pelo sha1, nos
dois bancos: E1 a E5 (D485), F1 a F6 (D427), T1 a T6 (D72: cada trava citada nas dispensas, arrancada) e G1 a G5 (D73), todos
vermelhos; o G5 (o filtro da unidade do combo) passava verde no `PesquisasNpsNoRelatorioTest`, e ganhou o caso. Os portões alterados
falsificados (a dispensa do caixa arrancada: 1; o parabéns concatenado: 1). Os portões, 77 ok, saída 0; o
`p48-medir-relatorios-pg.sh` saída 0. Fatia ímpar: sem o ciclo inteiro; o front não mudou. O CI do GitHub está parado por cobrança.
Não medido: o tempo da rotina dos atendimentos antes e depois. Nada da produção mudou.

**A verificação** (três lentes) confirmou 4 achados, todos consertados. O médio: o `ConvenioRepository::popularCombobox` interpolava a
unidade da sessão no texto (`= $unidadeId`, sem ponto), um sítio do D73 que nenhum contador via -- virou `:unidade`, com o caso
`testAUnidadeDaSessaoNoComboDeConvenios` e o contador novo do portão (falsificado: a forma de antes de volta, 1). Os baixos: o D485
dizia que os cinco privilégios ficavam "pela 182 (a)", e a resposta é só sobre três -- o título passou a PARCIAL e a pergunta dos
dois foi ao lote; a nota da A4 em `spec-atendimentos.md` mandava apagar a `selecionarUsuariosComVendas`, que o Vendas antigo
(`rel-vendas`) ainda chama -- na A4 sai só o ramo do ano; e o texto do caso do ano do `VendasNoRelatorioTest` ainda explicava o
`YEAR()` que saiu. Os testes de novo: os tocados e os vizinhos (75 arquivos, 1.405 casos) nos dois bancos, saída 0 (6 pulados);
portões 77 ok, saída 0. Mutantes novos, nos dois bancos e devolvidos pelo sha1: G6 (o combo de convênios interpolado e sem
`setParameter`) vermelho só no caso novo, e F7 (o filtro de ano sem efeito) vermelho no `testOFiltroDeAnoERespeitado`. De pé 143.

## 08/11/2026 -- PORTOES-E-COBERTURA (a fatia 126, debitos)

**O que muda para quem usa.** Nada que se veja numa tela, e nada no código que roda: a fatia é só de testes e de portões. O que
muda é o que fica vermelho antes de chegar a alguém. Um link novo que mude estado (ativar, remover, abrir comanda) ou uma ação
nova que grave dado ao ser só aberta passam a parar os portões; uma mudança da taxa de royalty por fora da porta que grava a
vigência também; e os relatórios da rede que seguram "um pedido de cada vez por pessoa" passam a ter caso que veja a trava presa
durante as consultas, e não só antes e depois.

**O que entrou.** D177 PARCIAL: os três pares de dinheiro sem teste (`selecionarPagamentosPorSaida`, `selecionarVendasPorConvenio`,
`selecionarPontuacaoValidadePorClienteUnidade`) no `D177ParesDeDinheiroTest`, e os dois `listagemIndex` da fila de vazamento
(unidades e usuários, com o recorte de quem não é administrador) no `D177ListagensDeUnidadeEUsuarioTest`, cada um chamado pela
própria classe sobre um cenário plantado, com os ids afirmados; o dos cupons já estava coberto (`CupomEmitidosTest`), conferido com
dois mutantes. O `conferir-cobertura-por-par.php` desceu o "sem menção" de 103 para 100 e os falsos positivos de 20 para 18, e a
coincidência do `ConvenioUnidadesTest` saiu; ficam os outros 100. D362 CORRIGIDO: o `conferir-remocao-por-post.php` acha sozinho a
ação que grava sem conferir o verbo (lida no corpo e no inventário) e exige a linha dela, com o motivo, em
`ACOES_QUE_GRAVAM_PELO_GET` -- oito hoje, todas já conhecidas (as mensagens da agenda, o NPS das Vendas Realizadas, o PDV, a comanda
e a comanda da agenda, o `caixas/gerenciar`, o `clienteSelecionar` do aplicativo) --, e lê todo `href` de toda tela e todo endereço
do JavaScript até a ação: o que recusa o GET é botão morto, e a ação de estado ou que grava pelo GET só pode ser link dos lugares
que a linha dela nomeia. D612 CORRIGIDO: o portão novo `conferir-escritores-da-aliquota.php` (no `conferir-tudo.sh` e no `ci.yml`).
D666 CORRIGIDO: o caso do Caixa num trait (`TravaPresaNasConsultas`) e nos sete irmãos (Descontos, NPS, Indicação, Royalties,
Retirada, Canceladas e Profissional Parceiro, com as duas travas).

**O provado.** Os casos novos (D177: 5; D666: 7) com os vizinhos de trava nos dois bancos, saída 0. Mutantes, um por vez e
devolvidos pelo sha1: T1 a T9 (a `liberar` logo depois do `if ($trava === false)` nos nove pontos dos sete controllers) vermelhos
nos dois bancos pela asserção da trava presa; E1 a E4, V1 a V4, P1 a P4, N1 a N5, U1 a U4, C1 e C2 (os filtros, a ordem, o teto e o
zero dos repositórios) vermelhos nos dois bancos; os portões falsificados -- A1 a A9 (o da alíquota; o A3, só comentário, verde) e
R1 a R9 (o das remoções; o R6, só comentário, verde), com o portão do HEAD verde nos R1, R2, R4, R5 e R7. Os portões, 78 ok, saída 0.
Fatia par: o ciclo inteiro (ciclo-126) saída 0 nas seis etapas -- caracterização 7.190 em cada banco (24 e 17 pulados, os de
sempre), contrato 74 e 74, ensaios HTTP saída 0 nos dois bancos, portões 78 ok, front 4.779 com build e lint (o front não
mudou) --, e o `p48-medir-relatorios-pg.sh` saída 0.
O CI do GitHub está parado por cobrança. Não visto: nada num navegador (nada de tela mudou). Nada da produção mudou. De pé 140.

**A correção depois da verificação.** A verificação em três lentes confirmou oito achados baixos, todos consertados. No
`conferir-remocao-por-post.php` (D362): o endereço montado por partes (`"/mod-vendas/" + x`), o resto de 05/11 que a marca
CORRIGIDO não cobria, passa a ser acusado no JavaScript e nas telas (zero hoje); a conferência 8 lê também o endereço escrito por
extenso nas telas (`href="/mod-..."` e o `url:` de um `<script>`, 9); o motivo do `caixas/gerenciar` dizia "o GET só mostra", e o
GET recalcula e grava o total do caixa sob a trava (o D228); e a isenção da rotina passou do `CronJobController` inteiro para a ação
que começa pelo `tokenConfere()` -- as três `/api/unidade/vendas*`, que gravam pelo GET o uso do hash anterior, entraram na lista
com o motivo (o D742): onze declaradas. No `conferir-escritores-da-aliquota.php` (D612): passa a ler `postgres/`, `sql/`, `docker/`
e os `.sql` da raiz, e a escrita da taxa pela conexão e pelo QueryBuilder. No trait da trava (D666): as guardas têm de consultar
antes de soltar a sessão, como no Caixa -- e o `destaques` do Parceiro, que lê só a sessão, diz isso com `false`. Os textos: o
aviso do D666 no `spec-caixa.md`, o item do portão no `spec-royalties.md` (que abria com "não foi feito"). Mutantes, um por vez e
devolvidos pelo sha1: G1 a G4 (a trava e a sessão antes das guardas) vermelhos nos dois bancos, o G1 e o G2 verdes com o trait de
antes; R10 a R12, R14, R16 e R17 vermelhos e os controles R13 e R15 verdes; A10 a A14, A16 e A17 vermelhos e os controles A15 e
A18 verdes. Depois da correção: os testes da fatia e os irmãos de trava (445 casos) saída 0 nos dois bancos; o ciclo inteiro de
novo (ciclo-126c) saída 0 nas seis etapas -- caracterização 7.190 em cada banco, contrato 74 e 74, ensaios HTTP saída 0, portões
78 ok, front 4.779 --; e os portões, 78 ok, saída 0. Nenhuma pergunta nova ao dono.

## 08/11/2026 -- ARNES-DOS-TESTES-E-ENSAIOS (a fatia 127, debitos)

**O que muda para quem usa.** Nada numa tela, e nada no código que roda: a fatia é só do arnês dos testes e dos ensaios. O que
muda é o banco de contrato, onde os ensaios HTTP rodam: até aqui cada rodada deixava nele os acessos e os tokens dos logins (75
acessos e os tokens do gerente e do colaborador trocados numa rodada só), e o ensaio seguinte media sobre esse resto. Agora todo
ensaio que entra no ERP devolve o que o login gravou e sai vermelho, dizendo o usuário, se sobrar alguma coisa. E o dublê de
sessão dos testes que montam controller em processo responde como a sessão de verdade.

**O que entrou.** D626 CORRIGIDO: o `getUsuario()` sem campo da sessão dupla do `ControllerEmProcesso` devolve o arranjo inteiro,
como a `SessaoAcl` (o mesmo `empty`). Medido antes, com o dublê instrumentado na suíte inteira do MySQL: só quatro casos do
`ListagemDeVendasTest` passavam por ali, e nenhum muda (o `soDoCaixa` dá `false` para o nulo e para o arranjo sem a marca de
caixa). O `D626SessaoDuplaDevolveOArranjoTest` monta a tela antiga do Produtos / Serviços pelo trait: o gerente atendido, o
colaborador recusado, o administrador atendido. D61 PARCIAL: o ajudante comum `scripts/residuo-do-login.php` (a foto dos tokens
da fixture e do maior acesso antes do primeiro login, a devolução, a conferência que sai 1 nomeando o usuário) em 22 roteiros: 17
no `finally` (arquivos, banners, cupons, cursos, etiquetas, fidelidade, mensagens, pesquisas, saídas, sorteios, usuários, VIP
Class, WhatsApp, o provedor de mentira, notificações, a busca de cliente da agenda e os links consertados, onde a devolução vem
depois do "Sair") e 5 no fim do encerramento do PHP (permissão, sessão do ERP e os três medidores do PostgreSQL), cujo corpo sai
por `exit` ou declara `const`. A permissão e a sessão do ERP passaram a recusar rodar inteiras fora do banco de fixtures, e o
`medir-relatorios` e o `medir-telas` passaram a abrir o banco. Fica o `ensaiar-fechamento-da-agenda` (proibido rodar aqui, não
tocado) e a variante do DDI do `AplicativoService`.

**O provado.** D626: o caso novo, o `ListagemDeVendasTest` e o `ProdutosVendidosTest` (79 casos) nos dois bancos; a suíte inteira
com o conserto nos dois bancos (7.192 casos em cada, saída 0); o mutante M1 (o dublê de antes) deixa os dois casos novos
vermelhos nos dois bancos -- o gerente recusado com 404 --, devolvido pelo sha1. D61: com o banco de contrato montado do zero e um retrato
de fora (o sha1 ou o md5 da lista id:token da fixture, nenhum token impresso, e a contagem de acessos), 24 ensaios no MySQL na
ordem do CI saída 0, e 25 no PostgreSQL (com o `medir-adm-api`) saída 0, mais o `medir-telas` (187 telas) e o `medir-relatorios`
(17 relatórios, a mesma saída do `p48`) saída 0 -- o retrato igual antes e depois nas três rodadas, zero acessos. Os 22 mutantes,
um por roteiro (cópias sem a devolução, apagadas no fim): todos saída 1, a conferência nomeando o usuário e o resto, e nenhum caso
de ensaio errado; mais o H1 (o ajudante sem o `DELETE`) e o H2 (sem a volta dos tokens), saída 1. Os portões, 78 ok, saída 0;
`contar-debitos` e `conferir-debitos-repetidos` saída 0. Fatia ímpar: sem o ciclo inteiro, mas o arnês dos ensaios mudou e eles
rodaram todos nos dois bancos; o front não mudou. O CI do GitHub está parado por cobrança. Não visto: nada num navegador (nenhuma
tela mudou), e o `ensaiar-fechamento-da-agenda`. Nada da produção mudou. De pé 139.

**A correção depois da verificação (08/11/2026).** Cinco achados baixos confirmados e consertados, um refutado. D626: o caso novo
planta a venda de julho de 2023 que dizia vir das fixtures (não vinha) e confere a linha do produto na tela do gerente e do
administrador (mutante P1, sem o plantio, vermelho nos dois bancos; o M1 continua vermelho); oito testes que diziam que a sessão
dupla não devolve o usuário inteiro dizem que não devolvia até aqui, e por que ficam com a sessão deles (só comentário). D61: a
`ParadaDoEnsaio`, que nenhum roteiro usava, saiu do ajudante; a foto recusa sozinha o banco com mais de 10 unidades; e o
`D61ResiduoDoLoginTest` (6 casos, nos dois bancos) prova o ajudante e lê do disco os 22 roteiros que entram no ERP -- a guarda
antes da foto, a devolução no `finally` sem `exit` antes dela, ou no encerramento. A recusa das cinco guardas novas ou subidas
rodou contra o `franquiabv_dev` (só a contagem): saída 2 nas cinco, o banco igual. Os mutantes N1 a N4 (o ajudante) vermelhos nos
dois bancos, R1 a R4 (os roteiros) vermelhos no portão do teste, todos devolvidos pelo sha1. Os 19 ensaios do ajudante no MySQL e
os 19 mais o `medir-adm-api` no PostgreSQL, saída 0 e resíduo zero; os dez arquivos de teste tocados (178 casos) nos dois bancos,
saída 0. Refutado: o `$cookies` do `medir-telas` existe (linha 61), e sem o `@unlink` da recusa sobra um arquivo temporário (U1).
Fica, declarado: os cinco roteiros sem `try` seguem parando por `exit`, com a devolução no encerramento.

## 08/11/2026 -- ENTRADA-ICONE-GIRANDO (a fatia 135, o pedido do dono)

O pedido do dono: "Atualizar a tela de login conforme conversa e aplicar ao código" -- a conversa é a do desenho da entrada,
onde em 07/11/2026 ele pediu no canvas "Adicione esse icone ao fundo, estourado, meia tela, girando muito lentamente" (a cópia
do canvas está no main desde 4b653323). A fila dos débitos segue em pausa.

**O que muda para quem abre a tela de login do `/novo`.** Atrás do formulário, o ícone da casa (o anel com o chapéu e o bigode,
no dourado `#deb51f` a 10%) gira devagar, uma volta a cada 4 minutos, com o centro na borda direita da tela e na metade da
altura: no computador, com 1280 px, a metade que aparece ocupa meia tela e sai por cima e por baixo numa tela de 1280x800, como no
quadro (desde a escolha do dono, mais abaixo, o ícone acompanha a largura da tela e a meia tela vale em qualquer monitor);
no celular, 480 px. Ele
fica entre as estrelas e o brilho do horizonte, como nos quadros, e aparece no formulário vazio, na senha recusada, no
"entrando…", no "Você saiu do sistema." e nas páginas de pedir o link e de trocar a senha. Não aparece na janela da sessão caída,
por cima do Console, nem na barra. Fica parado para quem pede menos movimento ao sistema (`prefers-reduced-motion`) e pausa
quando a aba fica oculta, retomando do mesmo ângulo. O leitor de tela não o lê, o Tab não para nele e ele não rouba clique; o
contraste dos textos do formulário onde cruzam o ícone fica acima de 4,5:1 (a recusa, a mais baixa, 5,1:1). Dá para trocar a
cor, a opacidade, o tamanho ou apagá-lo por variáveis no começo da `.entrada-cena` do `App.css`, como o resto do fundo. O
README do desenho (`docs/f3-telas-restantes/desenho-entrada/README.md`) diz o que foi seguido e as decisões 10 a 13, minhas,
para conferir. Só o front mudou: `EntradaNoNovo.tsx` (a `CenaDaEntrada` com o ícone e o ouvinte da visibilidade), o `App.css`
e os testes.

**O provado.** Casos novos no `ConsoleLigadoEntrada.teste.tsx` (o ícone na cena, `aria-hidden`, sem foco e sem título; a aba
oculta pausa e a visível retoma; a cena que abre oculta nasce pausada; o ouvinte sai ao desmontar; a regra com 240 s linear, o
centro na borda direita, entre as estrelas e o brilho, o celular com 480 px, a parada do `prefers-reduced-motion` e a pausa da
aba oculta, lidas do disco), e o ícone conferido no envio e na recusa (o mesmo nó), no "Você saiu", ausente na sessão caída e
presente nas duas páginas da redefinição. Os arquivos da entrada e da redefinição (126 casos) verdes, também com
`AMPLIFICAR_EFEITOS_MS=50`; o vitest inteiro com `--maxWorkers=2` (4.785 casos, 206 arquivos), saída 0; tsc, lint e build saída
0. Os mutantes, um por vez, todos vermelhos e devolvidos pelo sha1: sem o `aria-hidden`; sem a parada do
`prefers-reduced-motion`; sem a pausa da aba oculta (no JS e no CSS); sem tirar o ouvinte; o ícone também na sessão caída; a
volta de 24 s; o giro com `ease`; o centro no meio; sem o `z-index` (o ícone por cima do brilho); a cena que nasce sempre
visível; o celular com 1280 px. Fatia ímpar: sem o ciclo inteiro; o PHP não mudou. Não visto num navegador: o giro, a posição e
o corte do ícone, a pausa com a aba oculta e o celular de 375 px sem rolagem lateral (o coordenador confere). O CI do GitHub
está parado por cobrança. Nada da produção mudou. De pé 139.

**A correção da verificação da 135.** Três achados baixos, confirmados no código. (1) O ícone tem tamanho fixo (1280 px): a "meia
tela" e o "passa das bordas" do pedido só valem perto de 1280 px de largura, e o README dizia isso sem a ressalva; agora a seção
do desenho e o comentário do `App.css` dizem que é no quadro de 1280x800, a decisão 14 do README registra a escolha, e a pergunta
(fixo como o quadro, ou acompanhando a largura da tela) foi ao lote; o código segue o quadro até a resposta. (2) O "entre as
estrelas e o brilho" dependia do `z-index: -1` da camada do fundo, sem caso: o caso da regra passa a conferir a camada
(posicionada, com `z-index: -1` e o fundo opaco), o `isolation` da cena e o brilho sem `z-index`. (3) O caso do celular achava os
480 px em qualquer `@media (max-width: 45rem)`, também nos que vêm antes da cena: agora confere que o tamanho é declarado só duas
vezes e que o bloco do celular abre depois da regra base. Só o teste e os textos mudaram; o CSS e o componente, não. Os arquivos
tocados (126 casos) verdes, também com `AMPLIFICAR_EFEITOS_MS=50`; o vitest inteiro com `--maxWorkers=2` (4.785 casos, 206
arquivos), tsc, lint e build saída 0. Os mutantes novos, um por vez e devolvidos pelo sha1, vermelhos: a camada sem o `z-index`,
a camada sem `position`, a cena sem o `isolation`, o brilho com `z-index: -2`, a linha do celular levada para o primeiro
`@media (max-width: 45rem)` do arquivo, e uma terceira declaração do tamanho depois do celular. Não visto num navegador.

**O tamanho acompanha a tela (08/11/2026, a escolha do dono).** A pergunta da correção acima (fixo como o quadro, ou
acompanhando a largura) não chegou a ir à página do lote: o dono respondeu no chat, "Sim, meia tela sempre". No computador o
diâmetro passa a ser `max(80rem, 1280px, 100vw)`: a largura da tela, nunca menos que os 1280 px do quadro. Com o centro na borda
direita, a metade visível ocupa sempre meia tela e sai por cima e por baixo em toda tela deitada; entre 720 e 1280 px o ícone
fica nos 1280 px, e o celular segue com 480 px. Mudaram uma linha do `App.css` (e o comentário das variáveis), o caso da regra no
`ConsoleLigadoEntrada.teste.tsx` e a decisão 14 do README do desenho. Os arquivos da entrada e da redefinição (80 casos) verdes,
também com `AMPLIFICAR_EFEITOS_MS=50`; tsc, lint e build saída 0; o mutante com o tamanho antigo vermelho e devolvido pelo sha1.
Visto no navegador (Vite, sem sessão), medido na página: em 1920x1080 o ícone com 1920 px, o centro em (1920, 540); em 2560x1440
com 2560 px, o centro em (2560, 720); em 1024x768 com 1280 px, o centro em (1024, 384); em 375x812 com 480 px, o centro em (375,
406); nos quatro, sem rolagem de lado nem de pé. Não visto: o Safari do iPhone. Nada da produção mudou. De pé 139.

## 08/11/2026 -- ROTEIROS-DO-CORTE-E-DA-CARGA (a fatia 128, debitos)

**O que muda para quem opera o corte** (nenhuma tela mudou; quem usa o sistema não vê nada). Cinco débitos dos roteiros do
corte e do dia 0, todos fechados, todos provados sem banco de verdade (dublês e arquivos inventados), e um aberto.

- **A carga para na tabela que a origem não tem** (o **D462**). O `migrar-dados.sh` jogava fora o erro do cliente `mysql`, e
  a tabela ausente virava `COPY 0`, o sucesso. Agora o erro sai na tela, o código de saída do cliente volta pelo contêiner, e
  a carga para nela com o nome ("A leitura de <tabela> no MySQL falhou"). Mudou o gerador; o roteiro foi regerado.
- **A carga versionada é a saída do gerador** (o **D584**): o portão novo `conferir-carga-gerada.php` compara os dois, no
  `conferir-tudo.sh` e no CI. Duas vezes a carga ficou para trás do gerador sem ninguém ver.
- **O ensaio geral do corte passa do começo no laboratório** (o **D469**): ele lê o `local.php` pelo PHP do contêiner (o
  ajudante `conexao-do-local-php.php`), e não mais pelo texto, e o `local.php` dos clones leva o banco escrito no lugar do
  `getenv`. O bloco do laboratório do BVIP ainda não rodou lá.
- **O passo 7 do corte troca só o bloco `doctrine`** do `local.php` de produção (o **D329**): o roteiro novo
  `trocar-doctrine-do-local-php.php`, no lugar do `cp` do modelo de desenvolvimento (que ligava o rastro de pilha e tirava o
  e-mail, o push, o Asaas e as rotinas). E o `verificar-local-php.php` roda antes do passo 8, com a saída 1 parando -- ele
  conectava sempre pelo MySQL e pararia todo corte ali; agora conecta no banco do driver declarado.
- **A apuração do incidente LGPD lê o log do site, ou diz que não leu** (o **D332**): o `remediacao-dia0.sh`, os passos 2 e 6
  do `dia0-remediacao-producao.md` e o `inventariar-versoes-app.php --dir=` leem todo `*.log` e `*.log.*` (o `.gz` também), e
  exigem o controle positivo (`/api/versao/app`); sem ele, "não li o log do site" e saída 2 (1 no inventário), e nunca
  "nenhum acesso externo".
- **Aberto:** o **D743**, o `varrer()` do `coletar-producao.sh` conta em dobro quando não há log rotacionado (medido num
  diretório inventado).

**O provado.** Quatro portões novos no `conferir-tudo.sh` e no `ci.yml` (82 no conjunto): `conferir-carga-gerada.php`,
`ensaiar-carga-que-para.sh` (o roteiro gerado inteiro, 92 tabelas, 4 casos, com um `docker` de mentira),
`ensaiar-troca-do-doctrine.sh` (8 casos, sobre um `local.php` inventado) e `ensaiar-varredura-dos-logs.sh` (4 casos, logs
inventados); o `ensaiar-clones-do-ensaio-de-corte.sh` ganhou os dois casos do modelo do Docker (13 casos); o caso novo
`D329VerificarConectaPeloDriverTest` (2 casos) nos dois bancos, com o `SegredosForaDoCodigoTest` e o `EnvioDePushTest`
(57 casos em cada banco). Os mutantes, um por vez, todos vermelhos e devolvidos (nas cópias, ou pelo sha1 no arquivo de
verdade): a coluna tirada da carga; o roteiro de carga do HEAD, o aviso do erro arrancado, o código de saída perdido, a
conferência arrancada e o `2>/dev/null` de volta; a guarda do ensaio pelo texto, o `sed` do clone de antes, o usuário pelo
texto e o ensaio do HEAD; o resto do `local.php` cortado e as quatro recusas da troca arrancadas, e o verificador sempre pelo
MySQL (no PostgreSQL); o glob antigo e o controle arrancado, na apuração e no inventário, e os dois do HEAD.
Fatia par: o ciclo inteiro (ciclo-128), as seis etapas com saída 0 -- a caracterização com 7.200 casos em cada banco
("OK, but some tests were skipped"), o contrato 74 e 74, os ensaios HTTP nos dois bancos, os portões 82 ok ("Nenhuma
falha inesperada") e o front 4.785 --, e o `p48-medir-relatorios-pg.sh` à parte, saída 0. O `local.php` voltou ao
`franquiabv_dev`, conferido pelo sha1.

**A correção da verificação (08/11/2026).** A verificação em três lentes confirmou oito achados (um médio, sete baixos),
todos consertados com caso ou no texto. **O médio, na apuração do dia 0 (o D332):** com o `LOGS_NGINX` errado ou ausente, o
`remediacao-dia0.sh` só avisava, deixava o relatório da rodada anterior, saía 0 e podia dizer "Nada pendente nos passos 1,
2 e 6"; agora segue com zero arquivos, troca o relatório pelo "não li o log do site" e sai 2. E o arquivo que não se lê (o
`.gz` truncado, o sem permissão, o `.bz2`) não conta mais como lido: vai à lista "Arquivos que NÃO li", fora da busca, a
conclusão diz que não cobre o período dele, e o roteiro sai 2; os passos 2 e 6 do documento ganharam a mesma conferência à
mão (o `gzip -t`). **No passo 7 (o D329):** o `verificar-local-php.php` sem a extensão do driver no PHP da linha de comando
pulava a conexão calado e dizia "Deploy liberado"; agora sai 1, "CONEXÃO NÃO PROVADA", com o nome da extensão. O
`--sem-conexao` diz "A CONEXÃO NÃO FOI CONFERIDA" no fim, e não "Deploy liberado" -- é o modo do item 13 da etapa 1 (a 201
(a) do dono), dado como feito lá. O `trocar-doctrine-do-local-php.php` apaga o temporário com os segredos também quando a
avaliação morre (a função de encerramento). E as três guardas da troca sem caso ganharam caso (a outra conexão, a chave a
mais no `orm_default`, a troca que muda outra chave), com o destino novo (permissão e grupo) e a avaliação que morre.
**Nos documentos:** o `ensaio-geral-do-corte.md` dizia o D462 e o D469 abertos e 11 casos; agora diz a carga que para, o
`local.php` avaliado e 13 casos. **Os testes:** o `ensaiar-varredura-dos-logs.sh` com 6 casos e o `ensaiar-troca-do-doctrine.sh`
com 13, no Mac e no contêiner; o `D329VerificarConectaPeloDriverTest` com 4 casos; os tocados e os que leem os documentos do
corte (106 casos) nos dois bancos, saída 0; o ciclo inteiro de novo depois da correção (ciclo-128c: a caracterização com 7.202 casos em cada banco, o contrato 74 e 74, os ensaios HTTP nos dois bancos, os portões 82 ok, "Nenhuma falha inesperada", e o front 4.785, as seis etapas com saída 0). Os mutantes novos, vermelhos e devolvidos: D1 a D5 na apuração; S1, S2,
R1, C1, G1 e L1 na troca, nas cópias; V2 e V3 no verificador de verdade, nos dois bancos, pelo sha1.

**O que não foi provado:** a carga de verdade (nunca roda fora da janela e dos clones do ensaio geral), o bloco do
laboratório no BVIP, o `local.php` de produção (nunca lido daqui) e a apuração na produção (é do dono). O CI do GitHub está
parado por cobrança: os passos novos não rodaram no runner. Nada da produção mudou. De pé 135.

## 08/11/2026 -- DEPLOY-TESTE-E-PUBLICAR (a fatia 129, debitos)

**O que muda para quem opera o servidor de teste e a produção** (nenhuma tela mudou; quem usa o sistema não vê nada). Três
débitos do deploy do teste fechados e o da produção sem caminho de entrega com o roteiro escrito -- tudo provado sem tocar o
BVIP nem a produção: dublês, repositórios e contêineres descartáveis.

- **O deploy do teste não publica mais aberto por falta de escolha** (o **D451**). Sem `SENHA_HTTP` na linha de comando nem
  no `.env`, o `publicar`, o `atualizar`, o `voltar` (e o `_aplicar` deles) recusam ligar o vhost antes de mexer, e dizem os
  dois comandos: o `htpasswd` e o `SENHA_HTTP=sim ... publicar`. O `instalar_vhost` recusa de novo; o `conferir` reprova o site
  no ar sem senha e sem a linha; e as frases que mandavam o `publicar` curto o dizem com a senha. O padrão `nao` de 24/09 e o
  comentário que o dava como escolha do dono saíram (a escolha é a senha, desde 02/10, o D326). O fim do `base` também diz o
  `publicar` com a senha: só o texto impresso depois da carga mudou, e a carga e o selo não. **Um clone do BVIP com o script
  de antes ainda publica aberto** até o `git pull` desta versão.
- **Um deploy do teste por servidor, e não por usuário** (o **D444**): a trava saiu do `$HOME` para
  `/var/lock/franquiabv-teste.trava`, do grupo `docker`, 640, aberta só para ler -- o root e quem faz deploy (do grupo)
  travam o mesmo arquivo, o que um criou não barra o outro, e quem está fora do grupo (o `www-data` de outro vhost) nem a
  abre: não segura o deploy nem o `tirar`. A plantada no lugar dela, o root refaz; o `tirar` segue sem ela.
- **A versão do aplicativo publicada no teste não trava o deploy seguinte** (o **D549**, o Dnovo-9 da fatia 23): as três
  conferências de "mudança versionada" (a do `atualizar`, a do `voltar` e a do composer, no meio do `_aplicar`) ignoram só o
  `public/app.json`; e o `atualizar` e o `voltar` recusam antes de tirar o site se o commit alvo também muda o arquivo.
- **O roteiro de publicar na produção existe** (o **D37**, PARCIAL; o item 9 de "O que falta" da etapa 1):
  `scripts/publicar-producao.sh`. O `montar` (na sua máquina: o `git archive`, o composer numa imagem do Dockerfile do PHP, a
  build com `--base=/novo/`, e a impressão digital do que a produção roda); no servidor, o `levar` (a pasta ao lado, com o
  `local.php` 640), o `trocar` (o da janela: o `data/`, as pastas de upload e o avatar por `mv -T`, o `app.json` de lá, a troca
  de nome e a tela nova), o `entregar` (o das entregas: o `trocar`, o PHP-FPM e a conferência do passo 10) e o `voltar` (na
  volta ao código antigo, na ordem de "A volta": com o bloqueio do `migrar-senhas` carregado, a árvore nova em
  `public_html.novo-<data>` e sem recarregar o PHP-FPM, que vem depois do `devolver-nomes`). O `trocar` e o `entregar`
  recusam fora da hora da 206 (a) e quando a entrega não cabe até a hora-limite pelos números do ensaio, sem a manutenção
  carregada no nginx, com o cron ativo, sem o crontab desta noite guardado (sem padrão; numa entrega, nunca o da janela, que a
  volta ao código antigo religa), com rotina em curso depois da espera (o usuário do `local.php` da release, o do que está no
  ar e, com o código antigo no ar, o `USUARIO_DB_NO_AR`), e com a produção divergente da impressão digital (a regra do D37);
  o `levar` só confere a hora (é o passo 1, antes da manutenção); o `voltar`, a manutenção, o cron e as rotinas, sem hora
  nem impressão. **Escrito e nunca rodado em servidor nenhum**, e **recusa sem a página de manutenção**, que não existe (o
  item 10). O dono revisa antes da janela.

**O provado.** Três ensaios novos no `conferir-tudo.sh` (85 portões no conjunto, "Nenhuma falha inesperada" depois do conserto
de uma referência nos débitos), e um deles no `ci.yml`: `ensaiar-escolhas-do-deploy-teste.sh` (30 casos: o roteiro do teste
inteiro com dublês -- o `publicar`, o `atualizar` com o site fora sem a marca do `tirar`, o `voltar` e o `_aplicar` sem a
escolha recusam sem ligar nada, e passam com ela; o `instalar_vhost` e o `conferir_publico` tirados do roteiro; o `app.json`
num repositório temporário, com o merge que para no arquivo reproduzido; e a ordem lida sem os comentários);
`ensaiar-trava-do-deploy-teste.sh` (5 casos, num contêiner descartável da imagem do app, com o root e o usuário `outro`, cada
um no seu clone, nos dois sentidos); e `ensaiar-publicar-producao.sh` (18 casos: o `montar` com um `docker` de mentira, e o
resto num contêiner descartável com uma produção inventada sobre a árvore da importação). **Os tempos do ensaio local**, um
pacote de cerca de 28 MB: desempacotar ao lado em cerca de 0,6 s, a entrega em cerca de 0,7 s e a volta em cerca de 15 ms
(com 340 MB de upload e 300 MB de `data/`, 0,8 s e 19 ms) -- trocas de nome e uma cópia; os da produção saem do ensaio no BVIP
(a 199 (a)). Os dois novos de contêiner ficam fora do CI (a `FORA_DO_CI` do `conferir-lacuna-do-ci.sh`: a imagem do app e o
histórico). Os testes que leem os documentos tocados (o procedimento da etapa 1 e o desenho da versão do aplicativo: 73 casos)
nos dois bancos, saída 0. Os mutantes, um por vez, nas cópias, todos vermelhos: no D451, o padrão `nao` de volta, a chamada
arrancada do `instalar_vhost` e a do `cmd_atualizar`, e a reprovação do `conferir` (e o roteiro do HEAD inteiro); no D444, o
roteiro do HEAD (a trava no `$HOME`) e o `exec 9>` no lugar do `9<`; no D549, o pathspec arrancado, a recusa do
`cmd_atualizar` arrancada e o `git status` cru no `subir_e_ajustar`; no D37, onze (sem a manutenção, sem a hora no `levar`,
sem a impressão, `cp -a` no lugar do `mv -T`, o dono do pacote mantido, a volta sem as pastas, o `app.json` do repositório na
volta do meio, sem o cron, sem a hora-limite, o `local.php` contado na impressão, a espera sem as conexões). A carga e o selo
do `deploy-teste.sh` (o `cmd_base`, o `selo_da_copia`, o `selo_da_base`, o `ARQUIVOS_DA_COPIA` e a `VERSAO_DA_CARGA`) são os do
HEAD, comparados função a função; a `VERSAO_DO_DEPLOY` segue 9. Fatia ímpar: sem o ciclo inteiro; o código da aplicação e o
front não mudaram. O `local.php` voltou ao `franquiabv_dev`, conferido pelo sha1.

**A verificação em três lentes** confirmou 12 achados (5 médios, 7 baixos), todos consertados com caso, código ou texto,
nenhum refutado: no `publicar-producao.sh`, o crontab guardado com padrão (o da janela, que numa entrega passava velho e era
o que o fim mandava reinstalar), a espera das rotinas que só contava o usuário do `local.php` da release (a leitura 10), o
`voltar` fora da ordem de "A volta" e com outro nome de árvore, o `Bearer` da forma nova impresso em claro, e os documentos
que diziam que todo subcomando recusa tudo; no ensaio dele, o `trocar` sem nenhum caso de recusa, as guardas do `voltar` sem
caso e a metade "depois das 00:05" sem caso; no `deploy-teste.sh`, o fim do `base` deixado de fora sem motivo, a trava que
nascia com o umask de quem a criava e a que qualquer usuário do servidor podia segurar; e, no ensaio das escolhas, a recusa
medida só pela mensagem (a que imprime e segue ficava verde) e o freio do `publicar` sem caso. **Depois da correção:** o
ensaio das escolhas com 31 casos, o da trava com 14 (com um terceiro usuário, de fora do grupo) e o de publicar com 41;
mutantes novos, um por vez sobre cópias, todos vermelhos -- 19 no `publicar-producao.sh`, 6 na trava, 4 nas escolhas (o
`( exigir... ) || true` no `atualizar` e no `voltar`, o freio armado depois da recusa, o fim do `base` curto de volta). O
ensaio da trava achou, no caminho, um `exec 9<... 2>/dev/null` que calava o stderr do roteiro inteiro, consertado antes de
qualquer rodada.

**O que não foi provado:** nada no BVIP (se o teste de lá foi publicado aberto segue a pergunta; o `conferir` desta versão,
lá, responde), o `fs.protected_regular` ligado (o kernel daqui o tem em 0; a trava não depende dele), e nada na produção: o
roteiro de publicar nunca rodou num servidor, os tempos são de um contêiner no Mac, e a página de manutenção, o ensaio no BVIP
os nomes da leitura 7 (o serviço do PHP-FPM, o usuário e o grupo do pool) e o usuário do banco do código antigo (a leitura
10) seguem do dono; e, no BVIP, o grupo `docker` e quem está nele (quem roda o deploy fora dele sai 1, dizendo isso). O `ensaiar-build-do-publicar.sh`
(fora do corredor: docker e rede) não rodou; o `instalar_vhost` manteve os dois `auth=` que ele lê. O CI do GitHub está parado
por cobrança. Nada da produção mudou. De pé 132.

## 08/11/2026 -- PREPARO-DO-PHP-E-DO-ARMAZENAMENTO (a fatia 130, debitos)

**O que muda para quem usa:** nada que se veja. A foto do colaborador (no topo do ERP, na lista e na foto do usuário, e nas
respostas do aplicativo) passa a pedir o endereço ao `Armazenamento`, e no disco local o endereço é o mesmo de sempre. O
que muda é para o corte e para depois dele: o projeto deixa de estar preso ao PHP 8.1, e o dia em que o armazenamento virar
para o bucket a foto vai junto.

- **O PHP 8.1 deixa de ser trava** (o **D11**, PREPARADO). O `laminas/laminas-cache` foi de 3.6.0 (só até o 8.1) para 3.13.0,
  e o adaptador de filesystem de 2.3.0 para 2.5.0 (os dois do 8.1 ao 8.4); saiu o `stella-maris/clock`. O `composer update`
  rodou dentro do contêiner, e o `vendor/` do host foi recopiado dele e conferido (4.901 arquivos, o mesmo sha1). O lock
  agora aceita do 8.1 ao 8.3: o `composer install` do lock antigo numa imagem com `PHP_VERSION=8.2` recusa, o do novo
  instala, e **a suíte de caracterização inteira passou no PHP 8.2.35**, nos dois bancos (7.211 casos em cada; um aviso de
  depreciação do `doctrine/dbal` 2 no PostgreSQL, que não é nosso). O `vendor/` novo **sobe na janela**, no pacote da
  release (`procedimento-de-corte-etapa-1.md`, a leitura 8); trocar o PHP do servidor é do corte. O contêiner do dia a dia,
  o CI e o padrão do `Dockerfile` seguem no 8.1, e o Composer segue o 2.2 (o `laminas-dependency-plugin` ainda o exige).
- **A foto do colaborador sai pelo `Armazenamento`** (o passo 3 do **D33**, PARCIAL): os sete pontos que montavam o endereço
  à mão -- três telas antigas, o endereço da foto recortada e as três respostas da API (a agenda e o consumo do cliente, a
  lista de colaboradores da unidade). Na API, pelo `urlCompleta` novo: **no disco, a URL sai byte a byte a de sempre**, com
  as duas barras que a lista de colaboradores sempre mandou, porque há aplicativos antigos em campo (o D5). Seguem à mão a
  URL dos banners na API, as capas dos assistentes (esperam o dono) e o `app.json` (o D36).

**Os testes:** o `D33EnderecoDaFotoPeloArmazenamentoTest` (9 casos, nos dois bancos) -- no disco, a API igual à fórmula
antiga e as telas pedindo ao site; no bucket com a `url_publica`, as três telas, as três respostas e a foto recortada
apontando para ele. O ciclo inteiro (fatia par), saída 0 em todas as etapas: caracterização 7.211 casos no MySQL (24
pulados) e 7.211 no PostgreSQL (17 pulados), contrato 74 e 74, os ensaios 22 e 22 com saída 0, portões 85 ok, front 4.785;
o `p48-medir-relatorios-pg.sh` à parte, saída 0. A suíte inteira também no PHP 8.2, nos dois bancos, saída 0. **Os
mutantes**, um por vez, nos dois bancos, devolvidos pelo sha1: M1 (o `urlCompleta` do disco tirando a barra a mais), M2 (o
do bucket colando o prefixo do site), M3 e M4 (a agenda e o consumo de volta à mão), M5 (a lista de colaboradores de volta
à mão), M6 a M8 (as três telas de volta à mão), M9 (a foto recortada de volta ao `str_replace`), M10 (a lista sem a barra
que sempre colou) e M11 (a agenda sem a barra do `sistema.url`): todos vermelhos. No D11, o controle é o lock antigo
recusado no 8.2. **Não provado:** o PHP 8.3 rodando a suíte (só o lock o aceita), e nada num navegador (as telas mudaram só
no `src`, que sai pelo `escapeHtmlAttr`). A imagem do 8.2, o contêiner e o volume do ensaio foram apagados. O código da
tela nova não mudou. Nada da produção mudou. De pé 131.

**A verificação** (três lentes) confirmou cinco achados baixos, todos tratados na correção. (1) e (5) O texto velho da trava:
o `docs/f1-5-armazenamento.md` ("Por que o protocolo S3"), o comentário do `ArmazenamentoS3`, o do `Push/ContaDeServico.php` e o
`docs/notificacoes-push-comparativo.md` ainda diziam que o `laminas-cache` prende o projeto no PHP 8.1 (ou citavam o 3.6.0); agora
dizem o que ficou -- o Composer 2.2 pelo `laminas-dependency-plugin` e o `psr/cache` 1, que o 3.13.0 ainda pede. (2) **A virada do
armazenamento passa a pedir a `url_publica`** (ou um bucket de leitura pública): sem ela, o `url()` do bucket é o endereço cru, que
o navegador e o aplicativo pedem sem assinatura, e com o bucket privado as fotos sumiriam (1.498 usuários com `padrao*` e 934 com
`avatar_*` no `franquiabv_dev`); entrou como o passo 0 de "Migrar os 346 MB" no `f1-5`, com os três `padrao*` versionados que
também sobem, no comentário da classe e no D33. (3) **A agenda da API nova** (`/api/v4/clientes/:id/agenda`) devolve só o nome do
arquivo da foto e fica de fora, escrita no D33 e no `f1-5`: trocar muda o contrato, e nenhum aplicativo fala o `/api/v4` ainda; o
caminho é um campo com o endereço pronto quando o aplicativo novo for escrito. (4) **A trava do D11 tem guarda**: o
`D11LockAceitaOPhpNovoTest` (2 casos) confere, pelo `composer/semver`, que o `composer.json`, a `platform` do lock e cada pacote
aceitam o 8.1, o 8.2 e o 8.3. Os mutantes, nos dois bancos, devolvidos pelo sha1: o `composer.json` e o `composer.lock` do
`281c9c0a` no lugar (vermelho, nomeando o `laminas-cache` 3.6.0 e o adaptador 2.3.0) e o `composer.json` pedindo só `~8.1.0`
(vermelho, nomeando o `composer.json`). Os testes depois da correção: os tocados (o D11, o D33, o `VipClassArquivosTest`, o
`ArmazenamentoConfinadoTest`, o `EnvioDePushTest` e o `BannersPelaApiTest`), 82 casos e 690 asserções em cada banco, saída 0. Sem
pergunta ao dono. De pé 131.

## 08/11/2026 -- CONVENIO-REVISTO-NA-VENDA (a fatia 136, as respostas 362 e 363)

**O que muda para quem usa (a 362 (a) do dono, o D738, PARCIAL: três ramos voltam ao dono, abaixo):** a venda que perde o motivo do convênio que guarda
revê o convênio. Ao trocar o cliente da comanda (o "Abrir comanda" do PDV antigo e o da tela nova, que reaproveitam a venda
em andamento; e a venda sem cliente que ganha o do pedido) e ao sair da venda o item do convênio (tirado pelo PDV antigo ou
pela tela nova, ou com o cupom vencendo na linha), a venda passa ao convênio do último item que ficou com o desconto de um
convênio (o de maior id, o nome que bate com exatamente um convênio -- o critério do acerto do passado); sem nenhum, ao do
cliente de agora, como se fosse aberta agora (a mesma conta da abertura, que passou a morar num lugar só,
`VendaService::convenioDaAbertura`). A comanda trocada de cliente deixa de fechar na conta do convênio do anterior, e a
venda cujo item do convênio saiu deixa de ficar com ele: a lista de vendas do convênio e a "última venda" dele passam a
contar a venda certa. O item sem convênio que sai e o "Abrir comanda" do mesmo cliente não mexem em nada. Nenhum valor
muda: só a coluna `vendas.convenio`. O dia sem convênio na abertura fica como está, como a resposta diz.

**O acerto do passado (a 363 (a), o D632, segue PREPARADO, espera o dono):** `scripts/` e `postgres/acertar-convenio-das-vendas.sql`
também avançam a "última venda" dos convênios que recebem venda concluída acertada, para a data da mais nova delas, só
quando passa da gravada (ou a gravada é nula) e nunca para trás, com a lista do convênio, da data de antes e da nova antes
da escrita. Na cópia (só SELECT de contagem), 8 convênios andariam. Os procedimentos de corte dizem isso.

**Os testes:** `D632VendaSegueOConvenioDoItemTest` e `AcertoDoConvenioDasVendasTest` (42 casos, nos dois bancos, saída 0):
o item do convênio que sai pelas quatro portas, sem item que ficou (o do cliente de agora), o item sem convênio que sai, a
troca pelas duas portas, a venda sem cliente, o nome do último item; o roteiro com a data que fica, a que anda, a nula, a
aberta e a cancelada que não contam; e **o lançamento e o roteiro dizem o mesmo** (a revisão nas dez vendas do cenário do
roteiro, e o exemplo da pergunta: X, depois Y, tira-se o Y -- os dois dão o X; fora dos três ramos que esperam o dono). Os
vizinhos (451 casos) nos dois bancos,
saída 0. O roteiro inteiro pelo `mysql` e pelo `psql`, duas vezes, em dois bancos descartáveis apagados no fim. O ciclo
inteiro (fatia par), saída 0 em todas as etapas: caracterização 7.234 casos no MySQL (24 pulados) e 7.234 no PostgreSQL
(17 pulados), contrato 74 e 74, os ensaios 22 e 22 com saída 0, portões 85 ok, front 4.785 com build e lint; o
`p48-medir-relatorios-pg.sh` à parte, saída 0.
**Os mutantes**, um por vez, devolvidos pelo sha1, nos dois bancos: M1 (a troca sem rever), M1b (a venda sem cliente sem
rever), M2 (a remoção sem rever), M3 (o cupom que vence sem rever), M4 (a ordem do "último" trocada), M7 (a troca sem a
guarda do mesmo cliente), M8 (a remoção que sempre revê), M10 (o nome de dois convênios escolhe o primeiro), M12 (sem o
nome cortado em 100) -- todos vermelhos nos dois; M11 (sem a comparação byte a byte) vermelho no MySQL e verde no
PostgreSQL, que já compara exato; e nos roteiros, S1/P1 (a data que volta para trás), S2/P2 (sem tocar a data) e S3/P3
(sem o filtro da venda concluída), vermelhos no banco de cada um. Nada num navegador (nenhuma tela mudou de aparência).
Nada da produção, do BVIP nem do franquiabv_dev mudou (só contagem).

**A verificação (três lentes) e a correção:** dois casos a mais no `D632VendaSegueOConvenioDoItemTest`, nos dois bancos --
a lavagem relançada pelo "+" da tela nova não mexe na venda (a guarda do `alterar`, que nenhum caso pegava; o mutante MA,
o `alterar` que sempre revê, vermelho só nele) e o corte do convênio que herda a sessão do pacote do corte tirado leva a
venda ao convênio do cliente de agora (o mutante MB, a revisão antes de a sessão passar, vermelho só nele). **Três ramos
que a 362 (a) não decide voltam ao dono**, com três perguntas no lote (D738), e o código segue o critério do roteiro até a
resposta: o último item que fica com um nome que não é de um convênio só deixa a venda no convênio do item que saiu (o
sintoma do D738, que os casos "espera o dono" fixam como o de hoje); o "+" de uma linha antiga depois de o convênio do
cliente mudar uma vez leva a venda ao convênio novo, e o roteiro, rodado depois, ao do maior id (o texto da fatia dizia
"indo e voltando": basta uma mudança); e o motivo "Convênio X" digitado no balcão conta, na revisão, como item do X. Por
isso o D738 é PARCIAL, e não CORRIGIDO. O D632 segue PREPARADO, como desde a 132: o código está pronto e o que resta é o
dono rodar o roteiro (PARCIAL o poria de novo entre os de pé sem nada de código pendente). Os testes da correção: os dois
arquivos da fatia (44 casos) nos dois bancos, saída 0; os portões tocados, saída 0. De pé 131.

## 08/11/2026 -- GORJETA-DE-QUEM-APARECE-NO-PDV (a fatia 137, a resposta 372)

**O que muda para quem usa (a 372 (c) do dono, o D739 CORRIGIDO):** o troco que vira gorjeta só pode ir para quem está
na lista de "quem atendeu" da mesma tela. **Quem tem "Aparece no PDV: não" deixa de poder receber gorjeta nas duas
telas** -- no PDV antigo e na comanda nova. No PDV antigo, o seletor "gorjeta para" deixa de ser o grupo "Colaborador"
inteiro e passa a ser a lista de "quem atendeu" do carrinho: saem os colaboradores com "Aparece no PDV: não" (9 ativos na
cópia) e entram os de outros grupos que aparecem no PDV (gerente, caixa, recepção: 88 ativos na cópia). Na comanda nova,
o destino do troco oferece a mesma lista de "quem atendeu", a da porta do PDV, e não mais todos os ativos da unidade.
O servidor confere ao finalizar, nas duas telas: quem não está na lista (inativo, de outra unidade, sem o PDV, ou um id
escrito à mão) é recusado com a frase "A gorjeta do troco vai só para quem está na lista de quem atendeu desta unidade:
ativo e com "Aparece no PDV: sim".", sem gravar nada -- nem a venda, nem o caixa. O crédito do cliente do troco e a
lista de "quem atendeu" não mudaram.

**Os testes:** `D739GorjetaDeQuemApareceNoPdvTest` (9 casos) e os vizinhos que finalizam, desenham o seletor do troco ou
a lista de usuários (832 casos), nos dois bancos, saída 0. No front, os três arquivos tocados (324 casos) também com
`AMPLIFICAR_EFEITOS_MS=50`, o vitest inteiro com `--maxWorkers=2` (4.785 casos), tsc, lint e build, saída 0. **Os
mutantes**, um por vez, devolvidos pelo sha1, vermelhos nos dois bancos: o seletor do PDV antigo de volta ao grupo
Colaborador; o servidor sem a conferência (as duas portas); a recusa que grava pela metade (depois do fechamento da
venda); a conferência arrancada só da API e só do PDV antigo; a consulta sem o teto do administrador; as frases depois
da recusa do serviço; a regra sem a conferência dos algarismos; e, no front, a lista da gorjeta de volta a todos os
ativos. Os portões, saída 0 (a base do `conferir-cobertura-por-par` sobe para 457 pares, com o par novo
`UsuarioRepository::quemAtendeNoPdv`). O D516 fica PARCIAL sem o destino do troco (falta a busca e o ERP antigo).
**Não visto:** nada num navegador. Nada da produção, do BVIP nem do franquiabv_dev mudou (só contagem). De pé 130.

**A verificação (seis achados confirmados):** a lista do caixa da comanda nova (`?porta=pdv`) e a conferência da gorjeta
ao gravar passam a usar a mesma subconsulta (a constante `UsuarioRepository::QUEM_ATENDE_NO_PDV`), em vez de duas cópias
dos critérios: a porta com `?estado=inativo` não traz mais quem está inativo. Quem só entra pelo PDV recebe sempre a
lista do caixa, com ou sem a porta: a lista de antes, que a comanda nova pedia só para a gorjeta, entregava nomes que
nenhuma tela lhe mostra. Corrigidas as contas e os textos velhos do D739 e do D517. **Uma pergunta ao dono:** a (c) dá a
gorjeta, no PDV antigo, a gerentes, caixas e administradores que aparecem no PDV (88 ativos na cópia), e a opção não
o dizia; o código segue a (c) até a resposta. Os testes: `D517` e `D739` (16 casos), os vizinhos (833 e 734 casos),
nos dois bancos, saída 0; três mutantes novos vermelhos nos dois bancos (a porta com a DQL própria, a porta só com
`?porta=pdv` para quem só entra pelo PDV, e a subconsulta sem o "Aparece no PDV", que derruba as duas listas juntas).
O front não mudou na verificação.

## 08/11/2026 -- RESPOSTAS-DE-08-11-API-E-CADASTRO (a fatia 138, as respostas 373 a 376)

**O que muda para quem usa.** As exportações de vendas da unidade (`/api/unidade/vendas`, a `V2` e a `V3`, que o sistema de
cada unidade chama com o hash dela) entendem a data com traço e com barra nas três versões -- `2026-03-10` e `10/03/2026` dão o
mesmo período, e no banco novo a barra não vira mais mês/dia (a v1 lia `01/02/2026` como 2 de janeiro). A data que não existe ou
vem torta (`31/02/2026`, `1/03/2026`, `abc`) recebe "pedido inválido" (HTTP 400, em JSON, com a frase que diz qual das duas
datas), no lugar do erro do servidor; a página da API diz as duas formas e o 400 (a resposta 374 (a), o D742). A unidade nova
cadastrada quando a unidade 1 não tem os textos das mensagens (ou, no banco novo, por alguém de outra rede) nasce com os textos
em branco, e a aba "Mensageria" abre; a das 4 unidades da cópia que já nasceram sem eles também abre, em branco, e só grava
quando alguém salva -- nenhuma linha foi escrita em banco nenhum (a resposta 373 (a), o D377 PARCIAL). Com os textos em branco,
nenhuma mensagem automática sai; duas delas (o crédito do programa usado e o cupom gerado) gravavam um aviso sem canal e
derrubavam a venda, e não gravam mais. A meta da Cota-parte da rede passa a até 30% acima do Profissional Parceiro, com a
medida de 26% a 30% (a resposta 375 (a), o D675 encerrado, sem código); e os cinco privilégios sem tela ficam na lista, sem
efeito (a resposta 376 (a), o D485 decidido, sem código).

**Os testes:** o ciclo inteiro (`ciclo-138`): a caracterização com 7.256 casos nos dois bancos, saída 0 (25 e 17 pulados); o
contrato com 74 nos dois; os ensaios do MySQL e do PostgreSQL, saída 0; o front com 4.785 casos (sem mudança no front); os
portões, 84 ok e um vermelho -- o `conferir-agendamento` deixou de ver a v1 e a v2 como consulta, porque a leitura da query saiu
da ação para o ajudante; a ação voltou a ler a query, e os portões, rodados de novo inteiros, deram 85 ok; o
`p48-medir-relatorios-pg.sh`, à parte, saída 0. Os casos novos:
`D742DatasDaExportacaoDaUnidadeTest` (5), quatro em `TelasDasUnidadesDoErpTest` (um só no PostgreSQL, a outra rede pela RLS) e
um em `D351MensagensDoBalcaoTest`, nos dois bancos; e o caso 4 do `ensaiar-erro-json-da-api.php` (18 conferências por HTTP, 28 de
28 nos dois bancos do contrato). **Os mutantes**, um por vez e devolvidos pelo sha1, vermelhos nos dois bancos: cada versão da
exportação sem a barra, a data torta de volta ao 500, a barra lida como mês/dia, o cadastro sem a linha em branco, a aba de volta
ao 404, o push do crédito e o do cupom sem a guarda, e o salvar da aba seguindo o `id` do POST; pelo ensaio, a v1 sem a barra no
PostgreSQL e a torta de volta ao 500 no MySQL. **Não visto:** nada num navegador; o sistema de cada unidade, que chama a rota, não
está aqui. Nada da produção, do BVIP nem do franquiabv_dev mudou (só contagem). De pé 127.

**A verificação da fatia 138** achou seis pontos, e cinco viraram conserto. O salvar da aba "Mensageria" passa ao hidratador só
as quinze chaves de texto (`UnidadeService::TEXTOS_DA_MENSAGERIA`): `Status=0` ou `DATA_CRIACAO`, em qualquer caixa, não
desativam mais a linha (as mensagens automáticas da unidade paravam) nem refazem a data de criação. O parabéns, a primeira visita,
o "sentimos sua falta" e o NPS da venda foram rodados no banco da suíte com a linha em branco: não enfileiram nada, e com os textos
escritos saem pelo WhatsApp. A página da API diz que, sem a data ou com ela vazia, a resposta é o texto "Acesso negado." (o 200,
sem JSON), e o ensaio o confere por HTTP. O comentário do passo do `erro-json` no `ci.yml` diz que o caso 4 depende das fixtures.
**Vai ao dono** (o D744, novo, no lote de 08/11/2026): a v1 lia pelo banco a data com hora, sem o zero e junta, e agora dá 400 a
essas formas; o 400 fica até a resposta, travado por um caso. **Os testes**, em série: a caracterização com 7.259 casos nos
dois bancos, saída 0 (25 e 17 pulados); o contrato com 74 nos dois; os ensaios dos dois bancos, saída 0, o `erro-json` com 34 de 34
em cada um; os portões, 85 ok, "Nenhuma falha inesperada"; o front não mudou e não rodou de novo. Casos novos:
`testOPostDaAbaSoGravaOsTextosEmQualquerCaixa`, `testComOsTextosEmBrancoAsRotinasNaoMandam` e
`testAsFormasQueAV1LiaPeloBancoSaoRecusadasAteODonoDecidir`, e a página conferida também no "Acesso negado.". **Os mutantes**,
um por vez e devolvidos pelo sha1, vermelhos nos dois bancos: o `unset` das minúsculas no lugar da lista, a lista com
`data_criacao`, a lista com `status`, sem lista nenhuma, cada uma das quatro rotinas sem o teste do texto vazio do WhatsApp, e a
página sem o "Acesso negado.". De pé 128 (o D744).

## 08/11/2026 -- ASSISTENTES-DESENHO-DAS-RESPOSTAS (a fatia 139, as respostas 364 a 371)

**O que muda para quem usa: nada ainda.** É uma fatia só de documentos: o desenho dos assistentes
com a API da Anthropic (`docs/f3-telas-restantes/assistentes-anthropic.md`) foi reescrito pelas oito
respostas do dono de 07/11/2026, e as fatias 133, 134, 87, 88 e 89 ficam prontas para rodar como ele
decidiu. **O que o desenho passa a dizer:** o modelo padrão é o Claude Haiku 5.5 (a 366 (c)), com o
Opus 5.5 e o Sonnet 5.5 escolhíveis por assistente; o esforço vai `low` e o raciocínio sempre ligado;
no Haiku a recusa é a resposta final (sem a troca automática), e a pessoa lê "O assistente não pode
responder a isso. Escreva de outro jeito."; o gasto típico cai para perto de US$ 6 por mês, e o pior
caso para ~US$ 80 a ~US$ 300 (as contas refeitas pela skill `claude-api`). Os limites e os tetos do mês
ficam os da 367 (a). As páginas `/rh`, `/ajudantevip` e `/motivacionalvip` passam a pedir o login do
ERP e levam ao assistente na tela nova (a 368 (b)); saíram a porta sem login, o cookie do visitante, a
vaga própria das páginas e o limite por IP. O `/invencivel` sai do ar e leva a
`https://barbeariavip.com.br/` (a 371 (b)). A conversa fica guardada para sempre, sem rotina de apagar,
e a tela da administração que as lê ficou registrada fora da fila (a 369 (c)); os dados do sistema por
ferramentas ficaram registrados como projeto novo, fora da fila, com o que ele precisa decidir (a
370 (c)). A 365 (a) já estava no desenho. A fatia 89 mudou de nome (ASSISTENTES-PAGINAS-COM-LOGIN) e
não espera mais as perguntas 4, 5 e 8. **Depois da verificação** (dez achados, todos consertados no
desenho, na seção 7, "A conferência da 139"): o lado das páginas na cota e no teto do mês saiu da 133
para a 89, porque a pergunta nova das páginas pode desfazê-lo, e a 133 passou a não depender de nada
de verdade (~1.850 linhas); a 89 ganhou a peça da tela que leva a marca da página até a conversa, a
volta à página depois de entrar (o entrar do ERP não guarda o destino hoje, e a 89 o guarda só para as
três páginas, com uma regra pura que só aceita caminho do próprio site) e os testes certos (sem o
`ler`, 403 da API e a tela recusada no console; de grupo não marcado, 404), e subiu para ~1.600 linhas
(M); a 87 ganhou o detalhe pelo endereço, que a 88 e a 89 usam, e a recusa na personificação.

**O rascunho das instruções** (a 364 (b)): `docs/f3-telas-restantes/assistentes-instrucoes-rascunho.md`,
um bloco por assistente (RH VIP, Consultor VIP, Review do google e Motivational Coach), escrito pelo
nome e pela descrição de cada um, sem nada secreto, pronto para o dono ler, corrigir e colar no campo
"Instruções" depois da 134. Não fica igual ao Zapier.

**Com o dono:** (1) **o aviso e o registro da LGPD** pela guarda das conversas para sempre e pela tela
futura da administração (o desenho não os escreve, nem decide se a tela entra antes ou depois do
aviso: a 369 (c) não deu a ordem), e a retenção dos pedidos no painel da Anthropic, quando a conta for
criada; (2) **três perguntas novas** no lote de 08/11/2026, que a letra das respostas não decide: se as mensagens que chegam pelas páginas, agora com
login, contam no limite do ERP ou num limite próprio (até a resposta, o próprio, com os números da
367 (a)), quem pode usar as três páginas depois do login (até a resposta, a regra da tela nova: na
cópia, 117 dos 371 ativos para o `/rh` e o `/ajudantevip` e 187 para o `/motivacionalvip`, contando os
10 administradores, que passam sem linha; para abrir a mais alguém é preciso o grupo marcado **e** o
`ler` na ficha da pessoa), e se quem entra como outra pessoa ("Autenticar como") pode ler e escrever
nas conversas dela (até a resposta, a 87 recusa: para ela, o administrador personificando é a pessoa,
e veria todas as conversas guardadas para sempre); (3) a conferência na produção antes da 88, e colar
as instruções dos quatro assistentes.

**O provado:** os portões, 85 ok, "Nenhuma falha inesperada" (saída 0), e o dos segredos; o
`contar-debitos` e o de débitos repetidos, saída 0. **O portão das referências da documentação não
prova nada sobre este desenho:** ele lê só `docs/*.md`, e não `docs/f3-telas-restantes/` (o D745,
aberto na verificação; de pé 129); as citações com linha que a 139 acrescentou foram conferidas à
mão, e uma antiga, velha, consertada (`menu-do-erp.ts:909`, hoje `:924`). **Um teste lê o desenho do
disco:** o `EvasaoSaiuTest::testNenhumDocumentoDizQueAEvasaoAindaLeOCache` percorre todo `.md` de
`docs/`, subpastas inclusive (a primeira escrita deste parágrafo dizia que nenhum lia, pelo grep do
nome do arquivo, que ele não cita); rodado nos dois bancos com os documentos da 139, verde. **Não visto:** nada num navegador; nenhuma chamada à Anthropic; o tamanho em tokens
dos rascunhos (contar pede a API). Nada da produção, do BVIP nem do franquiabv_dev mudou (só SELECT:
os nomes e as descrições dos assistentes e contagens). Abriu o D745; nenhum fechou.

## 08/11/2026 -- ASSISTENTES-PORTA-DO-CLAUDE (a fatia 133)

A ordem do dono nesta retomada: "retornar a fila"; a decisão de 07/11/2026: "as fatias de assistentes
serão substituídas do zapier para Anthropic". A especificação é o bloco 133 de
`docs/f3-telas-restantes/assistentes-anthropic.md`, revisto pela 139 com as respostas 364 a 371.

**Para quem usa, nada muda ainda:** os assistentes seguem no Zapier, e a tela nova ainda leva à antiga.
O que ficou pronto é a porta do servidor para a fatia 87 (a rota e a tela) chamar: o esquema (as cinco
colunas novas de `assistentes` -- as instruções, com utf8mb4 na coluna, o modelo que nasce
`claude-haiku-5-5`, o esforço `low`, o teto de 4.096 tokens e a página -- e as quatro tabelas da conversa,
da cota e do uso, pela `migracao-assistentes-anthropic.sql` no passo 0 do corte e nas duas cópias
locais, às 18:00 de hoje, duas vezes, a segunda sem efeito); o cliente por cURL com streaming
(`ISBase\Claude`), sem endereço padrão no código; a `RegraDoAssistente` com a lista dos modelos, o
pedido, o reenvio depois da troca de modelo, o tempo, os preços datados, a regra do 400 e as frases; e o
`ConversaDoAssistenteService`: o `system` congelado por conversa, a conversa só acrescentada, a sessão
solta antes da vaga, as duas vagas, uma resposta por vez na mesma conversa, a cota do ERP por UPDATE condicional numa transação que fecha antes
da chamada, o teto do mês do ERP, a recusa e o erro fora do histórico, a reserva devolvida quando a
Anthropic não chegou a responder, e o 400 do raciocínio com o `drop_block` em todo pedido seguinte da
conversa. O lado das páginas na cota é da 89. Sem a chave, o endereço ou o sal no `local.php` -- o
estado de todo ambiente daqui --, o assistente diz "O assistente está indisponível no momento.".

**Nenhuma chamada saiu para a Anthropic:** os testes usam o dublê (`tests/Caracterizacao/DubleDoClaude.php`)
e servidores de mentira no 127.0.0.1, com uma chave inventada; o portão novo,
`conferir-sem-anthropic-nos-testes.php` (no corredor e no CI), prende que o endereço real não está
escrito em `module/`, `tests/`, `scripts/` nem `frontend/src` (os PHP lidos sem comentário) e que o
cliente não tem endereço padrão -- falsificado nos dois sentidos (o endereço no código de um teste, o
padrão no construtor e o endereço escrito no cliente: saída 1 nos três; o endereço só num comentário:
saída 0). A referência da API (lida, não chamada) confirmou o `metadata.user_id` como identificador
opaco: vai o HMAC do id com o sal.

**O provado:** os casos novos (`ConversaDoAssistenteTest`, `ClienteDoClaudeCurlTest`,
`RegraDoAssistenteComAnthropicTest`) e os vizinhos que leem o esquema, a regra ou os documentos do corte:
319 em cada banco, verdes, e depois da trava da conversa em gavetas (o último conserto) o `ConversaDoAssistenteTest` de novo, 27 em cada (o `EsquemaQueSoAcrescentaTest` ficou vermelho com a chave primária nova da
cota, que segue a regra do gerador, e a lista dele ganhou a linha); 30 mutantes, cada um vermelho no
seu caso nos dois bancos (dois só num: o padrão da coluna e o utf8mb4 vivem no `mysql.sql`, e o padrão
tem o irmão no `schema.sql`), devolvidos pelo sha1; os portões, 86 ok, "Nenhuma falha inesperada"
(o da limpeza por trás da unicidade passou a não contar o nulo como repetido -- a página nasce nula nos
quatro assistentes --, e o do `schema-tool` subiu de 17 para 22 pelas cinco colunas fora do
mapeamento); o contrato, 74 e 74; as migrações ensaiadas em quatro bancos descartáveis, iguais ao
`mysql.sql` e ao `schema.sql` de hoje, apagados no fim. **Não visto:** a velocidade real do modelo e o
nginx da produção segurando o streaming (a 88); nada num navegador (não há tela). Nada da produção nem
do BVIP mudou; no `franquiabv_dev`, só a migração que acrescenta. Abriu o D746 (PREPARADO: a porta pronta,
o que falta e de quem); nenhum fechou. De pé 129.

**A verificação da 133 (no mesmo dia):** onze pontos, todos conferidos no código e corrigidos, sem mudar o que a
pessoa vê. **O que faltava:** a resposta boa não gravava no log o tempo e os tokens de saída, a linha que a
conferência do dono antes da 88 lê para medir a velocidade (agora `respondida em <ms> ms, tokens de saida <n> (<x>
tokens/s), ...`, e todo erro leva o tempo); o assistente inativo dizia "sendo preparado", e o desenho manda o 404
(agora `nao_encontrado`, que a 87 traduz no 404 de sempre). **A leitura escrita:** a reserva da cota continua
devolvida em todo erro antes do primeiro byte, os 4xx inclusive (o 2.7 e o 2.8 do desenho se contradiziam; o 4xx não é
cobrado), na seção 7 do desenho e com o caso que a prende. **Os casos que não provavam o que diziam:** os dois envios
no último lugar agora correm contra um processo à parte que confirma a linha enquanto o serviço espera (o "ler e
depois subir" passava no caso antigo), a gaveta da conversa com um id acima de 256, as duas fronteiras do teto do mês e
o gasto igual ao teto, a conversa de outro assistente e a que não existe, a resposta inteira só com raciocínio, a
chave e o sal fora do log e da exceção do cliente, e a fábrica com o registro no service manager (sem pedido nenhum).
**O que espera o dono:** as conversas guardadas ficam fora da limpeza da base do teste do BVIP (o
`exportar-base-teste.sh` copia o banco inteiro, e a limpeza não conhece as tabelas novas); hoje as quatro estão
vazias, e o conserto muda os arquivos selados da cópia -- o D747 e a pergunta do lote de 08/11/2026 (recomendada a
(a), apagar as conversas na cópia). **O provado:** o `ConversaDoAssistenteTest` com 34 casos e o
`ClienteDoClaudeCurlTest` com 13, verdes nos dois bancos; a vizinhança (os testes que citam os assistentes, os
documentos, o portão do relógio e o processo à parte, e os de esquema), 1.782 em cada banco, verdes (6 e 5 pulados);
20 mutantes novos ou refeitos, cada um vermelho no seu caso nos dois bancos e devolvido pelo sha1 (o "ler e depois
subir" e a cota somada no caso novo dos dois envios; os dois do log do tempo; a reserva mantida nos 4xx; o inativo como
"sendo preparado"; a chave no log do erro, no aviso de indisponível e na exceção do cliente; a resposta só com
raciocínio gravada; a trava por conversa sem gavetas; a conversa de outro assistente aceita; a fábrica com o endereço e
a chave trocados, com a seção errada e fora do `Module.php`; o teto sem a fronteira de cima e com `>`; e, refeitos
sobre as linhas que mudaram, os blocos tirados à mão, a reserva não devolvida no 529 e o teto somando outro mês); os
portões, 86 ok, "Nenhuma falha inesperada" (o do relógio com a linha nova do cronômetro do caso dos dois envios). O
contrato não foi rodado de novo: nada desta correção muda esquema nem rota. Nenhuma chamada saiu para a Anthropic.
Abriu o D747; nenhum fechou. De pé 130.

## 08/11/2026 -- ASSISTENTES-ADMIN-ANTIGA-INSTRUCOES (a fatia 134)

A ordem do dono: "retornar a fila"; o bloco "### 134." de `docs/f3-telas-restantes/assistentes-anthropic.md`.

**O que muda para quem usa:** no cadastro antigo dos assistentes (Configurações > Assistentes VIP Admin, o novo e o
editar), o administrador vê dois campos novos: "Instruções", onde se escreve (ou se cola) o texto que diz ao assistente
quem ele é, o tom e o que não deve responder -- até 20.000 caracteres, com emoji, e uma linha de ajuda que avisa para
não escrever nada secreto ali e que mudar o texto vale para as conversas novas --, e "Modelo", com o Haiku 5.5
(padrão, mais barato) marcado no assistente novo, o Sonnet 5.5 e o Opus 5.5 (melhor, mais caro). O "iFrame" (o código
do Zapier) deixou de ser obrigatório: para o assistente ficar ativo basta um dos dois, e vazios os dois ele é recusado
com "Preencha as instruções (ou o código do Zapier, enquanto ele existir).", no formulário e, desde a correção abaixo,
também no botão "Ativar" da lista. Os quatro assistentes de hoje continuam no
Zapier até a 87 e a 88; o rascunho das instruções (`assistentes-instrucoes-rascunho.md`) pode agora ser lido,
corrigido e colado pelo dono -- nenhum roteiro o gravou, e nada foi gravado no `franquiabv_dev`. O esforço e o limite
de tokens ficam fora do formulário, como o bloco manda (mudam por SQL). Sem esquema novo: as colunas são as da 133, e
no banco sem elas (a base do teste do BVIP) o formulário fica como era.

**O provado:** os casos novos nos quatro testes da administração (`AssistentesPelosControllersTest`,
`TelasDosAssistentesDoErpTest`, `AssistenteVinculosTest` e `RegraDoAssistenteTest`): grava só com as instruções, só
com o Zapier, edita e apaga as instruções, recusa o modelo fora da lista, o vazio, 20.001 caracteres e o que passa dos
bytes da coluna, aceita os 20.000 com a quebra do navegador, o inativo sem os dois, o emoji gravado e lido igual, as
instruções escapadas no campo da edição, a falha nos vínculos desfazendo as instruções, e o banco sem as colunas --
170 casos verdes em cada banco; o ciclo inteiro (CICLO=134): a caracterização 7.343 em cada banco, verde (25 e 17
pulados), o contrato 74 e 74, os ensaios dos dois bancos sem saída diferente de zero, os portões 86 ok ("Nenhuma falha
inesperada"), o front 4.785; o `p48-medir-relatorios-pg.sh` saiu 0. 13 mutantes, cada um vermelho no seu caso nos
dois bancos e devolvido pelo sha1: o modelo fora da lista aceito no filtro e na regra, o ativo sem os dois aceito, o
iFrame obrigatório de volta, as instruções fora da transação e não gravadas na edição, as instruções sem escape na
edição, sem o teto de 20.000, sem o teto em bytes, o pedido sem o campo passando, a quebra do navegador sem virar LF,
a edição tratando todo assistente como ativo e o formulário com os campos sem as colunas. **Não visto:** a tela num
navegador (o coordenador confere no banco do contrato). Nenhuma chamada à Anthropic. Abriu o D748 (o botão "Ativar"
da lista não confere a regra dos dois; vai com a 88); o D746 segue PREPARADO, com a parte da 134 feita. De pé 131.

**A correção da 134 (a verificação):** quatro achados confirmados, todos consertados. **O "Ativar" da lista** ativava o
assistente inativo sem instruções e sem iFrame (o D748, que a primeira versão deixara para a 88): agora ele aplica a
mesma regra do formulário às instruções e ao iFrame gravados, e, sem os dois, a lista volta aos inativos com "Para
ativar, preencha as instruções (ou o código do Zapier, enquanto ele existir) na edição do assistente." e nada muda; o
D748 fechou. **O modelo gravado fora da lista** (mudado por SQL) virava o Haiku calado ao salvar a descrição: agora a
edição o mostra marcado, "(fora da lista: escolha outro)", e salvar sem escolher é recusado com "Escolha um modelo da
lista.". **O novo recusado** ganhou o caso que prende o escape das instruções devolvidas (o irmão do editar). **O
bloco 88 do desenho** passou a citar as duas portas do ativo e as fixtures; e o D748 dizia que os quatro assistentes da
fixture são ativos -- são três e um inativo. Casos novos: dois do "Ativar", um do modelo gravado, dois da tela e um da
regra; o `D199AtivarRemoverPorPostTest` dá o código do Zapier ao assistente que cria. Sete mutantes novos (N01 a N07),
cada um vermelho no seu caso nos dois bancos e devolvido pelo sha1. Os testes da administração e o do D199, 223 verdes
em cada banco; a caracterização inteira, 7.349 em cada banco, verde (25 e 17 pulados); os portões, 86 ok ("Nenhuma
falha inesperada"). O front não mudou. Sem esquema, sem chamada à Anthropic, nada no `franquiabv_dev`. De pé 130.
