# Fase 2 — a quarta fatia: o cadastro de clientes

Escolhida em 06/09/2026, depois de a terceira fatia fechar e de a busca de
cliente ser medida e corrigida pela metade (D119).

---

## Por que os clientes

**Porque é o maior módulo do ERP e o que uma tela nova mais usa.** São 24 ações
no `ClientesController` — mais que o dobro do PDV. E porque as três fatias
anteriores já esbarraram nele: a comanda abre para um cliente, o agendamento
marca para um cliente, o caixa recebe o dinheiro de um cliente.

E porque a busca acabou de ser medida. O D119 mostrou que `tags LIKE` respondia
5.710 no MySQL e 3.576 no PostgreSQL para o mesmo termo; a metade da caixa foi
corrigida, e a fatia é onde essa busca ganha uma face HTTP com paginação e
formato.

---

## O recorte: cadastro e busca

Das 24 ações, esta fatia cobre **cinco**. O resto fica, e o motivo de cada
grupo está escrito:

| fica de fora | por quê |
|---|---|
| pontuação, créditos, pacotes (7 ações) | é um assunto próprio — programa de fidelidade tem regra de vencimento, resgate e código, e merece a fatia dele |
| histórico de consumo e pontuação (2) | devolvem **arquivo**, e arquivo não é recurso REST |
| as quatro telas `ia*` | são assistentes de IA, e nenhuma delas tem regra de cadastro |
| `validarCadastro` | é do fluxo da venda, não do cadastro — pertence à fatia do PDV |
| `pesquisar*` (4 ações auxiliares) | alimentam campos de outra tela; entram quando aquela tela entrar |

O que fica é o que uma tela de clientes precisa para existir: **listar com
busca, ver, criar, alterar e desativar**.

---

## O mapa, com o verbo tirado do que o serviço faz

| recurso | verbo | por que este verbo |
|---|---|---|
| `/adm/api/clientes` | `GET` | `listagemIndex()` é consulta paginada, com filtro |
| `/adm/api/clientes/:id` | `GET` | leitura |
| `/adm/api/clientes` | `POST` | `insert()` faz `new Cliente()` e persiste — cria |
| `/adm/api/clientes/:id` | **`PUT`** | `update()` altera a linha e **o id sobrevive** |
| `/adm/api/clientes/:id/desativacao` | `POST` | `ativarRemover()` vira um booleano; a linha fica |
| `/adm/api/clientes/:id/ativacao` | `POST` | o mesmo, para o outro lado |

### O `PUT` aqui é honesto, e na agenda não era

Na fatia da agenda, `PUT /agendamentos/:id` foi descartado porque remarcar
**cria outra linha e apaga a anterior** — o recurso daquele endereço deixava de
existir. Aqui `update()` faz o contrário: pega a linha pelo id, muda os campos e
grava. O id sobrevive, e `PUT` promete exatamente isso.

É o mesmo raciocínio das duas vezes; o que muda é o que o serviço faz. Vale
registrar que a emenda à regra 1 não empurra sempre para o mesmo lado.

### Desativar não é `DELETE`, pelo terceiro slice seguido

`ativarRemover($cliente, $status)` faz `setStatus($status)` e grava. O cliente
continua existindo — e precisa continuar: ele tem vendas, agendamentos e
pontuação apontando para ele.

E são **dois** endereços em vez de um interruptor, pela mesma razão do check-in
da agenda: `POST` que alterna não é idempotente, e quem reenvia por rede ruim
desfaz o que acabou de fazer.

---

## As três perguntas que esta fatia tem de responder

### 1. O telefone repetido, que é regra e não defeito

Até **26 clientes compartilham o mesmo telefone** (D32) — filho, cônjuge,
responsável. A validação de hoje recusa telefone repetido **a menos que** venha
um `responsavel_id`, e nesse caso o cadastro é aceito como dependente daquele
número.

A API tem de carregar isso inteiro: recusar com `409` quando o telefone já é de
outro cliente **e** dizer de quem é, para a tela poder oferecer "cadastrar como
dependente". A mensagem de hoje é `- Telefone já cadastrado<br/>` mais um
`responsavel_id` solto no JSON.

### 2. O cliente 2, que não é cliente

`c.id != 2` aparece na listagem, na busca e na gravação: o id 2 é o
"SEM CADASTRO", como o id 1 é o "Cliente Avulso (balcão)" do D18. São
sentinelas, e a API não deve devolvê-los como se fossem gente.

### 3. `SUM()` na listagem, que muda de tipo entre os bancos

`listagemIndex()` traz `SUM(cc.valor)` e `SUM(cc.pontuacao)`. É exatamente o
caso do D118 — número no MySQL, texto no PostgreSQL. A regra 7 já resolve por
construção: dinheiro sai como texto decimal, e a pontuação como inteiro. O que
esta fatia precisa é **não esquecer de aplicar a regra na borda**.

---

## O que esta fatia não vai resolver

- **O acento da busca** (D119) continua como está: é decisão de janela. Desde
  11/09/2026 o código da outra metade está no repositório e **desligado** — a
  passada é `scripts/normalizar-tags.php`, e a chave só entra depois dela.
- **As telas atuais** continuam servindo o ERP. Nada é substituído.
- **O `tags`** continua sendo montado pelo mesmo `setTags()`; a fatia não muda
  como o cliente é indexado, só como ele é pedido.

---

## O diário da construção

### 07/09/2026 — os seis recursos, e três defeitos que só o cadastro novo revelava

**Trinta e um endpoints** em `/adm/api`, somando as quatro fatias; 305 testes de
caracterização nos dois bancos.

| recurso | verbo |
|---|---|
| `/adm/api/clientes` | `GET`, `POST` |
| `/adm/api/clientes/:id` | `GET`, `PUT` |
| `/adm/api/clientes/:id/desativacao` | `POST` |
| `/adm/api/clientes/:id/ativacao` | `POST` |

#### Três avisos impressos, achados na primeira gravação pela API

O ensaio de fluxo passou a cadastrar o cliente que ele mesmo vai atender — e o
primeiro `POST` parou três vezes seguidas, cada uma num aviso do PHP 8.1
**impresso dentro da resposta**, com o cabeçalho já enviado em 200 (a família
D100):

| onde | o que |
|---|---|
| `Cliente::setEmail()` | `strtolower(null)` |
| `Cliente::setAnotacoes()` | `str_replace` com `null` no terceiro argumento |
| `IdealizeUtil::normalizarNome()` | `mb_ereg_replace` com `null` |

Todos os três são campos **opcionais**, e por isso nunca apareceram: o
formulário da tela antiga manda todos os campos, mesmo vazios; **um cliente
JSON omite o que não tem.** O conserto do terceiro ficou no ajudante, e não em
cada chamador — `normalizarNome()` é usada por setters de `cidade`,
`complemento` e `subcategoria`, e todos tinham o mesmo buraco.

#### O cliente 2, que o banco não reservava

O cadastro pela API criou o primeiro cliente do banco de contrato — e ele nasceu
com **id 2**, que é o "SEM CADASTRO" do sistema. O recurso marcou corretamente
`sentinela: "sem-cadastro"`, e foi assim que se viu.

**Os ids 1 e 2 são reservados por convenção, não pelo banco.** Em produção eles
foram tomados há anos; num ambiente novo — o de contrato, o de um teste, e a
rigor qualquer instalação nova — são entregues a quem chegar primeiro, e o
sistema passa a tratar uma pessoa como marcador. As fixtures agora reservam os
dois, com o mesmo `INSERT ... WHERE NOT EXISTS` que já protegia o id 1.

#### E um nome que volta diferente do que foi mandado

`setNome()` passa por `IdealizeUtil::normalizarNome()`: caixa de título, com as
partículas em minúsculas. Mandei `"Cliente Do Ensaio"` e o sistema guardou
`"Cliente do Ensaio"`. Não é defeito — é a regra da casa —, e o ensaio passou a
afirmar o que o sistema faz, e não o que eu tinha escrito.

#### Como ficou, no CI

```
  1. cadastrar o cliente                               201
  ...
  O dia fechou: 16 passos, todos como o esperado.
  Chegaram ao trabalho (2xx): 10 de 31. Os outros 21 pararam na guarda.
```

O ensaio de fluxo agora começa pelo cadastro: **o cliente que ele cria é o
mesmo que marca horário, é atendido e paga**. Dez dos 31 endpoints chegam ao
trabalho — eram sete de 25 antes da fatia.

E foi preciso mais um conserto para chegar aqui, que a fatia não previa: as
fixtures gravam com id explícito, e no PostgreSQL isso **não move a sequência**
— a primeira gravação feita pela aplicação pedia o id 1 e colidia com o cliente
avulso. A migração de produção já tratava disso; os ambientes de teste, não
(D121).

---

## 27/09/2026 -- a tela de clientes na tela nova (fatia 1: consultar)

A sétima tela nova é a lista de clientes da rede e o cadastro de cada um, **só
leitura**. "Clientes", no menu da tela nova, deixou de abrir a tela genérica: a
lista abre nos ativos, com busca por nome, telefone ou e-mail -- o telefone
digitado com máscara perde a máscara, porque as `tags` guardam só os dígitos --,
a situação (ativos ou inativos) e páginas de 20; o nome abre o cadastro em
leitura: contato, cadastro, endereço e histórico.

**Cadastrar e editar continuam na tela antiga**, em outra aba, e só para quem
tem `clientes/cadastro/escrever` -- como a lista do ERP, que só põe o link no
nome para quem escreve. O cadastro aberto tem "atualizar", para quem salvou lá
e voltou.

**Por que ainda não edita:** lendo o caminho de gravação, o `PUT` desta API
apaga o que o corpo omite (D233) -- e-mail, origem, convênio... --, grava o
logradouro com o prefixo `' -- '`, e deixa a origem virar nula, o que desliga a
trava da primeira visita. Nenhuma tela usa esse `PUT` ainda; a fatia 2 (a
edição) conserta o servidor antes de oferecer "editar".

**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. Um
teste reprova se eles aparecerem.

**No servidor, um acerto só:** o envelope da lista passou a dizer a página que
o paginador entregou, e não a pedida -- o mesmo das vendas e dos devedores. E a
consulta da listagem, que a tela antiga já usava e nada provava, ganhou
caracterização nos dois bancos: o "SEM CADASTRO" fora, o avulso dentro, a ordem
por nome com o id desempatando, cada palavra do filtro obrigatória (um E, e não
um OU), a caixa do termo normalizada -- no PostgreSQL o `LIKE` distingue
maiúscula, e só lá a falta da normalização aparece --, o estado e o total.

### A revisão adversarial, e o que ela mudou

24 revisores, nenhum morto; 21 achados, 14 confirmados (dois deles repetidos),
todos consertados. Os que mudaram alguma coisa:

- **O teste da consulta só passava num banco só de fixtures.** A listagem cobre
  a rede inteira em páginas de 20, e contra a cópia da produção -- 243 mil
  clientes -- quatro dos seis casos caíam sem se declarar pulados. Cada consulta
  passou a ser recortada por um termo que só o teste grava nas `tags`, e o caso
  vale nos dois cenários.
- **Na tela:** a resposta atrasada de um pedido anterior do MESMO cliente
  trocava o cadastro já na tela (a guarda comparava o id, e não o pedido); a
  resposta que chegava depois de voltar podia reabrir o cadastro; depois de
  "atualizar", voltar mostrava a linha velha -- agora a lista é relida; e,
  durante a releitura, a contagem descrevia o pedido novo sobre a lista velha.
- **No desenho:** a paginação cobria a última linha do cartão, a célula das
  marcas deixava de ser célula (`display: flex` no `td`), o nome longo virava
  reticências no título do cadastro no telefone, e o selo "inativo" tinha 3,62:1
  no escuro.
- **Nos ensaios:** o comentário do 35a prometia o detalhe sem sessão, e só a
  lista era ensaiada -- entrou o 35a2.

### O navegador achou o que ninguém tinha achado

Contra o banco de contrato, depois dos testes, dos mutantes e da revisão, a tela
no navegador mostrou três defeitos de desenho no telefone de 375 px:

- **a página inteira rolava de lado 32 px**: o cabeçalho escondido da última
  coluna (`sr-apenas`, que é `position: absolute`) se posicionava pela página, e
  não pela rolagem da tabela -- `scrollWidth` 407 contra `clientWidth` 375;
- **a coluna do e-mail virava uma tira de poucas letras**: com
  `overflow-wrap: anywhere` e sem largura mínima, a tabela a espremia;
- **o telefone quebrava no meio**, "+55 (48) 3400-" numa linha e o resto na
  outra.

Os três corrigidos e medidos de novo: a página em 375, o e-mail com 180 px, o
telefone numa linha, o nome longo quebrando em duas linhas no título do cadastro,
e o CPF e o RG -- que o cadastro de fixture tem no banco -- fora da tela.

**Provado:** a caracterização nos dois bancos (925); 5 mutantes da consulta, 2 do
controller pelo HTTP e 39 do front, todos mortos; os seis ensaios HTTP, com o de
recusas em 105 de 105; o contrato do aplicativo, 67 nos dois bancos; e o
navegador contra o banco de contrato.

## 27/09/2026 -- a edição do cadastro (fatia 2), com o `PUT` consertado antes (D233)

"Editar" deixou de abrir a tela antiga: o cadastro aberto tem **editar**, para
quem tem `clientes/cadastro/escrever` -- e não no cadastro do sistema, que o
servidor recusa. O formulário parte do que o cadastro tem: nome, nascimento,
como chegou, convênio, CPF, RG, profissão e anotações; DDI, telefone, e-mail e
"aceita mensagens"; e o endereço. **Salvar manda o cadastro inteiro**, e a
resposta do `PUT` -- o cadastro como ficou -- é o que a tela mostra, com
"Cadastro salvo." e o foco de volta no "editar". Voltar à lista depois de
salvar relê a lista: a linha dela é a de antes.

**Cadastrar cliente novo continua na tela antiga.** O `POST` já usa a mesma
regra do corpo, mas o cadastro novo tem o telefone repetido que vira
dependente (`responsavelId`), e isso pede uma pergunta ao operador -- é a fatia
seguinte.

### O servidor, antes da tela

O `PUT` apagava o que o corpo omitia (D233). O corpo passou a morar numa regra
sem banco, `RegraDoCadastroDeCliente`, e o `PUT` a exigir as 12 chaves e as de
dentro do telefone e do endereço: a ausente é 422 com o campo nomeado, e `null`
explícito limpa. A origem é exigida nos dois verbos; o logradouro só ganha o
prefixo `' -- '` no cadastro novo; o DDI só com dígitos; o convênio que não
existe é 422 no campo, e não 500; o e-mail repetido é comparado em minúsculas;
os 409 dizem o motivo em `detalhes` -- a tela decide por ele, e não pela frase.
O cadastro ganhou `telefone.numero`, e o formulário, os catálogos
(`GET /adm/api/clientes/catalogos`).

**A prova é a volta:** o corpo montado com o que o `GET` devolve, passado pelo
serviço de verdade, deixa o cadastro como estava -- nos dois bancos, e com o
dependente continuando dependente. O teste achou a única diferença: o serviço
normaliza nome, rua, cidade e complemento ("Rua Das Flores" vira "Rua das
Flores"), como a tela antiga faz ao salvar.

### A tela

- **O telefone brasileiro vai na forma da tela antiga**, "(47) 99999-0001",
  qualquer que seja a forma digitada; fora do Brasil, só os dígitos.
- **A origem e o convênio que não estão no catálogo ficam na lista** -- há
  origens gravadas por outro fluxo e convênios de outra unidade --, e salvar
  sem tocar os devolve como estavam.
- **As marcas de campo** vêm da conferência da tela, antes de enviar, e da
  recusa do servidor, com a mesma chave; o foco vai ao primeiro campo marcado,
  a contagem é anunciada, e mexer num campo apaga a marca dele. O e-mail e o
  telefone que já são de outro cliente marcam o campo; o resto aparece no fim
  do formulário.
- **Durante o salvamento** o segundo clique não manda de novo, e o voltar e o
  desistir esperam a resposta.

### A revisão adversarial, e o que ela mudou

30 agentes, nenhum morto; 26 achados, 15 confirmados e todos consertados. Os que
mudaram alguma coisa:

- **O cadastro novo sem rua imprimia um aviso antes da resposta.** A regra passou
  a mandar o logradouro nulo quando não há rua (para não gravar `' -- '`), e o
  `insert()` fazia `explode(" -- ", null)` -- Deprecated no PHP 8.1, o mecanismo do
  D100. O serviço passou a tolerar o nulo, e um teste passa o corpo pelo
  `insert()` de verdade, com o aviso virando exceção.
- **O que não é texto passava como vazio**: um arranjo no lugar do e-mail era
  lido como "sem e-mail", e o `PUT` o apagava com 200 -- o D233 por outra porta.
  Agora é 422 no campo.
- **O tamanho da coluna não era conferido**, e o texto longo demais virava 500 no
  `flush` (no PostgreSQL, e no MySQL estrito). Agora é 422, medido depois da
  normalização que o serviço aplica -- 100 letras com pontos viram 148. E o ano
  de nascimento antes de 1900, e o convênio acima do maior inteiro da coluna
  (500 só no PostgreSQL).
- **Na tela:** depois de salvar e voltar, com o cliente fora da lista relida (a
  busca era pelo e-mail que mudou) ou em outro lugar dela, o foco caía no
  `body`; e os cinco campos obrigatórios não diziam que eram -- nem ao leitor de
  tela, nem à vista.
- **Nos ensaios:** as recusas que mandavam corpo válido não fotografavam o
  cliente -- se a guarda caísse, o `PUT` gravava e nada devolvia --, e o
  `finally` não devolvia os cadastros quando o laço morria no meio. O
  dependente e os motivos da ativação não tinham caso: ganharam quatro (35s a
  35v). E a frase da sentinela dizia "não pode ser desativarado".

Dos 11 refutados, os da integridade dos dados foram os mais úteis de ler: a
edição exige o sobrenome, a origem e o nascimento, e trava o cadastro cujo
e-mail está repetido em outro ativo -- como a página de edição do ERP. Não é
defeito novo, e fica dito aqui.

### Os mutantes

**Servidor, 30** (a regra, o JSON, o combo de convênios e o `insert()`), pela
caracterização; **controller, 12**, pelo ensaio de recusas -- um deles só o
PostgreSQL mata: tirar o `strtolower` do e-mail, porque a collation do MySQL já
ignora a caixa; **front, 54**. Todos mortos, cada um pelo caso que devia. Os que
sobreviveram no caminho mudaram o código, e não o teste:

- **duas guardas contra o segundo clique**, uma na tela e outra no invólucro:
  arrancar qualquer uma deixava tudo verde. Ficou a do invólucro, que é quem
  sabe se há `PUT` no ar;
- **o fechamento da edição ao abrir um cadastro** nunca mudava nada -- toda
  entrada no cadastro já passa pelo "voltar", que fecha: saiu;
- **o foco na linha que mudou de lugar**: o happy-dom não tira o foco do `<tr>`
  movido, e o Chrome tira. O teste passou a reproduzir o que o navegador faz, e
  o mutante que mandava o foco sempre ao título morreu.

### No navegador

Contra o banco de contrato, a edição de ponta a ponta no Chrome: o cadastro sem
nascimento nem origem e com o telefone gravado sem o espaço (`+5547900000000`)
abriu com os quatro campos que faltavam marcados ao salvar e o foco no primeiro;
preenchidos, o `PUT` gravou o telefone na forma da tela antiga,
`+55 (47) 90000-0000` -- com o `semMascara` igual, e por isso sem a conferência
de repetido --, o "Cadastro salvo." apareceu e o foco voltou ao "editar". Salvar
de novo sem mexer devolveu o mesmo cadastro, só com outra hora de alteração; e
voltar releu a lista e pôs o foco na linha do cliente.

**O navegador achou o que os testes não viam: buracos na grade.** No computador
de tela estreita e no telefone, em duas colunas, o RG ficava sozinho numa linha
(o convênio largo empurrava a profissão) e o CEP também (a rua larga vinha
depois dele). A ordem mudou -- CPF e RG lado a lado, o convênio largo; a rua
primeiro e o CEP no fim -- e a grade fecha nas duas larguras: quatro colunas em
1280 px, duas em 375, sem a página rolar de lado, nos dois temas.

**Uma forma de telefone fica dita:** o gravado sem o espaço depois do DDI -- só
nas linhas inseridas por SQL, como as fixtures; todo fluxo do sistema grava
`+DDI número` -- abre com o número inteiro no campo DDI e o telefone vazio. O
formulário marca o DDI, e o operador separa os dois. Não há adivinhação: um
`+5547...` pode ser DDI 55 ou 554.

**Provado:** a caracterização nos dois bancos (965 cada, 40 novos); o contrato
do aplicativo, 67 nos dois; os seis ensaios HTTP no MySQL e os nove no
PostgreSQL, com o de recusas em 122 de 122 nos dois (17 casos novos) e o do
fluxo com o `PUT` inteiro; os portões (48 verdes e o D50, o vermelho conhecido);
728 testes no front, de 672; 96 mutantes, todos mortos; e o navegador contra o
banco de contrato.

## 27/09/2026 -- o cadastro novo (fatia 3), com o dependente conferido (D234)

"Cadastrar cliente novo" deixou de abrir a tela antiga: a lista tem o botão,
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 -- como o formulário do ERP.
Cadastrado, o cliente abre no detalhe, com "Cliente cadastrado."; voltar à
lista a relê. Desistir volta com o foco no botão que abriu.

**O telefone que já é de alguém**, o caso que a tela antiga resolve com uma
pergunta: o formulário marca o telefone com o nome do dono -- "Este telefone já
é de Ana Cliente." -- e oferece **"cadastrar como dependente de Ana
Cliente"**. Um número pode ser de mais de um cliente da mesma família (D32), e o
novo nasce dependente de quem já o tem. A oferta vale para o número que foi
recusado: se o telefone muda no campo, ela sai.

### O servidor, antes da tela (D234)

O `responsavelId` do cadastro novo não era conferido: qualquer id dispensava a
conferência do telefone repetido, o que não existe virava 500 e o id 1 fazia o
cliente dependente do "Cliente Avulso". Agora o responsável tem de existir,
estar ativo, não ser cadastro do sistema e ter o mesmo telefone; o 409 diz o
nome do dono; e o dono nunca é uma sentinela -- com o avulso dono do número, o
409 mandava reenviar com quem a guarda recusa. O catálogo passou a dizer o DDI
e o país da unidade.

### A revisão adversarial, e o que ela mudou

14 agentes, nenhum morto; 10 achados, 5 confirmados e todos consertados:

- **o dono do 409 podia ser o cadastro do sistema**, e a guarda nova o
  recusava como responsável: o número ficava sem saída;
- **"Cliente cadastrado." nascia numa região viva recém-montada**, e o leitor
  de tela não o anunciaria: o aviso passou a ser a descrição do título, que é
  quem recebe o foco;
- **clicar em "cadastrar como dependente" apagava o próprio botão**: o foco ia
  ao `body`, e a rede que caía levava a oferta embora;
- **a oferta era mostrada com o número que estava no campo, e não com o
  recusado** -- os campos seguem editáveis durante o envio;
- **a máscara do telefone ia pelo DDI, e o ERP parte o telefone pelo país do
  cliente**: um "(47) 99999-0001" de cliente de Portugal voltaria ao ERP como
  "(47)". A máscara brasileira passou a valer só para cliente do Brasil. Na
  edição vale o país do cliente: o que não tem país (2.576 na cópia) tem o
  telefone gravado só com os dígitos quando é salvo -- a forma que o ERP lê sem
  cortar, e o `semMascara` continua igual.

### Os mutantes

36 novos, somados aos das fatias anteriores e rodados de novo com eles: **servidor,
6** (as quatro recusas do responsável, a forma do `responsavelId` e o e-mail
comparado sem a caixa), pela caracterização; **controller, 6** (o responsável
sem conferir, a sentinela como responsável e como dona do telefone, o nome do
dono no 409, o DDI e o país no catálogo), pelo ensaio de recusas; **front, 24**.
Os 132 mortos, cada um pelo caso que devia -- o do e-mail, de novo, só pelo
PostgreSQL. Dois sobreviveram no caminho, e os dois mudaram o teste, e não o
código: o DDI fixo em 55 no formulário vazio passava porque todo catálogo dos
testes era de unidade brasileira (um de Portugal, DDI 351, o matou); e a
sentinela aceita como responsável passava porque o avulso do caso tinha outro
telefone, e a recusa por telefone vinha antes -- com o avulso dono do número, só
a guarda da sentinela recusa.

**A suíte inteira do front achou o que os mutantes não achavam:** o teste do
Console ainda esperava o link "cadastrar cliente novo" para a tela antiga. Os
mutantes rodam os testes da tela de clientes, e não os do Console -- que é quem
liga o `podeEditar` ao privilégio. O caso passou a conferir o botão da tela nova
para quem escreve, nenhum dos dois para quem só lê, e nenhum link à tela antiga.

### No navegador

Contra o banco de contrato, no Chrome, só pela tela: um cliente novo com um
número que ninguém tinha, e outro com o mesmo número. O primeiro nasceu com o
telefone na forma da tela antiga, `+55 (47) 95555-0001`, abriu no detalhe com
"Cliente cadastrado." como descrição do título, que recebeu o foco; voltar releu
a lista e pôs o foco na linha dele. O segundo voltou com o telefone marcado --
"Este telefone já é de Zzq Navegador Primeiro." --, o foco no campo e a oferta de
dependente; o clique a manteve em foco durante o envio (desabilitada, e não
removida) e o cadastro nasceu com o responsável certo e o mesmo número. Em 375 px
e no tema escuro, o formulário vazio fecha a grade em duas colunas, com o DDI 55
e as marcas de obrigatório legíveis, sem a página rolar de lado; desistir volta
com o foco no botão que abriu.

**O que o navegador mediu e não é desta fatia:** todo pedido com sessão levou ~3
s neste ambiente -- inclusive `/adm/api/sessao`, que a fatia não toca --, contra
0,5 a 1,4 s sem sessão. É o ambiente (Docker no Windows) até prova em contrário, e não
vira número publicado.

**Provado:** a caracterização nos dois bancos (968 cada, 3 novos); o contrato do
aplicativo, 67 nos dois -- no PostgreSQL, depois de uma primeira rodada com a
máquina carregada, anotada no D66; os oito ensaios HTTP no MySQL e os nove no
PostgreSQL, com o de recusas em 129 de 129 nos dois (7 casos novos); os portões
(48 verdes e o D50, o vermelho conhecido); 754 testes no front, de 728; 132
mutantes, todos mortos; e o navegador contra o banco de contrato.
