# Fase 2 — a décima terceira fatia: os devedores

Escolhida em 08/09/2026, logo depois das saídas, e pelo mesmo motivo: procurar
onde o dinheiro se move sem que ninguém esteja olhando.

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

---

## O recorte

| endpoint | verbo | origem |
|---|---|---|
| `/adm/api/devedores` | `GET` | `indexAction` |
| `/adm/api/devedores/:id` | `GET` | `selecionarDebitoAction` |
| `/adm/api/devedores/:id/quitacao` | `POST` | `pagarDebitoAction` |

**Três endereços, e nenhum `POST` de criação.** Dívida não se cadastra: ela
nasce do fiado, na fatia do PDV — `VendaPagamentoService` cria o débito quando o
recebido fica abaixo do total — e morre quando alguém paga.

---

## `aberto`, e não `estado`

As doze fatias anteriores usam `estado` para `ativo`/`inativo`. Aqui a coluna
`status` quer dizer outra coisa: se a dívida ainda **existe**.

Um débito quitado não está "inativo" — ele foi pago, que é o desfecho normal.
`aberto: false` diz isso sem pedir emprestado um vocabulário que significaria a
coisa errada.

---

## O valor não vem do corpo

O serviço quita o débito **inteiro**, pelo valor gravado nele. Não há pagamento
parcial de dívida neste sistema — diferente das parcelas de despesa, onde há.

Aceitar um `valor` no corpo daria a impressão de uma capacidade que não existe, e
alguém acabaria mandando metade e recebendo um débito baixado por inteiro.

---

## Os dois defeitos

### D136 — a funcionalidade existia e não guardava nada

```php
// clientes/index.phtml
if ($aclDevedores['escrever']) { ... <a>Devedores</a> ... }
```

O **botão** depende de `clientes/devedores`. A **porta** conferia
`clientes/cadastro`, e as outras duas ações não conferiam nada.

Entre os 1.171 usuários com privilégio na cópia de produção:

| | |
|---|---:|
| tinham `cadastro/ler` e **não** `devedores/ler` | 5 |
| tinham `devedores/ler` e **não** `cadastro/ler` | **4** |

Os quatro do segundo grupo **viam o botão e recebiam 404 ao clicar**. É o que faz
da correção um reparo, e não uma política nova: alinhar a porta ao botão conserta
um sintoma que já existia.

### D137 — quitar atravessava a franquia, e o dinheiro entrava no caixa errado

```php
$caixa = ...->selecionarPorUsuario($usuarioId, true);   // o caixa de QUEM CHAMA
...
$caixa->setValorFinal($caixa->getValorFinal(false) + $debito->getValor());
```

Não é só "baixar linha alheia". A rotina cria uma venda, um pagamento, baixa o
débito **e credita o caixa de quem está recebendo**. Um usuário da franquia A
quitava a dívida de um cliente da franquia B: o *a receber* sumia de B, e o
dinheiro aparecia no caixa de A.

**O ensaio vermelho:** com a guarda removida de propósito, o teste falha com
`Failed asserting that true is false` — o débito da unidade 90001100 é baixado
num pedido feito pela 90001000. É o mesmo desenho do D132, no financeiro, uma
hora antes.

**E apareceu um terceiro, ao escrever o teste.** `selecionarPorUsuario()` devolve
`null` quando o usuário não tem caixa aberto, e a linha seguinte fazia
`$caixa->getUsuario()`. Era um fatal. Agora recusa — que é a verdade: não há onde
lançar o valor.

---

## Uma coluna que existe e ninguém usa

`clientes_debitos` tem `unidade` própria. A listagem do ERP **não a usa**: ela
chega à unidade pela venda, `deb.venda → ven.usuario → usu.unidade`.

Duas fontes para o mesmo fato costumam ser um defeito esperando acontecer, então
medi antes de tratar como uma: **9.096 débitos, zero divergências**. Elas
concordam.

A guarda segue o caminho da listagem — não porque seja mais barato (é mais caro),
mas para que guarda e lista falem da mesma coisa se um dia divergirem.

---

## O que ficou de fora

| o quê | por quê |
|---|---|
| histórico de dívidas pagas | a consulta só traz as abertas; quem quer o histórico pede pelo cliente, na fatia dele |
| `pagarDebitos` do PDV | é outra tela (`VendasPagamentosController`), e ali o débito é quitado dentro de uma venda em andamento — recorte próprio |

---

## 26/09/2026 -- a tela do fiado a receber, e a quitação sob trava

A sexta tela nova é o fiado a receber: a lista das dívidas abertas e o
recebimento. Não há destino no menu -- no ERP ela é um botão da lista de
clientes --, e na tela nova ela se abre pelo caixa ("receber fiado"), que é onde
o dinheiro entra. Só para quem vê **e** recebe o fiado: o ERP também só mostra o
caminho com os dois, e sem o segundo cada "receber" terminaria num 403.

**Receber é dinheiro, e vai em duas etapas**, como fechar o caixa: escolher a
forma, e confirmar o valor com o foco no "voltar" -- um Enter distraído não
recebe. As formas vêm no envelope da lista (o catálogo de formas é guardado por
outro privilégio), já sem crédito, fidelidade e pacote.

**Ao ler a quitação para ligar a tela, apareceu o D232**: quitar não conferia se a
dívida já estava quitada, e gravava sem transação nem trava. Na cópia, 16 débitos
foram quitados duas vezes -- R$ 838,98 somados de novo em caixas. A quitação
virou uma transação com trava, para as duas telas, e as recusas ganharam motivo
(`ja_quitado`, `outra_unidade`, `sem_caixa`, `caixa_de_outro_dia`,
`forma_sem_dinheiro`), que a tela trata: o caixa ausente ou de outro dia oferece o
caixa; o já quitado relê a lista.

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

26 revisores, nenhum morto; 23 achados, 18 confirmados, todos consertados. Os que
mudaram o desenho:

- **A releitura do caixa perdia dinheiro no MySQL.** O `refresh` sem trava lia a
  foto da transação, tirada antes da espera pela trava do caixa -- e a soma apagava
  a retirada que o outro balcão acabara de gravar. O teste de então gravava pela
  mesma conexão, e a transação sempre vê o que ela mesma escreveu. Agora a
  releitura é com trava, e o teste grava por outra conexão depois da foto; o
  mutante do `refresh` sem trava só morre nele.
- **A ordem das travas cruzava com a finalização** pela chave estrangeira do
  cliente. Virou cliente -> débito -> caixa -- e é a ordem que a finalização terá
  de seguir quando ganhar trava.
- **A unidade vem antes do estado**: a tela antiga contava à franquia A se a dívida
  de um cliente da B tinha sido paga.
- **O portão de recorte por unidade lia a assinatura** -- o `$unidadeId` recebido
  bastava para ele sair verde com a comparação arrancada, aqui e em
  `excluirParcelas`, desde sempre. O recorte começa depois da `{`, e os dois
  arrancamentos agora ficam vermelhos.
- **Na tela**: com a quitação no ar nada se escolhe, busca nem vira página (a
  recusa voltava para um painel desmontado, e o operador ficava com o dinheiro na
  mão e a tela calada); desistir devolve o foco à linha; o painel não renasce
  sozinho quando a linha volta à lista; o botão veio para a primeira coluna (no
  telefone a última ficava fora da vista); e a falta de resposta diz que o
  recebimento **pode** ter entrado e relê a lista, em vez de mandar tentar de novo
  -- e o "já quitado" deixou de afirmar "noutro balcão".

**Provado:** a caracterização nos dois bancos (919); 13 mutantes do servidor, 4 do
controller pelo HTTP e 33 do front, todos mortos; os seis ensaios HTTP, com uma
quitação de verdade conferida no banco; e o navegador contra o banco de contrato --
a quitação de ponta a ponta, o telefone de 375 px e o "já quitado" no meio da
confirmação.

## 07/11/2026 -- o número da confirmação na quitação (a correção da fatia 103)

A quitação é a quarta escrita de dinheiro com o número da confirmação (a resposta 87 (a) do dono, "um número por
confirmação"; o D285; a regra 12 de `docs/convencao-api.md`). `POST /adm/api/devedores/:id/quitacao` aceita o cabeçalho
`Idempotency-Key`: a nova tentativa da mesma quitação, depois da resposta perdida, recebe o `200` da primeira vez, e não o
`409` `ja_quitado` que a tentativa anterior causou; sem o número, o `ja_quitado` de sempre. A conferência vem logo depois
da guarda do débito da rota (o `404` do débito de outra unidade continua antes) e antes do `ja_quitado`; a escrita, com a
recusa do serviço, vai dentro de `comConfirmacao`. A tela (`DevedoresLigado.tsx`) gera o número ao confirmar, o repete na
nova tentativa do mesmo débito com a mesma forma e o libera quando dá certo. O aviso do `ja_quitado` continua: vale para
o outro balcão e para quem não manda número.
