# F3 — Relatórios

A etapa 6 da ordem das telas (`docs/roadmap.md`, "A ordem das telas que faltam") dizia:
**"todos serão remodelados, uns entram e outros saem — perguntar ao dono antes de
começar"**. Em 29/09/2026 o dono pediu primeiro o levantamento de tudo o que existe,
com as duplicidades, e depois decidiu por onde começar. Este documento guarda as duas
coisas: o que existe e o que foi decidido.

---

## 1. O levantamento de 29/09/2026

**Como foi feito.** Nove leitores, um por família de relatório, mais um para o que é
relatório fora do módulo, um para a história e a infraestrutura e um para uso, volume e
privilégio. Depois, um agrupador de duplicidades e um crítico de completude (que achou 21
itens que os leitores não tinham visto). Por fim, dez céticos, cada um tentando REFUTAR
um lote de duplicidades, com SQL agregado na cópia da produção (`franquiabv_dev`, dados
até 16/08/2026). Só leitura: nenhum arquivo do repositório mudou durante o levantamento,
conferido por impressão digital da árvore antes e depois.

**Resultado:** 76 cartões de relatório, 99 fatos de história e uso, 35 sobreposições
verificadas — 13 confirmadas como escritas, 22 confirmadas com o número corrigido pelo
cético, nenhuma refutada.

### O que existe

| Onde | O quê |
|---|---|
| Menu Relatórios do ERP | 24 itens (`module/ISBase/view/layout/layout.phtml`, e a cópia em `frontend/src/telas/menu-do-erp.ts`), sobre 20 controladores de `module/ISRelatorio/config/module.config.php`. Um é fantasma (Fidelização de Clientes: não tem controlador; saiu em 25/10/2026, a seção 8) e o Consultor Visagista está fora da migração por decisão do dono |
| Fora do menu | cerca de 20 telas que são relatório na prática: fechamento de caixa, histórico de consumo e de pontos do cliente, extrato do convênio, estatísticas do cupom, planilha do sorteio, faturas, clientes novos e sem retorno da agenda, acessos por unidade e por usuário, a lista de Vendas Realizadas |
| Fora do sistema | o Yungas (único painel externo vivo: receita, planejado e nota do mês corrente de 27 das 32 unidades) e as exportações por hash da unidade (`/api/unidade/vendas`, V2 e V3), de consumidor desconhecido. O Power BI saiu em 08/09/2026 e nada entrou no lugar |
| Rotinas | cerca de doze rotinas noturnas pré-calculam números para os relatórios (`module/ISApi/src/Service/CronJobService.php`). **Boa parte da definição dos números mora nelas**, e não nas telas; cinco gravam errado (D307) |

**Quem vê.** Quase todos os relatórios são de gerente (perto de 105 pessoas em cada um,
mais os 10 administradores). Vendas é o de maior alcance (346 pessoas: o colaborador o usa
como extrato de comissão), depois o Dash. Colaboradores (282) e o Caixa (167). Royalties e
Potencial de Franquia, só os administradores.

**Uso real de cada relatório: não se mede.** O banco grava só o login
(`usuarios_acessos`), nunca a tela aberta; a evidência de uso estaria no log de acesso do
servidor de produção, que não foi lido.

### As 35 sobreposições

Numeradas S01 a S35 para não se confundirem com os débitos. "Parcial" quer dizer que a
sobreposição existe e o número foi corrigido pelo cético; o texto abaixo já é o corrigido.
Os números são da rede em julho de 2026, salvo indicação.

| | Tipo | O que é | Veredito |
|---|---|---|---|
| S01 | mesmo número, cinco definições | **faturamento**: R$ 1.871.383,01 (Dash. Faturamento, itens), 1.873.417,51 (Royalties, retrato diário), 1.885.744,81 (Vendas Realizadas e Fluxo, total das vendas com compra de crédito), 1.816.084,56 (Radar, só o grupo 1); o Yungas soma R$ 0,10 a todo valor redondo | parcial |
| S02 | mesmo número, três definições | **quanto o colaborador vendeu**: no PDF da Cota-parte o pacote conta duas vezes — a venda do pacote e cada sessão | confirmado |
| S03 | mesmo relatório duas vezes | **Vendas e Vendas Ver. 2**: a mesma consulta; a cota-parte tem quatro contas nos quatro arquivos (R$ 643.785,25 gravada na venda contra R$ 645.796,99 recalculada com o percentual de hoje) | parcial |
| S04 | mesmo número, cinco definições | **ticket médio**: R$ 116,02 (Radar), 92,54 (por venda), 127,98 (por cliente), 60,29 (por item, no Potencial) | parcial |
| S05 | mesmo número, duas contas | **clientes atendidos**: 14.622 (distintos por unidade) contra 15.653 (soma por colaborador) | parcial |
| S06 | mesmo número, três definições | **cliente novo**: cadastro no mês (1.332) contra primeira compra no mês (1.406) contra a lista da agenda | parcial |
| S07 | mesmo número, três definições | **fidelização**: uma delas (o cache da Fidelização de Clientes) mede o cliente que passou para OUTRO colaborador | confirmado |
| S08 | mesmo rótulo, sete números | **"NPS"**: nenhum é o NPS de -100 a 100; o clássico da rede daria 96,85 | parcial |
| S09 | mesma régua, fronteiras diferentes | **faixas da satisfação**: a nota 8 é promotor e passivo ao mesmo tempo; o cabeçalho e o detalhe da tela NPS não se reproduzem | confirmado |
| S10 | mesmo número, duas contagens | **quantidade de vendas**: a Evasão conta linhas de item (31.898) onde as outras contam vendas (20.222) | parcial |
| S11 | mesmo número, composições diferentes | **serviços e produtos**: cada tela soma um conjunto; Produtos/Serviços usa o preço de catálogo (R$ 1.868.925,20 contra R$ 1.790.968,09 cobrados) | parcial |
| S12 | mesmo cálculo, três cópias | **capacidade da agenda**: três populações; as unidades 10 e 39 têm dois potenciais | parcial |
| S13 | mesmo número, duas definições | **consumo do cliente**: a coluna do cron (4.708 clientes com ela diferente da soma) contra o histórico e o aplicativo | parcial |
| S14 | mesmo número, duas fontes | **pontos do cliente**: coluna contra histórico — 8.029 saldos divergem, 815.247 pontos | confirmado |
| S15 | mesma pergunta, três caminhos | **indicação**: o relatório enxerga só o convite pelo aplicativo | parcial |
| S16 | mesmo número, três definições | **valor final do caixa**: coincidem no caixa fechado (0 divergências desde 2025); nenhuma desconta o troco | parcial |
| S17 | a mesma conta gravada duas vezes | **Atendimentos e Dash. Faturamento** leem o mesmo cache; as vendas do fim de 31/12 ficam fora dos dois (R$ 93.739,00 em 12/2025) | parcial |
| S18 | um relatório é a soma do outro | **Radar = soma do Dash. Colaboradores**, mas o Dash esconde 30 linhas e R$ 119.152,26 | confirmado |
| S19 | mesma pergunta, duas rotinas | **Atendimentos e Dash. Colaboradores**: populações e congelamentos diferentes; até R$ 8.376,00 num mês | confirmado |
| S20 | mesma lista, três arquivos | **Clientes em PDF, XLS e TXT** | confirmado |
| S21 | mesmo relatório em dois módulos | **VIP Class** — e em uso: 2.131 checkpoints de 341 usuários | parcial |
| S22 | tela nova cobre a antiga | **Vendas Realizadas**: faltam na nova a planilha e três filtros | parcial |
| S23 | subconjunto com filtro | **Vendas Canceladas = Vendas Realizadas canceladas**; 294 das 543 canceladas de julho são comanda abandonada, cancelada pela rotina | confirmado |
| S24 | subconjunto com filtro | **Descontos = Vendas Realizadas com desconto**; o "Total vendas" do cabeçalho ignora o filtro (R$ 1.885.744,81 no lugar de R$ 249.760,81) | confirmado |
| S25 | tela nova cobre quase tudo | **Retirada de Produtos** está quase toda na tela de Estoque; o público e o solicitante diferem | parcial |
| S26 | mesma pergunta, três lugares | **a conferência do dia do caixa**, com dois "hojes" (data da venda e abertura do caixa) | parcial |
| S27 | o mesmo dinheiro em duas telas | **a sangria financeira** vira saída paga em Contas a Pagar: 305 em 2026, R$ 65.671,21 | confirmado |
| S28 | mesma pergunta, cinco réguas | **cliente sumido**: 30, 31 e 120 dias, as janelas da Evasão e o filtro livre | parcial |
| S29 | mesma saída, proteções diferentes | **exportar contato de cliente**: cinco portas, e só a das campanhas impõe o consentimento e o teto | parcial |
| S30 | mesma pergunta, dois recortes | **cliente da unidade**: 30.023 ativos têm a unidade da última visita diferente da do cadastro, e 11.691 não têm nenhuma | confirmado |
| S31 | mesmo nome, outro assunto | **Aniversariantes** lista a equipe e a rede, não clientes | parcial |
| S32 | o mesmo dinheiro, telas que não se cruzam | **Royalties e Faturas**: a fatura bate com a base ao centavo, mas a base só o administrador vê e a fatura só o gerente | confirmado |
| S33 | lacuna | **o BI não cobre mais relatório nenhum**; o Yungas só o mês corrente | confirmado |
| S34 | mesma exportação, formatos diferentes | **as vendas do período**: a planilha de Vendas Realizadas e as APIs V1 e V2; "bruto" tem duas definições | parcial |
| S35 | mesma pergunta, três cortes | **quanto se vendeu de um produto**: Catálogo (desde sempre), Produtos/Serviços (preço de tabela) e Cota-parte (valor cobrado) | parcial |

### O que atravessa todos eles

- **A venda não guarda a unidade**: ela vem do usuário que fechou a comanda, pela unidade
  ATUAL dele. Recalcular 2025 assim dá R$ 21,62 milhões, contra R$ 22,52 milhões guardados
  nos painéis antigos.
- **A data da venda é o fechamento**: o fechamento regrava `data_criacao`
  (`module/ISVenda/src/Service/VendaPagamentoService.php`). O cancelamento não grava data
  nem autor, e muda o passado.
- **Formas de pagamento não se conferem na cópia**: `vendas_pagamentos` tem 1.003 linhas
  para 2,27 milhões de vendas (D181).
- **Clientes sentinela**: o cliente 1 é o "Cliente Avulso (balcão)" e o 2 é o "SEM
  CADASTRO" (61.290 vendas). Nenhuma contagem de cliente os considera.

---

## 2. As decisões do dono em 29/09/2026

**O menu Relatórios da tela nova passa a ter doze itens**, nesta ordem: Atendimentos,
Caixa, Programa de Indicação, Profissional Parceiro (o Dash. Colaboradores), Descontos, NPS,
Produtos/Serviços, Retirada de Produtos, Royalties (só administrador), Vendas (a tela nova
de Vendas Realizadas), Vendas Canceladas e Cota-parte (o Relatórios › Vendas de hoje). O
que ficou fora é o D306, e volta à mesa antes de a etapa fechar. As rotinas que gravam
errado são o D307.

**A home do gerente de unidade vira um painel do mês**, visível só para ele. O conteúdo vem
de um mockup feito fora do sistema; o layout segue o sistema de desenho da tela nova, e
depois passa pelo Claude Design. As definições, escolhidas pelo dono entre as opções
medidas na unidade 1 em julho de 2026:

| Pergunta | Decisão | Unidade 1, jul/2026 |
|---|---|---|
| O que é o faturamento | **líquido**: a soma dos itens depois do desconto, sem a compra de crédito (que conta quando o crédito é gasto) | R$ 286.189,40 |
| Taxa de retorno | **três**: a do mês anterior, a dos clientes novos e a de até 45 dias | 58%, 41% e 57% |
| Cliente novo | **primeira visita na rede**: nunca tinha comprado em nenhuma unidade | 55 |
| "Vendas" no menu | **Vendas Realizadas** | — |

**Os números da última coluna não são todos os da definição que ficou.** Eles foram
medidos para o dono escolher, antes de a definição ser escrita. O 58% é o retorno do mês
anterior como ficou (58,1%). O 41% contava os clientes novos de junho voltando em qualquer
unidade, e o 57% contava os clientes de junho a partir da última visita deles -- com
vendas de agosto, que o painel de julho ainda não pode ver. Na definição que ficou, o
painel de julho da unidade 1 mostra **58,1%, 40,8% e 58,7%** (conferido em SQL
independente pela revisão de 29/09/2026). O retorno em até 45 dias passou a olhar uma
janela que já se fechou no fim do mês: o número do mês não muda depois que o mês acaba.

### As quatro perguntas da revisão, respondidas pelo dono (29/09/2026, noite)

A revisão do painel mediu quatro casos em que a definição escrita podia não ser o que o dono
queria. As respostas, nas palavras dele, e o que cada uma virou:

| Pergunta | Resposta do dono | O que o painel faz |
|---|---|---|
| A cortesia de R$ 0,00 conta como produto vendido? | "criar uma nova categoria 'cortesias' e não tratar como produto vendido, separar os números" | **Cortesias** é um número à parte, no mês e por colaborador; produtos vendidos não a contam |
| A comanda só de cortesia é atendimento? | "comanda apenas com cortesia trata como atendimento" | como já era: conta em atendimentos, clientes atendidos e dias trabalhados, e é visita para o retorno |
| O "faturamento bruto" dos colaboradores é antes ou depois do desconto? | "o faturamento bruto é o total depois dos descontos (o que realmente entrou para empresa), mas vamos rastrear o que foi dado como desconto" | como já era, e **Descontos concedidos** entra no mês e por colaborador |
| Quem estreia comprando crédito é cliente novo? | "O cliente é tratado como novo se executou um serviço ou comprou crédito no mês, o que vier primeiro" | a régua do cliente novo passa a ser o **primeiro serviço ou a primeira compra de crédito na rede** |

**O que é cortesia, medido e não suposto.** Na cópia, em julho de 2026, os itens de preço
zero que não vieram de pacote são de dois tipos. Os de **produto** (1.304 itens: "OPEN
AGUA", "Café Free", "Cerveja Spaten Free"...) são a cortesia. Os de **serviço** (292 itens)
são quase todos do "CLUBE DA VIP" — assinatura, em que o serviço sai a zero no balcão porque
já foi pago —, mais "ATRASO" e "Barba 2 horários". Serviço de preço zero continua serviço
realizado. Por isso a cortesia é **o item de produto (`probar` ou `proemp`) cujo preço
unitário na venda é zero**; o produto com preço e desconto de 100% é produto vendido, com
desconto.

**O que é desconto, medido e não suposto.** Nos itens sem pacote, `valor_total` é
`valor_unitario × quantidade − valor_desconto` em 31.529 de 31.529 itens de julho, e o
`desconto_total` da venda é a soma dos descontos dos itens em 20.163 de 20.163 vendas. A
sessão de pacote tem preço unitário, total zero e desconto nenhum: derivar o desconto de
"unitário × quantidade − total" a contaria como desconto. O painel soma `valor_desconto`.

**Duas consequências da régua nova do cliente novo, que a resposta do dono não cita:** a
venda **só de produto** (ou só de cortesia) não faz ninguém novo, e também não faz ninguém
"antigo" — quem comprou uma pomada em abril e cortou o cabelo pela primeira vez em junho é
novo em junho. E quem estreia comprando crédito é novo no mês da compra, mas não é cliente
atendido nesse mês (a compra de crédito não é atendimento): os clientes novos deixam de ser,
sempre, parte dos atendidos. Na unidade 1, os clientes novos de julho de 2026 passam de 55
para **57** com a régua nova (medido na cópia pelo mesmo caminho da API).

---

## 3. O painel do gerente

### Quem vê

O gerente e o administrador que têm o privilégio de ver as vendas realizadas -- desde
27/10/2026 (a fatia 46, abaixo), o administrador também, pelo menu e com a escolha da
unidade. **A home** muda só para o gerente que não é administrador (os grupos com a marca de
gerente e sem a de administrador: hoje o "Gerente unidade" e o "Gerente"): para ele o painel
é a home, e a agenda continua no menu. Para o administrador -- o "Gerente geral", que tem as
duas marcas, e quem tiver só a de administrador -- a home continua a agenda, e o painel é um
item do menu. O servidor decide isso e o diz na sessão (`painelDaUnidade` para a home,
`painelNoMenu` para o item); a tela não compara nome de grupo.

A API do painel recusa quem não tem o privilégio e quem não é gerente nem administrador (o
caixa vê vendas realizadas, e só do dia: o 403 `papel`). O privilégio que o servidor confere
é o mesmo que a tela usa para a home e o menu, e um teste lê os dois e compara.

**O administrador abre o painel pelo menu, e escolhe a unidade (27/10/2026, a resposta 181 (b)
do dono; o D487).** *"O painel abre também para o administrador, como item do menu (a home dele
continua a agenda), com a escolha da unidade -- o molde do Profissional Parceiro [...] Sem a
rede: os clientes novos da rede são a consulta que leva até 70 segundos (a sua 118)."* Desde a
fatia 46:

- **O item "Painel" do menu** aparece ao gerente e ao administrador -- a guarda `painelNoMenu`
  da sessão (`SessaoJson::menu`), a mesma conta que a API confere. A `painelDaUnidade` continua a
  da HOME, só do gerente que não é administrador: a home do administrador continua a agenda. O
  item é o mesmo destino, e o menu segue com 15 grupos e 51 destinos.
- **A unidade:** os três pedidos (`/painel`, `/painel/clientes` e `/painel/semana`) aceitam
  `?unidade=<id>`, pela regra da unidade do filtro dos relatórios (`RecorteDoRelatorio::unidadeDoFiltro`)
  com o escopo da unidade para todos: o id só entre as que a lista suspensa do ERP oferece a quem
  pede -- as ativas ao administrador, a da sessão ao gerente --, e `todas` (a rede), a unidade
  inativa, a que não existe e o texto são o 422 no campo `unidade`. Sem o parâmetro, a da sessão,
  como sempre. A lista é `GET /adm/api/painel/unidades`, e o recorte das três respostas diz a
  unidade dos números (`unidade`, com o nome do cadastro). **O nome custa uma consulta** por
  chave à tabela das unidades (`UnidadeRepository::nomesDoCadastro`) em cada um dos três
  pedidos, depois de soltar a sessão -- também na home do gerente, que não escolhe nada: a
  mesma forma do Profissional Parceiro, e o cabeçalho diz o nome do cadastro venha a unidade de
  onde vier. Antes da fatia 46 os pedidos do gerente não a faziam.
- **O gerente continua só na dele:** o parâmetro de outra unidade, que até aqui era ignorado, é o
  422. **Quem tem o privilégio sem ser gerente nem administrador** continua recusado (403 `papel`) e
  sem o item -- a nota da 181: o painel é do gerente.
- **Na tela**, só o administrador vê a escolha (a `EscolhaDaUnidade` do Parceiro, sem "todas as
  unidades"), ao lado do mês; a troca guarda o mês do último pedido (na primeira consulta, que vai
  sem mês, o da tela) e a comparação, e pede os clientes e a semana da unidade nova. O cabeçalho
  diz a unidade dos números, pelo recorte. O "ver detalhes" dos colaboradores leva o mês e a
  unidade escolhida ao Profissional Parceiro.

### A base de tudo

Uma venda entra no painel quando está **finalizada** (`status = 1`), **tem pelo menos um
item**, foi fechada **dentro do período** (faixa meio-aberta sobre `data_criacao`, que é o
instante do fechamento) e foi fechada por um usuário **da unidade**. Venda sem item — a
compra de crédito e a quitação de fiado avulsa — não é atendimento. A compra de crédito só
entra numa conta: a do cliente novo.

### O dicionário

| Número | Definição |
|---|---|
| Faturamento | soma de `vendas_produtos.valor_total` das vendas da base — **depois do desconto**. É a mesma base dos royalties e o "valorLiquido" da exportação V2. A sessão de pacote vale zero: o pacote conta quando é vendido |
| Descontos concedidos | soma de `vendas_produtos.valor_desconto` dos itens da base (nulo é zero). O faturamento já é depois dele. A sessão de pacote não é desconto |
| Atendimentos | vendas da base (comandas). A comanda só de cortesia é atendimento |
| Ticket médio | faturamento ÷ atendimentos |
| Clientes atendidos | clientes distintos das vendas da base, sem os sentinelas 1 e 2 |
| Clientes novos | clientes cujo **primeiro serviço ou primeira compra de crédito na rede**, o que vier primeiro, em qualquer unidade, caiu no período. Serviço é item `ser` ou `lavavip` numa venda finalizada (a sessão de pacote e o Clube da VIP contam); compra de crédito é a venda finalizada com `venda_credito = 1`. Candidato na unidade é quem teve serviço numa venda da base ou comprou crédito com alguém da unidade, no período. A venda só de produto não faz ninguém novo nem antigo |
| Produtos vendidos | quantidade dos itens de produto (`probar` e `proemp`) **com preço**; o insumo não conta, e a cortesia conta à parte |
| Cortesias | quantidade dos itens de produto (`probar` e `proemp`) cujo **preço unitário na venda é zero** — o café, a água, a cerveja "free". Não é produto vendido. O serviço de preço zero (Clube da VIP) é serviço realizado |
| Serviços realizados | quantidade dos itens de serviço (`ser` e `lavavip`), sessões de pacote incluídas |
| Valor dos serviços | soma de `vendas_produtos.valor_total` dos itens de serviço (`ser` e `lavavip`) da base, depois do desconto: o total do cartão dos serviços mais vendidos. A sessão de pacote vale zero; o pacote vendido não é serviço (27/10/2026, a resposta 183 b) |
| Valor dos produtos | soma de `vendas_produtos.valor_total` dos itens de produto (`probar` e `proemp`) da base, depois do desconto; a cortesia vale zero, e o insumo não entra. Com o valor dos serviços não fecha o faturamento: o pacote vendido e o insumo entram só nele (27/10/2026, a resposta 183 b) |
| Serviços extras | quantidade e valor dos itens cujo produto tem a categoria `extra` — a mesma divisão base/extra do relatório de Atendimentos |
| Dias trabalhados | dias com pelo menos uma venda da base |
| Faturamento por dia trabalhado | faturamento ÷ dias trabalhados |
| Retorno do mês anterior | dos clientes atendidos no mês anterior, quantos voltaram neste mês, à unidade |
| Retorno em até 45 dias | dos clientes atendidos numa janela de 30 dias que terminou 45 dias antes do fim do período, quantos voltaram à unidade em até 45 dias depois da primeira visita nessa janela (outra venda no mesmo dia não é retorno) |
| Retorno dos clientes novos | a mesma conta, só para os clientes novos da janela (a régua acima), e **contada a partir da estreia**: o início é a primeira visita da janela no dia da estreia ou depois. Uma visita anterior a ela (só produto, só cortesia) não é o início nem a volta — senão o próprio serviço que fez o cliente novo contaria como retorno. Quem não tem visita na janela a partir da estreia (o que estreou só comprando crédito, por exemplo) fica fora da base |
| Recebido por forma de pagamento | soma de `vendas_pagamentos.valor` das vendas finalizadas do período, por forma, na ordem da tela de Caixa. **Não é o faturamento**: inclui o troco que virou gorjeta ou crédito, os débitos antigos pagos na comanda, a compra de crédito e a quitação avulsa, e não inclui o fiado |
| Top serviços | os cinco serviços de maior faturamento no período, com a fatia de cada um no faturamento de serviços |
| Colaboradores | todo colaborador com item numa venda da base, pelo item (`vendas_produtos.colaborador`), do maior faturamento para o menor. Os atendimentos de cada um são as comandas com item dele — por isso a soma passa do total da unidade. O "faturamento bruto" é o total que o colaborador gerou (e não a cota-parte dele), **depois do desconto** — confirmado pelo dono em 29/09/2026 —, e ao lado vêm os descontos e as cortesias dos itens dele. Os **clientes novos de cada um** contam para quem fez serviço no dia da estreia do cliente — se dois fizeram, os dois (decisão do dono de 30/09/2026; antes contavam para todo colaborador com item na visita, e na unidade 39 um caixa que só serviu café "ganhava" 23) |

**O período** é o mês escolhido. No mês em andamento, vai até agora, e a comparação "contra
o período anterior" usa os mesmos dias do mês anterior; num mês fechado, compara com o mês
anterior inteiro. As taxas de retorno usam o fim do período como âncora.

### O que o painel não vai bater, e por quê

Na mesma unidade e no mesmo mês, o painel antigo (Dash. Faturamento) mostra 3.107
atendimentos, ticket de R$ 92,11 e 45 clientes novos; o painel novo, 3.101, R$ 92,29 e 57
(55 pela régua de antes, a da primeira compra). A diferença é de definição, e está escrita
acima: o antigo conta a venda sem item e chama de novo quem se cadastrou no mês. Com as
definições de 29/09/2026 à noite, o mesmo mês mostra R$ 21.198,60 de descontos concedidos na
unidade 1, que não dá cortesia; na unidade 39, 41 produtos vendidos e 568 cortesias, onde o
painel de antes mostrava 609 produtos vendidos. Vendas Realizadas soma o total das vendas (R$ 287.349,40),
com a compra de crédito. A Cota-parte conta a sessão de pacote pelo valor da sessão; o
painel e o Profissional Parceiro, por zero (a frase de antes dizia que o Profissional
Parceiro contava pelo valor; o levantamento de 30/09/2026 mediu que não: a sessão tem
`valor_total` zero nas 7.233 sessões de 2024 a 2026).

### Onde está

*(Desde 13/10/2026 o painel tem as duas opções do Radar e do Fluxo -- a comparação com o ano anterior e o movimento por
dia da semana --, na seção 7.)*

| Parte | Arquivo |
|---|---|
| Os períodos (o mês, a comparação, as janelas do retorno) | `module/ISVenda/src/Regra/RegraDoPainelDaUnidade.php` |
| As contas, sem banco | `module/ISVenda/src/Regra/ContasDoPainelDaUnidade.php` |
| As consultas | `VendaRepository::painelItens`, `::painelComprasDeCredito`, `::painelPrimeiroServicoOuCredito`; `VendaPagamentoRepository::painelRecebidoPorForma`, `::painelRecebidoForaDeComanda` |
| A resposta | `module/ISVenda/src/Api/PainelDaUnidadeJson.php` |
| A API | `module/ISVenda/src/Controller/Adm/PainelDaUnidadeController.php` — `GET /adm/api/painel`, `/adm/api/painel/clientes`, `/adm/api/painel/semana` (seção 7) e `/adm/api/painel/unidades` (a lista do administrador, desde a fatia 46) |
| A tela | `frontend/src/telas/Painel.tsx` e `PainelLigado.tsx`; o item e a home em `menu-do-erp.ts` e `ConsoleLigado.tsx` |
| Os testes | `RegrasDoPainelDaUnidadeTest` (sem banco), `PainelDaUnidadeTest` (os dois bancos), `SoltarASessaoTest`, `PainelLigado.teste.tsx` |

### Dois pedidos, e o que cada um custa

Os números saem de uma consulta aos itens do mês e do anterior -- dois meses: na unidade
1, a maior da rede, 0,4 a 0,7 s. Os clientes novos e o retorno leem os itens de três meses
e meio (0,9 a 1 s), as compras de crédito da unidade no mesmo intervalo, e pedem o primeiro
serviço ou crédito de cada cliente em qualquer unidade -- 3.672 clientes na unidade 1 --;
são um pedido à parte, que a tela faz depois dos números, com o mês deles. (Tempos medidos
dentro da sessão do banco, na cópia, em 29/09/2026.)

**O custo dos clientes depende da memória do banco, e não do código.** A pergunta é, de
cada cliente, "teve serviço ou compra de crédito antes do período?", e ela para na primeira
venda que responde sim (ver `VendaRepository::DQL_DOS_ANTIGOS`, e o teste que lê o plano).
Medida na cópia, unidade 1, julho de 2026, os quatro lotes de mil, dentro da sessão do banco. As
três linhas usam a mesma lista, a dos 3.682 clientes da regra de antes, para as formas serem
comparáveis; o painel de julho, pela regra nova, pede 3.672 -- ainda quatro lotes:

| Forma da pergunta | MySQL, 128 MB (fábrica) | MySQL, 2 GB, vendas na memória | PostgreSQL, cache quente |
|---|---|---|---|
| "comprou antes?" (a regra de antes), sem índice | 5 a 8 s | 37 a 74 ms por lote | 6 a 83 ms por lote |
| "serviço ou crédito antes?", sem índice | 13 a 14 s | — | 35 a 68 ms por lote |
| "serviço ou crédito antes?", com o índice do D308 | 8 a 9 s | 33 a 100 ms por lote | 25 a 119 ms por lote |

A regra nova custa mais que a de antes porque, para cada venda, olha também os itens: é
preciso saber se ela teve serviço. O índice do D308, criado na cópia em 29/09/2026 com a
autorização do dono, tira da tabela a busca das vendas de cada cliente, mas não a dos itens
— por isso ajuda muito com pouca memória e quase nada com as vendas na memória. No
PostgreSQL, a primeira rodada da noite, com o disco frio, levou 35 s; as seguintes, dezenas
de milissegundos por lote. A primeira versão da regra de antes levava de 27 a 41 s com
128 MB: o MySQL a transformava numa leitura de todas as vendas de cada cliente.

**Os dois pedidos não esperam um ao outro no servidor.** A sessão do ERP fica em arquivo, e
o PHP trava o arquivo durante o pedido inteiro: os números esperavam os clientes, e trocar
de mês esperava o pedido anterior. As duas ações do painel soltam a sessão depois de
conferir quem pede (`ApiAdmController::soltarASessao`).

**O preço disso, escrito:** a trava também fazia os pedidos caros da mesma pessoa andarem
em fila, um de cada vez. Agora, quem troca de mês várias vezes seguidas enquanto os
clientes ainda contam põe várias dessas consultas no banco ao mesmo tempo -- a tela descarta
as respostas velhas, mas o servidor não interrompe a consulta que já começou. Com as vendas
na memória isso custa milissegundos; com 128 MB, cada uma são segundos disputando a mesma
memória. A navegação continua livre de propósito: travá-la até os clientes chegarem
prenderia o gerente por segundos a cada mês. Se um dia pesar, o caminho é uma trava curta
só em volta da consulta dos clientes, por sessão -- ou guardar a estreia de cada cliente
(seção 4).

### As perguntas que ficaram para o dono — respondidas

**Respondidas em 29/09/2026, à noite; as respostas e o que mudou estão na seção 2.** O
texto abaixo é o que a revisão perguntou, mantido para se ver de onde veio cada regra.

A revisão de 29/09/2026 mediu quatro casos em que o painel segue a definição escrita, mas
a definição pode não ser o que o dono quer. Nada disso foi mudado sem ele:

1. **Cortesia conta como produto vendido.** O café, a água e a cerveja "free" são itens de
   R$ 0,00 na comanda. Na unidade 39, em julho de 2026, 568 dos 609 "produtos vendidos"
   eram cortesia (na 10, 457 de 488; na 32, 400 de 449). O painel antigo conta do mesmo
   jeito. Tirar a cortesia é contar só o item com valor.
2. **A comanda só de cortesia é atendimento.** Na unidade 39, 37 das 650 comandas de julho
   tinham só cortesia, e 14 eram do mesmo cliente no mesmo dia de outra comanda.
3. **"Faturamento bruto" dos colaboradores** foi lido como o total que cada um gerou,
   depois do desconto. Antes do desconto, a ordem muda em 2 das 27 unidades (em julho de
   2026; na unidade 1, não).
4. **Quem estreou comprando crédito não é cliente novo** no mês do primeiro corte: a regra é
   a mesma da "primeira visita" do PDV (qualquer venda finalizada). Afeta 1 cliente na rede
   em sete meses e meio.

---

## 4. Calcular na hora ou pré-calcular (a pergunta do dono de 29/09/2026)

**A pergunta, nas palavras dele:** "com esse novo schema, faz sentido precalcular os
relatorios ou ficam a ponto de serem gerados rapidamente?"

**A resposta curta: o mês de uma unidade se calcula na hora; o que pesa é o histórico.**
Medido na cópia MySQL em 29/09/2026, dentro da sessão do banco, com a máquina sem outra
carga (a mesma base do painel: vendas finalizadas com item, pela unidade de quem fechou):

| Consulta | 128 MB (fábrica) | 2 GB, vendas na memória |
|---|---|---|
| Os itens do mês da unidade 1, a maior da rede (o que o painel lê) | 0,28 s | 0,15 s |
| Os itens do mês da rede inteira | 0,42 s | 0,37 s |
| Os itens de 12 meses da unidade 1 | 4,7 s | 1,6 s |
| A rede em 12 meses, somada por unidade e mês | 8,3 s | 4,7 s |
| A rede em 36 meses, somada por unidade e mês | 21 s | 14 s |
| O primeiro serviço ou crédito de 3.682 clientes (o "cliente novo" da unidade 1; o painel de julho pede 3.672) | 8 a 9 s | 0,3 s |

**O que se calcula na hora.** A visão principal dos onze relatórios do menu é um recorte de
uma unidade num período curto -- o mês, a semana, o dia. Isso custa menos de meio segundo
mesmo com a memória de fábrica. Pré-calcular não economiza nada que se veja, e traz o
defeito que as rotinas noturnas de hoje mostram: o número gravado de madrugada fica velho
ou errado (o D307 tem cinco pontos, e um mês que nunca fecha).

**O que não cabe na hora, e como pré-calcular sem repetir as rotinas de hoje.** Duas
coisas, cada uma gravada por quem muda o dado, e não por um relógio:

1. **A estreia de cada cliente** -- a data do primeiro serviço ou da primeira compra de
   crédito na rede. É a única pergunta do painel que olha a vida inteira do cliente. Ela
   seria gravada quando a venda é finalizada, e refeita para aquele cliente quando uma
   venda dele é cancelada; o "cliente novo" vira uma leitura. Só vale a pena se, em
   produção, o pedido dos clientes for lento -- e isso depende da memória do banco de
   produção, que nunca foi lida (a mesma incógnita do D189).
2. **O mês fechado de cada unidade** -- os números do mês, gravados quando o mês fecha e
   regravados se uma venda de mês fechado for cancelada. As séries longas (12 ou 36 meses,
   a rede inteira: o Dash. Faturamento do D306, os royalties do administrador) leem dele; o
   mês corrente continua na hora.

**E o "schema novo" (PostgreSQL).** As mesmas perguntas, na cópia PostgreSQL, levam
dezenas de milissegundos por lote com o cache quente; a primeira rodada da noite, com o
disco frio, levou 35 s. A conclusão é a mesma: o que decide é a memória do servidor, e não
o banco escolhido.

**A recomendação.** Construir os onze relatórios calculando na hora. Fazer a estreia do
cliente (1) só se a medição em produção mostrar o pedido dos clientes lento. Fazer o mês
fechado (2) quando o primeiro relatório que precise de série longa for construído. E
aposentar cada rotina noturna quando o relatório que a lê for refeito (D307).

---

## 5. O Profissional Parceiro: o levantamento (30/09/2026)

O "Profissional Parceiro" é o nome que o dono deu, no menu novo, ao **Dash. Colaboradores**
(`/isrelatorio/rel-dashboard-colaboradores/index`). Levantado por quatro leitores e um
crítico de completude, só lendo o código e medindo na cópia, julho de 2026.

### O que ele é hoje

- **A tela não calcula nada.** Ela lê `dashboard_colaboradores`, uma linha por colaborador
  e mês, gravada pela rotina das 23:30 (`CronJobService::relatoriosDashboardColaboradoresAtualizar`),
  que só refaz o mês corrente e só para usuários ativos do grupo 1. O Radar (D306) lê a mesma
  tabela; nenhuma API a lê.
- **Os números da linha:** vendas (soma de `vendas_produtos.valor_total` pelo colaborador do
  item, em qualquer unidade -- a sessão de pacote vale zero, como no painel), clientes (com os
  sentinelas 1 e 2), cliente novo (quem **se cadastrou** no mês), serviços e produtos
  (quantidade; a cortesia conta como produto), médias por cliente, agendamentos por app e por
  recepção, ocupação da agenda, fidelização (retrato da última visita), NPS (média de 0 a 10,
  e 0 quando não há pesquisa) e as estrelas do plano de carreira.
- **A lista** é filtrada pela área 3 (Barbeiro) e pela unidade atual do colaborador. O "melhor
  da rede" é o máximo da rede entre quem tem `visivel_dashboard`, sem volume mínimo.
- **Quem vê:** 282 não administradores ativos com o privilégio, mais os 10 administradores. O
  colaborador do grupo 1 vê só a si mesmo e não vê o cartão de vendas; o gerente vê a unidade;
  o administrador escolhe a unidade ou a rede, e é o único que vê o plano de carreira. Não há
  gráfico (as barras estão fixas em 100%) nem exportação.

### O que está errado, medido

| O quê | Medido em julho de 2026 |
|---|---|
| **A tela esconde quem não tem a área 3** | 30 linhas e R$ 119.152,26 na rede (o S18). 26 delas são de colaboradores **sem área nenhuma cadastrada** -- falta de cadastro, e não outra profissão; 13 têm venda (R$ 111.307,66). Na unidade 1, o painel mostra 16 colaboradores e R$ 286.189,40; a tela, 12 e R$ 277.163,80 |
| **A tabela deixa fora quem não é do grupo 1** | R$ 54.181,45 na rede, dos quais R$ 43.164,40 e 482 serviços de gerentes que atendem |
| **O mês não fecha de um jeito só** | de janeiro a julho, 20 linhas divergem do recálculo: 9 congeladas porque o colaborador saiu do grupo 1 no meio do mês (faltam R$ 17.151,50), 9 com venda cancelada depois do fim do mês (sobram R$ 652), 2 com venda depois das 23:30 do último dia (R$ 515). Não existe "lançamento atrasado": a data da venda é o fechamento |
| **A sessão de pacote** | vale zero aqui como no painel; mas só 42% das 369 sessões de julho foram feitas por quem vendeu o pacote, e 47% do valor dos pacotes vendidos foi lançado em caixas e gerentes |
| **O NPS** | 0 em 146 das 189 linhas, que não têm resposta nenhuma; só 12 linhas têm 10 respostas ou mais; a média de quem tem é 9,93 |
| **O "melhor da rede"** | em consumo por cliente, um colaborador com 3 clientes, que nenhuma lista mostra |
| **O acesso** | a guarda de `visivel_dashboard` só existe no menu (27 do grupo 1 abrem pelo endereço); 18 do grupo 1 abrem a própria tela e a acham vazia, por falta da área 3; o extrato do plano de carreira deixa o barbeiro ler o de um colega da mesma unidade (D309) |

**O custo de calcular na hora** (cópia MySQL, 128 MB): o mês da unidade 1 por colaborador,
0,18 a 0,35 s; o mês da rede, 0,53 a 0,92 s; 12 meses da unidade, 4,1 a 5,0 s; 12 meses da
rede, 8,9 a 9,3 s. A ocupação da agenda de um mês passado **não se reconstrói**: a
capacidade usa o horário semanal de hoje.

**"Quanto cada colaborador fez" já tem seis respostas** em reais: o painel (R$ 1.871.383,01
na rede), a tabela do Dash e do Radar (R$ 1.816.084,56), a tela do Dash (sem os 30),
Atendimentos e Dash. Faturamento (outra população), o "total do colaborador" do PDF da
Cota-parte (a sessão pelo valor e as gorjetas: R$ 1.890.314,47) e a cota-parte em si. Não
existem metas nem aplicativo do colaborador no sistema.

### As respostas do dono (30/09/2026)

Nas palavras dele, pergunta a pergunta: "1. sim e ver detalhes. 2. correto. 3. correto. 4.
aprovado. 5. correto. 6. correto 7. manter aqui também, métrica importante. 8. correto, mas
o período deve ser móvel, colocar régua padrão de 45 dias para manter como fidelizado. 9.
correto. 10. correto, voltamos nisso depois. 11. correto. 12. correto, mas ter a opção também
de toda a visualização (todo período em que o colaborador é cadastrado - histórico
completo). 13. correto"

O que isso decide:

- **A régua é a do painel**, e o "ver detalhes" da lista de colaboradores do painel abre o
  Profissional Parceiro no mesmo mês.
- **A lista** é de quem fez pelo menos um serviço no período.
- **Duas colunas de dinheiro**: faturamento (a sessão de pacote vale zero) e produção (a
  sessão pelo valor dela). **A gorjeta** numa coluna à parte.
- **O cliente novo conta para quem fez o serviço da estreia** -- e o painel muda junto.
- **O número se calcula na hora**: o cancelamento muda o mês passado.
- **Agendamentos e ocupação da agenda ficam** ("métrica importante"), com régua a desenhar:
  a capacidade de um mês passado não se reconstrói do banco de hoje.
- **A fidelização tem janela móvel, com 45 dias por padrão**: o cliente se mantém fidelizado
  ao colaborador enquanto volta a ele dentro da janela.
- **O NPS** vem com o número de respostas, e "sem pesquisa" no lugar do 0.
- **O plano de carreira fica fora** da primeira versão; o dono volta nele depois.
- **O colaborador vê só a si mesmo, com o faturamento**; o "melhor da rede" só com volume
  mínimo.
- **O período** é o mês, com o histórico de 12 meses de cada colaborador -- e a opção do
  **histórico completo**, desde o cadastro dele.
- **A comissão** fica no relatório Cota-parte.

**Duas respostas a mais, depois do desenho (30/09/2026):**

- **A venda do pacote entra na produção de quem vendeu**: "Entra, dependendo da unidade pode
  utilizar isso como meta." A produção é, então, a venda do pacote para quem vendeu mais cada
  sessão pelo valor dela para quem executou -- o pacote conta duas vezes no total da rede, de
  propósito: a produção é volume de trabalho e de venda, e não dinheiro que entrou (esse é o
  faturamento). Rede, julho de 2026: R$ 1.887.983,62 de produção contra R$ 1.871.383,01 de
  faturamento.
- **E a cota-parte do pacote**, na mesma resposta: "a venda do pacote não deve gerar cota-parte
  automática, na configuração do pacote, que seja definido qual % deve gerar de cota-parte. Ou
  seja, conta como volume de venda mas depende de configuração adicional para gerar a
  cota-parte. A cota-parte deve ser gerada sempre na execução do serviço." É regra do PDV, e não
  deste relatório: o D310.
- **A ocupação da agenda tira os horários bloqueados** (folga, feriado, "Fechado", ausente), e
  a tela mostra os bloqueados ao lado da taxa.

**Uma regra que a revisão de 30/09/2026 fixou, sem voltar ao dono:** as médias por cliente
(consumo, serviços e produtos por cliente) dividem **só o que foi feito para os clientes com
cadastro** pelos clientes com cadastro. O avulso e o "sem cadastro" (os sentinelas 1 e 2) ficam
fora dos dois lados da conta. Antes, o numerador levava o que foi feito para eles e o
denominador não: na unidade 1, em julho, um colaborador aparecia com R$ 515,00 de consumo por
cliente, e os dois clientes que contam tinham consumido R$ 280,00 cada. O dono já tinha decidido
que os sentinelas não contam como cliente (seção 3); esta regra só leva a decisão até o fim.
Faturamento, serviços, produtos e ticket da linha não mudam: o ticket divide pelos
atendimentos, que incluem o balcão.

### As perguntas que foram ao dono

As recomendações eram minhas; as respostas estão acima.

1. **A régua**: as definições do painel (e o "ver detalhes" do painel abre no mesmo mês), ou
   régua própria? *Recomendo as do painel.*
2. **Quem entra na lista**: só barbeiros (área 3, como hoje, completando 26 cadastros), todo
   colaborador com item (como o painel, com os caixas que só deram cortesia), ou quem fez pelo
   menos um serviço no mês (entram os 4 gerentes que atendem, saem os 20 caixas só de
   cortesia)? *Recomendo quem fez serviço -- 143 colaboradores na rede.*
3. **A sessão de pacote**: zero (o pacote conta para quem vendeu), o valor da sessão, ou as duas
   colunas -- faturamento com a sessão a zero e produção com a sessão pelo valor? *Recomendo as
   duas colunas.*
4. **A gorjeta** (R$ 2.330,85 em julho): fora, somada, ou numa coluna à parte? *Recomendo à
   parte.*
5. **O cliente novo, por colaborador**: para todo colaborador com item na visita (a regra do
   painel hoje -- na unidade 39, um caixa que só serviu café "ganhou" 23 clientes novos), só
   para quem fez o serviço da estreia, ou só na unidade? *Recomendo só quem fez o serviço, e
   corrigir o painel junto.*
6. **O mês passado muda com cancelamento?** Calcular sempre na hora (o mês reflete o
   cancelamento), ou retrato gravado no fim do mês (e aí gravar a data do cancelamento, que hoje
   não existe)? *Recomendo calcular na hora, como a seção 4.*
7. **Agendamentos e ocupação da agenda**: saem, ficam com régua nova (e a capacidade gravada mês
   a mês), ou ficam como estão? *Recomendo saírem daqui e irem para um relatório da agenda.*
8. **Fidelização ao colaborador**: sai, régua nova (dos clientes do mês, quantos tiveram a visita
   anterior com serviço também com ele), ou como está? *Recomendo a régua nova.*
9. **O NPS do colaborador**: sai, fica com o número de respostas e "sem pesquisa" no lugar do 0,
   ou vira o NPS de -100 a 100 com mínimo de respostas? *Recomendo o número de respostas e "sem
   pesquisa".*
10. **O plano de carreira (as estrelas)**: sai, fica só para o administrador, ou fica com limiares
    revistos e visível ao colaborador? *Recomendo ficar fora da primeira versão e voltar com
    você.*
11. **O que o colaborador vê**: só a si mesmo sem o faturamento (como hoje), com o faturamento, ou
    com a posição no ranking da unidade? E o "melhor da rede": sai, fica com volume mínimo, ou como
    está? *Recomendo só a si mesmo, com o faturamento, e o melhor da rede com volume mínimo.*
12. **O período**: um mês, intervalo livre, ou um mês mais o histórico de 12 meses de um
    colaborador (no lugar do extrato de seis meses)? *Recomendo o mês mais os 12 meses.*
13. **A comissão (cota-parte)**: fica no relatório Cota-parte, ou aparece aqui? *Recomendo ficar
    na Cota-parte, que tem as suas próprias perguntas (S02, S03).*

### A tela nova, em fatias

O desenho (quatro desenhistas e um crítico, 30/09/2026, só lendo e medindo na cópia) partiu o
relatório em fatias, servidor e regras primeiro:

| Fatia | O quê | Situação |
|---|---|---|
| F0 | no painel, o cliente novo conta para quem fez serviço no dia da estreia | feita em 30/09/2026 |
| F1 | a lista do mês: `GET /adm/api/profissionais` e `/adm/api/profissionais/unidades`, a tela, o "ver detalhes" do painel com o mês, o escopo pelas marcas e o D309 | feita em 30/09/2026 |
| F2 | os clientes do mês (novos pela regra da estreia, fidelização com janela de 30/45/60/90 dias) e o melhor da rede (volume mínimo de 30 atendimentos), num pedido à parte; o índice do D308 no gerador do PostgreSQL | feita em 30/09/2026 |
| F3 | o detalhe de um colaborador: 12 meses e o histórico completo desde o cadastro, com os novos da série | feita em 30/09/2026 |
| F4 | a agenda: os agendamentos (a família, app × recepção), a vigência do horário semanal (tabela nova, gravada por quem muda o horário) e a ocupação sem os bloqueados | feita em 02/10/2026: F4.1 (a vigência do horário), F4.2 (a régua sem banco), F4.3 (a API do mês e a tela da lista) e F4.4 (a agenda do histórico) |
| F5 | aposentar a tela antiga e a rotina das 23:30 -- **sem apagar `dashboard_colaboradores`**, que é a régua antiga dos meses sem vigência (decisão B6 do dono, 01/10/2026) | feita no código em 13/10/2026, com a resposta 137 do dono (a seção 7): a tela antiga, o extrato, as estrelas do plano de carreira e a rotina das 23:30 saíram; a tabela ficou. Os casos PHP que mudaram com ela rodaram no mesmo dia, depois do reinício da máquina, nos dois bancos (seção 7); a prova de que a tela saiu é estática |

**Onde está a F1:**

| Parte | Arquivo |
|---|---|
| O escopo e a unidade do filtro, sem banco | `module/ISRelatorio/src/Regra/RegraDoProfissionalParceiro.php` (`escopo`, `unidadeDoFiltro`, `veOMenu`, `periodo`) |
| As contas, sem banco (a régua do painel por import) | `module/ISRelatorio/src/Regra/ContasDoProfissionalParceiro.php` |
| A resposta | `module/ISRelatorio/src/Api/ProfissionalParceiroJson.php` |
| A API | `module/ISRelatorio/src/Controller/Adm/ProfissionalParceiroController.php` |
| As consultas | `VendaRepository::painelItens` (a unidade `null` é a rede), `GorjetaRepository::profissionalGorjetasDasVendas`, `NpsItemRepository::profissionalPorColaborador` |
| A tela | `frontend/src/telas/ProfissionalParceiro.tsx` e `ProfissionalParceiroLigado.tsx`; o item e o caminho em `menu-do-erp.ts` (`CAMINHO_DO_PARCEIRO`, `PRIVILEGIO_DO_PARCEIRO`); o mês do "ver detalhes" no `Console.tsx` |
| Os testes | `RegrasDoProfissionalParceiroTest` (sem banco), `ProfissionalParceiroTest` (os dois bancos), `ProfissionalParceiroLigado.teste.tsx` |

**Medido na cópia, julho de 2026, pelo caminho da API** (128 MB): unidade 1, 14 colaboradores
na lista e 2 fora, 1,5 a 2,0 s; unidade 39, 6 e 2, 1,4 s; a rede, 143 e 25, 3,0 s e 88 MB de
memória no PHP. A lista mais o que ficou fora dá o faturamento do painel, ao centavo, nas três.

### A F2: os clientes novos, a fidelização e o melhor da rede

Dois pedidos novos, cada um depois dos números da lista e com o mês que eles aplicaram -- o
molde do painel. A lista não espera por eles: enquanto não chegam, o cartão diz "…".

- **`GET /adm/api/profissionais/clientes?mes=&unidade=&janela=`**, com as mesmas guardas da
  lista (numa função só, `recorteDoPedido`):
  - **o cliente novo** de cada colaborador pela regra B do painel -- conta para quem fez
    serviço no dia da estreia --, e o total do recorte com os que não contaram para ninguém
    (`semColaborador`: estrearam comprando crédito, ou com serviço em outra unidade);
  - **a fidelização pela variante C**: dos clientes com serviço do colaborador no mês (os novos
    inclusive), fidelizado é quem, em alguma visita do mês, teve a visita anterior -- procurada na
    rede, em qualquer unidade -- com serviço dele até a janela antes, inclusive. Visita é o dia com
    serviço; a venda só de produto ou de cortesia não conta. Os não fidelizados vêm explicados pela
    primeira visita do mês: vieram de outro colaborador, eram novos, ou não tinham visita com
    serviço dentro da janela (quase sempre, quem voltou depois dela; também quem estreou antes
    comprando crédito e faz agora o primeiro serviço -- a tela diz o que a conta é, e não "voltou");
  - **a janela** é uma de 30, 45, 60 e 90 dias (45 por padrão); qualquer outra é 422. Ela alarga a
    leitura das visitas para `[início − janela, fim)`, e a lista fechada limita esse custo;
  - no escopo `proprio`, só a linha de quem pede, sem os totais -- e o servidor nem pergunta pelo
    histórico dos clientes dos outros.
- **`GET /adm/api/profissionais/destaques?mes=`**: o melhor da rede nos dez números da lista
  (faturamento, produção, atendimentos, clientes, serviços, ticket, faturamento por dia e os três
  "por cliente"), entre quem teve pelo menos 30 atendimentos no mês. É sempre a rede; o valor vai
  para todos, e quem é o melhor, só para o administrador. As razões se comparam pelo valor exato;
  no empate, fica o primeiro da lista.
- **O nome da unidade** no cabeçalho e ao lado de cada colaborador passou a ser o do cadastro --
  o mesmo que a sessão publica e o painel escreve. Era o rótulo da lista suspensa ("Brasil - SC -
  Florianópolis - Centro"), e o cabeçalho do relatório dizia uma coisa e o do painel outra: na
  cópia, 4 das 36 unidades ativas têm o nome diferente do rótulo. A lista suspensa continua com o
  rótulo, que é o do ERP inteiro.
- **O índice do D308 entrou no gerador do PostgreSQL** (`$indicesAmpliados`), e o
  `postgres/schema.sql` foi regenerado -- levando junto a linha do D189, que estava atrasada desde
  88a7a54 sem ninguém ver, porque o CI está parado.

**Onde está a F2:**

| Parte | Arquivo |
|---|---|
| A janela, a busca das visitas e o mínimo do melhor da rede | `RegraDoProfissionalParceiro` (`JANELAS`, `JANELA_PADRAO`, `janela`, `buscaDaFidelizacao`, `MINIMO_DE_ATENDIMENTOS`) |
| A fidelização, sem banco | `module/ISRelatorio/src/Regra/FidelizacaoAoColaborador.php` (`visitas`, `base`, `doPeriodo`, `somar`) |
| O melhor da rede e os novos sem colaborador, sem banco | `ContasDoProfissionalParceiro::destaques` e `::novosSemColaborador` |
| As respostas | `ProfissionalParceiroJson::clientes` e `::destaques` |
| A API | `ProfissionalParceiroController::clientesAction`, `::destaquesAction` e `::recorteDoPedido` |
| As consultas | `VendaRepository::parceiroVisitasComServico` (em lotes de mil clientes) e `::parceiroVisitasComServicoDaRede` (a rede, pela faixa de data), lidas em fluxo (`toIterable`); `::painelComprasDeCredito` aceita a rede |
| A tela | `ProfissionalParceiroLigado.tsx` (os três pedidos), `ProfissionalParceiro.tsx` (as duas linhas do cartão, a explicação dos não fidelizados, "Fidelização em" e o cartão "O melhor da rede") |

**Medido na cópia, julho de 2026, pelo caminho do controller** (MySQL com 128 MB, o de fábrica,
com o índice do D308):

- **Unidade 1, janela de 45 dias**: fidelização de 44,8% em 2.449 pares -- o número do desenho,
  reproduzido. O colaborador 200 em 57,1% ("323 de 566"), o 998 em 37,1, o 380 em 61,8, o 1760 em
  48,7 e o 2330 em 34,4, os mesmos do crítico. 57 clientes novos, 1 sem colaborador (estreou
  comprando crédito). Levou 13 s: 1,7 s dos itens, 1,0 s das visitas e **10 s da pergunta "teve
  serviço ou crédito antes?"** dos 2.233 candidatos, com o cache frio -- a mesma do painel.
- **A rede**: 41,9% em 15.788 pares; 1.403 novos, e a soma por colaborador dá 1.445, o número da
  F0. **Cerca de 70 s e 122 MB de pico no PHP**, e 64 s disso são os 14.489 candidatos a novo.
  Cabe nos 300 s e nos 512 MB do Docker local; os limites de produção nunca foram lidos. No
  PostgreSQL, que é o destino do corte, a mesma pergunta levava de 3 a 6 s na medição do desenho --
  e a medida de 01/10/2026, pelo caminho do controller, não confirmou: a rede levou 25 a 49 s com o
  disco frio e 7 a 8 s quente (o D311, com o que cada número significa).
- **O melhor da rede**: 130 concorrentes de 143, em 1,7 s; os vencedores são os do crítico (o
  200 em faturamento, produção, atendimentos, clientes, serviços e faturamento por dia; o 2591 em
  ticket, R$ 142,28; o 2594 em consumo e produtos por cliente; o 2451 em serviços por cliente).

**O custo da rede virou débito, o D311**: os clientes da rede levam cerca de 70 s no MySQL de 128
MB, e nada limita pedidos iguais em paralelo -- a trava da sessão é solta de propósito, e o pedido
que a tela descarta continua rodando no servidor. A decisão fica para a medida no PostgreSQL.

**Provado:**

- **A revisão adversarial**: três revisores só lendo -- as contas do servidor, o acesso e o
  vazamento entre franquias, a tela --, e o sha1 de cada arquivo conferido depois de cada um.
  Nenhum vazamento entre franquias nem entre colaboradores. Consertados: o foco que caía no
  `body` no "tentar os clientes de novo"; a frase da unidade que chamava pares de "clientes"; a
  venda sem cliente, que entrava na base como o "cliente 0"; as chaves dos lotes de visitas, que
  recomeçavam em zero (armadilha para quem lesse com `iterator_to_array`); o ruído do `double`
  que desempatava o melhor da rede; o rótulo "voltou depois", que também valia para quem não
  tinha visita nenhuma; e cinco mutantes da tela que sobreviviam e agora morrem. O `$so` da base
  do colaborador ganhou vigia no portão de recorte, e o ensaio de permissão passou a olhar o
  corpo das respostas, e não só o código.
- **39 mutantes, todos mortos**, cada um aplicado sozinho e devolvido com o sha1 conferido: 26 no
  servidor (a janela inclusiva, a anterior com o colaborador, qualquer visita e não só a
  primeira, a ordem da explicação, os sentinelas, o cliente 0, a busca que começa a janela antes,
  o mínimo, o empate, o valor exato, os novos sem colaborador, a linha da unidade escondida do
  colaborador, o nome do melhor só para o administrador, as consultas, os lotes e o índice no
  gerador) e 13 na tela.
- **O ciclo inteiro, em série**: caracterização em **1374 nos dois bancos** (13 pulados, os de
  sempre); contrato do aplicativo, 67 de 67 nos dois; os ensaios por HTTP nos dois bancos, o de
  permissão com as rotas novas -- sem a marca, 403 nas três; com ela, 200 e o corpo sem a conta
  dos outros --; os portões com o D50 de sempre; **1492 testes no front**, build e lint no piso de
  dois avisos. Na primeira rodada, quatro casos do `Console` contavam os pedidos da tela e não
  esperavam os dois da F2 -- consertados. A suíte da tela inteira, com os processos que o
  Windows dá, estoura o tempo de 5 s em um ou dois casos diferentes a cada rodada, em arquivos
  que a F2 não tocou; com quatro processos, passa inteira.
- **No navegador, contra o banco de contrato** com agosto e setembro semeados e 30 cortes do
  colaborador de fixture: o "ver detalhes" do painel abriu a lista, e os clientes chegaram
  depois dela ("…" enquanto isso). **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, 2 de 5 e 1 de 5 fidelizados,
  com a mesma explicação, e 3 de 10 (30,0%) somados; na rede, 2 de 6, 1 de 5 e 3 de 11 (27,3%). A
  janela de 30 dias pediu só os clientes de novo. O administrador viu o nome e a unidade do
  melhor; o gerente, só os valores. A 375 px, "Fidelização em" desce para a linha de baixo,
  alinhada ao título, e nada rola de lado. Depois, a sessão de teste foi apagada e o banco de
  contrato remontado.

### A F3: o histórico de um colaborador -- 12 meses, e o completo desde o cadastro

Cada cartão da lista ganhou "ver o histórico" -- o do próprio colaborador também, no escopo
`proprio`. O histórico abre no lugar da lista, com o mês, a unidade e a janela dela; ao voltar, a
lista não é pedida de novo, e o foco volta ao botão de quem se abriu.

- **`GET /adm/api/profissionais/historico?colaborador=&mes=&unidade=`**: os **12 meses** que
  terminam no mês pedido (`RegraDoProfissionalParceiro::MESES_DO_HISTORICO`), o mês em andamento
  até hoje. O bloco não começa antes do **mês do cadastro** (`usuarios.data_criacao` -- "todo
  período em que o colaborador é cadastrado", a resposta 12), e `anterior` diz o mês a pedir para
  os 12 de antes, nulo quando o bloco já começa no cadastro.
  - **Cada mês é a linha que ele teria na lista daquele mês**, no mesmo recorte: os itens dele no
    bloco (`painelItens` com o colaborador, uma condição a mais na mesma consulta) passam, mês a
    mês, pelas mesmas funções da lista (`ContasDoProfissionalParceiro::doColaborador`,
    `ProfissionalParceiroJson::linha`). O desenho propunha somar no banco, com a regra escrita de
    novo em SQL; assim, o igual é por construção -- e o teste nos dois bancos confere mesmo assim.
  - O mês sem item vem com zero e as razões nulas (férias e afastamento ficam à vista); o mês com
    item e sem serviço vem com os números dele e `naLista` falso -- a tela marca "sem serviço".
  - **O total é o bloco como um período**: os clientes distintos nos 12 meses, e não a soma dos
    meses (no colaborador 200, em julho de 2026, 1.206 contra 6.218 somados).
  - A **gorjeta** é a dele nas vendas da base, pelo mês da venda, buscada pelo colaborador da
    gorjeta (`GorjetaRepository::parceiroGorjetasDoColaborador`) -- inclusive a de venda em que ele
    não teve item, como a lista conta. O **NPS** é o dele, pelo mês da resposta e pela unidade da
    pesquisa (`NpsItemRepository::parceiroNpsDoColaborador`).
- **`GET /adm/api/profissionais/historico/clientes?...&janela=`**: os clientes novos e a
  fidelização de cada mês, e do bloco, com as funções da F2: os candidatos são os clientes com
  serviço dele no bloco; a estreia é perguntada uma vez, desde o início do bloco; as visitas, desde
  a janela antes dele. Mês a mês, o mesmo número do `/profissionais/clientes` daquele mês.
- **O histórico completo é a tela pedindo os blocos um depois do outro**, pelo `anterior`, até o
  cadastro -- nunca um pedido com a vida inteira (o maior colaborador tem 97 mil itens, e a
  fidelização da vida toda de uma vez levou de 119 a 127 s no desenho). No máximo um pedido de
  números e um de clientes no ar; a tela mostra "Carregando o histórico: N de M períodos". Os
  clientes novos e a fidelização entram no completo também, bloco a bloco -- e não
  "indisponível", como o desenho do histórico propunha e a crítica recusou.
- **Quem vê:** as guardas da lista (`recorteDoPedido`, a mesma função), e depois as do
  colaborador (`historicoDoPedido`): no escopo `proprio`, só ele mesmo (outro id é 403, com o
  motivo `proprio`); que ele exista; para o gerente, que seja do cadastro da unidade **ou** tenha
  item em venda finalizada fechada por ela -- o de outra franquia é o mesmo 404 de quem não existe,
  e o nome e o cadastro dele não saem pelo id; e o mês não antes do cadastro (422). Só então a
  sessão é solta.

**Onde está a F3:**

| Parte | Arquivo |
|---|---|
| O bloco, o colaborador do pedido | `RegraDoProfissionalParceiro` (`MESES_DO_HISTORICO`, `bloco`, `colaborador`) |
| A linha de um colaborador num período | `ContasDoProfissionalParceiro::doColaborador` |
| A gorjeta e o NPS por mês, os candidatos e os clientes da série | `module/ISRelatorio/src/Regra/HistoricoDoParceiro.php` (`gorjetasPorMes`, `npsPorMes`, `candidatos`, `clientes`) |
| As respostas | `ProfissionalParceiroJson::historico` e `::historicoClientes` |
| A API | `ProfissionalParceiroController::historicoAction`, `::historicoClientesAction` e `::historicoDoPedido` |
| As consultas | `VendaRepository::painelItens` (o colaborador), `::parceiroAtendeuNaUnidade`, `GorjetaRepository::parceiroGorjetasDoColaborador`, `NpsItemRepository::parceiroNpsDoColaborador` |
| A tela | `HistoricoDoParceiro.tsx` (a vista), `HistoricoDoParceiroLigado.tsx` (a cadeia dos blocos), `textosDoParceiro.ts` (os textos que a lista e o histórico dividem); o botão e a troca em `ProfissionalParceiro.tsx` e `ProfissionalParceiroLigado.tsx` |

**Medido na cópia, julho de 2026, pelo caminho do controller** (MySQL com 128 MB):

- **O colaborador 200, o maior da rede, na unidade 1**: os números em cerca de 10 s (9.474 itens,
  5 a 6 s só dos itens; 38 MB de pico no PHP); os clientes em cerca de 17 s (1.185 candidatos;
  42 MB). O histórico completo dele são **12 blocos** (135 meses, de 2015-05 a 2026-07).
- **O 2305, a mediana da lista, na unidade 39**: cerca de 7 s e 6 s; 2 blocos.
- **Julho do histórico é a linha da lista e os clientes da F2, nos dois**: o 200 com R$ 53.221,20,
  703 atendimentos, 567 clientes, 13 novos e 323 de 566 fidelizados; o 2305 com R$ 13.434,00,
  118 atendimentos, 90 clientes, 13 novos e 39 de 90.
- **A guarda do gerente** -- quem não é do cadastro da unidade -- responde primeiro pelos itens do
  próprio bloco, que a ação usaria de qualquer jeito: quem veio da lista tem item ali, e a guarda
  não custa nada a mais. Só com o bloco vazio ela pergunta pela história inteira, e essa pergunta
  lê todos os itens do colaborador quando não acha: 5 a 6 s para o 200 numa unidade em que nunca
  atendeu, e 1,7 s no único caso legítimo da cópia (o 200, com um item na unidade 13 em 2018, a
  30.149 itens do começo). A revisão mediu, e a guarda passou a rodar **com a sessão já solta** --
  antes, o gerente ficava parado atrás dela. Cada pedido de clientes relê os itens do bloco (5 a
  6 s no 200).
- **O que o 404 ainda conta, pelo relógio:** o id que não existe sai em milissegundos; o de um
  usuário de outra franquia, depois da pergunta pela história -- de milissegundos a segundos,
  conforme a produção dele. O nome e o cadastro não saem; que o id é de alguém, sai. Fechar isso
  pede uma pergunta barata pelo par (colaborador, unidade) -- um índice composto ou uma tabela de
  pares --, e fica registrado aqui.

**Provado:**

- **A revisão adversarial** -- cinco revisores, só lendo (as contas, as consultas nos dois bancos,
  o acesso, os testes, a tela), cada achado conferido por um verificador independente -- achou
  sete pontas, as sete confirmadas e consertadas: a guarda do gerente, que lia a história inteira
  com a sessão presa (passou a responder pelos itens do bloco e a rodar com a sessão solta); o
  tempo do 404, registrado acima; a janela de 60 e 90 dias sem teste no histórico; o mês em
  andamento sem teste na resposta; a guarda de unidade dos clientes sem teste na tela; o foco que
  as respostas puxavam ao título depois de um clique no vazio; e a mensagem de falha cortada entre
  721 e 796 px com o menu aberto. Nenhum vazamento entre franquias nem entre colaboradores.
- **Mutantes, todos mortos**: 8 no servidor na construção e 6 depois da revisão (os quatro da
  guarda nova, a janela fixa, o mês em andamento), e 23 na tela mais 2 depois da revisão -- cada um
  aplicado sozinho e devolvido com o sha1 conferido.
- **O ciclo inteiro, em série**: caracterização em **1398 nos dois bancos** (13 pulados, os de
  sempre); contrato do aplicativo, 67 de 67 nos dois; os ensaios por HTTP nos dois bancos, o de
  permissão com as duas rotas novas -- sem a marca, 403; com ela, o próprio histórico em 200 e o
  de outro id em 403 `proprio`; os portões com o D50 de sempre; **1514 testes no front**, build e
  lint no piso de dois avisos.
- **No navegador, contra o banco de contrato semeado**: o histórico do colaborador de fixture
  bateu, mês a mês e no total, com uma conta direta nas linhas do banco (176 atendimentos,
  R$ 12.194,90 e 13 clientes distintos no bloco, e não a soma dos meses); os clientes da série
  bateram com o `/profissionais/clientes` de cada um dos seis meses com movimento; com o cadastro
  recuado para março de 2024, o completo encadeou três blocos, um pedido de cada vez, até o
  cadastro; a volta à lista não a pediu de novo e devolveu o foco ao botão; a 375 px, a tabela
  rolou dentro do próprio cartão, com o mês preso, e a página não rolou de lado. Depois, as duas
  sessões de teste foram apagadas e o banco de contrato remontado.

### A F4: a agenda

A agenda entrou em quatro fatias, servidor primeiro, pelo desenho do juiz de 01/10/2026 (`docs/f3-relatorios/f4-final.md`; três
desenhos -- do dado, do mínimo e do risco -- conferidos no código e na cópia) e pelas respostas do
dono do mesmo dia (seção 6, "A F4 do Profissional Parceiro"). Duas respostas **mudaram o desenho**,
e valem a partir da F4.2:

- **B4, todos com agenda, como o Dash:** a capacidade e a taxa do total da unidade (e da rede) somam
  todos os usuários do recorte com agenda no período -- a lista da F1 **mais** o "com agenda e sem
  serviço no mês". O cartão de cada colaborador continua com a dele, e o "fora da lista" continua à
  parte como número, mas a taxa da unidade é a de todos.
- **B6, a régua antiga:** no mês sem o horário da época, a ocupação do colaborador sai de
  `dashboard_colaboradores` (a linha dele daquele mês), menos os bloqueados do mês, marcada como
  "régua antiga" e nunca somada com a régua nova sem dizer; quem não tem linha no Dash (fora do
  grupo 1) fica sem cobertura. Por isso a F5 não apaga aquela tabela.

| Fatia | O quê | Situação |
|---|---|---|
| F4.1 | a vigência do horário: a tabela, a semente, quem grava e o autor | feita em 02/10/2026 |
| F4.2 | a régua sem banco: a grade de cada dia pela vigência, os agendamentos e a ocupação | feita em 02/10/2026 |
| F4.3 | a API do mês e a tela da lista | feita em 02/10/2026 |
| F4.4 | a agenda do histórico do colaborador | feita em 02/10/2026 |

#### F4.1: a vigência do horário

**O horário mora em `usuarios` e é sobrescrito**: as 28 colunas de hora, o tempo de atendimento, as
férias (um intervalo só), o `status` e o `visivel_agenda`. A ocupação de um mês passado precisa do
horário daquele mês, e até aqui ele se perdia na edição seguinte. Agora:

- **`usuarios_horarios`** guarda o retrato inteiro a cada mudança -- uma linha com o instante
  (`vigente_desde`), a porta, quem gravou e os 33 campos (`RetratoDoHorario::CAMPOS`). Sem `UNIQUE`,
  sem cascata (o histórico não some calado com o usuário), sem entidade Doctrine: escrita e leitura
  por DBAL, como `agendas_exclusoes`. No PostgreSQL ela tem `inquilino_id`, o índice
  `(inquilino_id, usuario, vigente_desde)` e o RLS, todos do gerador, que não mudou.
- **Quem grava são as quatro portas do `UsuarioService`**, na transação delas, depois do flush:
  `inclusao` (`inserir`), `cadastro` (`atualizarDados`, o `visivel_agenda`), `estado`
  (`mudarEstado`, pelas duas telas) e `agenda` (`editarAgenda`, as horas, o tempo e as férias).
  **Só grava quando o retrato difere da última linha** -- a tela da agenda posta os 28 campos em
  todo salvamento, e salvar só a cor regravava o horário idêntico. **Se a vigência falhar, o
  salvamento falha junto.** A cor, a cor da letra e a ordem no aplicativo não entram: não mudam a
  grade.
- **Dois editores ao mesmo tempo** (a revisão de 02/10/2026): o retrato gravado é a linha de
  `usuarios` relida com `FOR UPDATE`, e não o objeto na memória -- o flush do Doctrine só manda as
  colunas que o pedido mudou, e o `status` que outro pedido mudou depois da leitura ficava no banco
  diferente do retrato (a vigência dizia "ativo" de um barbeiro desligado). E a última vigência é
  lida com `FOR UPDATE`, depois da trava do usuário: no MySQL (REPEATABLE READ) a leitura sem trava
  usa a fotografia da primeira leitura da transação, e não via a vigência do outro editor. Na
  `inclusao` a última vigência não é procurada -- a pessoa nasceu na transação --, porque a busca
  travada, no MySQL, trava o vão do fim do índice, e duas inclusões ao mesmo tempo se travavam
  (deadlock medido no banco do CI local). Dois usuários sem linha nenhuma editados ao mesmo tempo
  ainda podem se travar assim, o que só existe antes da semente: um dos salvamentos falha inteiro.
- **O autor é quem entrou**: `CrudController::autorDaEscrita` lê `SessaoAcl::loginDaSessao` (o D315).
  Na personificação, `gravado_por` é o administrador e `em_nome_de` a pessoa assumida; fora dela,
  `em_nome_de` é nulo. ~~**Menos no caminho do D127**: o administrador assume B e, da lista de B,
  "volta" como C; a sessão vira a de C, sem marca, e o que ele gravar dali fica com `gravado_por = C`
  e `em_nome_de` nulo.~~ **Fechado em 12/10/2026** (a resposta 105 do dono, o D127): a volta é só
  para quem entrou, e o "Autenticar como" de C, de dentro da sessão de B, é recusado -- a sessão
  continua sabendo quem entrou, e o que ele gravar fica com `gravado_por` = o administrador e
  `em_nome_de` = B (`D315PersonificacaoTest::testDepoisDaVoltaRecusadaOAutorDaEscritaContinuaQuemEntrou`).
- **A semente** dá a cada usuário uma linha `semente` com o horário de hoje, valendo desde
  `COALESCE(data_alteracao, data_criacao)`: todo escritor do horário grava `data_alteracao`, então o
  horário de hoje vale, no mínimo, desde a última edição -- e o que vinha antes se perdeu (esses meses
  são os da régua antiga, B6). Roda de novo sem duplicar. Os arquivos do MySQL e do PostgreSQL têm o
  mesmo corpo, e o teste confere.
- **No corte**, o passo 0 ganhou a linha da tabela (antes do passo 1, que refaz o `mysql.sql` de
  produção; a migração e o bloco do `mysql.sql` são o mesmo DDL, e o teste confere), a semente entrou
  depois da carga (5b), e a volta atrás para o MySQL semeia antes de reabrir
  (`procedimento-de-corte.md`). **O conferidor e o ensaio rodam no 7b**, depois de apontar a
  aplicação: os dois leem o banco do `local.php`, e no 5b o conferidor media o MySQL de produção. Cada
  um recebe a plataforma (`--plataforma=postgresql`) e sai com 2 no banco errado. O ensaio
  (`ensaiar-vigencia-do-horario.php`) troca o "salvar o horário de um usuário de teste", que mudaria
  de verdade a grade de alguém: passa pela porta `estado` numa transação que ele desfaz. O ensaio
  geral (`ensaiar-corte.sh`) ganhou as fases `semente` e `vigencia`. O passo 0 continua incompleto
  para outras mudanças de esquema: D323.

**Onde está a F4.1:**

| Parte | Arquivo |
|---|---|
| A tabela | `mysql.sql` (entre `usuarios_comissoes_extras` e `usuarios_servicos`), `postgres/schema.sql` regenerado (o diff só acrescenta o bloco dela) |
| A migração e as sementes | `scripts/migracao-usuarios-horarios.sql`, `scripts/semente-usuarios-horarios.mysql.sql`, `postgres/semente-usuarios-horarios.sql` |
| O retrato, sem banco | `module/ISConfiguracao/src/Regra/RetratoDoHorario.php` (`CAMPOS`, `doUsuario`, `daLinha`, `iguais`) |
| Quem grava | `module/ISConfiguracao/src/Service/VigenciaDoHorario.php` (no SM, com fábrica); `UsuarioService::registrarHorario` e as quatro portas |
| O autor | `CrudController::autorDaEscrita`; as cinco chamadas em `UsuariosController` (ERP) e `Adm\UsuariosController` |
| Os portões | `scripts/conferir-escritores-do-horario.php` (no `conferir-tudo.sh`, lê o código: os setters do retrato, os receptores e os alvos do hidratador conhecidos, o UPDATE por qualquer caminho, e o `UsuarioService` e o `Usuario` método a método); `scripts/conferir-vigencia-do-horario.php` (fora dele, lê o banco: usuário sem linha, ou última linha diferente do cadastro); `scripts/ensaiar-vigencia-do-horario.php` (a porta `estado` numa transação desfeita, para o corte) |
| As fixtures | `DELETE FROM usuarios_horarios WHERE usuario >= 90001000` antes do `DELETE FROM usuarios`, e a marca refeita |
| Os testes | `RetratoDoHorarioTest` (sem banco), `UsuarioHorarioVigenciaTest` (os dois bancos); `UsuarioDadosTest`, `UsuarioAtivacaoTest` e `D315PersonificacaoTest` passam o autor |

**Medido nas duas cópias locais (`franquiabv_dev`), com a autorização A1 e A2 do dono:**

- a tabela criada no MySQL (`migracao-usuarios-horarios.sql`) e no PostgreSQL (o bloco do
  `schema.sql` regenerado: tabela, índice, as duas chaves estrangeiras, RLS e a política); o papel
  da aplicação recebeu os privilégios pelos `DEFAULT PRIVILEGES`;
- a semente rodada **duas vezes** em cada banco: **2.432 linhas** na primeira (2.427 da cópia e 5 de
  fixtures que estavam lá), **0** na segunda; `vigente_desde` de 2015-06-02 a 2026-08-16 nos dois;
- `conferir-vigencia-do-horario.php` saiu com **0** nos dois (2.432 usuários, 2.432 com vigência,
  nenhum diferente do cadastro);
- **o índice do D189** trocado no lugar do de uma coluna, com o mesmo nome: no MySQL,
  `FK_AGENDAS_colaborador (colaborador, data)` em 25 s (3,98 milhões de linhas, `INPLACE`); no
  PostgreSQL, `agendas_fk_agendas_colaborador (inquilino_id, colaborador, data)` em 11 s (`CREATE
  INDEX CONCURRENTLY`, `DROP` do antigo, `RENAME`). A chave estrangeira do MySQL continua de pé.
  Para desfazer: o mesmo `ALTER` com `(colaborador)`; o mesmo trio com `(inquilino_id, colaborador)`.

**Provado:**

- **Os testes da fatia nos dois bancos**, em série: `UsuarioHorarioVigenciaTest` (11),
  `RetratoDoHorarioTest` (4) e os três atualizados -- 57 testes e 436 asserções no MySQL e no
  PostgreSQL; os vizinhos (privilégios, a API de usuários, o um-dispositivo-por-vez, os arquivos),
  89 testes nos dois, 1 pulado (o de sempre).
- **Os mutantes da F4.1, cada um sozinho e devolvido com o sha1 conferido**, todos mortos: gravar com
  o retrato igual (3 casos vermelhos); o autor como `usuarioId` (2, um deles no
  `D315PersonificacaoTest`); a comparação sem `ferias_fim` (2); a porta trocada (3, e o portão); a
  vigência gravada depois do `commit` (1 -- o dublê anota o nível de transação em que foi chamado:
  depois do `commit`, o `rollback` do `catch` desfaria a transação de quem chamou, e o banco do teste
  não acusaria).
- **O portão `conferir-escritores-do-horario.php`, falsificado:** cada uma das quatro chamadas
  arrancada (uma delas deixada só num comentário), um `setVisivelAgenda` num serviço do ISCadastro, o
  homônimo `setSabadoAbertura` sobre `$usuario` noutro módulo, um hidratador sobre `$colaborador`, um
  `UPDATE usuarios` e um `insert` em `usuarios_horarios` fora da `VigenciaDoHorario` -- todos
  vermelhos; o mesmo homônimo sobre `$unidade` no `UnidadeService` ficou verde (a separação da
  `Unidade`).
- **O conferidor do banco, falsificado no banco do CI local**: 5 usuários sem linha (saída 1); depois
  da semente, 0; com o tempo de atendimento de um usuário mudado por `UPDATE`, 1 diferente (saída 1).
- `sh scripts/conferir-tudo.sh` saiu com **0** (o D50 de sempre como vermelho conhecido);
  `rodar-contrato.sh --montar`, 67 de 67 no MySQL; `rodar-contrato-postgres.sh`, 67 de 67 na
  segunda rodada. A primeira deu 27 falhas, todas "o curl esperou 60 s e nao recebeu nada": o
  Windows estava com o disco do contêiner parado (um `find` em `module/` levou 110 s, e a tela de
  entrar, 66 s); repetida com o host calmo, passou inteira.

**A revisão de 02/10/2026** (nove achados de uma verificação adversarial, todos conferidos no código
e consertados, menos o D127, que é do dono):

- **Dois editores ao mesmo tempo.** `testDoisEditoresAoMesmoTempoAVigenciaEOQueOBancoGuardou` (um
  segundo `EntityManager` na mesma conexão desativa o ALVO entre a leitura e o salvamento da agenda)
  saiu vermelho antes do conserto -- a última vigência com `status = 1`, o banco com 0 -- e verde
  depois. O mutante "o retrato do objeto na memória" volta a deixá-lo vermelho. A fotografia do
  MySQL não cabe num caso da suíte (pede duas conexões e um commit de verdade, e a suíte não grava
  nada que fique): foi medida com duas sessões no banco do CI local -- a leitura sem trava, numa
  transação que já tinha lido, deu 0 linhas; a travada, 1. E o deadlock da busca travada na
  inclusão, também com duas sessões: dois usuários sem linha, um deles morto com `ERROR 1213`; com
  linha, ou sem a busca, nenhum. Por isso os mutantes "sem a trava na última vigência" e "a inclusão
  procurando" sobrevivem à suíte, e o motivo está escrito na `VigenciaDoHorario`.
- **O portão dos escritores, reescrito.** 36 falsificações, cada uma sozinha e desfeita com o sha1
  conferido: as 33 que tinham de ficar vermelhas ficaram, e os 3 controles (o homônimo sobre
  `$unidade` no `UnidadeService`, um `$venda->setStatus` a mais no `VendaService`, um comentário que
  cita as chamadas) ficaram verdes. **O portão antigo deixava passar 24 delas**: a cadeia
  (`find(...)->setSabadoAbertura`, `getColaborador()->...`), o índice, `$colaboradorDaUnidade`, o
  hidratador sobre `$alvo`, `$this->usuario` e `$user`, o QueryBuilder, a crase, a DQL com duas
  barras e com `Usuario::class`, o apelido do `use`, o `setStatus`, o `call_user_func`, a subclasse,
  o `DELETE` com crase, e os quatro casos de dentro do `UsuarioService` e os dois de dentro do
  `Usuario`.
- **Os usuários de fixture começam sem vigência.** Com a semente rodada antes de cada caso (o estado
  do `franquiabv_dev`), o arquivo deu 9 vermelhos de 12 antes do conserto, e 12 de 12 depois.
- **O DDL da migração e o do `mysql.sql`:** `testAMigracaoEOMysqlSqlTemOMesmoDdl`; uma coluna posta
  só no `mysql.sql` o deixa vermelho.
- **Os roteiros do corte:** `testOsRoteirosDoCorteRecusamOBancoDeOutraPlataforma` (os dois, pela
  linha de comando, pedindo a outra plataforma: saída 2) e
  `testOEnsaioDoCorteGravaPelaPortaENaoDeixaNada` (o ensaio contra o banco desta execução: uma linha
  `estado` com o autor dentro da transação, e o cadastro e as vigências iguais depois). O conferidor
  sem a guarda da plataforma e o ensaio que commita em vez de desfazer deixam cada um o seu vermelho.
  As fases novas do `ensaiar-corte.sh`, tiradas do próprio arquivo e rodadas sozinhas contra o
  `franquiabv_dev` (o ensaio inteiro apaga o esquema e roda a carga): a semente do PostgreSQL disse 0;
  a `vigencia` com o `local.php` no MySQL saiu com 2; as duas linhas novas da volta atrás, a semente
  do MySQL e o conferidor com `--plataforma=mysql`, 0 (2.432 de 2.432).
- **Os números:** a fatia (`UsuarioHorarioVigenciaTest`, 15 casos, eram 11; `RetratoDoHorarioTest`;
  os três atualizados, o D315 com o caso novo do D127) deu **62 testes e 458 asserções** no MySQL e no
  PostgreSQL; os vizinhos (os privilégios, a API de usuários, o um-dispositivo-por-vez, os arquivos,
  a sessão, a regra do usuário), **104 e 631** nos dois; `conferir-tudo.sh` saiu com 0 (49 ok e o
  D50); o contrato, 67 de 67 no MySQL (`rodar-contrato.sh --montar`) e no PostgreSQL.

**O que a F4.1 nao provou:**

- **O navegador.** Salvar o horário pela tela antiga contra o banco de contrato pedia entrar no ERP
  com a senha de fixture, e isso fica com o dono. O caminho da tela foi exercitado em processo: o
  controller de verdade, o formulário com o CSRF, a sessão de personificação lida pelo construtor e
  o redirecionamento (`testNaPersonificacaoOAutorEQuemEntrou`).
- **Produção.** O passo 0 e a semente no corte (A3) e a F4.1 antes do corte (A4) não foram
  autorizados: produção está congelada.

#### F4.2: a régua sem banco

A régua inteira da agenda, pura -- sem banco, sessão nem relógio --, com as duas respostas do dono
que mudaram o desenho (B4 e B6). A F4.3 a liga às consultas e à tela.

- **A grade de cada dia sai da vigência em vigor no fim dele** (a de maior `(vigente_desde, id)`
  que começou até aquele dia), por um usuário montado na memória com o retrato
  (`Usuario::daVigencia`) e as **mesmas funções** da agenda e da rotina do Dash: `getFerias`,
  `getAgendaAberta` e `Util\Agenda::getTempoAtendimento`. Sem grade quando o `status` não é 1, o
  visível não é `ext_int` nem `int`, ou o tempo é nulo ou não positivo -- sem esta guarda, o tempo
  zero dá horários sem sentido (`09:00`, `21:00`, `21:00`... até 71: o mutante mostrou). A grade de
  cada vigência é calculada uma vez por dia da semana, porque as funções da agenda só leem a data
  por ele; as férias, que leem a data, são perguntadas dia a dia.
- **O período coberto** é o que não toca o vão entre o dia do cadastro e o dia da primeira vigência:
  antes do cadastro, grade zero; a partir da primeira vigência, a dela. A vigência nunca é apagada,
  então essas duas datas bastam (`AgendaDoParceiro::coberto`).
- **A conta de cada colaborador** segue a seção 1 do desenho: os agendamentos pela família (a raiz,
  com a origem dela; o `''` como "sem origem"; os sentinelas como "sem cadastro"; os lançados depois
  do horário); ocupado é o horário da grade com agendamento, e vence o bloqueio; bloqueado, o da grade
  só com bloqueio, qualquer que seja o motivo; o bloqueio fora da grade não tira nada; o agendamento
  fora da grade, em dia fechado ou em férias fica à parte (`foraDaGrade`); a taxa é
  `ocupados / (ocupados + livres)`, nula sem base; os motivos são os do menor fechamento de cada
  horário, em minúsculas, e o total soma os mapas inteiros antes de cortar os cinco.
- **A régua antiga (B6).** No período que a vigência não cobre, a capacidade é a
  `agenda_horarios_disponiveis` da linha do colaborador em `dashboard_colaboradores` daquele mês,
  **menos os bloqueados** do mês. Os ocupados e os bloqueados vêm das mesmas linhas da agenda, como
  na régua nova -- só que sem grade para conferir: todo horário só com bloqueio é bloqueado, e todo
  horário com agendamento ocupa, até caber no retrato. A conta sai com `regua: antiga` e a situação
  `reguaAntiga`, sem férias nem fora da grade (a régua antiga não os conhece). Três escolhas desta
  fatia, que o dono não respondeu e ficam escritas:
  - **só o mês inteiro**: a linha do Dash é a capacidade do mês todo, e no mês em andamento a lista
    vai até amanhã -- os agendamentos de até hoje sobre a capacidade até o fim do mês. Fora do mês
    inteiro, quem não tem a vigência fica `semCobertura`; a linha com a capacidade nula também;
  - **os ocupados vão só até caber no retrato**: a base é a capacidade menos os bloqueados (a conta
    do dono), e `ocupados + livres` é sempre ela, como na régua nova. A capacidade do retrato é a
    grade do fim do mês, e o agendamento que caiu fora dela não se separa: o que não cabe vai em
    `alemDoRetrato`, à vista e **fora da taxa**, como o `foraDaGrade` da régua nova. Sem base
    (capacidade zero, ou tudo bloqueado) a taxa é nula, como a de `semHorario`. **Os bloqueados vão
    só até a capacidade** (a verificação da F4.3, abaixo): o resto vai em `bloqueadosAlemDoRetrato`,
    à vista e fora da conta, e `horarios = ocupados + bloqueados + livres` nas duas réguas. Os motivos
    são os de todos os bloqueios do mês -- não há grade que diga quais ficaram de fora;
  - **os ocupados são recontados das linhas** (horários distintos), e não o
    `total_agendamentos_geral` gravado: do Dash vem só o que não se recalcula, a capacidade.
- **O total é de todos com agenda (B4)** (`AgendaDoParceiro::totais`): a lista da F1 mais quem, no
  recorte, teve agenda no período sem fazer serviço -- grade ou férias na régua nova, capacidade
  maior que zero no retrato do Dash, ou **algum agendamento** (com régua ou sem). O bloqueio sozinho
  não é agenda: a F4.2 contava "alguma linha na agenda", e a verificação da F4.3 (abaixo) achou na
  cópia inativos com centenas de horários fechados entrando como "com agenda, 0 horários". Fica de
  fora quem não tem nada disso: o inativo, o de visível `nenhuma` e o retrato do Dash com zero, que
  se sabe sem agenda; quem, sem cobertura, nunca teve tempo de atendimento no horário registrado (a
  recepção, a gerência sem agenda), que também se sabe; e o sem cobertura e sem agendamento, de quem
  não se sabe -- nada diz que tinha agenda, e o Dash também não o contava. **Este último não some
  calado**: o total e o fora da lista dizem quantos (`semCoberturaSemAgendamento`; na F4.2,
  `semCoberturaSemLinha`). A conta diz qual é o caso (`comAgenda`: verdadeiro, falso, ou nulo quando
  não se sabe). Os de fora da lista saem também à parte (`foraDaLista`), com a mesma forma. Quem está
  na lista sem conta é um erro de quem chama -- o de outra unidade entra com `deOutraUnidade`, só
  contado.
- **A soma é por régua** (`AgendaDoParceiro::somar`): `ocupacao.nova` e `ocupacao.antiga`, cada uma
  com a sua taxa, e os motivos também; nunca uma taxa das duas. Diz quantos em cada situação
  (`medida`, `semHorario`, `reguaAntiga`, `semCobertura`, `outraUnidade`); os agendamentos somam
  todos. **Aqui a fatia se afastou da T8 do juiz**, que anulava a ocupação do total quando faltava
  cobertura a alguém da lista, e o fora da lista quando faltava a alguém do recorte: com a régua
  antiga, quem fica sem cobertura é a exceção (fora do grupo 1, ou o mês em andamento), e anular o
  total por ela esconderia as duas réguas medidas. A anulação vinha junto com a recomendação do juiz
  para a B6 ("o total da unidade sai nulo se faltar alguém", seção 6b do desenho), que o dono trocou
  pelo retrato do Dash. Na cópia, em julho de 2026, 116 usuários não têm a vigência do mês inteiro;
  92 têm a linha do Dash, e 24 não -- um destes com 258 agendamentos. Pela T8, o fora da lista da
  rede sairia nulo, e o total também, se ele fez serviço no mês. A soma de cada régua sai com os
  colaboradores dela, e os sem cobertura vêm contados ao lado -- a tela diz "N na régua nova, M na
  antiga, K sem o horário da época". **É uma pergunta para o dono**: se ele preferir a T8, a mudança
  é só a tela esconder as somas do total quando `semCobertura > 0` ou `semCoberturaSemAgendamento >
  0`, e as do fora da lista quando `semCobertura` dele ou `semCoberturaSemAgendamento` passar de zero.

**Onde está a F4.2:**

| Parte | Arquivo |
|---|---|
| O usuário de uma vigência | `Usuario::daVigencia` (`module/ISConfiguracao/src/Entity/Usuario.php`): um usuário novo, nunca persistido -- aqui porque o portão dos escritores só aceita os setters do retrato neste arquivo e no `UsuarioService`, e novo porque recusa um método da entidade que mude o próprio `$this` |
| A régua, sem banco | `module/ISRelatorio/src/Regra/AgendaDoParceiro.php` (`grades`, `coberto`, `reguaAntiga`, `doPeriodo`, `deOutraUnidade`, `totais`, `somar`, `porMotivo`) |
| Os testes | `RegrasDaAgendaDoParceiroTest` (30 casos, sem banco), `RetratoDoHorarioTest` (o caso do `daVigencia`, 5 no arquivo) |

**O que a F4.3 recebe** (as formas que `AgendaDoParceiro` lê):

- `grades($linhas, $de, $ate)`: as linhas do `LEFT JOIN` de `usuarios` com `usuarios_horarios`, com
  `usuario`, `cadastro` (o `data_criacao` do usuário), `vigencia` (o id da vigência, nulo sem par),
  `vigente_desde` (no fuso local, como `ContasDoPainelDaUnidade::dia`) e os 33 campos com os nomes
  das colunas. Para o histórico, as grades do bloco inteiro; `doPeriodo` recusa período fora delas.
- `doPeriodo($grades, $agenda, $reguaAntiga, $de, $ate)`: as linhas da agenda com `colaborador`,
  `id`, `idPai`, `fechamento`, `cliente`, `hora`, `data`, `origem`, `dataCriacao` e `motivo`; as de
  fora do período são ignoradas, então o bloco do histórico é lido uma vez. Para decidir quem está
  "fora da lista", as linhas de **todos** os usuários do recorte (o sem cobertura só entra se tiver
  agendamento).
- `reguaAntiga($linhas, $de, $ate)`: as linhas de `dashboard_colaboradores` com `colaborador`, `ano`,
  `mes` e `horarios` (o apelido de `agenda_horarios_disponiveis`).
- A conta de cada colaborador traz `situacao`, `cobertoDesde`, `agendamentos`, `ocupacao` (com
  `regua`, `horarios`, `ocupados`, `bloqueados`, `livres`, `taxa`, `ferias`, `foraDaGrade`,
  `alemDoRetrato` e -- desde a verificação da F4.3 -- `bloqueadosAlemDoRetrato`), `motivos` (o mapa
  inteiro: a resposta usa `porMotivo`) e `comAgenda`. Nas duas réguas, `ocupados + livres` é a base da
  taxa e `horarios = ocupados + bloqueados + livres`; na antiga, `ocupados + alemDoRetrato` são os
  horários com agendamento, e `bloqueados + bloqueadosAlemDoRetrato` os só com bloqueio.
- `totais($contas, $lista)` devolve `total` e `foraDaLista` com a forma de `somar` mais
  `semCoberturaSemAgendamento` (na F4.2, `semCoberturaSemLinha`).

**Medido** (sem banco): a régua no tamanho da rede, com entrada sintética -- 2.432 usuários, 180 com
agenda e duas vigências cada, julho inteiro, 40 mil linhas de agenda (a rede de julho tem 40.465) --
levou de 0,52 a 0,70 s nas grades e 0,12 a 0,13 s no `doPeriodo`, com 98 MB de pico no PHP, entrada
inclusive (três rodadas). E o horário do colaborador 200 da cópia (de segunda a sexta das 08:40 às
21:00, sábado das 08:00 às 18:00, de 20 em 20 minutos) dá **894 horários em julho**, o número que o
Dash gravou -- e o mesmo de `CronJobService::totalHorariosDisponiveisAgenda`, chamada por reflexão
sobre um usuário hidratado como a tela da agenda o hidrata.

**Provado:**

- **Os testes da fatia e os vizinhos nos dois bancos**, em série (`rodar-caracterizacao-como-ci.sh` e
  `rodar-caracterizacao-postgres.sh`, com as regras da agenda, o retrato, as regras e a API do
  Profissional Parceiro, a vigência, os dados, a ativação, a regra e a API do usuário, o D315, a grade,
  o dia e o fechar da agenda, e o Dash no relatório): **247 testes e 2.111 asserções** no MySQL e no
  PostgreSQL, 2 pulados (os do Dash que pedem a cópia).
- **27 mutantes, todos mortos**, cada um aplicado sozinho e devolvido com o sha1 conferido -- os doze
  do desenho (a vigência do início do dia; o empate sem o id; as férias ignoradas; o status e o
  visível ignorados; o tempo zero sem guarda; o bloqueio que vence o agendamento; o bloqueio fora da
  grade contado; o fora da grade como ocupado; a contagem por linha; a origem da filha; o dia antes
  do cadastro sem cobertura; o mês parcial com taxa), os três das respostas do dono (o total só da
  lista; a régua antiga sem tirar os bloqueados; a régua antiga somada com a nova), e mais doze: o
  corte dos cinco motivos antes da soma, a régua antiga no mês em andamento, o sem cobertura com linha
  fora do total, a grade que não cobre o período aceita calada, o motivo do maior fechamento, o empate
  dos motivos sem a ordem alfabética, o `daVigencia` sem o fim das férias e com as férias no formato do
  banco, o lançado depois no mesmo segundo, o sentinela 1 fora do "sem cadastro", e quem está na lista
  sem conta. Todos caíram em falha de asserção, nenhum em erro.
- O portão dos escritores do horário saiu com 0 com o `daVigencia` no `Usuario.php`.

**A verificação de 02/10/2026** (seis achados, dois deles o mesmo; todos conferidos no código):

- **A capacidade zero do Dash dava 100%.** A régua antiga tirava os livres de zero, e o que passava
  do retrato ficava nos ocupados -- no numerador e no denominador. Com a capacidade 0 e 20
  agendamentos, a taxa saía 100,0% (o Dash mostrava 0%; a régua nova, no caso análogo, dá
  `semHorario` com a taxa nula), e na soma puxava a taxa antiga do total: 20,0% sozinho, 33,3% com
  ele. Na cópia, 212 linhas de `dashboard_colaboradores` têm capacidade 0 e agendamentos (11.792, de
  2019 a 2026), e 47 têm mais agendamentos que capacidade. **Agora os ocupados vão só até a base** (a
  capacidade menos os bloqueados), o resto vai em `alemDoRetrato`, fora da taxa, e sem base a taxa é
  nula. `testAReguaAntigaComCapacidadeZeroNaoTemTaxaENaoInflaOTotal` e
  `testAReguaAntigaOcupaSoOQueCabeNoRetrato` (o antigo "não passa de 100", agora com 7 ocupados e 2
  além do retrato) saíram vermelhos antes e verdes depois.
- **O sem cobertura e sem linha sumia calado** do total e do fora da lista. Agora é contado
  (`semCoberturaSemLinha`, hoje `semCoberturaSemAgendamento`); o caso B4 ganhou a conta e saiu
  vermelho antes. A anulação da T8 continua como pergunta ao dono (acima), agora com o que a tela
  precisa para fazê-la.
- **Sem caso para o histórico**: tirar o filtro dos dias do período na conta (o bloco lido uma vez
  e passado mês a mês) deixava os 27 verdes -- julho com as grades de julho e agosto dava 264
  horários, e não 138. `testCadaMesDoBlocoEOMesSozinho` compara cada mês do bloco de junho a agosto
  com o mês sozinho, e a cobertura mês a mês (a vigência de 20/07: junho e julho na régua antiga,
  agosto medido).
- **Sem caso para a taxa do total (B4)**: a taxa sem recálculo, a média das taxas (como o Radar), e
  os ocupados, os livres, as férias ou o além do retrato fora da soma passavam.
  `testATaxaDoTotalERefeitaDosContadoresSomados` soma dois de cada régua, com bases diferentes:
  13,0% (21 de 161), e não a média 16,7%; na antiga, 27,3% (27 de 99), e não 60,9%.
- **O dia antes do cadastro com vigência** (`$dia < $cadastro` em `grades`) e **a guarda do período
  pelo início** (`$de < $grade['de']` em `doPeriodo`) passavam sem caso: o caso do cadastro ganhou
  uma vigência anterior a ele, e o da guarda, as duas pontas.

Os números, depois: `RegrasDaAgendaDoParceiroTest` com **30 casos e 362 asserções** (sem banco); a
fatia e os vizinhos com **250 testes e 2.146 asserções** no MySQL e no PostgreSQL, 2 pulados (os
mesmos). **43 mutantes, todos mortos** em falha de asserção, cada um sozinho e devolvido com o sha1
conferido: os 27 da fatia (o M14 reapontado para o `return` novo de `totais`), os nove da
verificação (o filtro dos dias do mês; a taxa sem recálculo; a média das taxas; os ocupados, os
livres, e as férias com o além do retrato fora da soma; a grade antes do cadastro; a guarda só pelo
fim; a capacidade zero com linhas fora do total) e sete do conserto (os ocupados sem o teto do
retrato; a base negativa; a ocupação de antes do conserto inteira; o sem linha sem conta, contando
quem se sabe sem agenda, ou só no total; a base sem tirar os bloqueados).

**O que a F4.2 não provou:** nada contra o banco -- as consultas, a resposta e a tela são da F4.3, e
o "igual à rotina" ali passa a ser nas linhas de verdade, nos dois bancos. Os números da régua antiga
contra o Dash da cópia (julho, unidade 1 e rede) também ficam para a medida da F4.3.

#### F4.3: a API do mês e a tela da lista

**`GET /adm/api/profissionais/agenda?mes=&unidade=`** é o quarto pedido da lista: as guardas dela
(`recorteDoPedido`, chamada pela ação), a sessão solta depois delas, e a tela o faz quando os números
chegam, com o mês que eles aplicaram. As contas são as de `AgendaDoParceiro` (a F4.2); aqui só se lê e
se formata.

- **O recorte da agenda é o cadastro**: `agendas` não tem unidade, e a agenda de alguém é a da unidade
  do cadastro dele. Por isso a ação lê as vigências (`UsuarioRepository::parceiroHorarios`, por DBAL;
  **todas**, inclusive as de depois do mês, que dizem o `cobertoDesde` do mês de antes delas)
  de **todos os usuários do cadastro da unidade** -- na rede, de todos --, e as linhas da agenda deles no
  mês (`AgendaRepository::parceiroAgenda`: a faixa de data do D189, em lotes de 100 colaboradores, em
  fluxo). É o que a decisão B4 pede: o total soma quem teve agenda sem fazer serviço.
- **O colaborador da lista com o cadastro em outra unidade** (para o gerente, e para o administrador que
  filtrou uma) sai `outraUnidade`, só contado, e a agenda dele **nem é lida** -- é a de outra franquia.
  Na rede, ninguém é de outra unidade.
- **A régua antiga** (`DashboardColaboradorRepository::parceiroReguaAntiga`) é lida só para quem não
  está coberto pela vigência no mês, e só a linha do mês.
- **No escopo `proprio`, só ele**: a lista cortada a ele, a vigência e as linhas dele -- de qualquer
  unidade, porque a agenda é a dele --, e `total` e `foraDaLista` nulos. Sem serviço no mês, ele não
  está na lista, e nada é lido.

**A resposta** (`ProfissionalParceiroJson::agenda`): `recorte`; `colaboradores`, em ordem de id, cada
um com `situacao` (`medida`, `semHorario`, `reguaAntiga`, `semCobertura`, `outraUnidade`),
`cobertoDesde`, `agendamentos` (`total`, `app`, `recepcao`, `semOrigem`, `semCadastro`,
`lancadosDepois`), `ocupacao` (`regua`, `horarios`, `ocupados`, `bloqueados`, `livres`, `taxa`, `ferias`,
`foraDaGrade`, `alemDoRetrato`, `bloqueadosAlemDoRetrato`) e `bloqueadosPorMotivo` (os cinco maiores e os
demais somados); e `total` e `foraDaLista` com a mesma forma: quantos em cada situação,
`semCoberturaSemAgendamento`, `outraUnidade`, os agendamentos, e a ocupação e os motivos **por régua** (`nova`, `antiga`) -- a forma
que a F4.2 deixou, e não a do desenho, que tinha uma taxa só.

**A tela** (`ProfissionalParceiro.tsx`, `ProfissionalParceiroLigado.tsx`, `textosDoParceiro.ts`):

- **no cartão**, três linhas depois da fidelização: "Agendamentos" (`822 · 721 app · 101 recepção`),
  "Ocupação da agenda" (`92,9% · 822 de 885`; na régua antiga com "· régua antiga"; sem taxa, o porquê:
  "sem o horário da época", "sem horário semanal", "agenda de outra unidade", "só férias no mês", "todos
  os horários bloqueados") e "Horários bloqueados". Enquanto a agenda não chega, "…"; se ela falha, "—"
  -- e o travessão é **só** da falha: de outra unidade, as três linhas dizem "de outra unidade" (a da
  ocupação, "agenda de outra unidade"), e sem o horário da época os bloqueados dizem isso;
- **embaixo do cartão**, uma frase (`parceiro-agenda`) com o que não cabe na linha e não é zero: os
  bloqueados por motivo, as férias, o agendado fora do horário semanal ou além do retrato do Dash, o
  bloqueado além do retrato, os sem cadastro, os lançados depois do horário e desde quando o horário
  semanal está registrado;
- **na unidade** (`AAgendaDaLista`), embaixo dos clientes: os agendamentos do mês; a ocupação de cada
  régua com quantos colaboradores ela mede (na nova, os medidos; a régua nova só se escreve com alguém
  medido nela), os bloqueados e os **motivos deles** ao lado (a B5), e na antiga o bloqueio além do que o
  Dash gravou; quem ficou sem o horário da época; "Com agenda e nenhum serviço nesta unidade no mês: N
  colaboradores, H horários e A agendamentos — na conta acima, e fora da lista" (na rede, "nenhum
  serviço no mês"; o "nenhum", e não "sem serviço": a nota de quem teve item sem serviço já diz "sem
  serviço no mês" de outra gente; os agendamentos só quando houver); e quem tem a agenda em outra
  unidade. Falhou: "A agenda não veio." e "tentar a agenda de novo", que pede só ela e leva o foco ao
  título da lista;
- **o pedido**: sai quando os números chegam, com o mês deles; trocar de mês ou de unidade a zera e
  espera os números novos; a janela da fidelização não a pede de novo; e a resposta de outro mês ou de
  outra unidade não entra nos cartões;
- **"Como os números são contados"** ganhou os agendamentos, a ocupação pelo horário da época, a régua
  antiga e a soma de todos com agenda. É a mesma nota do histórico, que só ganha as colunas da agenda na
  F4.4.

**A pergunta da T8 continua com o dono** (a F4.2): o total não some quando falta cobertura a alguém; a
tela diz quantos estão sem o horário da época. Se ele preferir a T8, é só a tela esconder as somas. A
verificação de 02/10/2026 (abaixo) não a seguiu: com a B4, o total é o da lista mais o fora da lista, e a
T8 anula o fora da lista quando falta cobertura a qualquer um do recorte -- o total da B4 sumiria em todo
mês de antes do corte (na unidade 1, em julho de 2026, por 1 só). O que ela consertou é o número que a
pergunta usa: o "não se sabe" deixou de contar quem nunca teve tempo de atendimento (a rede em janeiro de
2025 caiu de 116 para 18; julho de 2026, de 21 para 5).

**Onde está a F4.3:**

| Parte | Arquivo |
|---|---|
| A rota | `adm-api-profissionais/agenda/ler` (`module/ISRelatorio/config/module.config.php`) |
| A ação | `ProfissionalParceiroController::agendaAction` |
| As consultas | `UsuarioRepository::parceiroHorarios` (DBAL), `AgendaRepository::parceiroAgenda`, `DashboardColaboradorRepository::parceiroReguaAntiga` |
| A resposta | `ProfissionalParceiroJson::agenda` |
| A tela | `frontend/src/api/profissionais.ts` (os tipos e `lerAgendaDoParceiro`), `ProfissionalParceiroLigado.tsx` (o quarto pedido), `ProfissionalParceiro.tsx` (as linhas do cartão, a frase, `AAgendaDaLista`, a nota), `textosDoParceiro.ts` (os textos que o histórico vai dividir) |
| Os testes | `ProfissionalParceiroTest` (a faixa 90004300-90004399, nos dois bancos); `ProfissionalParceiroLigado.teste.tsx`; `textosDoParceiro.teste.ts` (a taxa sem base, da verificação); os dublês de `Console.teste.tsx`, `ConsoleLigado.teste.tsx` e `HistoricoDoParceiroLigado.teste.tsx` |
| Os portões | `conferir-recorte-por-unidade.php` (a ação e o filtro das vigências), `conferir-cobertura-por-par.php` (os três pares), `conferir-rotas-sem-login.php` (a rota), `ensaiar-permissao-adm-api.php` e `medir-adm-api-no-postgres.php` (a rota nos dois) |

**Medido nas duas cópias locais** (`franquiabv_dev`, julho de 2026, pela ação despachada em processo;
MySQL com 128 MB e o índice do D189, PostgreSQL de desenvolvimento), três rodadas:

| | MySQL | PostgreSQL |
|---|---|---|
| Unidade 1 | 1,9 a 2,5 s; 30 MB de pico | 1,6 a 3,8 s; 28 a 30 MB |
| Rede | 3,0 a 5,1 s; 116 MB | 3,6 a 5,8 s; 116 MB |

No PostgreSQL, a consulta final das vigências (pelo construtor do DBAL, abaixo em "Provado") foi medida
de novo, também três rodadas: a unidade 1 em 4,5, 2,3 e 1,8 s, e a rede em 16,7 s (a primeira depois
de trocar o banco da aplicação, com o cache frio), 5,7 e 4,5 s -- e os mesmos números de julho. No
MySQL, uma rodada com ela: 1,9 s e 3,6 s, e a resposta igual byte a byte à de antes.

- **Parte por parte** (MySQL), a maior é a lista -- os itens do mês, que a F1 já media. A agenda da
  unidade 1 (127 usuários do cadastro, 4.217 linhas da agenda) leva de 0,7 a 2,1 s; a da rede (2.432
  usuários, 40.465 linhas, 116 descobertos, 92 deles com a linha do Dash), de 1,9 a 2,1 s. O host
  estava carregado nessa rodada: só os itens da unidade 1 levaram 6,6 e 7,6 s, e a ação inteira, na
  rodada da tabela, de 2,1 a 2,5 s.
- **Os 116 MB da rede** são a lista (88 MB na F1) mais as grades. Cabem nos 512 MB do Docker local; os
  limites de produção nunca foram lidos (o D311).
- **Os números de julho, iguais nos dois bancos.** Unidade 1: 14 na lista (7 na régua nova, 7 na
  antiga), 3.264 agendamentos (2.407 app, 857 recepção; 268 sem cadastro, 102 lançados depois); régua
  nova 58,5% (2.178 de 3.726, com 559 bloqueados fora da conta), antiga 43,8% (1.119 de 2.554, 350
  bloqueados); ninguém com agenda fora da lista, e 1 sem o horário da época e sem agendamento (eram 2:
  o outro nunca teve tempo de atendimento). Rede: 143 na lista e 189 com agenda (46 fora da lista;
  eram 195 e 52 -- os 6 a menos só tinham bloqueios); 25.972 agendamentos; régua nova 25,1% (10.657 de
  42.518), antiga 42,3% (16.326 de 38.566, sem os 6.782 bloqueados e mais 87 além do que o Dash gravou:
  45.348 horários); 5 sem o horário da época e sem agendamento (eram 21). Números da verificação de
  02/10/2026 (abaixo). **A rede da cópia tem uma fixture dentro** (o D167): o colaborador 90001000,
  semeado na F4.1 com o horário de fixture (todo dia, das 08:00 às 20:00), entra no fora da lista com
  1.116 horários e nenhum agendamento. Sem ele, 188 com agenda, 45 fora da lista e a régua nova em
  25,7% (10.657 de 41.402). A unidade 1 não a tem.
- **Contra o Dash da cópia** (o que a F4.2 deixou para cá): o colaborador 200 tem os 894 horários e os
  822 agendamentos (721 app, 101 recepção) que o Dash gravou, e 92,9% sem os 9 bloqueados (o Dash:
  91,95%). O 782, na régua antiga: os 452 horários do Dash, 173 ocupados e 19 bloqueados, 40,0% (o Dash:
  38,27%). Os 7 da régua antiga da unidade 1 somam no Dash 1.119 agendamentos em 2.904 horários
  (38,5%); a régua antiga dá os mesmos 1.119, em 2.554 -- a diferença é só a decisão B6, os 350
  bloqueados fora da base.
- **A régua nova da rede (25,1%) fica abaixo dos 32,3% da decisão B4**, e não é defeito: aquele número
  era de todos com o horário de hoje, numa régua só. Aqui, dos 46 de fora da lista, 39 são medidos na
  régua nova -- 19.792 horários (1.116 deles da fixture) e 52 agendamentos, a "capacidade de quem não
  trabalhou" do levantamento --, e quem estava na régua antiga ficou nela.

**O oráculo em Python** (`docs/f3-relatorios/oraculo-f43/oraculo.py`, com o `medir-f43-agenda.php` que grava a resposta ao lado; o primeiro era o `medir-f43-oraculo.py`, que ficou no rascunho da sessão):
lê por SELECT as linhas cruas do `franquiabv_dev` -- do MySQL ou do PostgreSQL -- (os usuários, todas as
vigências, quem fez serviço, a agenda com o motivo, o Dash) e escreve a régua de novo, sem o código da
aplicação (a grade, as férias com as pontas vazias, o dia antes do cadastro, a cobertura, a família, o
ocupado que vence o bloqueado, a régua antiga com os dois tetos, quem tem agenda, o total de todos com
agenda, o corte dos cinco motivos). **Confere a resposta inteira**: de cada colaborador, a situação, o
`cobertoDesde`, os agendamentos, a ocupação com todos os campos e os motivos cortados; do total e do fora
da lista, os colaboradores, cada situação, o "não se sabe", os de outra unidade, os agendamentos, a
ocupação e os motivos de cada régua. O período é o da resposta, e a régua antiga só vale no mês inteiro.
**0 divergências em seis recortes, nos dois bancos** -- a unidade 1 e a rede em julho de 2026, a unidade
39 em junho de 2026 (com um sem cobertura), a unidade 1 e a rede em janeiro de 2025 (quase todos na régua
antiga, 18 "não se sabe" na rede) e a rede em agosto de 2026 --, e as respostas dos dois bancos saíram
iguais byte a byte. Falsificado duas vezes: contra a resposta de antes do conserto, 143 divergências; e
com quatro erros plantados numa resposta nova (um motivo, um `cobertoDesde`, uma situação do total e as
férias do fora da lista), os quatro acusados. A primeira versão (da F4.3) conferia só a situação, os
agendamentos e a ocupação de cada um, quatro contas do total, e só no MySQL -- a verificação de
02/10/2026 a pegou por isso. Lá, a primeira rodada acusou uma divergência, e era o oráculo: 140 de 448 é
31,25%, e o `:.1f` do Python arredonda o meio para o par (31,2), e o `number_format` do PHP para cima
(31,3).

**Provado:**

- **Os casos novos de `ProfissionalParceiroTest`, nos dois bancos**: a agenda de março de 2025 contada à
  mão, com tudo ao mesmo tempo (a vigência que muda no dia 15 de 30 para 20 minutos, a "Folga" de 2
  horários, a família de 2 horários pelo app, a raiz `web`, o cliente 2, a raiz lançada depois, 2 dias de
  férias, o fora da grade, o bloqueio sob o agendamento, o agendamento duplicado, o colaborador semeado
  no meio do mês na régua antiga do Dash, o de grade zero, o da unidade sem serviço, o sem nada e o de
  outra unidade): A com 141 horários, 8 ocupados, 5 bloqueados e 5,9%; B na régua antiga com 40, 5 e 2,
  13,2%; o total de todos com agenda, 4,5% na régua nova e 13,2% na antiga, com o fora da lista à parte.
  E mais: a rede e o filtro do administrador; o `proprio` só com a linha dele, sem total, e só as
  vigências e as linhas dele lidas (a pilha do SQL), e nada lido sem serviço no mês; as recusas da lista;
  a grade da vigência igual ao cadastro (a semente de verdade) e a da rotina do Dash
  (`CronJobService::totalHorariosDisponiveisAgenda`, por reflexão): **752 horários e 102 de férias** em
  março de 2025, contados à mão; e as três consultas contra um oráculo em SQL, com os lotes. As guardas
  (401; 403 sem o privilégio, e com todos os privilégios menos ele; 403 com o motivo `marca`; a sessão
  solta depois delas, e antes de qualquer consulta que não seja a das unidades aceitas) pelo mesmo
  provedor das outras ações. `ProfissionalParceiroTest` inteiro: **109 testes e 1.195
  asserções** no MySQL e no PostgreSQL.
- **A fatia e os vizinhos, em série** (as regras e a API do Profissional Parceiro, a vigência, o retrato,
  os dados, a ativação, a regra e a API do usuário, o D315, a agenda -- API, grade, dia, fechar e os dois
  recortes por unidade --, o Dash no relatório e o painel): **366 testes**, 2.658 asserções no MySQL e
  2.657 no PostgreSQL (uma asserção de um vizinho depende do banco), 3 pulados -- os do
  `DashboardDeColaboradoresNoRelatorioTest`, que pedem a cópia.
- **A tela**: `ProfissionalParceiroLigado.teste.tsx` com 54 casos (8 novos da agenda, e a lista do cartão
  e a nota com as linhas novas); os dublês do `Console`, do `ConsoleLigado` e do histórico ganharam o
  quarto pedido. O front inteiro: **1.522 testes em 41 arquivos**, com quatro processos. A primeira
  rodada inteira deu 1 vermelho em `AgendaLigada.teste.tsx`, arquivo que a fatia não tocou; sozinho, ele
  passou duas vezes (157 de 157), e a segunda rodada inteira passou -- o Windows de sempre. O build saiu
  com 0; o lint com 0 e 6 avisos, os 2 de sempre e 4 em `.design-sync/` (do cd857ba), nenhum dos
  arquivos da fatia. Depois do respiro entre as frases da unidade (abaixo, no navegador), os quatro
  arquivos que montam a tela (187 casos), o build e o lint de novo, com o mesmo resultado.
- **23 mutantes, todos mortos**, cada um aplicado sozinho e devolvido com o sha1 conferido. No servidor,
  14 (no MySQL): a resposta sem o filtro da lista; sem `outraUnidade` (o total recusa a lista sem conta, e
  a ação cai); o `proprio` vendo o total; os motivos do total sem o corte, e os do colaborador; as
  vigências do `proprio` sem o `$so`, e a consulta sem o filtro do id; a consulta sem o filtro da unidade;
  a agenda só com o primeiro lote; o Dash sem o filtro dos colaboradores; a régua antiga lida para todos;
  os colaboradores fora da ordem do id; o `proprio` sem serviço lendo; e a régua antiga de ninguém. Na
  tela, 9: trocar de mês sem zerar a agenda; o "tentar de novo" que não pede; o "…" como falha; a janela
  pedindo a agenda; a agenda de outra unidade entrando; a régua antiga sem a marca; a unidade sem o fora
  da lista; o pedido com o mês do pedido, e não o dos números; e a base da taxa com os bloqueados. Os
  pedidos do desenho estão todos aí: o filtro da lista, `outraUnidade`, o `proprio` vendo o total, o corte
  dos motivos (o da soma é o M17 da F4.2), o reset ao trocar, o "tentar de novo" e o "…".
- **Os portões**: `conferir-recorte-por-unidade.php` ganhou seis entradas (a ação e o filtro das
  vigências), e foi falsificado -- cada guarda arrancada do código, sozinha, uma delas deixada só num
  comentário: os 8 vermelhos; o controle, a mesma chamada partida em duas linhas, verde.
  `conferir-cobertura-por-par.php` passou de 308 para 311 pares, com o sha1 conferido (sem os três
  pares novos, ele volta ao de 308), e a coincidência nova (o `popularCombobox` de Unidade e a entidade
  `Usuario` no mesmo teste) registrada. `conferir-rotas-sem-login.php`, a rota no bloco congelado.
  `conferir-debitos-repetidos.php`, os dois pares novos (D324 e D325, e o D325 com o D196) com o motivo.
  A primeira rodada do `conferir-tudo.sh` achou ainda dois vermelhos meus: a consulta das vigências
  montada com ponto (o D73, de 32 para 34) e o `ORDER BY` por um alias sem entidade, que o portão das
  ordenações por coluna anulável não resolve. A consulta passou ao construtor do DBAL, com a resposta
  igual byte a byte, e os dois voltaram a 0. E o construtor levou o `conferir-booleano-em-coluna-inteira.php`
  de 13 para 15 "não resolvidas" (ele não acha o `FROM` de consulta montada assim): as duas, conferidas
  à mão, são `u.id` e `u.unidade`, inteiras, ligadas a `?int` -- a linha de base subiu com o motivo
  escrito. Depois disso, `conferir-tudo.sh` saiu com **0**: 49 ok e o D50 de sempre.
- **O ensaio de permissão por HTTP, nos dois bancos de contrato** (`ensaiar-permissao-adm-api.php`,
  com a rota nova): sem o privilégio, 403 (60 de 60 recusas no MySQL); com o privilégio e sem a marca,
  403 com o motivo `marca`; com a marca, 200 -- e o corpo só com a linha do próprio, `total` e
  `foraDaLista` nulos e o escopo `proprio`. Saída 0 no MySQL e no PostgreSQL (172 s). O
  `medir-adm-api-no-postgres.php`, depois do ensaio do fluxo (a ordem do CI: sozinho, num banco recém-
  montado, ele acusa a lista de vendas vazia de propósito), saiu com 0: 124 rotas, nenhuma quebrou, 26
  no 2xx; a rota nova para na guarda, como as irmãs -- o gerente de fixture não tem o privilégio do
  relatório.
- **A tela no navegador, a 375 px, com dados sintéticos e sem sessão**: o componente de verdade servido
  pelo Vite e montado na página com uma resposta escrita à mão (sem login e sem banco). A página não
  rola de lado (375 de 375); nenhum valor do cartão passa da caixa dele; "Agendamentos" quebra no "·"
  como a fidelização; a régua antiga, o sem cobertura e o de outra unidade dizem o que são. **Achado e
  consertado ali**: as sete frases da unidade saíam coladas num bloco só; ganharam um respiro entre
  elas, no componente (o vitest entrega o CSS vazio).

**O que a F4.3 não provou:**

- **A tela com sessão, contra o banco de contrato semeado**: entrar no ERP com a senha de fixture no
  navegador não veio do dono nesta rodada (o mesmo limite da F4.1). O que a tela faz com os dados está
  nos testes dela; o que ela mostra a 375 px, na montagem sem sessão acima.
- **Produção** e o limite de memória dela: a rede pede 116 MB no PHP, e os limites de produção nunca
  foram lidos (o D311). O corte (A3) não foi autorizado.
- **A pergunta da T8** continua com o dono, e a tela segue a escolha da F4.2.

**A verificação de 02/10/2026** (nove achados, todos conferidos no código e na cópia, e todos
consertados -- menos a anulação da T8, que um deles pedia e que continua com o dono, com o porquê):

- **"Só férias no mês" sem férias nenhuma.** A soma da régua nova com só quem não tem horário semanal tem
  zero horários e zero férias, e a frase da unidade dizia "em 1 colaborador: só férias no mês" (a unidade
  1 em janeiro de 2025). Agora a régua nova só se escreve com alguém medido nela, o "em N
  colaboradores" é dos medidos (na rede de julho, eram 100 com 13 sem horário; hoje 87 -- dos 7 sem
  horário que ficaram, 4 estão na lista, no cartão de cada um, e 3 têm agendamento e entram na frase do
  fora da lista), e `textoDaTaxa` diz "sem horário semanal" quando não há férias.
- **O bloqueio sozinho contava como agenda.** Coberto e sem grade, ou com o Dash em zero, quem tinha só
  bloqueios entrava no total e no fora da lista com 0 horários ("1 colaborador, 0 horários"). Na cópia,
  em julho de 2026, 6 dos 9 sem horário de fora da lista só tinham bloqueios -- um com 555. Agora ter
  agenda é ter capacidade ou algum agendamento (`comAgenda`), e a frase do fora da lista diz os
  agendamentos de quem entrou sem horário. A rede de julho foi de 195 para 189 com agenda (52 para 46 fora
  da lista); as taxas não mudaram -- eles não tinham capacidade.
- **O `cobertoDesde` saía nulo no mês de antes do registro**, o único em que ele aparece: a consulta só
  lia as vigências de antes do fim do período. Agora lê todas (a de depois não muda a grade de dia
  nenhum); na cópia, os nulos da régua antiga e do sem cobertura foram de 39 de 88 (rede, julho) e 14 de
  15 (unidade 1, janeiro de 2025) para zero. E o valor deixou de depender do fim da leitura, que a F4.4
  precisava.
- **O "não se sabe se tinham agenda" era quase todo de quem nunca atendeu** (a recepção, a gerência sem
  agenda): 116 na rede em janeiro de 2025. Agora não conta quem nunca teve tempo de atendimento no
  horário registrado -- sem ele não há grade (T4) --, e o nome passou a `semCoberturaSemAgendamento`
  (o bloqueio sozinho, que antes tirava a pessoa da conta, já não é agenda). Em janeiro de 2025, 116
  viraram 18; em julho de 2026, 21 viraram 5. Os 100 que saíram em janeiro nunca tiveram um agendamento
  em dia nenhum. **A anulação da T8 não foi feita** (acima): continua a pergunta ao dono.
- **Os motivos da unidade vinham e não apareciam.** A frase de cada régua agora diz os bloqueados e os
  motivos deles, cortados nos cinco (a B5): "…, sem os 559 bloqueados:" e os cinco motivos com mais
  horários, como foram escritos, com os outros somados.
- **"X de B horários, sem os N bloqueados" não fechava na régua antiga** quando alguém tinha mais
  bloqueios que o Dash gravou (o 1701 em julho: 0 horários e 87 bloqueios), e a rede escrevia 38.566
  "sem os 6.869 bloqueados" de 45.348 gravados. Agora os bloqueados da régua antiga vão só até a
  capacidade, o resto vai em `bloqueadosAlemDoRetrato` (dito no cartão e na unidade), e `horarios =
  ocupados + bloqueados + livres` nas duas réguas: 38.566 + 6.782 = 45.348, e mais 87 além. A taxa não
  mudou (a base já tinha o teto).
- **O oráculo conferia pouco** (os motivos, o `cobertoDesde`, as situações do total e o fora da lista
  ficavam de fora, e só no MySQL). Refeito: confere a resposta inteira, nos dois bancos (acima).
- **O travessão dizia "falhou" e também "outra unidade" e "sem cobertura".** Agora é só da falha; as
  linhas sem número dizem por quê.
- **"Com agenda e nenhum serviço no mês"** de quem tem o cadastro aqui e atendeu só noutra unidade (na
  cópia não há, mas o próprio teste da rede monta o caso): agora "nenhum serviço nesta unidade no mês"
  fora da rede.

Os números, depois: `RegrasDaAgendaDoParceiroTest` com **31 casos e 370 asserções** (sem banco; o caso
novo é o do bloqueio sozinho e do "não se sabe"); `ProfissionalParceiroTest` com **110 testes e 1.203
asserções** nos dois bancos (o caso novo é o do `cobertoDesde` no mês de antes do registro, e o cenário
ganhou a recepção registrada depois do mês e o inativo só com um bloqueio); a fatia e os mesmos vizinhos
com **368 testes**, 2.673 asserções no MySQL e 2.672 no PostgreSQL, os 3 pulados de sempre. A tela: os
cinco arquivos que montam a lista e os textos, **195 casos** (5 novos em `ProfissionalParceiroLigado.teste.tsx`
e 3 no novo `textosDoParceiro.teste.ts`); o build com 0; o lint com 0 e os mesmos 6 avisos. **21 mutantes
novos, todos mortos** em falha de asserção, cada um sozinho e devolvido com o sha1 conferido -- 9 no
servidor (o bloqueio sozinho como agenda, no coberto, na régua antiga e no sem cobertura; o "não se sabe"
sem o tempo registrado; o tempo só das vigências de antes do fim; o sem vigência como "se sabe"; as
vigências cortadas no fim do período; os bloqueados sem o teto do retrato; a soma sem o bloqueado além) e
12 na tela (o "em N" com os sem horário; a régua nova sem ninguém medido; o "só férias" sem férias; a
unidade sem os motivos; o bloqueado além fora da unidade e fora do cartão; o travessão nos agendamentos e
nos bloqueados de outra unidade e nos bloqueados sem cobertura; o "no mês" e o "nesta unidade" trocados; o
fora da lista sem os agendamentos).

**O que a F4.4 recebe:**

- `UsuarioRepository::parceiroHorarios($unidade, $so)` lê um colaborador só pelo `$so` (o nome é do
  escopo `proprio`, mas a consulta é "só este id"), com **todas** as vigências dele -- sem o fim do
  período desde a verificação de 02/10/2026, e por isso o `cobertoDesde` de um mês é o mesmo lido pelo
  `/agenda` ou pelo bloco do histórico; `AgendaDoParceiro::grades` faz o bloco inteiro de uma vez.
- `AgendaRepository::parceiroAgenda([$id], $inicio, $fim)` lê o bloco numa faixa só, e `doPeriodo`
  ignora as linhas de fora de cada mês -- o bloco é lido uma vez.
- `DashboardColaboradorRepository::parceiroReguaAntiga` lê **um mês** (o de `$de`): o histórico precisa
  de cada mês do bloco sem vigência -- doze chamadas, ou uma consulta nova por faixa de `(ano, mes)`.
- `outraUnidade` vale para o bloco inteiro, como o desenho diz; os textos da linha da agenda
  (`textoDosAgendamentos`, `textoDaOcupacao`, `textoDosBloqueados`, `textoDaAgenda`) estão em
  `textosDoParceiro.ts` para o histórico usar, e a nota "Como os números são contados" já os explica.
- Os portões a acompanhar: o recorte (`historicoAgendaAction` passa pelas guardas do histórico), a
  cobertura por par (se entrar consulta nova), as rotas sem login, e os dois que a F4.3 tropeçou -- nada
  de SQL montado com ponto, e nada de `ORDER BY` por alias de tabela sem entidade em texto.
- A faixa de ids da F4.4 em `ProfissionalParceiroTest` é a 90004400-90004499.

#### F4.4: a agenda do histórico

**`GET /adm/api/profissionais/historico/agenda?colaborador=&mes=&unidade=`** é o terceiro pedido de cada
bloco do histórico, ao lado dos números e dos clientes da série: as guardas do histórico
(`recorteDoPedido` e `historicoDoPedido`, chamadas pela ação), a sessão solta depois delas, e a tela o faz
quando os números do bloco chegam, com o mês que eles aplicaram. As contas são as de `AgendaDoParceiro` (a
F4.2); aqui só se lê, mês a mês.

- **Cada mês é a agenda dele no `/agenda` daquele mês**, pelas mesmas funções, e não por uma conta do bloco:
  as grades são calculadas uma vez para o bloco inteiro (`grades` com as vigências dele); as linhas da agenda
  são lidas numa faixa só (`parceiroAgenda([$id], início do bloco, fim do bloco)`); e cada mês passa por
  `doPeriodo` com o início e o fim **dele** -- o mês em andamento até hoje, como a lista. A régua antiga (B6)
  é a linha do Dash daquele mês, lida só nos meses que a vigência não cobre (uma consulta por mês, com o id
  dele), como no `/agenda`. Por isso o mês do histórico é o `/agenda` daquele mês por construção -- e o teste
  e a cópia conferem mesmo assim (abaixo).
- **A agenda é a da unidade do cadastro**, como no mês: com o recorte numa unidade (o gerente, e o
  administrador que filtrou uma), quem é cadastrado em outra -- o gerente o vê porque atendeu aqui -- sai
  `outraUnidade` no bloco inteiro, e a agenda dele nem é lida (nem as vigências, nem as linhas, nem o Dash).
  No escopo `proprio`, a agenda é a dele, de onde for o cadastro: entrado noutra unidade, ele vê a mesma
  agenda que o `/agenda` dele mostra lá.
- **Só o que é dele é lido**: as vigências pelo `$so` da consulta (o id dele, e não a unidade -- a resposta
  seria a mesma, e o pedido leria o horário da unidade inteira), as linhas e o Dash com o id dele.
- **O total é o bloco somado por régua** (`AgendaDoParceiro::somar`, a soma da unidade, agora sobre os
  meses): os agendamentos de todos os meses; a ocupação da régua nova e a da antiga, cada uma com a taxa
  refeita dos contadores somados, e nunca uma taxa das duas; os motivos somados inteiros antes do corte dos
  cinco; e quantos meses em cada situação. Somar os meses é o mesmo que contar o bloco: os horários e as
  raízes são de um dia, e cada dia é de um mês só. O mês sem o horário da época fica fora das duas
  ocupações, contado (`semCobertura`). O total sai **nulo** quando a agenda é de outra unidade.
- **A pergunta da T8 segue a escolha da F4.2**: o total não some quando falta cobertura a um mês; a tela diz
  quantos meses ficaram sem o horário da época, na ocupação e nos bloqueados. Se o dono preferir a T8 -- que
  é também o que o desenho pedia para este total (seção 4.1: "senão nulos") --, a tela esconde a ocupação e os
  bloqueados do total quando `semCobertura > 0`.

**A resposta** (`ProfissionalParceiroJson::historicoAgenda`): `recorte` (o do bloco, mais a unidade e o
escopo); `colaborador: {id}`; `meses`, em ordem crescente, cada um com `mes`, `emAndamento` e a forma da
agenda de um colaborador no `/agenda` (`situacao`, `cobertoDesde`, `agendamentos`, `ocupacao`,
`bloqueadosPorMotivo`, pela mesma função `agendaDe`); e `total` com `meses`, `medida`, `semHorario`,
`reguaAntiga`, `semCobertura`, `agendamentos` e a `ocupacao` e os `bloqueadosPorMotivo` por régua (`nova`,
`antiga`) -- ou nulo. As recusas são as do histórico: 401, 403 (privilégio, marca e `proprio`), 404 e 422.

**A tela** (`HistoricoDoParceiro.tsx`, `HistoricoDoParceiroLigado.tsx`, `textosDoParceiro.ts`):

- **três colunas no fim da tabela**, com os nomes das linhas do cartão: "Agendamentos", "Ocupação da agenda"
  e "Horários bloqueados" -- escritas pelas mesmas funções do cartão da lista (`textoDosAgendamentos`,
  `textoDaOcupacao`, `textoDosBloqueados`): "822 · 721 app · 101 recepção", "92,9% · 822 de 885", "… · régua
  antiga", "sem o horário da época", "de outra unidade". No mês da régua antiga, a célula diz também o que
  passou do retrato do Dash, que o cartão diz na frase de baixo (`textoDaOcupacaoDoMes`,
  `textoDosBloqueadosDoMes`): "o Dash gravou zero horários · régua antiga; 10 agendados além do que o Dash
  gravou" e "0; 87 além do que o Dash gravou";
- **a linha de total**: os agendamentos somados; a ocupação de cada régua à parte -- com as duas, cada uma em
  quantos meses, como a unidade diz "em N colaboradores" (a B6 do dono) --, o agendado além do retrato e os
  meses sem o horário da época ("26,3% · 21 de 80 em 3 meses; régua antiga: 12,0% · 6 de 50 em 1 mês; 8 meses
  sem o horário da época"); e os bloqueados **parte por parte como a ocupação**, a régua nova só com mês
  medido, o bloqueado além do retrato e os mesmos meses de fora ("4; régua antiga: 0; 8 meses sem o horário da
  época") -- uma parte em cada linha da célula (abaixo, no navegador);
- **embaixo da tabela, a nota do desenho** (seção 3): "O horário semanal de antes de DD/MM/AAAA não ficou
  registrado: …", com o `cobertoDesde` da agenda que já chegou -- só a do mesmo bloco, como as células;
- **"…" enquanto conta e "—" na falha**, e a linha "A agenda de {período} não veio." com o "tentar a agenda de
  novo", que pede só ela e leva o foco ao título da tabela; a agenda de outro mês ou de outra unidade não
  entra nas linhas do bloco;
- **a fila**: um pedido de agenda por bloco, com o mês que os números aplicaram, do mais recente para o mais
  antigo, e no máximo um no ar; a janela da fidelização não está na chave (trocá-la pede os clientes, e não a
  agenda). É uma fila **à parte** da dos clientes, no molde dela: os clientes são o pedido caro, e a agenda não
  espera por eles -- no máximo três pedidos no ar por tela (números, clientes e agenda). **O estado
  `agendaNoAr` é o que faz a fila andar**: com um pedido no ar o próximo é nulo, e a chegada o devolve a um
  bloco, mudando a dependência do efeito; sem ele, o "tentar a agenda de novo" feito com a de outro bloco no ar
  deixava o próximo igual antes e depois da chegada, e a fila parava. A trava na referência é só defesa (o
  `StrictMode` não a exercita: a agenda não sai na montagem);
- **"Como os números são contados"** ganhou "A agenda no histórico".

O que a tabela não mostra: o resto da frase de baixo do cartão (`textoDaAgenda` -- os motivos, as férias, o
fora do horário, os sem cadastro, os lançados depois). A resposta os traz mês a mês; a tabela já tem catorze
colunas. O além do retrato do Dash e o "registrado desde" ela mostra (acima).

**Onde está a F4.4:**

| Parte | Arquivo |
|---|---|
| A rota | `adm-api-profissionais/historico/agenda/ler` (`module/ISRelatorio/config/module.config.php`) |
| A ação | `ProfissionalParceiroController::historicoAgendaAction` |
| As consultas | as três da F4.3, com o id dele: `UsuarioRepository::parceiroHorarios(null, $id)`, `AgendaRepository::parceiroAgenda`, `DashboardColaboradorRepository::parceiroReguaAntiga` (um mês de cada vez) |
| A resposta | `ProfissionalParceiroJson::historicoAgenda` |
| A tela | `frontend/src/api/profissionais.ts` (os tipos e `lerAgendaDoHistorico`), `HistoricoDoParceiroLigado.tsx` (a fila), `HistoricoDoParceiro.tsx` (as três colunas, a linha de erro, a nota do horário registrado), `textosDoParceiro.ts` (o total do período e o mês da régua antiga), a nota em `ProfissionalParceiro.tsx` |
| Os testes | `ProfissionalParceiroTest` (a faixa 90004400-90004499, nos dois bancos, e a ação no provedor das guardas do histórico); `HistoricoDoParceiroLigado.teste.tsx`; `textosDoParceiro.teste.ts` |
| Os portões | `conferir-recorte-por-unidade.php` (cinco entradas), `conferir-cobertura-por-par.php` (126 para 129 chamadas no ISRelatorio, os mesmos 311 pares), `conferir-rotas-sem-login.php` (a rota), `ensaiar-permissao-adm-api.php` e `medir-adm-api-no-postgres.php` (a rota nos dois) |

**Medido nas duas cópias locais** (`franquiabv_dev`, pela ação despachada em processo, como na F4.3; MySQL com
128 MB e o índice do D189, PostgreSQL de desenvolvimento), o colaborador 200 -- o maior da rede -- na unidade 1:

| | MySQL | PostgreSQL |
|---|---|---|
| O bloco até julho de 2026, três rodadas | 3,40 s com o cache frio, depois 0,26 e 0,25 s; 34 MB de pico | 3,22 s frio, depois 0,30 e 0,27 s; 32 MB |
| O histórico completo (12 blocos, de 2015 a 2026), um bloco de cada vez | 7,3 s somados | 13,1 s somados |

- **O bloco até julho de 2026**: de agosto de 2025 a fevereiro de 2026 na régua antiga (a vigência dele começa
  em 17/02/2026: fevereiro já não é coberto inteiro), de março a julho na nova. Julho dá os 822 agendamentos e
  92,9% (822 de 885) da F4.3. O total: 8.896 agendamentos; régua nova 92,5% (3.674 de 3.974); antiga 93,3%
  (5.222 de 5.595); nenhum mês sem o horário da época. **As respostas dos dois bancos saíram iguais byte a
  byte.** As linhas lidas: 9.643 do 200 nesses doze meses (o maior da rede no mesmo período tem 9.991).
- **O mês do histórico é o `/agenda` daquele mês, na cópia**: para o 200 na unidade 1 e na rede, e para o 782 na
  unidade 1 (o da régua antiga da F4.3), os 12 meses do bloco iguais à linha dele no `/agenda` de cada mês --
  0 diferentes, nos dois bancos.
- **O histórico completo do 200**: os blocos de 2020 a 2025 todos na régua antiga; o que termina em julho de
  2019 com 7 meses nela e 5 sem o horário da época (o Dash começa em 2019); e os de antes, sem o horário da
  época, só com os agendamentos (8.787 no bloco até julho de 2018). Cada bloco leva de 0,01 a 2,8 s.

**Provado:**

- **Os casos novos de `ProfissionalParceiroTest`, nos dois bancos** (a faixa 90004400-90004499):
  - **o mês do histórico é o `/agenda` do mês, pedido de verdade**: janeiro de 2025 sem cobertura, fevereiro na
    régua antiga (50 horários do Dash, 1 bloqueado "Curso": 3 de 49, 6,1%), março na nova -- e B, na régua
    antiga em março, também; os meses vazios com zero e o `cobertoDesde`; o total por régua, contado à mão (14
    agendamentos; nova 5,9%; antiga 6,1%; 10 meses sem o horário da época); as linhas do bloco lidas uma vez, as
    vigências e o Dash só com o id dele, e o Dash só nos 11 meses que a vigência não cobre;
  - **a agenda de outra unidade não é lida**: o gerente que vê o histórico de quem atendeu aqui e é cadastrado na
    unidade fiscal, e o administrador que filtra a unidade fiscal e pede A -- `outraUnidade` em todo mês, total
    nulo, e nenhuma consulta à agenda, às vigências ou ao Dash; na rede, a agenda dele é lida, e março é o
    `/agenda` da rede;
  - **o `proprio`**: o mesmo que o gerente vê dele, só as consultas dele (a das vigências por `u.id`); e,
    entrado na unidade fiscal, a agenda dele, igual ao `/agenda` dele lá;
  - **a sessão solta**, em todo pedido, antes de qualquer consulta que não seja as unidades aceitas e o cadastro;
  - e a ação entrou no provedor das guardas do histórico (as recusas na ordem; a sessão solta depois delas) e
    nos casos do gerente e do colaborador.

  `ProfissionalParceiroTest` inteiro: **115 testes e 1.432 asserções** (eram 110). A fatia e os mesmos vizinhos
  da F4.3, em série: **373 testes**, 2.902 asserções no MySQL e 2.901 no PostgreSQL, os 3 pulados de sempre.
- **A tela**: `HistoricoDoParceiroLigado.teste.tsx` com 10 casos novos (as três colunas iguais ao cartão da lista
  de junho, o total por régua e uma parte em cada linha; o "…" e a agenda que não espera os clientes; a falha e o
  "tentar a agenda de novo"; o completo, um pedido de cada vez, do mais recente para o mais antigo; a janela que
  não pede a agenda; a agenda que espera os números e vai com o mês deles; a de outro mês e a de outra unidade
  que não entram; a de outra unidade dita; o `StrictMode`) e os casos de antes com as colunas novas;
  `textosDoParceiro.teste.ts` com 4 casos do total do período. O front inteiro: **1.544 testes em 42 arquivos**, com quatro processos; o build com 0; o lint com 0 e os mesmos 6 avisos. (Antes da verificação; os números de depois dela estão abaixo.)
- **26 mutantes**, cada um aplicado sozinho e devolvido com o sha1 conferido. **No servidor, 10, todos mortos em
  falha de asserção** (no MySQL): sem `outraUnidade`; `outraUnidade` também no `proprio`; as vigências da
  unidade; a agenda só do primeiro mês; cada mês com o período do bloco; o Dash em todo mês; o Dash de mês
  nenhum; o Dash do primeiro mês do bloco; o total de outra unidade; a sessão presa. **Na tela, 16**: 14 mortos
  em falha de asserção (a janela na chave; o pedido com o mês do pedido; o "tentar de novo" que não pede; a agenda
  de outro bloco entrando; a agenda antes dos números; o "…" como falha; a fila do mais antigo para o mais
  recente; o total sem os meses sem cobertura; o total só com a régua nova; a régua nova sem mês medido; os
  bloqueados das duas réguas somados; a falha sem a linha de erro; o total com as células de um mês; o total numa
  linha só); **um morto por travar** -- sem as duas travas da fila (o `agendaNoAr` e a referência), a tela pede a
  agenda sem fim e a suíte não termina (cortada à mão aos 20 min); e **um que sobrevive, equivalente**: a trava na
  referência sozinha. A agenda nunca sai na montagem (espera os números de um bloco), e o `StrictMode` só repete
  os efeitos da montagem -- o caso que a distinguiria não existe. Ela fica como defesa, no molde dos clientes,
  com o porquê escrito no código.
- **Os portões**: `conferir-recorte-por-unidade.php` ganhou cinco entradas (as guardas do histórico, a
  `outraUnidade` e o id nas três consultas), e foi falsificado -- cada uma arrancada do código, sozinha (a
  `outraUnidade` deixada só num comentário): os 5 vermelhos; o controle, a mesma chamada partida em várias linhas,
  verde. `conferir-cobertura-por-par.php`: 126 para 129 chamadas no ISRelatorio, os mesmos 311 pares e o mesmo
  sha1. `conferir-rotas-sem-login.php`: a rota no bloco congelado (a primeira rodada a acusou, como devia).
  `conferir-tudo.sh` saiu com **0**: 49 ok e o D50 de sempre.
- **Os ensaios por HTTP, nos dois bancos de contrato** (o de MySQL montado de novo antes):
  `ensaiar-permissao-adm-api.php` com a rota nova -- sem o privilégio, 403 (61 de 61 recusas, nos dois); com o
  privilégio e sem a marca, 403 com o motivo `marca`; com a marca, a própria agenda do histórico em 200 (o corpo
  só dele, no escopo `proprio`, com os meses) e a de outro id em 403 `proprio`. Saída 0 no MySQL e no
  PostgreSQL. O `medir-adm-api-no-postgres.php`, depois do ensaio do fluxo (a ordem do CI): saída 0, 125 rotas,
  nenhuma quebrou, 26 no 2xx; a rota nova para na guarda, como as irmãs.
- **A tela no navegador, a 375 px, com dados escritos à mão e sem sessão**: o histórico ligado de verdade, servido
  pelo Vite, com o `fetch` trocado por respostas do tamanho do 200 (a prévia saiu do repositório depois). A página
  não rola de lado (375 de 375); a tabela rola dentro do cartão; a falha da agenda mostra a linha de erro presa à
  esquerda, e o "tentar a agenda de novo" leva o foco ao título da tabela. **Achado e consertado ali**: a célula
  da ocupação na linha de total, numa linha só ("58,5% · 21.780 de 37.260; régua antiga: …; 1 mês sem o horário
  da época"), levava a coluna de todos os meses a 576 px; agora cada parte desce para a linha dela (238 px), no
  componente, e o texto não muda.

**A verificação de 02/10/2026** fez sete achados, todos na tela ou na prova -- o servidor não mudou. Dois eram
o mesmo; todos consertados com o caso que os pega, menos a metade da T8 de um deles, que fica com o dono:

- **Os bloqueados do total liam a régua nova sem o filtro da ocupação ao lado** (dois achados): um bloco com
  meses sem horário semanal e meses na régua antiga dizia "12,5% · 6 de 48 · régua antiga" e "0; régua antiga:
  2" -- uma parte de uma régua que a célula vizinha não mostra --, e os bloqueados não diziam que o número era
  só dos meses medidos. As duas colunas seguem agora a mesma regra, parte por parte (`reguasDoPeriodo`,
  `comOsMesesSemOHorario`).
- **O total não dizia quantos meses em cada régua** (a B6: "quantos em cada régua"): com as duas, "em N meses"
  em cada uma. **A outra metade do achado não muda**: o total com meses sem cobertura segue mostrando a ocupação
  de cada régua (o afastamento da T8 da F4.2, a pergunta ao dono, abaixo) -- desfazê-lo só no histórico
  deixaria o histórico diferente da lista.
- **A régua antiga escondia o que passou do retrato do Dash, e o `cobertoDesde` não aparecia**: o mês do Dash
  com zero horários e 87 bloqueados dizia "0". Agora a célula diz o além do retrato, e a nota do desenho fica
  embaixo da tabela.
- **Nenhum caso pegava a fila sem o estado `agendaNoAr`**: o mutante que só o arranca (a referência fica)
  deixava os quatro casos da fila verdes, e a tela parava no "tentar a agenda de novo" feito com a de outro
  bloco no ar. Caso novo, "com a de outro bloco no ar"; o comentário do código dizia que o estado só evitava o
  pedido em dobro, e agora diz o papel dele.
- **Nenhum caso conferia a unidade no pedido da agenda**: o caso da rede passa a conferir o `unidade=todas` dela.
- **O caso do `StrictMode` da agenda dizia proteger a trava na referência, e passava sem ela** (o Q8). Ele foi
  reescrito: prova que a agenda não sai na montagem e sai uma vez depois dos números, e diz que a trava não é o
  que ele prova -- fica como sentinela, para o dia em que a agenda puder sair na montagem.

Os testes depois da verificação: `HistoricoDoParceiroLigado.teste.tsx` com 34 casos (2 novos: o além do retrato
no mês e no total, e o "tentar" com a de outro bloco no ar; e os de antes com as asserções novas -- a nota, a
unidade, a nota que não vem de outro bloco) e `textosDoParceiro.teste.ts` com 9 (2 novos); o front inteiro,
**1.548 testes em 42 arquivos**, com quatro processos; o build com 0; o lint com 0 e os mesmos 6 avisos. **Treze
mutantes novos, todos mortos em falha de asserção**, cada um sozinho e devolvido com o sha1 conferido: os
bloqueados do total sem o filtro da régua nova; sem os meses de fora; as réguas sem "em N meses"; o mês sem o
agendado e sem o bloqueado além do retrato (dois); o total sem um e sem o outro (dois); sem a nota; a nota e as
células com a agenda de outro bloco; só a nota com ela; a fila sem o `agendaNoAr`; o pedido sem a unidade no
invólucro e na API (dois). O Q8 rodou de novo como controle e sobrevive, como devia. No navegador a 375 px
(a prévia sem sessão, de novo, e tirada do repositório depois): a página não rola de lado; a coluna da ocupação
vai de 238 a 314 px no pior caso da prévia (a linha "régua antiga: 48,3% · 11.190 de 23.190 em 1 mês", em
negrito no total), e a nota fica dentro do cartão.

**O que a F4.4 não provou:**

- **A tela com sessão, contra o banco de contrato semeado**: o mesmo limite da F4.1 e da F4.3 -- entrar no ERP com
  a senha de fixture no navegador não veio do dono. O que a tela faz com os dados está nos testes; o que ela mostra
  a 375 px, na prévia sem sessão.
- **Produção** e o limite de memória dela (o bloco do maior colaborador pede 34 MB no PHP).
- **A T8** continua com o dono, e o histórico segue a escolha da F4.2.

**Com a F4.4, a F4 está inteira.** O que fica: a pergunta da T8 ao dono; a F4.5, só se ele quiser (a origem
guardada na remarcação, a lista fechada de motivos no fechar, o arquivo das reaberturas, a agenda do dia sem os
bloqueados -- o D324 e o D325); e o `docs/roadmap.md` e o Artifact do roadmap, que nenhuma fatia da F4 atualizou.

---

## 6. Os dez relatórios restantes e a F4: as decisões do dono de 01/10/2026

**Como saíram.** Dez leitores (um por relatório) levantaram e mediram, só lendo; dez desenhistas
propuseram a versão nova na régua do painel; um consolidador juntou as perguntas que mudam o que o
número quer dizer, deduplicadas entre os relatórios. Para a F4, quatro leitores, três desenhistas
(do dado, do mínimo e do risco) e um juiz. O dono respondeu tudo no mesmo dia, depois de mandar
"continue de forma autonoma a criação das telas faltantes. quando necessario abra a
perguntas/decisões necessárias". Os desenhos ficaram no rascunho da sessão; as perguntas como foram feitas, o juiz da F4 e as onze especificações escritas depois das respostas estão em `docs/f3-relatorios/` (o `README.md` de lá diz o que é cada um); o que vale
está abaixo e, relatório por relatório, nas subseções que cada fatia acrescentar.

### Os relatórios

| # | Pergunta | Decisão do dono |
|---|---|---|
| 1 | O período | **O mês do painel**, com quatro exceções: o Atendimentos no ano; o Caixa por dia, num intervalo de até 31 dias; o Royalties no ciclo da fatura (16 a 15); a Cota-parte em intervalo livre |
| 2 | O PDV grava quem cancelou e quando, e quem lançou o desconto manual? | **Os dois**: o instante e o gerente que autorizou o cancelamento (a senha dele já é conferida e era descartada), e quem lançou cada desconto manual. A história só começa quando a coluna existir |
| 3 | Quem tem o privilégio sem ser gerente vê o quê? | **Só a si mesmo**: a própria linha onde o relatório tem uma (Atendimentos, Cota-parte); nos outros, a recusa. É a resposta 11 do Profissional Parceiro levada aos outros |
| 4 | O operador de caixa vê o quê? | **Só o dia de hoje**: no Caixa, os caixas de hoje da unidade; na Cota-parte, o valor do dia por colaborador, sem a cota |
| 5 | Descontos e Vendas Canceladas | **Telas de análise**, e a lista das vendas fica em Vendas Realizadas: o Descontos mostra de onde veio o desconto (95,8% é convênio na unidade 1, em julho); as Canceladas separam a venda desfeita da comanda que nunca fechou; "ver as vendas" abre Vendas Realizadas filtrada no mesmo mês |
| 6 | O Atendimentos | **Absorve o Dash. Faturamento**: a linha da unidade ganha as médias e o crescimento, e saem as duas rotinas e o cache |
| 7 | Qual cota-parte | Nas palavras do dono: "Por padrão, a recalculada. Mas crie um espelho deste relatório como cota-parte v2 - disponivel apenas para gerente geral, com a gravada e com aviso". A Cota-parte do menu usa o percentual de hoje; a **Cota-parte v2**, só do administrador, usa a gravada na venda e marca os itens cujo percentual difere do cadastro |
| 8 | A nota do NPS | **Três médias de 0 a 10** (recepção, experiência, colaboradores), cada uma com o número de respostas e as faixas, e os clientes com alguma nota até 6. A pesquisa não tem a pergunta "recomendaria?", e o nome NPS fica sem o cálculo de -100 a 100 |
| 9 | A retirada de produto sem venda | **O uso interno vai para a comanda nova**, com o privilégio do PDV; a saída pela tela de Estoque passa a pedir o motivo (perda, vencimento, acerto), e o acerto fica fora do total |
| 10 | A exportação | **Como hoje**: continuam o PDF, o XLS, o telefone na lista da Indicação e o TXT do NPS -- gerados dos números novos (o XLS do Atendimentos deixa de pôr os clientes da unidade inteira em cada colaborador) |
| 11 | As categorias do catálogo | **Todas, mais "sem categoria"** (na unidade 1, em julho, 351 serviços e 19 produtos estão sem categoria; a correção de 27/10/2026, a fatia 47 -- os 22 eram os 19 mais os 3 de Empório que a tela antiga do Atendimentos esconde, `spec-produtos.md`, seção 5) |
| 12 | O royalty do ciclo | **O faturado e o devido, lado a lado**, com a diferença explicada dia a dia. O relatório só mostra: nenhuma rotina de cobrança roda |
| 13 | As faturas | **Os dois lados**: o Royalties mostra as faturas da rede; a tela de Faturas do gerente ganha a base, a alíquota e o abatimento do ciclo |
| 14 | O que conta como indicação | **O aplicativo, o formulário e a origem**: os convites das duas portas, e à parte os clientes novos que disseram ter vindo por indicação. O formulário para de apagar quem indicou |
| 15 | A gaveta do caixa | **A gaveta esperada e o valor contado**: o fechamento passa a pedir o dinheiro contado, e o relatório mostra a quebra |
| 16 | A cota-parte do pacote (D310) | **De quem vendeu, por sessão executada**, e só pelo que foi usado |
| -- | O ciclo de pagamento da cota-parte | **Varia por unidade**: a tela oferece mês, quinzena e semana, e lembra o último usado |
| -- | A gorjeta do caixa | **Fica na gaveta** até o repasse: a gaveta esperada a inclui, e a Cota-parte a soma ao total do colaborador |

O que os desenhos deixaram "para depois" segue a recomendação deles, até o dono dizer outra coisa:
a venda cancelada conta pelo mês da venda até existir a data do cancelamento; o motivo do desconto
e do cancelamento é agrupado pelo texto livre até haver lista fechada; a base do royalty e da
cota-parte é depois do desconto; a alíquota nova vale do ciclo seguinte; o NPS é do mês da resposta
e destaca só unidade com pelo menos 30 pesquisas; a rede e o custo de Produtos/Serviços vêm em
fatias próprias.

**As mudanças no PDV** (perguntas 2, 9, 15 e 16) criam colunas novas, e por isso pedem a
autorização do dono para aplicá-las nas cópias locais antes de o código rodar contra elas -- a mesma
regra do índice do D308.

### A F4 do Profissional Parceiro

| # | Pergunta | Decisão do dono |
|---|---|---|
| A1, A2 | Criar `usuarios_horarios`, rodar a semente e o índice do D189 nas duas cópias locais | **Autorizado**, tabela e índice. Produção não: o passo 0 do corte e a F4.1 antes do corte ficam com o dono |
| B4 | Quem conta na ocupação e no total da unidade | **Todos com agenda, como o Dash**: a taxa da unidade soma também quem tem agenda e não fez serviço no mês (rede, julho de 2026: 32,3% contra 42,5% só da lista). O cartão de cada colaborador continua com a dele |
| B5 | Quais bloqueados saem do denominador | **Todo bloqueio**, com os cinco motivos mais comuns ao lado, como foram escritos |
| B6 | O mês antes do horário da época | **O retrato antigo do Dash**: a ocupação da `dashboard_colaboradores` menos os bloqueados, marcada como "régua antiga" (só o grupo 1). Por isso a F5 não apaga aquela tabela |

As outras decisões do juiz (o agendamento é a família, pela data da raiz; app e recepção pela
origem gravada; desmarcados e faltas fora; reabrir continua apagando os bloqueios; os atendimentos
sem agenda numa fatia própria; a ocupação fora do "melhor da rede"; no mês em andamento, o período
da lista) seguem a recomendação, com o caminho de volta escrito no desenho (`docs/f3-relatorios/f4-final.md`, tabela 6b).

### A parte comum (as fatias 42 a 45, de 25 a 27/10/2026)

As peças que os dez relatórios usam, escritas uma vez (`docs/f3-relatorios/spec-comum.md`, da C0 à C7, com as
resoluções do `spec-ordem.md`; o que saiu diferente do desenho está no bloco "Em dia" do começo do `spec-comum.md`, itens 7
a 10). **O primeiro relatório novo ligou na fatia 47**, o Produtos / Serviços, com a família de rota (a subseção abaixo).

**Quem vê o quê** (a tabela 1.1, pelas MARCAS da sessão, nunca pelo id do grupo -- o D124): o administrador vê a rede
inteira, ou qualquer unidade, em todos; o Royalties e a Cota-parte v2 são só dele; o gerente vê a unidade da sessão; o caixa
que não é gerente vê só o dia de hoje no Caixa e na Cota-parte, a unidade na Indicação, e cai nos outros no resto; os outros
veem só a si mesmos no Atendimentos, na Cota-parte e no Parceiro (este com a marca "Acesso dashboard"), e são recusados nos
demais. Contado na cópia em 25/10/2026 (usuários ativos com o privilégio, sem os administradores): 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). A regra é uma só, `RecorteDoRelatorio::escopo`,
com a política de cada relatório em `PoliticaDoRelatorio`; o menu da tela nova usa a mesma (`SessaoJson::menu`, as nove
guardas `relatorio<Nome>` da fatia 45).

**O período** (a tabela 1.2): o mês do painel (Indicação, Descontos, NPS, Produtos, Retirada e Canceladas), comparado com o
mês anterior; o intervalo de datas (o Caixa até 31 dias; a Cota-parte e a v2 sem teto); o dia de hoje para o caixa; o ano no
Atendimentos; o ciclo de 16 a 15 no Royalties, pelo mês em que começa.

**A família de rota:** `/adm/api/relatorios/<rota>`, com `index`, `unidades` e, se exporta, `exportacao`, todos na base
`ISRelatorio\Controller\Adm\RelatorioAdmController`. Toda ação começa pelo `recorteDoRelatorio()` (a sessão, o privilégio,
o escopo, o período e a unidade, nessa ordem), depois as guardas do próprio relatório (`guardasDoRelatorio`), e só então
solta a sessão. O controller de cada relatório NÃO é `final` (o teste dele o estende para contar a solta da sessão e fixar
o "hoje" -- o `spec-ordem.md`, 1.2); o `recorteDoRelatorio()` é que é `final` na base. A família nasce na 47, com a linha
de `rotas-publicas.php`.

**A exportação "como hoje"** (a pergunta 10): o PDF, a planilha (`xls` no Atendimentos, `xlsx` na Cota-parte e na v2) e o
TXT dos contatos do NPS, pelos formatos da política, montados da mesma conta que a tela mostra; a regra está em
`docs/convencao-api.md`, a regra 9.

**Onde está:**

| Fatia | O quê | Arquivos |
|---|---|---|
| C0 (42) | o recorte neutro | `module/ISRelatorio/src/Regra/PoliticaDoRelatorio.php`, `RecorteDoRelatorio.php` |
| C1 (42) | o esqueleto e os portões | `module/ISRelatorio/src/Controller/Adm/RelatorioAdmController.php` (e a fábrica), `scripts/regras-da-familia-dos-relatorios.php`, `tests/Caracterizacao/RelatorioEmProcesso.php`, `UsuariosDoRelatorio.php` |
| C2 (43) | a consulta, as peças da resposta e os novos | `VendaRepository::relatorioItens`, `ISVenda\Api\NumerosDaResposta`, `ISRelatorio\Service\NovosDoRecorte`, `ISRelatorio\Regra\ColunasDoPdv` e a tabela `colunas_gravadas` |
| C3 (43) | a exportação | `ISRelatorio\Exportacao\DocumentoDoRelatorio`, `PlanilhaDoRelatorio`, `PdfDoRelatorio`, `TextoDoRelatorio`; na tela, `api/arquivo.ts` e `telas/BotoesDeExportacao.tsx` |
| C4 (44) | os componentes | `frontend/src/api/relatorios.ts`; em `frontend/src/telas/`: `pedidosDoRelatorio.ts`, `NavegacaoDoMes`, `intervaloDoRelatorio.ts`, `EscolhaDoIntervalo`, `DiaDeHoje`, `EscolhaDaUnidade`, `EstadoDoRelatorio`, `RecusaDoRelatorio`, `CabecalhoDoRelatorio`, `TabelaQueRola`, `ValorDeDepois`, `AindaNaoGravado`, `LinkDaFicha`, `glossario.tsx` |
| C5 (45) | o menu, a sessão e o Console | `frontend/src/telas/menu-do-erp.ts` (as constantes dos onze, o `podeVer` e o `podeAbrirNoMenu`), `SessaoJson::menu` (as nove guardas), `Console.tsx` (a `TELA_DO_RELATORIO`, a `AberturaDoRelatorio` e o `abrirRelatorio`), `VendasRealizadasLigada` (os `filtrosIniciais`), `tests/Caracterizacao/RelatoriosNoMenuTest.php` |

**O que foi medido:** o Parceiro da unidade 1, julho de 2026, em processo na cópia, com o nome das unidades pela peça
comum: 1,3 a 1,4 s frio e 0,14 a 0,16 s quente, igual ao de antes; a consulta comum (`relatorioItens`) contra a do painel,
na cópia, alternadas, buffer pool de 128 MB: +26% na unidade 1 (0,53 para 0,67 s, mediana) e +18% na rede -- acima dos 20%
que o desenho tolera na unidade, registrado na entrada da fatia 43 do `docs/roadmap.md`; o documento sintético de 5.000
linhas por 15 colunas: o XLS em 5,3 s, o XLSX em 5,5 a 6,0 s, o PDF em 0,6 a 0,7 s; o pacote da tela, ~4,1 KB comprimidos
para as peças da C4, dentro dos 10 KB. A C5 não tem medida (o desenho não pede).

**A nota do Líder** (a pergunta 3; o D620): na cópia, o grupo 8 ("Líder") está sem a marca de gerente, e por isso a regra
o recusa no Caixa e no Produtos e o põe no "só ele" da Cota-parte (uma pessoa em cada). A migração
`scripts/migracao-grupo-lider-gerente.sql` espera a janela desde 09/09 (o D124); rodá-la resolve sem código, e o código não
volta a comparar o id do grupo.

### Produtos / Serviços (a fatia 47, 27/10/2026; a F1 de `docs/f3-relatorios/spec-produtos.md`; a rede, quem fez e o Catálogo, a fatia 69, mais abaixo)

O primeiro relatório da tela nova, e o "ver todos" do cartão "Os serviços que mais faturaram" do painel: **quanto se vendeu
de cada serviço e de cada produto no mês, quanto isso faturou, e como foi contra o mês anterior**, numa unidade por vez.

**A régua é a do painel, importada** (1.1 a 1.4 do desenho): os itens das vendas finalizadas fechadas por alguém da unidade
(`VendaRepository::painelItens`, pelo `ContasDoPainelDaUnidade::normalizar`, que decide a cortesia), o faturamento depois do
desconto, o desconto pela coluna do item, a sessão de pacote valendo zero (e o que ela valia, ao lado, fora do faturamento).
Cada item cai numa gaveta só: **Serviços** (o serviço e o Lava VIP, as sessões inclusive), **Produtos vendidos** (o produto
com preço na comanda, o de 100% de desconto inclusive), **Cortesias** (o produto a preço zero, só pela quantidade),
**Pacotes vendidos** (a venda do pacote) e **Outros** (o insumo e o resto, para a soma fechar). Uma linha é um item do
catálogo pelo id, numa gaveta; a que só vendeu no mês anterior aparece com zero e −100,0%. A ordem é a do `topServicos` do
painel (faturamento, quantidade, id). As três gavetas com catálogo têm o subtotal por categoria -- todas, zeradas também, e
"Sem categoria" por último (a pergunta 11); a categoria é a do cadastro de hoje, pela regra comum
`ContasDoPainelDaUnidade::categoria`. Por construção, a soma das gavetas é o faturamento do painel no mesmo mês, e as cinco
primeiras linhas de Serviços são o top 5 dele (o `ProdutosVendidosTest` confere contra o painel de verdade, nos dois bancos).

**Quem vê:** o gerente, a unidade da sessão; o administrador, qualquer unidade ativa ou a rede (`todas`, desde a fatia 69;
o gerente que a pedisse leva o 422 da base); quem não é nenhum dos dois, 403 `papel` -- o colaborador, o caixa que não é gerente e, até a migração do D124, o Líder
(o D620). O menu da tela nova e o do ERP antigo mostram o item pela mesma regra (`relatorioProdutos` e
`RecorteDoRelatorio::veOMenu`), e **a tela antiga aplica a mesma política** (`ProdutosController::indexAction`).

**O que muda para quem usava a tela antiga:** saem o "Valor Compra", o "Valor Venda" (o preço de catálogo de hoje) e o
"Lucro" -- no lugar, o faturamento, os descontos e o preço médio cobrado; sai o filtro de tipo (as gavetas são as seções); o
intervalo livre vira o mês, com o anterior ao lado. A tela antiga continua no ERP até o corte, com a guarda nova. Não há
exportação (a tela antiga não exportava: a pergunta 10).

**Onde está:** `module/ISRelatorio/src/Regra/ContasDosProdutosVendidos.php` (as contas), `Api/ProdutosVendidosJson.php` (a
resposta), `Controller/Adm/ProdutosVendidosController.php` (`GET /adm/api/relatorios/produtos` e `.../unidades`); na tela,
`frontend/src/api/produtosVendidos.ts`, `telas/ProdutosVendidos.tsx` e `ProdutosVendidosLigado.tsx`, a linha da
`TELA_DO_RELATORIO` do Console, o `quando` do item do menu e o "ver todos" do Painel, que leva o mês e a unidade dos números
na tela. Os testes: `RegrasDosProdutosVendidosTest` (sem banco), `ProdutosVendidosTest` (em processo, nos dois bancos),
`ProdutosVendidosLigado.teste.tsx` e `ConsoleAbreProdutos.teste.tsx`.

**Medido** (a cópia, 128 MB de buffer pool, em processo, unidade 1, julho de 2026, três processos): a ação inteira em 1,01 a
1,21 s, com 29,8 MB de pico -- abaixo dos 2 s e dos 64 MB do desenho. O oráculo em Python sobre as linhas cruas (as unidades
1 e 39, junho e julho de 2026) bate ao centavo em todas as 395 conferências, e reproduz os números da seção 1.8 do desenho
(unidade 1, julho: R$ 286.189,40 contra R$ 257.223,45; 63 linhas).

**A rede, quem fez e o Catálogo (a fatia 69, 03/11/2026, REL-PRODUTOS-REDE; a F2, a F3 e a F4 do desenho).** Na rede, o
administrador vê o serviço com família (a etiqueta do cadastro, que só o administrador aplica) numa 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, e grafias diferentes são linhas diferentes. 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 pelos itens, e a rede trava por pessoa
(o 429; a fila `produtos` da tela). Na unidade, cada linha ganha o "quem fez (N)": o colaborador DO ITEM (e não quem fechou a
venda), com a quantidade e o faturamento do mês -- nas Cortesias, só a quantidade; a comissão fica na Cota-parte. E o Catálogo
de produtos da tela nova passou a mostrar o "Vendidos" desde sempre SEM a cortesia (a regra do painel,
`ContasDoPainelDaUnidade::ehCortesia`), com a coluna "Cortesias" ao lado e o "ver no relatório" (o mês de hoje, só a quem abre
o relatório); a tela antiga de Configurações continua contando a cortesia (o D676), e quem entra no catálogo só pela porta do PDV
(o D168, sem o privilégio de configuração) recebe a linha de antes, sem as cortesias. O "Como os números são contados" da rede
troca a frase da linha da unidade pela da rede, e não tem a de quem fez. Na cópia: 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, como antes); na unidade
39, 39 dos 125 produtos ativos mudam de "Vendidos" (12.144 unidades para "Cortesias"), e na unidade 1, nenhum. **Medido** (o Mac
Mini M4): a rede de julho de 2026 com junho de 0,51 a 0,78 s e 132 MB de pico nos dois bancos (abaixo da parada de 256 MB e
10 s; com `memory_limit=128M` cai, o D631); a resposta da unidade 1 de 29.187 para 51.450 bytes. **O oráculo:** a rede (360
conferências), os 212 pares produto × colaborador da unidade 1 (84) e o catálogo das unidades 39 e 1 (406), em cada banco, 0
divergências. Os testes novos estão no `RegrasDosProdutosVendidosTest`, no `ProdutosVendidosTest`, no `ProdutoApiTest`, no
`RegrasDoPainelDaUnidadeTest` (o `ehCortesia`), no `ProdutosVendidosLigado.teste.tsx`, no `CatalogoLigado.teste.tsx` e no
`ConsoleAbreProdutos.teste.tsx`.

**O custo e a margem (a fatia 74b, 03/11/2026, PDV-CUSTO-DO-ITEM; a resposta 224 (b) do dono e a seção 3 do desenho).** Desde a
linha `custoDoItem` de `colunas_gravadas`, cada produto de estoque vendido grava o custo do cadastro naquele momento
(`vendas_produtos.custo_unitario`, por unidade; nulo no serviço, no pacote e na sessão), e o relatório mostra nos Produtos
vendidos e nos Outros (o Insumo) as colunas "Custo" e "Margem" (o faturamento menos o custo gravado) e, nas Cortesias, o "Custo"
-- só dos itens com o custo gravado maior que zero (desde a fatia 74e; o gravado com zero é "sem custo no cadastro na venda", à
parte), e só com a data dentro do período; o resto, "sem registro", com a quantidade ao lado. A
gaveta de Produtos vendidos diz desde quando ("Gravado desde dd/mm/aaaa; antes disso, sem registro."), ou que ainda não é
gravado; o custo de hoje do cadastro nunca entra. **Na cópia** (só
SELECT): os itens de produto de estoque vendidos por mês em 2026, que ficam "sem registro" -- de 1.632 a 2.233 por mês de
janeiro a julho (1.113 em agosto), mais da metade deles cortesias; 441 dos 2.392 produtos ativos de estoque com custo zero (a margem era o
faturamento inteiro: o D684; desde a fatia 74e, pela resposta 283 (b), o gravado com zero é "sem custo", fora do custo e da
margem e contado à parte, como na Retirada); 131 com o custo maior que o preço (116 de preço zero). **Medido** (o Mac
Mini M4): 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 nos dois -- o da
69, com a coluna a mais. **Onde está:** `VendaProdutoService::gravarOCustoDoCadastro`, `ContasDosProdutosVendidos::temCustoGravado`,
`::situacaoDoCusto` (a fatia 74e: a regra uma só do Produtos e da Retirada) e `::custoEMargem`, o `ProdutosVendidosJson` (o
`gravadoDesde` e os quatro campos: `custo`, `margem`, `semCustoGravado` e, desde a 74e, `semCustoNoCadastro`), `ProdutosVendidos.tsx` e o glossário
`produtos.custoEMargem`; os testes `CustoDoItemGravadoTest`, `RegrasDosProdutosVendidosTest`, `ProdutosVendidosTest` e
`ProdutosVendidosLigado.teste.tsx`.

### Retirada de Produtos (a fatia 48, 27/10/2026; a F0 de `docs/f3-relatorios/spec-retirada.md`; o custo atual e a retirada na comanda nova, a fatia 49, mais abaixo)

O segundo relatório da tela nova, e o "ver por produto" do cartão Cortesias do painel: **quanto produto saiu da unidade sem
venda no mês, de qual produto, por qual porta, quem pediu e quem lançou** -- numa unidade, ou na rede para o administrador.

**As duas bases, nenhuma regra copiada** (a seção 1 do desenho): as saídas de estoque ativas do mês, da unidade do PRODUTO
(`ProdutoEstoqueRepository::retiradasDoPeriodo`, uma linha por saída, a faixa meio-aberta), e as cortesias do painel (os itens
de `VendaRepository::painelItens` pelo `ContasDoPainelDaUnidade::normalizar`, que decide a cortesia; a unidade é a de quem
fechou a comanda). Cada saída tem uma porta (`RegraDaRetirada::porta`): **uso interno** (com quem pediu, o PDV antigo) ou
**saída pela tela de estoque** (sem quem pediu). **Perda, vencimento e acerto** existem na resposta, nulos até a saída pedir o
motivo (a coluna é da fatia do PDV, a seção 3 do desenho): a tela as esconde e diz por quê (`AindaNaoGravado`). **Saiu sem
receita** é a soma de uso interno, saídas pelo estoque, perda, vencimento e cortesias; **o acerto fica fora** (a pergunta 9).
As tabelas "Pedida por" e "Lançada por" somam as saídas do estoque (o acerto inclusive), saem das próprias saídas do mês e
trazem quem hoje está inativo. O detalhe de um produto (as saídas dele, dia e hora, quem pediu e quem lançou) vem na mesma
resposta, filtrado na tela -- não há id de produto no pedido, e o D155 não tem por onde voltar. O filtro **"Pedida por"**
(todos, "sem quem pediu", ou uma pessoa que pediu no recorte) só existe numa unidade; com ele, as cortesias somem (a cortesia
não tem quem pediu). Por construção, as cortesias são o cartão do painel no mesmo mês.

**Quem vê:** o gerente, a unidade da sessão; o administrador, a rede (`todas`, com a unidade do produto em cada linha e sem
as pessoas) ou qualquer unidade ativa; quem não é nenhum dos dois, 403 `papel`. O menu da tela nova e o do ERP antigo mostram o
item pela mesma regra (`relatorioRetiradas` e `RecorteDoRelatorio::veOMenu`), e **a tela antiga aplica a mesma política** --
a lista (`ProdutosController::retiradasAction`) e o histórico (`retiradasHistoricoAction`), que passou a pedir o privilégio da
Retirada, e não o de Produtos (o D625, CORRIGIDO).

**O que muda para quem usava a tela antiga:** o intervalo livre vira o mês; a lista por produto ganha as portas, as cortesias e
o "saiu sem receita"; o histórico sem período vira o detalhe do mês; a quantidade sai sem a máscara de dinheiro; a rede diz de
que unidade é cada linha (o D628 fica na antiga até o corte). O dinheiro é o custo atual (a F1, a fatia 49, abaixo; o custo
gravado no item é a 74b, o D627); não há exportação (a tela antiga não exportava: a pergunta 10).

**Onde está:** `module/ISRelatorio/src/Regra/RegraDaRetirada.php` e `ContasDaRetirada.php` (as regras e as contas),
`Api/RetiradaDeProdutosJson.php` (a resposta), `Controller/Adm/RetiradaDeProdutosController.php` (`GET
/adm/api/relatorios/retiradas` e `.../unidades`); os repositórios `ProdutoEstoqueRepository::retiradasDoPeriodo` e
`ProdutoRepository::cadastroDosProdutos`; na tela, `frontend/src/api/retiradas.ts`, `telas/RetiradaDeProdutos.tsx` e
`RetiradaDeProdutosLigado.tsx`, a linha da `TELA_DO_RELATORIO` do Console, o `quando` do item do menu e o "ver por produto"
do Painel, que leva o mês e a unidade dos números na tela. Os testes: `RegrasDaRetiradaTest` (sem banco),
`RetiradaDeProdutosTest` (em processo, nos dois bancos), `RetiradaDeProdutos.teste.tsx`, `RetiradaDeProdutosLigado.teste.tsx`
e `ConsoleAbreRetiradas.teste.tsx`.

**As três correções ao desenho** (a seção 1.6, itens 1 a 3): a quantidade fracionada existe, e sai decimal
(`NumerosDaResposta::quantidade`); o privilégio do PDV na comanda nova é `vendas/comandasNova/escrever` (a F2); o detalhe vem
na mesma resposta.

**Medido** (a cópia, 128 MB de buffer pool, em processo, julho de 2026, três processos de cada): a unidade 39 em 1,03 a 1,25 s
com 20,0 MB de pico; a rede em 1,52 a 1,95 s com **122,5 MB de pico** e 72,6 KB de JSON (o D631: perto do `memory_limit` de
fábrica) -- os dois tempos acima do que o desenho esperava (menos de 1 s e menos de 1,5 s), registrados. Por isso **a rede anda
um pedido de cada vez por pessoa**: a trava no servidor (o 429 com a frase do Profissional Parceiro) e a fila `retiradas` na
tela; a unidade não espera. O oráculo em Python sobre as linhas cruas (as unidades 1, 10, 26, 32 e 39 e a rede) bate em todas as 1.299
conferências, e reproduz a tabela 1.2 do desenho: a rede 1.659 (121 linhas, 186 saídas, 905 comandas com cortesia), a U10 494,
a U26 36, a U32 466, a U39 577, a U1 0.

**O custo atual e a retirada na comanda nova (a fatia 49, 27/10/2026; a F1 e a F2 do desenho).**

- **O custo atual** (a R2): o cartão "Custo atual do que saiu sem receita" e a coluna "Custo atual" -- a quantidade de cada porta
  e das cortesias vezes o preço de compra que o cadastro tem HOJE (`produtos.valor_compra`), em centavos e arredondado por porta
  (`ContasDaRetirada::custo`); o acerto tem custo e fica fora do total. O produto sem custo (zero ou nulo) sai com "—" e conta em
  `produtosSemCusto`, que o cartão diz ("N produtos sem custo no cadastro ficam fora"). Na unidade e na rede, do mesmo
  `cadastroDosProdutos` que a rota já lia: nenhuma consulta a mais. Com o filtro "pedida por", o custo das cortesias some como
  a quantidade. O glossário ganhou o `retiradas.custoAtual`, com o texto do desenho. O oráculo (as unidades 1, 10 e 39 e a rede,
  julho de 2026) bate em 360 conferências e reproduz a tabela da F1: U10 R$ 1.298,66, U39 R$ 1.314,78, rede R$ 6.571,45. A
  medida não mudou (o D631): a U39 em 1,17 a 1,19 s com 20,1 MB, a rede em 1,58 a 1,99 s com 122,6 MB. **Desde a fatia 74b
  (03/11/2026)**, a cortesia vendida desde a linha `custoDoItem` entra pelo custo GRAVADO na venda, e o cartão diz quantas foram
  assim; a saída do estoque e a cortesia de antes continuam pelo custo de hoje (a seção da F1 do `spec-retirada.md`; o D685, e
  a resposta 284 (a) do dono: como está), e a cortesia gravada com zero fica "sem custo", fora do valor, nunca pelo de hoje. Com a data no período, o
  rótulo deixa de ser "Custo atual": "Custo do que saiu sem receita" e "Custo".
- **A retirada de uso interno na comanda nova** (a pergunta 9, a parte sem coluna): o botão "retirar do estoque" da lista de
  comandas, para quem escreve na comanda, abre o `RetirarDoEstoque` (o produto contado em estoque e não esgotado, com o estoque
  ao lado; quem pediu, entre os colaboradores ativos; a quantidade inteira, de 1 ao estoque), que grava pelo `POST
  /adm/api/produtos/:id/estoque/retirada` a mesma linha que o modal do PDV antigo -- e a Retirada de Produtos a conta como uso
  interno. A guarda é a da comanda (`vendas/comandasNova/escrever`, o D138), e não a do catálogo; quem pediu tem de ser alguém
  ativo da unidade do produto (`RegraDaRetiradaDeEstoque`, também no modal antigo: o D468, CORRIGIDO); acima do saldo, 409 com o
  saldo relido. O saldo novo volta ao catálogo da comanda, sem reler. Quem lança é a pessoa da sessão (na personificação, a
  assumida: o D629, que fica).
- **Onde está:** `module/ISConfiguracao/src/Regra/RegraDaRetiradaDeEstoque.php`, a ação `retirarDoEstoqueAction` do
  `Adm\ProdutosController` (e o 4º parâmetro do `produtoDaRota`, a guarda da tela que chama), a rota
  `adm-api-produtos/um/estoque/retirada/registrar`; na tela, `frontend/src/telas/RetirarDoEstoque.tsx`, a prop `retirada` da
  `ListaDeComandas` e do `Comanda`, e o `ComandasLigada`. Os testes: `RetiradaDeEstoqueNaComandaTest` (em processo, nos dois
  bancos), os casos novos de `RegrasDaRetiradaTest` e `RetiradaDeProdutosTest`, `RetirarDoEstoque.teste.tsx`, o bloco "retirar
  do estoque" do `ComandasLigada.teste.tsx`, os casos 192 a 196 do `ensaiar-recusas-adm-api.php` e a linha nova do
  `ensaiar-permissao-adm-api.php`.
- **O que fica para depois:** o custo gravado no item, a 74b. (O motivo da saída entrou na fatia 73, logo abaixo.)

**O motivo da saída ENTROU em 03/11/2026 (a fatia 73, PDV-MOTIVO-DA-RETIRADA; a pergunta 9 (a) do dono):** a saída pela tela de
estoque -- a antiga, a nova e a API -- passou a pedir o motivo (perda, vencimento ou acerto de inventário) e não sai sem ele; a
retirada de uso interno (o modal do PDV antigo e a comanda nova) não mudou para quem a lança, e passou a ficar registrada como
`uso`. A Retirada de Produtos, a partir da data (a linha `motivoDaRetirada` de `colunas_gravadas`), mostra as colunas Perda,
Vencimento e Acerto de inventário -- este "(fora do total)": dentro das saídas do estoque e das pessoas, 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; antes da data, as três colunas são nulas, como na F0. Contado na cópia (só SELECT, só
contagem): em 2026, 455 saídas pela tela de estoque (452 ativas; contadas todas, de 24 a 89 por mês, de janeiro a agosto), que ficam
"sem motivo", feitas por 23 pessoas; 543 retiradas de uso (541 ativas; contadas todas, de 54 a 126 por mês), que ficam "uso
interno" por quem pediu; e as 33.576 linhas de `produtos_estoque` com o motivo nulo. A seção 3 do `spec-retirada.md` está FEITA
inteira.

### Descontos (a fatia 50, 28/10/2026, a API; a fatia 51, 28/10/2026, a tela -- a D1, a D2 e a D3 de `docs/f3-relatorios/spec-descontos.md`)

O terceiro relatório: **quanto desconto a unidade deu no mês, de onde ele veio e com quem ficou**, e a lista dos itens com
desconto. A API entrou na fatia 50, e a tela nova na 51: desde ela, o item "Descontos" do menu novo abre a tela, e não mais a
tela não migrada (a seção "A tela", logo abaixo).

**As decisões do dono que ele aplica** (a 1.1 do desenho): o período é o mês do painel (a decisão 1); quem lançou o desconto
manual ainda não é gravado, e a tela o diz (a 2, o `AindaNaoGravado`; a coluna é da fatia do PDV, a D4); quem não é gerente
nem administrador é recusado (a 3 e a 4: não há linha própria, e o caixa cai na regra dos outros); a lista das vendas fica em
Vendas Realizadas, e o "ver as vendas" leva até ela (a 5); não há exportação, como hoje (a 10); o desconto sem motivo em venda
de convênio é convênio (D1); o motivo continua texto livre, agrupado sem acento (C4); e a régua do painel de 29/09/2026 (o
faturamento líquido, a cortesia à parte, os sentinelas fora das contagens de cliente, a sessão de pacote sem desconto).

**O total é o "Descontos concedidos" do painel, ao centavo** (a decisão 1): a mesma base (`VendaRepository::relatorioItens`, o
`painelItens` com o motivo e o convênio da venda) e o mesmo `ContasDoPainelDaUnidade::resumo`; o `DescontosTest` confere contra
o painel em processo, nos dois bancos. Cada número do cabeçalho vem com o do mês anterior (a fatia do desconto em pontos).

**A origem de cada desconto** (`ContasDosDescontos::origem`, a 1.3 do desenho): **cupom** (a marca em qualquer lugar do motivo),
**convênio** (o prefixo que o PDV escreve sozinho, ou o desconto sem motivo em venda de convênio, o gravado antes de
15/10/2026) e **manual** (o resto, o "convenio" e o "cupom" digitados à mão inclusive). **A linha do convênio com o desconto do
balcão** ("Convênio X; <o digitado>", a 177 (c)) **é dividida** -- a resposta 266 (c) do dono, de 25/10/2026: a parte do
convênio é a porcentagem dele **hoje** sobre o preço da linha, com o teto do desconto, e o resto é do balcão, que entra no
manual e no motivo digitado. O banco não guarda a porcentagem da época: se ela mudou depois da venda, a divisão sai errada, e
nada percebe (a tela o diz, no "Como os números são contados"). Sem a porcentagem de hoje, a linha fica inteira no convênio e conta à parte. **Na cópia e na
produção não há linha assim** (a cópia é de antes de 14/10/2026): a conta só se prova pelos casos.

**Os grupos e a lista:** por origem (sempre as três), por convênio (pelo convênio da venda, com o percentual de hoje; "sem o
convênio na venda" por último -- o D632), por colaborador (o do item; o desconto dele é o do painel) e por motivo (só a parte do
balcão, pela chave sem acento e sem caixa, igual nos dois bancos). A lista é um item com desconto por linha, do dia mais recente,
50 por página, com os filtros de origem, convênio, colaborador, motivo e "sem motivo", e a soma da lista filtrada -- que é a
linha do grupo do filtro (o fim do S24, o "Total vendas" que ignorava o filtro). Os nomes dos clientes, só os da página.

**Quem vê:** o gerente, a unidade da sessão; o administrador, a rede ou qualquer unidade ativa; quem não é nenhum dos dois, 403
`papel`. **A tela antiga aplica a mesma política** (`ISRelatorio\Controller\DescontosController::indexAction`), e o item do
menu antigo e o do novo também (`RecorteDoRelatorio::veOMenu` e a guarda `relatorioDescontos`). **Medido na cópia, só
contagens, sem as fixtures:** de 366 ativos, viam a tela antiga 115 (os 10 administradores, sem linha de privilégio, 104 dos 108
gerentes e 1 pessoa do grupo 1); pela política, 114 -- **perde a porta só essa uma pessoa do grupo 1**, como o desenho mediu em
02/10/2026 (a 1.5). Os 61 caixas que não são gerentes não tinham o privilégio.

**Os débitos que morrem com a tela antiga** (ela fica até o corte, com a guarda nova): o `DATE()` do período (13,8 a 14,1 s no
desenho), o `status` 2 que ela soma, o "Total vendas" que ignora o filtro (o S24) e o filtro de motivo que difere entre os
bancos (a família do D119).

**Onde está:** `module/ISRelatorio/src/Regra/ContasDosDescontos.php` e `RegraDosDescontos.php` (as contas e os filtros),
`Api/DescontosJson.php` (a resposta), `Controller/Adm/DescontosController.php` (`GET /adm/api/relatorios/descontos`,
`.../itens` e `.../unidades`); os repositórios `ClienteRepository::nomesPorId` e `ConvenioRepository::resumoPorId`. Os testes:
`RegrasDosDescontosTest` (sem banco) e `DescontosTest` (em processo, nos dois bancos).

**Medido** (a cópia, 128 MB de buffer pool, em processo, julho de 2026, três processos de cada; o primeiro despacho de cada
processo, frio, e o segundo, quente): o índice na unidade 39 em 1,08 a 1,24 s frio (0,15 a 0,18 s quente) e na unidade 1 em 1,16
a 1,41 s (0,27 a 0,31 s), com 20,3 e 29,9 MB de pico; a lista em 1,08 a 1,38 s frio (0,07 a 0,14 s quente); **a rede, o índice
em 2,53 a 2,67 s (1,35 a 1,45 s quente) e a lista em 1,62 a 2,37 s, com 122,5 MB de pico** (o D631, como a Retirada). O
oráculo em Python sobre as linhas cruas (as unidades 1, 10 e 39, julho e junho de 2026) bate em todas as 476 conferências, e
reproduz o desenho: a unidade 1 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 e 8 chaves de motivo. **A rede** (a verificação da 50: o primeiro oráculo não a
rodava), pelo mesmo oráculo sem o filtro da unidade e sem o painel, que recusa a rede: 1.228 conferências, 0 divergências, em
julho R$ 57.053,19, 4.206 itens, 119 linhas de convênio, 128 colaboradores e 199 chaves (junho R$ 50.784,55, 3.752 itens, 115
linhas, 134 colaboradores e 215 chaves). **A tela antiga, para comparar** (a rodada que a D2 pede, em processo e sem senha, como
administrador, o mesmo mês da unidade 1, `data_inicio=01/07/2026&data_fim=31/07/2026&unidade=1`: 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 `DATE()` do período; o desenho media 13,8 a 14,1 s pelo HTTP, com a máquina carregada.

#### A tela (a fatia 51, 28/10/2026; a D3)

**O que se vê:** o cabeçalho com o mês (‹ ›) e, para o administrador, a escolha da unidade, com "todas as unidades"; os nove
números do mês contra o anterior (o desconto e as fatias sem a cor de bom ou ruim, como o desenho -- e, desde 29/10/2026, a
resposta 270 (b) do D633, também os itens, as vendas e os clientes com desconto; o faturamento delas com a cor --, as fatias em
pontos, "5 de 8 atendimentos"); **"De
onde veio"**, uma barra das três origens (só desenho: a tabela logo abaixo diz o mesmo) e a tabela, com o "ainda não gravado"
de quem lançou o manual e, quando há, a nota das **linhas divididas** -- quantas linhas do convênio tinham também desconto do balcão, quanto foi a
cada lado, quantas foram divididas pela porcentagem de hoje (essas contam em cada origem em que têm parte, e uma vez só nos
itens) e quantas ficaram inteiras no convênio sem a porcentagem de hoje; **por convênio**, **por
colaborador** e **por motivo (só o manual)**, dez linhas e o "mostrar todos (N)", cada linha com o "ver os itens", que aplica o
filtro dela à lista e leva o foco ao título da lista; e **os itens com desconto**, com os filtros (origem, convênio -- "Sem o
convênio na venda" manda `sem` --, colaborador e motivo -- "Sem motivo" manda `semMotivo` --), o "limpar os filtros", a soma
("N itens · desconto R$ x · preço cheio R$ y"; **com o filtro que escolhe uma parte da linha dividida, "desconto do convênio" ou
"desconto do balcão"**, o item 7 da 266 (c)), a tabela -- a linha dividida com as duas partes, "convênio R$ 17,98 + balcão
R$ 10,00", e o motivo inteiro; o cliente com o link da ficha em outra aba (a resolução do item 2 da seção 2 do `spec-ordem.md`)
-- e a paginação de 50. O "Como os números são contados" tem o texto do desenho, com a frase do dono no lugar da do "inteira no
convênio": **a divisão usa a porcentagem de hoje**.

**Os pedidos:** os números, e só **depois** deles a lista, com o mês e a unidade que **eles** aplicaram; a lista só vale com o
mesmo recorte e o mesmo eco de filtros e página. Trocar o mês ou a unidade zera os filtros e volta à página 1, e com eles no ar os "ver os itens", os
`<select>`, o "limpar" e a paginação desligam; trocar um filtro volta à página 1. Outra página ou outros filtros releem com a
lista de antes na tela, marcada, e a paginação desligada; a página que falha leva o foco ao título da lista, e o "limpar os
filtros" também. A recusa não pede a lista, e a falha da lista não derruba os números (o "tentar de novo" é só dela). **Na
rede**, o índice e a lista dividem a mesma trava no servidor, e por isso a mesma fila da tela (`descontos`, em `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 Painel abre os Descontos:** o cartão "Descontos concedidos" ganhou o "de onde veio", com o mês e a unidade dos números na
tela (a unidade só vale para o administrador), só a quem abre o relatório.

**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 é a 0, todas -- só a quem abre Vendas Realizadas. A condição "só na unidade da
sessão" da D3 caiu com a C5 (a fatia 45), que deu a unidade à listagem. **E a frase de Vendas Realizadas diz a unidade pelo
nome** (o D623, CORRIGIDO nesta fatia): `GET /adm/api/vendas` devolve no `recorte.unidade` o nome do cadastro da unidade que
APLICOU, só quando a lista veio com o filtro `unidade` (uma consulta pequena, que a lista do menu não paga); sem o filtro, e na
unidade sem nome no cadastro, o nome é nulo, e a frase diz o número, como antes.

**Onde está** (`frontend/src`): `api/descontos.ts` (os tipos espelho da resposta -- onde a resposta e o desenho divergiam, vale a
resposta: o `divididas`, as `partes` só da dividida, a `parte` da lista, o colaborador nulo do item --), `telas/Descontos.tsx`
(a vista), `telas/DescontosLigado.tsx` (os pedidos), `telas/listaDosDescontos.ts` (a regra pura da lista: o pedido pelo recorte,
o eco, os valores dos `<select>`, as frases e os filtros do "ver as vendas"), as entradas `descontos.*` do `glossario.tsx`, a
linha da `TELA_DO_RELATORIO` e o "de onde veio" do `Console.tsx`, e o `aoVerDescontos` do `Painel.tsx` e do `PainelLigado.tsx`. O
`usePedidoDeDepois` passou a dar ao `ler` o `aindaQuer`, como o primeiro gancho, para a lista andar na fila. No servidor, só o
D623: `ISVenda\Controller\Adm\VendasController::recorteComONome`. A prévia é a `Descontos` (oito células: a `ListaRelendo` entrou na verificação), com o cenário em
`.design-sync/previews/_relatorios.ts`.

**Provado** (a fatia é ímpar: só os testes do que ela toca; o ciclo inteiro fica para a 52): os casos do front da D3 e os
vizinhos (o `DescontosLigado.teste.tsx`, o `Descontos.teste.tsx`, o `listaDosDescontos.teste.ts`, o `api/descontos.teste.ts`, o
`ConsoleAbreDescontos.teste.tsx`, os do Painel, de Vendas Realizadas, do Console, do menu e do glossário); o D623 nos dois
bancos (o `ListagemDeVendasTest`); os mutantes P51-P1 a P51-P10 (os MD29 a MD38 do desenho), menos o P51-P5 (o MD33), que não se aplica
(a condição da unidade da sessão saiu com a C5; o P51-X6 fica no lugar), e P51-X1 a P51-X21, um por vez, vermelhos pela
asserção, os do servidor nos dois bancos; e, na verificação, os P51-X22 a P51-X36, dos onze achados confirmados (sete distintos), todos tratados (o
`docs/roadmap.md`, a entrada da fatia 51). **Falta o navegador**
(a lista "Navegador" da D3, o lançamento pelo PDV, o oráculo do banco de contrato e os 375 px).

**A D4, quem lançou o desconto manual -- FEITA em 03/11/2026 (a fatia 71, PDV-DESCONTO-LANCADO-POR, com a P2 do PDV).** O PDV
grava quem digitou o desconto (`vendas_produtos.desconto_lancado_por`, quem ENTROU, também na personificação), e as cópias têm a
linha `descontoManual` de `colunas_gravadas` desde 03/11/2026. O índice ganhou `porQuemLancou` (`ContasDosDescontos::porQuemLancou`:
só a parte manual -- a do balcão da linha dividida também --, uma linha por quem lançou, a "não registrado" com o de antes da
data e o sem autor, por último; `null`, e não a lista vazia, quando o mês inteiro é de antes da data); cada item, `lancadoPor`
(só o com parte manual); a lista, o filtro `lancadoPor` (o id, ou `sem`), que escolhe a parte manual. Os nomes são os do cadastro
(`UsuarioRepository::nomesDoCadastro`, da 70: o `nomesPorId` do desenho não nasceu), na rede também. Na tela: o "Gravado desde
dd/mm/aaaa; antes disso, sem registro." no lugar do "ainda não gravado", o grupo "Por quem lançou (só o manual)", o filtro
"Lançado por", a coluna "Lançado por" na lista ("não registrado" no manual sem registro, o traço no convênio e no cupom --
desde a 74c, o nome também no cupom que tem o registro: a resposta 278 (a) do dono, o D680), e o
"Como os números são contados" com o "lançado por" (`ITENS_DOS_DESCONTOS_COM_A_DATA`). Sem a data, nada disso aparece. O
Descontos não exporta arquivo (a pergunta 10), e por isso não há coluna em arquivo. Na cópia, todo desconto manual até agosto
de 2026 fica "não registrado" (9.500 itens em 2026); julho, todo antes da data, sai com o grupo nulo (o oráculo da cópia).

### Vendas Canceladas (a fatia 52, 28/10/2026, as contas; a fatia 53, 28/10/2026, a API e a tela -- a V0 e a V1 de `docs/f3-relatorios/spec-canceladas.md`)

O quarto relatório: **do que a unidade fechou no mês, quanto foi desfeito, e se foi refeito no mesmo dia; e do que ela abriu,
quanto nunca virou venda** -- comanda duplicada, abandonada ou do balcão. As contas e as consultas entraram na 52 (sem rota), e
a API, a tela, o menu e a tela antiga com a política na 53: desde ela, o item "Vendas Canceladas" do menu novo abre a tela nova.

**A base e o tipo** (a 1.1 do desenho): entra a venda de `status = 0` (só o `VendaService::cancelar` e a rotina gravam o zero; a
comanda aberta é o 3 -- a anotação contrária estava errada, o D636), do dia dela no mês, da unidade de quem está na venda. **O
tipo sai do caixa:** com caixa é **venda desfeita** (foi fechada e cancelada depois; conta no dia do fechamento); sem caixa é
**comanda não fechada** (conta no dia em que foi aberta). **A venda cancelada sem item fica fora** dos números, contada à parte
("Fora da conta", o D638). O mês é o da venda: o dia do cancelamento e quem o autorizou ainda não são gravados (a fatia do PDV,
a 70), e a tela o diz (o `AindaNaoGravado`).

**O dicionário** (a 1.3; julho de 2026, junho entre parênteses, a unidade 1 e a rede da cópia, que o oráculo da 53 reproduz):

| Número | Definição | Unidade 1 | Rede |
|---|---|---|---|
| Atendimentos (a base) | os do painel no mês | 3.101 (2.824) | 20.163 (17.749) |
| Vendas desfeitas | canceladas com caixa e com item; o valor é o dos itens, depois do desconto | 7, R$ 848,00 (10, R$ 1.107,00) | 178, R$ 17.722,25 (170) |
| Corrigidas | a desfeita cujo cliente teve, no mesmo dia e em qualquer unidade, outra venda fechada com o mesmo valor de itens ou os mesmos produtos (a regra c) | 2, R$ 240,00 | 108 |
| Sem nova venda | as outras desfeitas, as do balcão dentro | 5, R$ 608,00 | 70 (11 do balcão) |
| Pela regra larga | qualquer outra venda fechada do cliente no dia (a regra b) | 3, R$ 410,00 | 131 |
| Comandas não fechadas | canceladas sem caixa e com item; o valor é o teto do que estava aberto, e não receita perdida | 62, R$ 7.197,00 (35) | 316, R$ 31.586,00 (237) |
| Duplicadas / abandonadas / do balcão | outra venda fechada do cliente no dia / nenhuma / o avulso ou o "SEM CADASTRO" | 27 / 33 / 2 | 132 / 162 / 22 |
| Pela rotina / à mão | o motivo é uma das frases da rotina, ou não | 62 / 0 | 257 / 59 |
| As taxas | desfeitas ÷ (atendimentos + desfeitas); comandas ÷ (atendimentos + comandas); valor desfeito ÷ (faturamento + valor desfeito) | 0,2% / 2,0% / 0,3% | 0,9% / 1,5% / 0,9% |

**As tabelas** (a 1.4): **por unidade**, só na rede, pela taxa exata de comandas (a multiplicação cruzada, e nunca o texto de
uma casa), depois a de desfeitas e o id; **por motivo**, os à mão agrupados sem maiúscula, acento e espaço (dez grupos, e
"Outros N motivos"), e depois as da rotina, nos três rótulos fixos; **por quem fechou ou abriu** (o usuário da venda: na
desfeita, quem fechou; na comanda, quem abriu -- não é quem cancelou), com a unidade dele só na rede. **A classificação olha a
rede inteira e devolve só a classe:** nenhum id, valor ou unidade da outra venda sai na resposta (o `VendasCanceladasTest` procura
o id dela no JSON).

**Quem vê:** o gerente, a unidade da sessão; o administrador, a rede ou qualquer unidade ativa; quem não é nenhum dos dois, 403
`papel`. **A tela antiga aplica a mesma política** (`ISRelatorio\Controller\VendasCanceladasController::indexAction`), e o item
do menu antigo e o do novo também (`RecorteDoRelatorio::veOMenu` e a guarda `relatorioCanceladas`). **Medido na cópia, só
contagens, sem as fixtures:** de 366 ativos, viam a tela antiga 118 (os 10 administradores, 104 do grupo 3, 3 do grupo 7 e 1
pessoa do grupo 1); pela política, 117 -- **perde a porta só essa uma pessoa do grupo 1**, como o desenho mediu (a 1.2). Nenhum
caixa que não é gerente tinha o privilégio. **A rede é um pedido de cada vez por pessoa** (o 429 com a frase do Parceiro) e anda
na fila `canceladas` da tela.

**Onde está:** `module/ISRelatorio/src/Regra/ContasDasVendasCanceladas.php` (as contas, a 52), os repositórios
`VendaRepository::canceladasVendas` e `::canceladasVendasDoDia` (a 52), `Api/VendasCanceladasJson.php` (a resposta) e
`Controller/Adm/CanceladasController.php` (`GET /adm/api/relatorios/canceladas` e `.../unidades`, sem exportação: a pergunta 10).
No front, `api/vendasCanceladas.ts` (o espelho da resposta: onde a resposta e o desenho divergem, vale a resposta -- o motivo sem
a divisão entre desfeitas e comandas, a rotina só com as frases que têm venda), `telas/VendasCanceladas.tsx` (a vista),
`telas/VendasCanceladasLigado.tsx` (o pedido, as unidades do administrador e o "ver as vendas"), as entradas `canceladas.*` do
`glossario.tsx`, a linha da `TELA_DO_RELATORIO` do `Console.tsx` e a fila `canceladas` do `filaDaRede.ts`. Os testes:
`RegrasDasVendasCanceladasTest` (sem banco), `ConsultasDasCanceladasTest` e `VendasCanceladasTest` (nos dois bancos, com o cenário
comum de `CenarioDasCanceladas`), e os do front (`VendasCanceladas.teste.tsx`, `VendasCanceladasLigado.teste.tsx`,
`ConsoleAbreCanceladas.teste.tsx`). A prévia é a `VendasCanceladas` (sete células).

**O "ver as vendas"** abre Vendas Realizadas no período dos números, com a situação "canceladas", **na unidade do recorte** -- a
rede é a 0 --, só a quem abre Vendas Realizadas, e desliga com outro mês ou outra unidade no ar. A condição "só na unidade da
sessão" do desenho (o MV26 e o débito V-e) caiu com a C5, como nos Descontos. Ao lado, a frase: "A lista de lá mistura as
desfeitas, as comandas e as vendas sem item, e soma o total de cada venda." **Ela é lenta:** 4,8 s para as 74 canceladas de julho
da unidade 1 na cópia, pelo `DATE()` da listagem (o D637). E o gerente que também tem a marca de caixa era travado lá nas ativas
de hoje (o D639; nenhum grupo real tem as duas marcas) -- desde 29/10/2026, a resposta 271 (c), a marca de gerente vence a de
caixa em Vendas Realizadas, na API e na tela antiga, e o "ver as vendas" dele cai no mês inteiro.

**Medido** (a cópia, 128 MB de buffer pool, a resposta inteira da API em processo, julho de 2026 com junho, três processos): a
unidade 1 em 1,52 a 1,64 s fria (0,24 a 0,29 s quente) e 31,3 MB; a unidade 39 em 1,30 a 2,31 s fria e 21,1 MB; **a rede em 2,48
a 2,68 s fria (1,31 a 1,33 s quente) e 125,5 MB de pico** -- a base lida um mês de cada vez tirou o pico dos 238,5 MB da 52 (o
D634, agora PARCIAL: a base da rede custa ~1,2 s quente, e o pico fica perto dos 128 MB de fábrica). 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.

**O que espera a fatia do PDV** (a 3 do desenho; a fila a põe na 70): as colunas `vendas.cancelado_em` e `cancelado_por`, o bloco
"Cancelamentos no período" pelo dia do cancelamento, as que mudaram um mês fechado, e o `gravadoDesde.cancelamento` com a data.
**FEITO em 03/11/2026 (a fatia 70, PDV-CANCELAMENTO):** o cartão "Cancelamentos no período" (desfeitas, comandas, as que mudaram
um mês fechado, no dia do fechamento ou depois, e "Por quem autorizou", com a "Rotina"), pela consulta
`canceladasPeloCancelamento`, a partir da data de `colunas_gravadas` (nas cópias, 03/11/2026); antes dela, nulo. A leitura sem
índice (o D677). A seção 3 do `spec-canceladas.md` diz o que divergiu.

### Royalties (a fatia 54, 28/10/2026 -- a R1 de `docs/f3-relatorios/spec-royalties.md`)

O quinto relatório da família, e o primeiro só do administrador: **quanto cada unidade deve de royalty no ciclo da fatura, de
onde vem o número, quanto foi faturado, e por que os dois diferem, dia a dia** (a decisão 12: "os dois, com a diferença
explicada dia a dia"), com a fatura de cada unidade no Asaas ao lado (a decisão 13, o lado do administrador; o do gerente é a
R2, a fatia 55, FEITA em 28/10/2026: ver "As Faturas do gerente", logo depois desta subseção). Desde a 54, o item "Royalties" do
menu novo abre a tela nova; a tela antiga e o item do menu antigo aplicam a mesma política.

**As decisões aplicadas:** o ciclo de 16 a 15 (a 1), pelo mês em que COMEÇA (`?ciclo=2026-09` é de 16/09 a 15/10 e vence em
29/10; o padrão é o que vence no mês de hoje; o piso, `2024-01`; o ciclo que ainda não começou é 422); só o administrador
(a política `royalties`, `soAdministrador`: o gerente, o caixa e os outros recebem o 403 `papel`, e o privilégio
`relatorios/franquias/ler` continua conferido); sem exportação (a 10, "como hoje"); a base depois do desconto (a RC1: os
descontos ao lado, fora da base); nada escreve (a RY1). **As respostas do dono que ela segue:** a 134 (b) ("Só a do royalty"):
a alíquota do ciclo é a da vigência (`RegraDosRoyalties::aliquotaDoCiclo` sobre `royalties_aliquotas`), e o cadastro só sem
linha -- desde o D368 o cadastro guarda a taxa nova, que vale do próximo dia 16; a 98 (a) (a trava das faturas, e a emissão em
qualquer chamada): a rotina não muda aqui, e o relatório conta as faturas repetidas de um vencimento (`faturasRepetidas`) em vez
de supor a unicidade que o banco não tem (o D29). 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: ela é da tela de Unidades (o D366),
e o relatório não edita o faturamento. O link da cobrança só mostrado (`asaas_link_cobranca`, como a tela antiga das Faturas)
vem da 1.7 do desenho -- nada aqui fala com o Asaas.

**Os números** (a 1.4 do desenho; o dinheiro em centavos, um arredondamento só por soma, o `round` do PHP):

| Número | Como se conta |
|---|---|
| Base por categoria | o faturamento do painel (`relatorioItens`, as concluídas, fechadas por alguém da unidade) dividido pela categoria do royalty: Serviços (`ser` e `pac`), Prod. Barbearia, Prod. Empório, Lava VIP e Sem alíquota (o resto, como o insumo vendido) -- as cinco somam o faturamento do painel |
| Alíquota | a do ciclo (a vigência), e o cadastro sem linha |
| Royalty bruto | por dia, a base de cada categoria × a alíquota; somado e arredondado uma vez |
| Crédito do Prog. de Fidelidade | o recebido na forma 6 pelas vendas finalizadas da unidade; a taxa da rede é total ÷ total |
| Abatimento | por dia: bruto × crédito ÷ faturamento, só com os dois acima de zero (como o retrato) |
| Devido | Σ (bruto − abatimento), arredondado uma vez; NULO quando a alíquota da época não confere com o retrato (a 1.5) |
| Faturado | a soma do retrato do ciclo (`royalties.ret_total`, as repetidas inclusive), convertida em centavos uma vez |
| Fatura | a de menor id do vencimento; as outras são `faturasRepetidas`; `semFatura` quando há faturado e não há fatura depois do dia 16, do `2024-03` em diante; o `quitada` não é lido (o D640) |
| Diferença | devido − faturado: positiva, por faturar; negativa, faturado a mais |

**Os motivos de cada dia com diferença** (a 1.6, nesta ordem): `emApuracao` (ontem e hoje, que a rotina das 05:25 refaz),
`semRetrato`, `retratoZerado`, `canceladaDepois` (o retrato tem mais base, e as finalizadas e hoje canceladas do dia -- só as com
caixa -- somam ao menos a diferença; o instante do cancelamento é a R4), `credito` e `outra`. **Contados pelo oráculo na cópia
(28/10/2026):** o ciclo `2026-07` da rede tem 128 dias com diferença (15 `canceladaDepois` e 113 `credito`, o D181: a cópia não
tem os pagamentos da forma 6 desse ciclo), a base de R$ 1.871.986,32, o devido de R$ 76.574,10, o faturado de R$ 76.319,36 e 26
faturas de R$ 76.319,36 -- o faturado bate com as faturas ao centavo pelo `round` do PHP; os R$ 76.319,34 do desenho eram o
`ROUND` do MySQL, que arredonda o meio centavo do `double` para o par, e é a mesma causa do devido (o desenho dava 76.574,02 e a
diferença da rede 254,68; o código dá 76.574,10 e 254,74, em centavos, o meio centavo para cima uma vez por unidade); a unidade 1
tem 18 dias com diferença, e não os 6 do desenho: 6 `canceladaDepois` e 12 `credito` (o retrato dela tem crédito em 16 dias do
ciclo e a cópia nenhum pagamento na forma 6, o D181; os 4 dias com as duas diferenças são `canceladaDepois`, que vem antes); o `2025-12` tem os 27 `retratoZerado` do 10/01/2026 (168
dias, a diferença de R$ 4.043,13); o `2024-10` tem 5 das 44 unidades com a alíquota da época (o devido nulo nelas, e 39 de 44
conferidas).

**Onde está:** `RegraDosRoyalties` (o ciclo, a faixa, o vencimento, a categoria), `ContasDosRoyalties` (pura: o dia vivo, o
retrato por dia, a conferência da alíquota, o devido, o faturado, as diferenças, a linha e a rede), `RoyaltiesJson`,
`Adm\RoyaltiesController` (`GET /adm/api/relatorios/royalties` e `.../unidades`; a lista do filtro soma as desativadas com
retrato ou fatura desde 16/01/2024, com " (desativada)"), as consultas `VendaPagamentoRepository::royaltiesCreditoDeFidelidade`,
`RoyaltiesRepository::royaltiesDoCiclo`, `RoyaltiesFaturaRepository::royaltiesDoVencimento`, `UnidadeRepository::royaltiesUnidades`
e `::royaltiesDesativadasComMovimento`; o `RecorteDoRelatorio::ciclo`; na tela, `Royalties.tsx`, `RoyaltiesLigado.tsx`,
`textosDosRoyalties.ts` (as situações da fatura, que a R2 importa), `api/royalties.ts`, o `PeriodoDoCiclo` e a prop `rotulos` da
`NavegacaoDoMes` (o ciclo anda como um mês, com o texto "16/07 a 15/08/2026 · vence 29/08").

**Medido** (o D646): a rede do `2026-07` em 1,70 a 1,91 s fria e 0,64 a 0,87 s quente, com 75,9 MB de pico; a unidade 1 em 1,05
a 1,19 s fria, 22,8 a 23,1 MB. Os itens da rede são lidos em duas metades e somados por unidade e dia: lidos de uma vez, o pico
chegava a 140,3 MB (o `2024-10`). A trava da rede por pessoa (o 429) e a fila `royalties` da tela, como na Retirada, nos
Descontos e nas Canceladas. A tela antiga: de 366 ativos, viam 10 e veem 10 (os administradores) -- ninguém perde a porta (o D643).

**Da verificação** (28/10/2026): o dia 15, o último do ciclo, conta como em andamento (`hoje + 1 dia <= fim`; com o `<` do
desenho, o ciclo de hoje saía fechado no dia 15 e o › levava ao 422); na rede, a linha "Todas as unidades" e o cartão da
diferença dizem de quantas unidades conferidas são o devido e a diferença, e sem nenhuma conferida a linha da rede mostra o
travessão; o resumo das faturas diz quantas têm erro do Asaas, contadas pela situação gravada; o "abrir a cobrança" e o "N dias"
levam o nome da unidade para o leitor de tela.

**A R4 (03/11/2026, a fatia 70, PDV-CANCELAMENTO):** a partir da data do cancelamento, o dia `canceladaDepois` é exato pelo
`escrito` do retrato, e aponta cada venda com o instante e quem autorizou (`canceladas.lista`, e a contagem e o valor são os
dela); antes dela, a soma da R1. A RY1 continua. A venda de dia antigo cancelada depois da data fica na soma: a resposta 279
(b) do dono (03/11/2026, a fatia 74c), "deixar como está".

### As Faturas do gerente (a fatia 55, 28/10/2026 -- a R2 de `docs/f3-relatorios/spec-royalties.md`)

O outro lado da decisão 13: **o gerente vê, em cada fatura de royalties da unidade dele, a base do ciclo que ela cobra, por
categoria, a alíquota efetiva e o royalty, o bruto, o crédito do Prog. de Fidelidade, o abatimento e o valor da fatura** -- "sem
abrir os números da rede": o recálculo de hoje e a diferença dia a dia ficam no Royalties, que é só do administrador. Não é
relatório: não tem política, período nem escolha de unidade, e não entra na `TELA_DO_RELATORIO`; o item Faturas do menu do
usuário (`CAMINHO_DAS_FATURAS`, `exige: "gerente"`) abre a tela nova pelo `ehFaturas` do Console, e a tela antiga fica como está
até o corte.

**A regra:** a guarda é a sessão e a marca de gerente, como a tela antiga (`apenasSessao()` e `usuarioGerente`; sem
funcionalidade no ACL); a unidade é sempre a da sessão; o ciclo da fatura é o mês ANTERIOR ao do vencimento
(`RegraDosRoyalties::cicloDoVencimento`: a que vence em 29/10/2026 cobra de 16/09 a 15/10, o `2026-09`); a base é o retrato do
ciclo, o número que gerou a fatura (`ContasDosRoyalties::faturado`), e não o recálculo de hoje; a lista é a da tela antiga, de 20
em 20, da mais nova para a mais velha; as situações com as palavras dos Royalties (o D645 PARCIAL: a antiga continua em branco).
A fatura sem vencimento gravado (a coluna aceita nulo; nenhuma na cópia) vem sem ciclo e sem base, nulos, e a tela diz por quê;
a página não cai.

**Onde está:** `GET /adm/api/faturas` (`Adm\FaturasDaUnidadeController`, `FaturasDaUnidadeJson`), a `RegraDosRoyalties`
(`cicloDoVencimento`, `paginaDasFaturas`), a `listagemIndex` com o `asaasErro`; na tela, `api/faturas.ts`, `Faturas.tsx`,
`FaturasLigado.tsx`, o gancho `pedidoDasFaturas.ts` e a regra pura `baseDaFatura.ts`.

**Medido:** 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 (o D647). Na cópia, de 366 ativos, 118 têm a marca de gerente e veem a tela antiga e a nova -- ninguém perde a porta. O
oráculo: as 890 faturas das 37 unidades contra o JSON do controller, 24.269 conferências, 0 divergências; o líquido do retrato é o
valor da fatura nas 890.

### O NPS (a fatia 56, 28/10/2026, a API; a fatia 57, 29/10/2026, a tela -- a N1 e a N2 de `docs/f3-relatorios/spec-nps.md`)

A decisão 8: **três médias de 0 a 10 -- a recepção, a experiência e os colaboradores --, cada uma com o número de respostas e as
faixas inteiras (0 a 6, 7 a 8, 9 a 10), e os clientes com alguma nota até 6.** A pesquisa não pergunta "recomendaria?", e por
isso nenhum número de −100 a 100. O mês é o da resposta (N1); na rede, só a unidade com 30 envios ou mais tem a média destacada
(N2); a nota do caixa entra (N3); os 12 meses de cada colaborador ficam no Profissional Parceiro (N5). A exportação é o TXT "como
hoje" (a decisão 10): `primeiroNome;telefone`, com `;email` só para o administrador.

**O dicionário** (a seção 1 do desenho; julho de 2026, a unidade 1 e a rede, conferidos pelo oráculo da N1 nas duas cópias):

| Número | Definição | Unidade 1 | Rede |
|---|---|---|---|
| Envios | as pesquisas do mesmo cliente, na mesma unidade, com as mesmas notas e até 60 s uma da outra são um envio (a E1) | 42 | 296 |
| Pesquisas gravadas | uma por venda avaliada | 42 | 317 |
| Clientes que responderam | distintos, sem os sentinelas | 40 | 290 |
| Taxa de resposta | dos atendidos do mês (a conta do painel), quantos responderam (a E2) | 1,8% (40 de 2.235) | 2,0% (290 de 14.532) |
| Recepção / Experiência | média por envio, com as respostas | 9,60 · 42 / 9,90 · 42 | 9,87 · 296 / 9,90 · 296 |
| Colaboradores | média de todas as notas, como o Parceiro | 10,00 · 44 | 9,93 · 387 |
| Clientes por faixa | a pior nota do cliente no mês | 1 / 3 / 36 | 4 / 10 / 276 |
| Unidades (só na rede) | as ativas e as com resposta (a E4); abaixo de 30 envios, a média apagada | -- | 36; 9 com resposta, 4 com 30 ou mais |

**As escolhas do desenho, com o caminho de volta:** E1, o envio pela cadeia de 60 s (`RegraDoNps::JANELA_DO_ENVIO = 0` dá a
regra do segundo); E2, a taxa pela interseção com os atendidos (tirar o `array_intersect_key` de `ContasDoNps::taxaDeResposta`);
E3, a lista abre sob pedido, com os filtros da faixa da pior nota e do colaborador; E4, sem taxa por unidade na rede (a coluna já
existe desde 13/10/2026, e mostrar a taxa de cada unidade é a pergunta 272 do dono, o D656, ainda sem resposta); E5, o "ver
histórico" abre o histórico do Parceiro dentro da tela, só para quem abre o Parceiro; E6, o nome do cliente sem link -- superada
pelo `spec-ordem.md` (1.1, item 10): o nome abre a ficha do ERP em outra aba (`LinkDaFicha`).

**Quem vê:** a política `nps` -- o gerente a unidade dele, o administrador a rede ou uma unidade, os outros 403 `papel`, o 0, a
inativa e a inexistente 422. **A tela antiga (`PesquisasNpsController`) aplica a mesma política nas quatro ações** (a tela, os dois
detalhes e o TXT) desde a 57: medido na cópia em 29/10/2026, só contagens e sem as fixtures, de 366 ativos **viam 117 e veem 116**
-- só a pessoa do grupo 1 que o desenho previa (a seção 0 dele) perde a porta, que já não tinha o item do menu antigo desde a 56.

**Onde está:** `GET /adm/api/relatorios/nps`, `.../meses`, `.../clientes`, `.../unidades` e `.../exportacao?formato=txt`
(`Adm\NpsController`, `NpsJson`, `RegraDoNps`, `ContasDoNps`; a base `NpsItemRepository::BASE_DAS_RESPOSTAS`, a do Parceiro); na
tela, `api/nps.ts`, `Nps.tsx`, `NpsLigado.tsx` e a regra pura `listaDoNps.ts`, a linha `[CAMINHO_DO_NPS]` da `TELA_DO_RELATORIO` do
Console, a fila `nps` do `filaDaRede.ts` (só o mês da rede, a única ação que o servidor trava) e as doze chaves do glossário
(`ITENS_DO_NPS`).

**Medido na N1:** o mês da rede de 1,60 a 2,27 s frio e 0,37 a 0,52 s quente, 73,6 a 94,5 MB com os itens em duas metades (o
D655); a unidade 1 de 0,60 a 1,40 s frio e 0,06 a 0,11 s quente; o oráculo em Python contra o JSON do controller, 1.828
conferências, 0 divergências. Na N2, o pacote do front subiu de 326,27 KB para 332,76 KB comprimido.

**O que ficou de fora:** o Yungas (N4) e a rotina `npsContabilizar`, que esperam a decisão de aposentá-los; aposentar a tela
antiga, que segue com o D651, o D652 e o D653 até sair; o cartão no painel (N6); e a taxa por unidade na rede (a 272).

### O Programa de Indicação (a fatia 58, 29/10/2026, as contas; a fatia 59, 30/10/2026, a API, a tela e a unidade do convite -- a I0, a I1 e a I2 de `docs/f3-relatorios/spec-indicacao.md`)

As decisões 14 e I1 a I3: **três portas -- o aplicativo, o formulário e a origem --, os cartões contam cada evento no seu mês, e a
lista conta os convites do mês com a situação de hoje.** A unidade do convite é a da última compra de quem convidou antes do
convite, ou a do cadastro dele (o ponto I2); a comissão e a adesão ficam na unidade gravada, que é a da venda. A exportação é
"como hoje" (a decisão 10): nenhum arquivo, e o telefone dos dois clientes na lista.

**O dicionário** (julho de 2026, conferido pelo oráculo da I1 nas duas cópias, contra o JSON do controller):

| Número | Definição | Unidade 1 | Rede |
|---|---|---|---|
| Convites | as linhas de `indicacoes` com o convite no mês (aplicativo; desde 10/10/2026 também o amigo do formulário com a ligação, até a porta ser gravada) e os amigos com a marca "Indicação - N" e sem a ligação (formulário), pela unidade do convite | 11 (3 e 8) | 33 (3 e 30) |
| Adesões | as do aplicativo no mês; as do formulário não são gravadas até a I3 | 0 | 0 |
| Comissão creditada | as linhas com a venda no mês, em quatro parcelas (na estreia, sem serviço, na venda hoje cancelada, sem a venda registrada) | R$ 0,00 | R$ 0,00 |
| Clientes novos por indicação | dos novos do painel no mês, os que vieram por uma das três portas | 1 de 57 (0, 0 e 1) | 218 de 1.403 (0, 1 e 217) |
| Pontos da indicação | os 10 pontos por compra do amigo sem a ligação, nas vendas da base | 0 | 10 em 1 venda |

**Quem vê:** a política `indicacao` -- o gerente e o caixa (a resposta 3 do dono, "fica como está") a unidade da sessão, o
administrador a rede ou uma unidade ativa, os outros 403 `papel`, o 0, a inativa e a inexistente 422. **A tela antiga
(`IndicacoesController`) e o item do menu antigo aplicam a mesma política** desde a 59: medido na cópia em 30/10/2026, 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 deles é caixa sem ser gerente, e continua vendo).

**Onde está:** `GET /adm/api/relatorios/indicacao`, `.../clientes` e `.../unidades` (`Adm\ProgramaDeIndicacaoController`, que chama o
`Service\ProgramaDeIndicacao` da 58; `ContasDoProgramaDeIndicacao`, `ProgramaDeIndicacaoJson`, `MarcaDaIndicacao`, e
`VendaRepository::programaUltimaUnidadeAntes` para a I2); na tela, `api/indicacao.ts`, `ProgramaDeIndicacao.tsx`,
`ProgramaDeIndicacaoLigado.tsx` e a regra pura `listaDaIndicacao.ts`, a linha `[CAMINHO_DA_INDICACAO]` da `TELA_DO_RELATORIO` do
Console, a fila `indicacao` do `filaDaRede.ts` (os números de qualquer recorte e os clientes da rede, que dividem a trava: desde a
I2 os números da unidade também leem a rede) e as nove chaves do glossário
(`ITENS_DA_INDICACAO`).

**Medido na 59** (o D660): os clientes novos da rede de 28 a 35 s no MySQL da cópia (o buffer pool de 128 MB) e de 2 a 4 s no
PostgreSQL; os números da unidade 1 de 3,8 a 4,6 s quentes no MySQL, porque a I2 lê os amigos do formulário da rede inteira, a
consulta sem índice que a I3 tira; pico de 53,3 MB em dezembro de 2025. O oráculo em Python contra o JSON do controller: 2.550
conferências, 0 divergências. O pacote do front subiu de 332,86 KB para 336,12 KB comprimido.

**O que ficou de fora:** a porta do convite (`indicacoes.porta`, o passado dos 183 e a linha do amigo de quem indica inativo; a I3,
a fatia 75); a aposentadoria da tela antiga (a I4); a base da comissão (o item 1 do D659); e a frase da recusa pelo papel, que não diz
o caixa (o D662; consertada em 08/11/2026, a fatia 122).

### O Caixa (a fatia 60, 31/10/2026, as contas e as consultas; a fatia 61, a API e a tela antiga; a fatia 62, a tela -- a CX0 a CX3 e a CX5 de `docs/f3-relatorios/spec-caixa.md`)

As decisões 1, 3, 4, 10, 15, +2 e 2, e o "fica para depois" CX1: **cada caixa entra pelo dia em que foi aberto, e tudo o que passou
por ele conta nele; a unidade é a do operador pelo cadastro de hoje** (a régua do painel). O período é um intervalo de até 31 dias
(padrão: hoje); o caixa que não é gerente vê só o dia de hoje da unidade da sessão; nenhuma exportação (a tela antiga não exportava).
A devolução de crédito é uma retirada (a resposta 89 (b)), dentro do saldo e da gaveta, contada à parte pelo registro; o saldo e a
gaveta podem sair negativos e saem como são (a 232 (c)).

**O dicionário** (a seção 1.3 do `spec-caixa.md`; medido na cópia em julho de 2026, a seção 1.4, e conferido pelos oráculos da 60 e
da 61 nas duas cópias; recebido, fora de comanda e gaveta só no banco de contrato semeado, porque a cópia não tem os pagamentos, o
D181):

| Número | Definição |
|---|---|
| Recebido por forma | os pagamentos das vendas finalizadas do caixa, por forma, com a moeda real e a interna ao lado (o cartão do painel) |
| Fora de comanda | do recebido, o que veio de venda sem item (compra de crédito e quitação de fiado) |
| Retiradas | as ativas na soma; as canceladas à parte e fora dela; as devoluções de crédito à parte e dentro dela |
| Saldo | inicial + recebido em todas as formas − retiradas ativas: o saldo da tela do caixa |
| Gaveta esperada | inicial + recebido em Dinheiro (a forma 1) − retiradas ativas − troco devolvido; a gorjeta fica |
| Descontos, gorjeta, vendas | das finalizadas do caixa |
| Vendas de outro dia | finalizadas cujo dia difere do da abertura (o D28) |
| Canceladas depois de finalizadas | as vendas do caixa com a situação cancelada (até o PDV gravar quando, sem dizer se foi antes ou depois do fechamento) |
| Pendências | aberto de outro dia, fechado em outro dia, venda de outro dia, cancelada depois de finalizada |
| Quebra | "—" até o fechamento pedir o dinheiro contado (a fatia do PDV) |

**Quem vê:** a política `caixa` -- o administrador a rede (`todas`, com a tabela por unidade) ou uma unidade; o gerente (a marca de
gerente vence a de caixa, a 271 (c)) a unidade da sessão, qualquer período até 31 dias; o caixa que não é gerente só hoje, sem
filtro nem o bloco dos esquecidos; os outros 403 `papel`. **A tela antiga (`CaixasController`) e o item do menu antigo aplicam a
mesma política** desde a 61: medido na cópia, só contagem e sem as fixtures, de 366 ativos viam o item 177 e veem 175.

**Onde está:** no servidor, `ISVenda\Regra\ContasDoCaixa`, `ISRelatorio\Regra\RegraDoRelatorioDeCaixa`, as consultas por conjunto
do `CaixaRepository`, `VendaPagamentoRepository`, `VendaRepository`, `VendaProdutoRepository` e `CaixaRetiradaRepository` (a 60);
`GET /adm/api/relatorios/caixa`, `.../caixa/:id` e `.../caixa/unidades` (`Adm\RelatorioDeCaixaController`, `RelatorioDeCaixaJson`;
a rede travada por pessoa, a 61). Na tela, `api/relatorioDeCaixa.ts`, `RelatorioDeCaixa.tsx` (a lista), `DetalheDoCaixaNoRelatorio.tsx`
(o detalhe, com os blocos `ResumoDoCaixa`, `RecebidoDoCaixa` e `RetiradasDoCaixa` do `Caixa.tsx`), `RelatorioDeCaixaLigado.tsx`, a
regra pura `regraDoRelatorioDeCaixa.ts`, o cartão `RecebidoPorForma.tsx` (tirado do painel), a linha da `TELA_DO_RELATORIO` do
Console, o "ver os caixas" do recebido por forma do painel (no período dele, e para o administrador na unidade dele), a fila `caixa`
do `filaDaRede.ts` (só a rede) e as dez chaves do glossário (`ITENS_DO_CAIXA`).

**Provado:** os oráculos em Python, só SELECT, contra o JSON do controller -- na 60, 148.724 conferências e 0 divergências; na 61,
15.442 e 0 na cópia e 770 e 0 no contrato semeado; na 62, pela TELA (a vista desenhada no vitest com o JSON do controller no contrato
semeado), 332 conferências e 0 divergências nos dois bancos (e 3 divergências com a gaveta da tabela trocada pelo saldo, a prova de
que o oráculo vê a tela). O extrator da tela original foi apagado sem cópia; o reconstruído mora no rascunho da sessão
(`p62-oraculo-extrator.teste.tsx`, que o `medir-p62-oraculo.py` copia para `src/telas` só durante a rodada) e, desenhando o mesmo JSON
gravado, deu o texto da tela do PostgreSQL byte a byte igual ao de 31/10 (o do MySQL guardado era o da falsificação, que o
sobrescreveu: difere nas 3 células da Gaveta, e só nelas). Os mutantes P60, P61 e P62, um por vez, vermelhos pela asserção (os
equivalentes ditos no `docs/roadmap.md`). A conferência no navegador da tela é do orquestrador, depois da 62.

**No escopo `dia`** (o caixa que não é gerente), o detalhe não tem "ver a venda" em venda nenhuma: Vendas Realizadas força ao caixa
as ativas e o dia de hoje, e a venda de outro dia de um caixa de hoje é de um dia anterior. O "ver a venda" leva a unidade do
caixa, e não a do recorte (na rede, a do recorte seria 0).

**Medido** (o D665): a rede fria de cerca de 3 s nas duas cópias (128 MB), quente abaixo de 1,1 s; a unidade fria de 2 a 3 s e
quente abaixo de 0,3 s; pico de 52 MB. O pacote do front subiu de 336,17 KB para 343,52 KB comprimido na 62.

**A quebra ENTROU em 03/11/2026 (a fatia 72, PDV-GAVETA-CONTADA; a pergunta 15 (c) do dono):** o fechamento do caixa, no ERP
antigo e na tela nova, passou a pedir o dinheiro contado na gaveta e a gravá-lo (`caixas.valor_contado`, dentro da trava do
fechamento); e o relatório mostra, a partir da data (a linha `valorContado` de `colunas_gravadas`, que só vale com a do
cancelamento), a quebra -- contado menos a gaveta ao fechar, que devolve o dinheiro das canceladas depois do fechamento --, na
coluna Quebra da tabela e da de unidades ("sobrou R$ X", "faltou R$ X", "bateu"), no resumo (a soma só dos que têm, e de
quantos) e no detalhe (o contado e a gaveta ao fechar). O caixa aberto e o fechado antes da data ficam "caixa aberto" e "sem
registro", nunca R$ 0,00. Contado na cópia (só SELECT): em 2026, 7.475 caixas fechados (de 505 a 1.067 por mês, pelo mês da
abertura), todos sem a contagem, que ficam "sem registro"; 101 pessoas fecharam caixa; numa unidade, nos dias com fechamento,
1,50 fechamentos por dia em média (no máximo 6). A seção 3 do `spec-caixa.md` está FEITA inteira.

**O que espera:** o "antes ou depois do
fechamento" das canceladas ENTROU em 03/11/2026 (a fatia 70): `antesDoFechamento`, `depoisDoFechamento` e `semRegistro`, e
quando e quem de cada cancelada no detalhe (a seção 3 do `spec-caixa.md`); fechar os esquecidos é escrita na produção (o D664); o saldo e o troco (o D663); a frase da recusa pelo papel, que
não diz o caixa (o D662; consertada em 08/11/2026, a fatia 122).

### O Atendimentos (a fatia 63, 01/11/2026, o período `ano` e as contas; a fatia 64, a API, a tela e o ERP antigo; a fatia 65, os clientes novos e a exportação -- a A0 a A3 de `docs/f3-relatorios/spec-atendimentos.md`)

As decisões 1, 3, 6, 10 e 11: **mês a mês no ano civil, quanto cada colaborador fez na unidade, e quanto fez a unidade inteira**
-- a linha da unidade é a do antigo Dash. Faturamento (a decisão 6), com as médias por cliente e o crescimento. O ano vai de
janeiro a dezembro; o mês em andamento vai até hoje (a régua do painel); os meses que ainda não chegaram vêm marcados e sem número;
o piso é o ano em que a unidade foi criada, e nunca antes de 2000. Os clientes novos (pela estreia na rede) e a exportação (o PDF
e o XLS "como hoje", a decisão 10) entraram com a A3, a fatia 65.

**O dicionário** (a seção 1.4 do `spec-atendimentos.md`): cada célula de colaborador é a linha que ele tem no Profissional
Parceiro daquele mês (sem a gorjeta e o NPS), mais as categorias e o `naLista`; quem entra é quem fez pelo menos um serviço na
unidade NO ANO, na ordem do faturamento do ano; o mês em que ele teve item e nenhum serviço vem marcado "sem serviço". O fora da
lista é quem teve item no ano e nenhum serviço, e as linhas mais o fora dão a unidade em cada mês e no ano. A coluna Ano é o ano
como um período só (os clientes distintos, as médias pelos clientes do ano; o resto é a soma dos meses). A linha da unidade é o
`resumo` do painel com as médias por cliente pela régua do Parceiro (só os clientes com cadastro; a pergunta P-a ao dono) e o
crescimento contra a comparação do painel (o mês anterior inteiro; no mês em andamento, os mesmos dias; janeiro contra dezembro do
ano anterior). As categorias são as do catálogo mais "Sem categoria" (a pergunta 11; a regra comum do `ContasDoPainelDaUnidade`).

**Quem vê:** a política `atendimentos` -- o administrador uma unidade ativa por vez (a rede é 422: o ano da rede espera o "mês
fechado"); o gerente, com ou sem a marca de caixa, a unidade da sessão; todos os outros com o privilégio, o caixa inclusive, só a
própria linha (a decisão 3), sem a unidade e sem o fora da lista -- o servidor nem lê os itens dos outros. O ano antes da criação
da unidade é 422. **O ERP antigo:** o item do menu pela mesma política (`veOMenu`; ninguém o perde, porque o escopo nunca é nulo
para quem está logado), e o arquivo antigo do próprio é só o PDF do bloco dele (`pedidoDaExportacao`; o D670).

**Onde está:** no servidor, `ISRelatorio\Regra\ContasDosAtendimentos` (as contas da 63, mais a leitura mês a mês --
`faixasDaBusca` -- e a grade numa passada -- `grade` -- da 64), `ISRelatorio\Api\AtendimentosJson`,
`GET /adm/api/relatorios/atendimentos` e `.../unidades` (`Adm\AtendimentosController`, com a trava por pessoa
`atendimentos-<id>` e o 429), e a tela antiga `ISRelatorio\Controller\AtendimentosController` com a política. Na tela,
`api/atendimentos.ts`, `Atendimentos.tsx` (o cartão da unidade e a tabela por colaborador com o seletor do número; o número
sem linha de colaborador, o crescimento, mostra só a linha da unidade, e o seletor só oferece o que tem linha na tabela),
`AtendimentosLigado.tsx` (o pedido na fila `atendimentos`, o 422 do ano com o "ver <ano de hoje>", o histórico do Parceiro em
lugar), `NavegacaoDoAno.tsx`, a regra pura `numerosDosAtendimentos.ts` (a mesma lista do `AtendimentosJson::NUMEROS`, que o
`AtendimentosTest` compara), a linha da `TELA_DO_RELATORIO` do Console e as chaves `atendimentos.*` do glossário
(`ITENS_DOS_ATENDIMENTOS`), com a `relatorios.mesEmAndamento` dos relatórios que comparam.

**Provado:** os oráculos em Python, só SELECT, contra o JSON do controller em processo (`medir-p64-oraculo.py` com
`medir-p64.php`, no rascunho): na cópia, a unidade 1 em 2026 e 2025, nos dois bancos, **18.614 conferências e 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 R$ 116,11 e do ano R$ 385,76); no banco de contrato, a unidade das fixtures
em 2026 e o cenário do `AtendimentosTest` semeado (2023, 2024 e 2026), **4.890 conferências e 0 divergências** nos dois bancos;
falsificado, 2 divergências. O `AtendimentosTest` confere cada célula de colaborador com o mês do histórico dele no Parceiro, e
cada mês da unidade com o painel daquele mês, nos dois bancos.

**Medido** (o D667; unidade 1, `memory_limit` 512M, o frio é a primeira rodada do processo): o pico do PHP de 88 a 90 MB em 2026
e de 131 a 133 MB em 2025 (com 128M, 2025 cai; com 256M, os dois passam); no 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, 2026 de 2,6 a 5,2 s e 2025 de 4,0 a
6,8 s; no PostgreSQL, 2026 de 1,6 a 4,5 s e 2025 de 2,7 a 5,4 s. O pacote do front de 343,59 KB para 346,76 KB comprimido.

**Os clientes novos e a exportação (a fatia 65, a A3).** `GET /adm/api/relatorios/atendimentos/clientes` é o pedido de depois:
os novos de cada colaborador da lista do ano (os mesmos ids, na ordem dos números) e da unidade, em cada mês e no ano, com os que
não contaram para ninguém (`semColaborador`); no próprio, só a linha dele, e o candidato é só cliente com serviço dele. As mesmas
guardas do `index`, e uma trava por pessoa própria (`atendimentosClientes-<id>`, a fila `atendimentosClientes` da tela), para a
troca de ano não esperar os clientes do ano velho. A estreia é a do painel (`VendaRepository::painelPrimeiroServicoOuCredito`), e
a forma agrupada que a 63 usou no oráculo NÃO a substituiu: dá o mesmo resultado e é mais lenta nos dois bancos (o D667).
`.../exportacao` é a da base: o PDF e o XLS da política, `atendimentos-2026-unidade-1.pdf`, um bloco por colaborador "como hoje"
(a decisão 10: os números nas linhas, cada uma com o tipo dela, e os meses e o ano nas colunas), mais o bloco do fora da lista e o
da unidade, montado das mesmas respostas que a tela recebe -- com os itens do ano lidos uma vez -- e na trava dos números da pessoa
(o gancho `travaDoArquivo` da base: o segundo arquivo da mesma pessoa é o 429, e cada pessoa ocupa no máximo dois workers do
PHP-FPM); o mês que não chegou é "—" no PDF e vazio no XLS; as notas são os
textos do "Como os números são contados". Na tela, o "Clientes novos" no seletor e na tabela da unidade ("…" contando, "—" na
falha, com o "tentar os clientes de novo"), a frase dos sem colaborador do ano, e os botões do PDF e do XLS no cabeçalho,
desligados sem os números ou relendo.

**As respostas de 01/11/2026 (274 a 276, todas (a)):** a régua nova das médias da unidade fica, e a diferença para o antigo está
no glossário e nas notas do 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 (a A4 do desenho).

**O que espera:** a aposentadoria do Atendimentos e do Dash. Faturamento antigos, das duas rotinas e do cache (a A4, depois do
corte; o D306, o D307, o D668 e o D669); a estreia gravada (o D311) para os clientes do ano no MySQL; o pico do ano de 2025 da maior
franquia acima de 128 MB (o D667; abaixo dele desde 08/11/2026, a fatia 122: de 103 a 111 MB nos números, nos clientes e no arquivo).

---

### A Cota-parte e a Cota-parte v2 (a fatia 66, 02/11/2026, o servidor das duas, sem tela -- a CP1 de `docs/f3-relatorios/spec-cota-parte.md`)

As decisões 1, 3, 4, 7, +2, a 134 (b) e o D158: **quanto de cota-parte cada colaborador gerou no período, sobre qual produção, e
quanto de gorjeta há para repassar junto**, por colaborador e, de cada um, o extrato por produto. A base é a do painel (a venda
concluída, com item, fechada no período por alguém da unidade). O período é um intervalo sem teto (padrão: hoje), lido mês a mês.
**Para quem usa, só uma frase muda:** o item "Cota-parte" do menu continua na tela antiga; a tela e o menu são a CP2 (a fatia 67), a
exportação a CP3 (a 68), o ERP antigo a CP4. A frase de "desde quando vale" da aba Cota-parte de Usuários (nas duas telas) já
descreve o relatório novo -- o percentual de hoje com a extra de hoje, a sessão de pacote com a gravada --, e até a CP2 e a CP4 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, 10 itens de 2026 têm
extra do par (de janeiro a junho; nenhum em julho), e 39 de 2025 (o D671).

**As duas réguas, por item e em centavos inteiros** (a 1.2 do desenho): a **gravada** é a cota que o PDV gravou na hora; a
**recalculada** é a conta do PDV refeita com o cadastro de hoje -- a base do item (a sessão de pacote pelo valor dela) vezes o
percentual de hoje mais a extra de hoje do par, arredondada no item. **A sessão de pacote fica com a gravada na recalculada**
(`ContasDaCotaParte::A_SESSAO_RECALCULA = false`, a resolução do `spec-ordem.md`, seção 2, item 3, sobre a pergunta 7: o número do
PDF que o colaborador vê hoje). A Cota-parte do menu mostra a recalculada; a **v2**, só do administrador, mostra a gravada e o
**aviso** de cada item em que ela difere da recalculada PURA (a sessão inclusive). A gorjeta fica fora da cota e entra só no "a
repassar".

**Medido na cópia** (o oráculo em Python, nos dois bancos, só SELECT): na unidade 1 em julho de 2026, 14 na lista e 2 fora, R$
102.170,12 nas duas réguas, R$ 421,00 de gorjeta em 20, R$ 286.189,40 de produção; na rede em julho, 143 e 25, R$ 654.911,28 de
gravada e **R$ 656.926,57 de recalculada** (R$ 3,76 a menos que os R$ 656.930,33 do desenho: a sessão gravada, R$ 6.549,38 contra
R$ 6.553,14), 2.802 itens com aviso de 15 colaboradores; na unidade 1 em junho, R$ 91.644,82 contra R$ 90.764,82, e 9 itens com
aviso de 1 colaborador, diferença −R$ 880,00.

**Quem vê:** a política `cotaParte` -- o administrador a rede (`todas`) ou uma unidade; o gerente a unidade da sessão; o caixa que
não é gerente só hoje, sem nenhum campo de cota (a pergunta 4); os outros só a própria linha e o próprio extrato, sem a produção, o
faturamento, os descontos, o valor por item e os percentuais (o D158; o P-CP1, a pergunta 277, foi respondido (a) em 02/11/2026: fica assim). Na
`cotaParteV2`, só o administrador. Na cópia, só contagem: 108 no escopo `unidade`, 58 no `dia`, 180 no `proprio` e os 10
administradores na `rede`.

**Onde está:** `ISRelatorio\Regra\ContasDaCotaParte` (as contas puras), `ISRelatorio\Service\MontagemDaCotaParte` (a leitura mês a
mês), `ISRelatorio\Api\CotaParteJson` (a resposta e o `ESCONDIDOS` de cada escopo), `Adm\CotaParteController` e
`Adm\CotaParteV2Controller` (`GET /adm/api/relatorios/cota-parte`, `.../extrato` e `.../unidades`, e o mesmo em `cota-parte-v2`),
`UsuarioComissaoExtraRepository::percentuaisExtras` e `RecorteDoRelatorio::colaborador` (movido do Parceiro). Os testes:
`RegrasDaCotaParteTest` (sem banco) e `CotaParteTest` (nos dois bancos, sobre o `CenarioDaCotaParte`, setembro de 2023 na faixa
90008000-90008099).

**A tela (a fatia 67, 02/11/2026, a CP2).** O item "Cota-parte" do menu da tela nova abre a tela nova para todo mundo com o
privilégio "Relatórios de vendas" -- um item só, guardado pela `relatorioCotaParte` (eram dois: a tela e o PDF do dia do caixa,
pela guarda `vendasExportadas`, que saiu da sessão) --, e a "Cota-parte v2" entrou no menu, só para o administrador, pelo papel.
Contado na cópia (só SELECT, usuários ativos não administradores e os administradores, sem as fixtures): 356 têm o privilégio e
viam um "Cota-parte" antes; os mesmos 356 veem um depois -- 10 na rede, 108 na unidade, 58 no dia (o "Colaborador caixa", que
passou do PDF à tela do dia, sem a cota) e 180 só a si --, e os 10 administradores ganham a v2. No ERP antigo, o item do
`layout.phtml` passou ao `veOMenu` da política (o mesmo conjunto: todo mundo com o privilégio tem escopo), e o caixa continua
com o PDF do dia ali até a CP4. **O que a tela mostra:** 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. As colunas seguem o escopo (`textosDaCotaParte.ts::colunasDoEscopo`, comparado com o `CotaParteJson::ESCONDIDOS` no
`RegrasDaCotaParteTest`): o caixa sem a cota, o percentual e o "a repassar"; o colaborador sem a produção, o faturamento, os
descontos e o percentual. O período é livre, com os atalhos (o mês é o padrão; a tela lembra o último por relatório e por
unidade), e o primeiro pedido vai sem período, o ciclo lembrado logo depois pelo hoje do servidor; o dia antes de 2000 não
sai da tela (o piso da escolha do período, dito ao lado do campo). A v2 tem a coluna "Aviso",
uma nota sempre no topo (o que ela é; com aviso, quantos itens de quantos colaboradores e a diferença que o servidor somou -- a
gravada menos o percentual de hoje refeito em cada item, a sessão inclusive, que não é a diferença entre as duas telas) e o
link para a Cota-parte no mesmo período e unidade; a v1 tem o link discreto para a v2, só para o administrador. A venda de
pacote leva a nota do "ainda não gravado" (a cota do pacote espera a fatia do PDV). **Onde está:** `frontend/src/api/cotaParte.ts`,
`telas/CotaParte.tsx`, `CotaParteLigado.tsx`, `ExtratoDaCotaParte.tsx`, `ExtratoDaCotaParteLigado.tsx`, `textosDaCotaParte.ts`, as
sete chaves `cota.*` do glossário, as duas linhas da `TELA_DO_RELATORIO` do Console e as filas `cotaParte` e `cotaParteExtrato`
do `filaDaRede.ts`. A exportação é a CP3 (a fatia 68); o ERP antigo, a CP4.

**O arquivo e o ERP antigo (a fatia 68, 02/11/2026, a CP3, a CP4 e a CP5).** Na tela nova, a Cota-parte e a v2 têm o PDF e a planilha
`.xlsx` (a de hoje, a pergunta 10) no cabeçalho da lista e do extrato, no escopo de quem pede: `GET .../cota-parte/exportacao` e
`.../cota-parte-v2/exportacao`. **O arquivo é a tela:** sai das mesmas respostas, já escondidas -- um bloco por colaborador (o extrato
dele, a gorjeta, o "A repassar" e o "Total do colaborador"), e, fora do `proprio`, o "Total da unidade" (ou da rede) com a linha de
cada um, a do fora e o total; a coluna que o escopo esconde não existe no arquivo, nem o rótulo (o colaborador sem "Valor", o caixa
sem "Cota-parte"); a gorjeta do colaborador vai na coluna da cota, dita "fora da cota-parte". As notas são os textos do glossário; a
v2 leva a frase do aviso com o número da resposta. Com `?colaborador=`, o arquivo é o bloco dele (o do extrato); o de outro, no
`proprio`, é o 403 de antes. O arquivo anda na trava da pessoa. **No ERP antigo** (`ISRelatorio\Regra\PedidoDaCotaParteAntiga`),
o PDF e o XLS antigos seguem até a aposentadoria (o D306), mas pelo escopo da política: o caixa recebe o dia, de todos, sem escolher
colaborador nem período pela URL; o colaborador, só a si; o `?versao=2`, só o administrador (o D376, CORRIGIDO). O PDF do dia do
caixa no menu antigo é o escopo `dia` da política, e não mais o grupo 2 (o D672, CORRIGIDO): na cópia, as mesmas 58 pessoas. Ninguém
perdeu o item nem o próprio PDF; o grupo 9 (0 ativos) cai no `proprio` -- 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, até a aposentadoria (o D671). O arquivo de um colaborador leva
`-colaborador-<id>` no nome, e as cortesias do fora vão nas notas, com a frase da tela. **Medido** (o oráculo da planilha contra o JSON do mesmo
pedido): na cópia, 21.662 conferências em cada banco (a unidade 1 e a rede em julho de 2026, a v2 da unidade 1 em julho e em
junho), 0 divergências; no contrato semeado, o gerente, o colaborador, o caixa e o administrador, 0 divergências. A planilha da rede
em julho, no Mac Mini M4 (02/11/2026; a máquina mudou, e o número não se compara com os do Windows), de 0,70 a 0,81 s e 160 MB
de pico, o PDF de 0,35 a 0,45 s e 140 MB (memory_limit 512M). **Onde está:** `CotaParteJson::documento`, o
`CotaParteController::guardasDoRelatorio`, `::travaDoArquivo` e `::documento`, o `RelatorioAdmController::documento` com
`array|Response`, o `PedidoDaCotaParteAntiga` e o `VendasController`, os `BotoesDeExportacao` em `CotaParte.tsx` e
`ExtratoDaCotaParte.tsx`; os testes `CotaParteTest`, `ExportacaoDoRelatorioTest` e `PedidoDaCotaParteAntigaTest`. Os pontos ao dono
da especificação estão respondidos: o P-CP1 (a pergunta 277, (a): o colaborador não vê a produção, também no arquivo) e o P-CP2 (a
sessão de pacote com a gravada na Cota-parte do menu, `A_SESSAO_RECALCULA = false`).

**A cota de quem vendeu o pacote (a fatia 74, 03/11/2026, PDV-COTA-DO-PACOTE; a pergunta 16 (b) e a seção 3 da especificação).** O
cadastro do pacote ganhou o percentual da cota-parte de quem vendeu (zero por padrão, nas três portas), e toda sessão de pacote
finalizada grava quem vendeu e a cota dele (o valor da sessão vezes o percentual de hoje). A partir da linha `cotaDoPacote` de
`colunas_gravadas`, a Cota-parte credita cada sessão duas vezes: a quem executou, no bloco das sessões, como antes, e a quem vendeu,
no bloco novo "Pacotes que vendeu, por sessão usada" (`pacotesPorSessao`), com a recalculada pelo percentual de hoje do pacote na
v1 também, a gravada na v2 e o aviso. A lista ganha a coluna "Cota das sessões dos pacotes que vendeu" quando o recorte a tem; o
`cota.pacotes` é nulo no período inteiro antes da data. A sessão fica na produção e na base de quem a executou, e não nas de quem
vendeu: com 0%, a lista, o "fora da lista", o total e a "% da cota" ficam como antes (a verificação da 74). O colaborador vê a
própria cota de quem vendeu, e nunca a de outro. **Na cópia** (só SELECT): nenhum dos 239 pacotes tem percentual; de 334 a 385
sessões de pacote por mês em 2026, de janeiro a julho (e 185 em agosto) -- o volume que, daqui em diante, passa a gravar quem vendeu; 2 pacotes de
cliente sem o item na venda de origem; 23 pessoas venderam pacote em 2026. Em julho, 369 sessões, R$ 16.600,61, nenhuma com
vendedor (são de antes da data). **Onde está:** `ContasDaCotaParte::QUEM_VENDEU`, `::creditarQuemVendeu` e `::separar`, a
`MontagemDaCotaParte::percentuaisDaCotaDoPacote`, o `CotaParteJson` (a coluna, o bloco e as notas), `textosDaCotaParte.ts` e o
glossário `cota.quemVendeu`; os testes `RegrasDaCotaParteTest`, `CotaParteTest` e `CotaDoPacoteGravaQuemVendeuTest`.

## 7. O Radar e o Fluxo no painel, e a F5 do Profissional Parceiro (13/10/2026)

**As respostas do dono** (o lote de 06/10/2026, no levantamento de 10/10): a **137 (a)** -- *"Viram opções do painel,
calculadas na hora -- A comparação com o ano anterior e o corte por dia da semana, com todos que venderam. Saem o atalho,
as duas telas, a tela antiga do 'Dash. Colaboradores' (o Profissional Parceiro já a substitui) e as rotinas; a tabela
antiga fica guardada"* -- e a **118 (a)** -- *"Um pedido da rede de cada vez, por pessoa -- O segundo é recusado com 'já
há uma consulta da rede em andamento', também no melhor da rede; quem troca de período antes da resposta espera a
primeira terminar."* Os débitos são o D306, o D307 (pontos 3 e 4), o D309 e o D311.

### A comparação com o ano anterior (o Radar de Temperatura)

Os números e os clientes do painel comparam com o mês anterior (o de sempre) ou, escolhido, com o **mesmo mês do ano
anterior**: `?comparar=ano-anterior` em `GET /adm/api/painel` e em `/painel/clientes`, e o recorte diz `comparar`. O mês
fechado contra o mesmo mês inteiro; o mês em andamento contra os mesmos dias dele (o 29 de fevereiro contra fevereiro
inteiro). Tudo o que se compara anda junto: os clientes novos do mesmo mês do ano anterior, o retorno do mês anterior
contado do mês antes dele, e as janelas do retorno em 45 dias terminando 45 dias antes do fim dele. Só o painel lê o
parâmetro (`RegraDoPainelDaUnidade::interpretar`, o quarto argumento): o Profissional Parceiro usa os mesmos períodos e
não o oferece.

**As buscas são faixas** (`buscasDosNumeros`, `buscasDosClientes`): com o ano anterior, um envelope leria treze meses de
itens para dois de conta. As faixas que se tocam viram uma -- com o mês anterior, cada pedido lê a mesma faixa de antes.

É a base do painel, e não a tabela do Dash: o Radar somava `dashboard_colaboradores`, só do grupo 1 (os caixas e gerentes
que venderam ficavam de fora: R$ 54.181,45 na rede em julho de 2026) e num mês que nunca fechava; aqui entra quem vendeu,
e o cancelamento muda o mês passado.

### O movimento por dia da semana (o Fluxo de Movimento)

`GET /adm/api/painel/semana?mes=`, com as guardas do painel, num cartão que abre fechado: ele lê a rede do mês
(`painelItens` com a unidade nula; 31.898 itens em julho de 2026, na cópia), e a tela só o pede aberto. Aberto, acompanha o
mês -- e a comparação não o pede de novo. Para cada dia da semana, de segunda a domingo
(`ContasDoPainelDaUnidade::porDiaDaSemana`):

| Número | Definição |
|---|---|
| Dias | os desse dia da semana no calendário do período, com venda ou sem |
| Dias com venda | os que tiveram pelo menos uma venda da base |
| Faturamento | o da unidade nesse dia da semana; a soma dos sete é o faturamento do painel |
| Por dia com venda | o faturamento pelos dias com venda -- a régua do painel; o dia sem venda nenhuma fica sem média, e não com zero |
| A rede | o faturamento de todas as unidades nesse dia da semana pelos pares (unidade, dia) com venda: a média de uma unidade num dia com venda, ponderada, e não a média das médias. A venda de quem não é de unidade nenhuma fica fora dos dois lados |
| Contra a rede | a variação do "por dia com venda" da unidade contra o da rede |

A tela antiga dividia pelo calendário (o domingo fechado puxava a média) e tirava a média das linhas da rotina, que
inseria sem procurar a do mês (159 trincas repetidas vieram da produção, o D307).

### O que saiu, e o que ficou

Saíram o atalho do topo do ERP; os itens Dash. Colaboradores, Radar de Temperatura e Fluxo de Movimento do menu antigo;
as três telas, com o extrato e as estrelas do plano de carreira; as rotinas das 23:30 e das 07:00 do dia 1, com as rotas;
e as consultas que só elas usavam nos repositórios do Dash e do Fluxo. **Ficaram** `dashboard_colaboradores` (a régua
antiga da ocupação, a B6) e `dashboard_movimentos`, sem ninguém gravando; e as consultas de outros módulos que ficaram sem
chamador (o D485). O Profissional Parceiro é destino só da tela nova (`/profissional-parceiro`). No corte, o passo 9 tira
as duas rotinas do crontab guardado.

**O que o painel não dá, e as telas antigas davam** foi ao lote, em duas perguntas. **Quem as vê** (o D487): o painel é só do
gerente de unidade que não é administrador (seção 3), e o administrador -- que no Radar escolhia a unidade ou a rede, e
no Fluxo a unidade -- perdeu as duas opções inteiras, como quem tinha o privilégio sem ser gerente; a recomendação é o
painel abrir ao administrador como item do menu, com a escolha da unidade e sem a rede. **Quatro números do Radar** (o
D488): o valor dos serviços e o dos produtos, a ocupação da agenda e os agendamentos pelo app contra o ano anterior; a
recomendação é levar ao painel os dois valores, e deixar a ocupação e os agendamentos no Profissional Parceiro. (A
primeira versão desta frase, e a do D487, diziam que o administrador via no painel a unidade da sessão: não vê.)

**Respondidas em 27/10/2026, e feitas na fatia 46:** a **181 (b)** -- o painel no menu do administrador, com a escolha da
unidade e sem a rede (seção 3, "Quem vê"; o D487 CORRIGIDO) -- e a **183 (b)** -- o valor dos serviços e o dos produtos nos
números comparados, das mesmas vendas, sem consulta a mais (seção 3, "O dicionário"; o D488 CORRIGIDO). A ocupação da agenda
e os agendamentos pelo app ficam no Profissional Parceiro, um mês de cada vez, como a resposta manda.

### Um pedido da rede de cada vez (o D311)

O servidor trava, por pessoa e por consulta, os clientes da rede e o melhor da rede: o segundo pedido da mesma pessoa com o
primeiro no servidor é `429 em_andamento`. A tela espera o primeiro terminar antes de mandar o seguinte, e manda só o
último que ainda quer. **A fila é da aba** (`filaDaRede.ts`), e não da tela: o Console remonta a tela a cada escolha no
menu, e sair e voltar com um pedido da rede no ar também espera. A semana do painel anda numa fila dela -- o servidor não
a trava. O tempo da consulta continua o do D311 -- o dono escolheu o freio, e não a estreia gravada (seção 4).

**A semana relida a cada troca de unidade (27/10/2026, a fatia 46).** Com a semana aberta, o administrador que troca de
unidade pede a semana de novo, e cada pedido lê os itens da rede do mês outra vez -- a média da rede não depende da
unidade, mas sai da mesma consulta que separa a unidade. A fila da aba manda um de cada vez, e só o último que a tela
ainda quer; abas paralelas somam leituras, como as do gerente já somavam. **Medido na cópia, só leitura** (o `painelItens`
com a unidade nula e o `porDiaDaSemana`, quatro rodadas, a máquina calma, o buffer pool de 128 MB): março de 2026, 29.195
itens, 0,58 s de mediana; julho de 2026, 31.898 itens, 0,67 s. Por isso a semana segue sem a trava do servidor: o custo é
o de um pedido de menos de um segundo por troca, e não o dos 70 s do D311.

**A trava de outro dono (o D499, 15/10/2026).** O arquivo da trava, em `/tmp`, é de quem o cria: a suíte, que roda como
root, deixava o do colaborador 90001000 de root, e o melhor da rede pedido pela web (www-data) saía 200 com a página de
erro -- o ensaio de permissão saiu 1 assim três vezes no MySQL. Não era a ordem do melhor da rede, que já desempata pelo
id, e quem não administra nunca vê quem é. `TravaDeExecucao::obter` abre para ler o arquivo que não abre para escrever, e
o `flock` trava igual: a mesma trava, venha o pedido de quem vier. E o ensaio passou a ter quem concorra (a verificação
de 15/10/2026): nas fixtures ninguém chega aos 30 atendimentos de fevereiro de 2026, e o melhor da rede saía com os dez
destaques nulos -- "nenhum traz o nome" era verdade sempre. O roteiro dá ao colega da unidade as vendas do mínimo no mês,
apaga-as no fim, e exige pelo menos um concorrente e um destaque preenchido, sem o nome.

### Onde está

| Parte | Arquivo |
|---|---|
| A comparação e as faixas | `RegraDoPainelDaUnidade::interpretar` (`COMPARACOES`, `buscasDosNumeros`, `buscasDosClientes`, `juntar`) |
| O movimento por dia da semana | `ContasDoPainelDaUnidade::porDiaDaSemana`; `PainelDaUnidadeJson::semana`; `PainelDaUnidadeController::semanaAction`; a unidade da venda em `VendaRepository::painelItens` |
| A trava da rede | `ProfissionalParceiroController::travaDaRede`; `RespostaApi::erroEmAndamento`; o arquivo de outro dono em `TravaDeExecucao::obter`, provado no `D499TravaDeOutroDonoTest` |
| A tela | `Painel.tsx` (a escolha da comparação em "Os números", o cartão `Semana`), `PainelLigado.tsx` (o terceiro pedido), `api/painel.ts`; a fila da rede em `filaDaRede.ts` (`naVez`), usada por `ProfissionalParceiroLigado.tsx` e pela semana do `PainelLigado.tsx` |
| Os testes | `RegrasDoPainelDaUnidadeTest`, `PainelDaUnidadeTest` (escrito para os dois bancos, com o oráculo do mês e da semana, e os três caminhos pelo roteador), `ProfissionalParceiroTest` (a trava), `RegrasDoProfissionalParceiroTest` (o layout), `MenuDosRelatoriosSemAsTelasQueSairamTest` (o menu do ERP antigo), `PainelLigado.teste.tsx`, `ProfissionalParceiroLigado.teste.tsx`. **Rodaram em 13/10/2026, depois do reinício da máquina** -- antes dele, com o Docker travado, só os dois `Regras*` tinham rodado, no PHP do host, por um arremedo do PHPUnit --: no PHPUnit, nos bancos só de fixtures que o CI monta, filtrados nos arquivos do item e nos vizinhos, 651 de 651 no MySQL (10.453 asserções) e no PostgreSQL (10.452), e os nove mutantes do controller vermelhos no MySQL, um por vez -- sete da trava e dois do painel (o D306 e o D311). Não rodaram os ensaios HTTP nem a tela no navegador |

## 8. A Fidelização de Clientes sai, com a rotina (25/10/2026)

**A resposta do dono:** a **138 (a)**, de 11/10/2026 -- *"A Fidelização sai, junto com a rotina; a fidelização ao
colaborador já está no Profissional Parceiro, com a sua janela de 45 dias"* -- à pergunta "Os cinco relatórios que ficaram
fora do menu novo seguem a proposta de cada um?" da página do lote, e o destino que a 120 pedia para o item escondido. A
138 não é a decisão 8 da seção 5 ("Fidelização ao colaborador", dentro do Profissional Parceiro): essa o dono respondeu em
30/09/2026 ("8. correto, mas o período deve ser móvel, colocar régua padrão de 45 dias"), e a opção (a) da 138 só cita a
janela de 45 dias que veio dela. Feita na fatia 41 da fila das telas que faltam
(FIDELIZACAO-SAI). Os débitos são o D306 e o D105; o novo é o D610.

**O que muda para quem usa:** nada que se visse. O item "Fidelização de Clientes" do menu Relatórios do ERP antigo estava
escondido (`display-none`) e levava a um controlador que nunca existiu; a rotina das 03:10 gravava um arquivo por unidade que
nenhuma tela lia. A fidelização ao colaborador é a do Profissional Parceiro (a seção 5, a F2), na tela nova.

### O que saiu, e o que ficou

| | |
|---|---|
| **Saiu** | a rotina `relatoriosFidelizacaoAtualizar` (`CronJobService`), a ação do `CronJobController`, a rota (o pedido é 404), a entrada da documentação da API, a linha das rotas públicas; o item do `layout.phtml`, o `Form/Fidelizacao.php` órfão, a view `is-relatorio/fidelizacao/index` que o desenhava e a linha do `Rel-fidelizacao` no `SessaoAcl::menuAtivo`; as consultas sem outro chamador, `VendaRepository::fidelizacaoPorColaborador` e `ClienteRepository::fidelizacao`; as duas entradas de `MORTOS_CONHECIDOS` do `conferir-links-mortos.php` e os dois caminhos do `conferir-caminhos-postgres.php` |
| **Ficou** | a `fidelizacaoAtualizar` das 23:50, que é outra rotina (desfaz a fidelização do cliente sem retorno); o privilégio `relatorios/fidelizacao` no cadastro dos grupos, sem tela; os arquivos já gravados em `data/cache/relatorios/fidelizacao/` na produção (o D610, com a pergunta no lote) |
| **No corte** | o primeiro bloco do passo 9 do `procedimento-de-corte.md` e o recuo do passo 12 da etapa 1 tiram a linha das 03:10 do crontab guardado da produção (22 linhas, das 25 de 27/08/2026; 21 desde 04/11/2026, quando a da Evasão das 04:10 saiu pelo mesmo caminho, a CA3 de `f3-relatorios/spec-carteira.md`); a tabela de `f1-6-agendamento.md` a tirou. Nada foi mexido na produção nem no BVIP |

### Onde está

| O quê | Onde |
|---|---|
| O caso | `tests/Caracterizacao/FidelizacaoDeClientesSaiuTest.php` (a rota e a da irmã, a ação e a rotina, a view, o layout desenhado sem o item, o `menuAtivo`), nos dois bancos; e o `SegredosForaDoCodigoTest`, que desde a verificação cobra também a página da documentação sem rota |
| O crontab | `scripts/ensaiar-crontab-do-passo-9.sh` (o caso das rotinas que saíram, com as duas irmãs que ficam, e o do `grep -vE` do recuo da etapa 1) |
| Os portões | `conferir-links-mortos.php`, `conferir-caminhos-postgres.php`, `conferir-cobertura-por-par.php` (369 pares, 118 sem menção, ISApi 106), `conferir-injecao-dql.php` (22), `conferir-rotas-sem-login.php`, `conferir-agendamento.php` (no `conferir-tudo.sh`, com o `--conhecidas=` do D50: a órfã nova reprova), o inventário de ações |

## 9. Os três relatórios da 138 (a): os desenhos (03/11/2026)

**A resposta do dono:** a **138 (a)**, de 11/10/2026 (a seção 8 fez a Fidelização). Os outros três ganharam a especificação na
fatia 76 da fila das telas que faltam (RELATORIOS-DA-138-DESENHO), só documentação: nada mudou para quem usa. As medidas são da
cópia, só por contagem, iguais nos dois bancos salvo onde dito.

| Relatório antigo | Na tela nova | A especificação | O que muda quando as fatias rodarem |
|---|---|---|---|
| Clientes e Evasão | **Carteira de clientes** (gerente e administrador) | `docs/f3-relatorios/spec-carteira.md`, a CA0 a CA4 (a CA0, as contas e a política, FEITA em 03/11/2026, a fatia 76a), e a CA5 a CA7 das respostas de 04/11/2026 | Um retrato de hoje: oito faixas pelo tempo desde a última venda finalizada com item (a visita do painel), a régua única de "sumido" de 45 dias (a do painel; a do Parceiro tem o mesmo valor), os 11.813 ativos sem visita na unidade do cadastro, o resumo por colaborador, a lista com o telefone e o próximo agendamento. A Evasão, o PDF e a rotina das 04:10 saíram na CA3, FEITA em 04/11/2026 (a fatia 76d; as "vendas" contadas por item, 363.789 para 232.802 -- o S10, o D691). O Clientes antigo recebe a política na CA1 e sai na CA4, depois da CA5 (a busca, o convênio, o mês do aniversário e o consumo na lista, pela 292 (b) do D696) e da CA6 (o TXT, o PDF e a planilha da lista na tela, com o contato de todos, pela 289 (b) do D690) -- as respostas de 04/11/2026 |
| Aniversariantes | **Datas da equipe** (gerente e administrador) | `docs/f3-relatorios/spec-datas-da-equipe.md`, a DE0 a DE2, as três FEITAS em 04/11/2026 (a DE0, o servidor com o item do menu e a tela antiga pela política, na fatia 76f; a DE1, a tela, na 76g; a DE2, a saída do Aniversariantes antigo do ERP, na 76h); a DE3, as quatro permissões com o nome da tela (a 298 (c)), a fazer | O nome diz o que é (o S31): o nascimento e o tempo de casa da equipe no mês do calendário; para o administrador, também a inauguração das unidades e o aniversário dos sócios, como hoje. Saem as quatro consultas mortas do D73 |
| Potencial de Franquia | **Capacidade × realizado** (só o administrador) | `docs/f3-relatorios/spec-capacidade.md`, a CR0 a CR2, as três FEITAS em 04/11/2026 (a CR0, o servidor com o item do menu e a tela antiga pela política, na fatia 76i; a CR1, a tela, na 76j; a CR2, a saída do Potencial antigo do ERP, na 76k); a CR3, da resposta 299 (b), e a CR4, da 300 (b), que espera a pergunta nova do D705, a fazer | A capacidade da agenda pela régua da F4 (a vigência do horário, e não o horário de hoje aplicado ao passado), a ocupação e o realizado do painel, unidade por unidade e na rede, no mês fechado ou no corrente (o mês futuro e o intervalo livre saíram com o Potencial antigo, na CR2). O potencial em reais (o ticket por item que a rotina das 04:10 sobrescreve, o S04, e os 70%) não entra: a 291 (a) do D692, de 04/11/2026; saiu com o Potencial antigo na CR2 |

**As réguas de "sumido" que ficam** (o S28): o lembrete "Sentimos sua falta" aos 30 dias, a lista dos 31 dias da Agenda e a
fidelização que cai aos 120 dias continuam onde estão; a Carteira usa a de 45 dias e as cita no "como se conta". A coluna
`clientes.ultima_visita`, que o cancelamento não desfaz, é o D694.

**As perguntas ao dono** (no lote, "TELAS-RESTANTES [relatorios] (03/11/2026)"): o contato na Carteira (D690), os arquivos da
Evasão na produção (D693), o potencial em reais (D692) e, da verificação, a lista filtrável do Clientes antigo (D696). Nenhuma
trava a primeira fatia de cada relatório. **Respondidas em 04/11/2026** (registradas na fatia 76r, RESPOSTAS-289-A-293): a 289 (b)
(a Carteira exporta como hoje, também o contato de quem recusou mensagens), a 290 (b) (os arquivos da Evasão ficam), a 291 (a) (sem
potencial em reais) e a 292 (b) (a Carteira ganha os quatro filtros). As especificações ganharam a CA5 e a CA6 e perderam as esperas;
foram ao lote o D698 (o consumo do cadastro atrasado), o D699 (o e-mail do gerente nos arquivos) e o D700 (o tamanho do PDF e
da planilha da Carteira, que a CA6 espera). A 294 (o D697, o tempo da
Carteira) ficou para a segunda leva.

**A segunda leva, respondida em 04/11/2026** (registrada na fatia 76u, RESPOSTAS-294-A-300; só documentação; as sete foram a
recomendada): a 294 (a) (a Carteira com a conta exata e a espera de 1 a 2 s, e o índice das vendas por cliente no passo 0 da etapa 1 do
corte, o que muda a decisão do D308; a CA1 deixa de esperar); a 295 (a) (o consumo do cadastro consertado: a rotina refaz também quem
teve venda cancelada, e o corte refaz todos uma vez -- a fatia nova CA7); a 296 (b) (o e-mail nos arquivos da Carteira como hoje); a
297 (a) (o PDF e a planilha da unidade inteira, a rede só com uma unidade, uma faixa ou um filtro -- a CA6 deixa de esperar); a 298 (c)
(as quatro permissões das telas novas com o nome da tela, renomeadas no corte -- a DE3 de `docs/f3-relatorios/spec-datas-da-equipe.md`);
a 299 (b) e a 300 (b) (na Capacidade × realizado, a ocupação da rede pela soma, com o aviso de quem ficou fora, e a unidade hoje
desativada que vendeu no mês como linha marcada -- as fatias novas CR3 e CR4). **Na verificação da 76u, duas perguntas novas foram ao
lote:** o D705 (a agenda vazia da unidade fechada que vendeu, na ocupação da rede: a CR4 espera) e o D706 (a Carteira da rede com uma
faixa grande no PDF e na planilha: a CA6 segue o teto até a resposta, e a CA4 espera).
