Repository navigation
fix(financial): o DV da agência do favorecido passa a ser conferido por cálculo - #1008
Merged
Merged
Conversation
…or cálculo O Validador Universal recusou a remessa de 08/09/2026 apontando as colunas 024-028 do segmento A — a agência —, e nunca a posição do DV, que estava em branco. O pré-voo aprovava porque conferia a agência só por FORMA: cinco dígitos cabem em cinco posições, e era tudo o que se perguntava. O manual 4008-523-0096 v16 (p. 30) abre a seção do cálculo com "Agência Bradesco: Módulo 11 Universal, com pesos 2 a 7" e exemplifica com a AGÊNCIA; só depois estende o critério à conta. O algoritmo sempre foi dos dois campos — a #734 implementou o consumidor da conta, e o nome da função ficou por ela. `verifyAgencyCheckDigit` delega ao mesmo núcleo em vez de copiá-lo, e `agencyHasEmbeddedCheckDigit` prova por aritmética o campo que traz o dígito colado no número. Nenhuma das duas decompõe: saber que `12343` é `1234-3` não autoriza escrever a agência derivada na posição 024-028 — um dígito errado ali não falha o arquivo, paga a conta de outra pessoa. O que elas produzem é lacuna nomeada, e a decisão volta a quem tem a informação. O DV AUSENTE segue aceito. G009 o declara "Campo Não Obrigatório – Informação Opcional", e o laudo confirma pelo silêncio: nunca apontou a posição do DV. Quatro fixtures traziam `1234-5`, e o `5` nunca foi o dígito de `1234` — a mesma classe de defeito que chegou ao banco, morando na suíte. O comentário de `bank-code-extraction.test.ts` já previa isto para a conta; faltava a agência entrar na régua. Escopo deliberado: só o favorecido. O DV do cedente é a #816, que a #819 pede para não entregar sozinha — juntas elas encalham o operador entre uma remessa recusada e um cadastro travado por histórico. Refs #816 Refs #820 Assisted-by: claude-code:claude-opus-5
…hida com zeros
Achado do code-review, e o defeito era pior que o que a mudança corrigia.
`readAgency` passava `agency` — o valor já com `padStart(5, '0')` — ao
`agencyHasEmbeddedCheckDigit`. Como o argumento chegava sempre com cinco
caracteres, a guarda de largura do detector nunca disparava pelo caminho de
produção, e uma agência legítima de quatro dígitos `abcd` era lida como
`0abc` + `d`. Sempre que `DV('0abc') === d`, era recusada: 900 das 9000
agências de quatro dígitos, uma em cada dez, com `1007` e `1236` entre elas.
E a recusa era a pior possível. A correção que a lacuna sugere ao operador é
separar `1236` em `123-6` — que escreveria `00123` nas posições 024-028. Ou
seja: o gate recusava cadastro correto e ensinava a corromper o destino do
pagamento, que é exatamente o modo de falha descrito no cabeçalho deste
arquivo como a razão de nada aqui decompor por conta própria.
A suíte ficou verde sobre o defeito porque as duas guardas que o pegariam eram
exercitadas de formas incompatíveis: o teste unitário chamava o detector SEM
padding, caminho que a produção nunca toma, e os três valores do loop de
regressão (`1234`, `0920`, `12345`) não pertencem à classe do falso positivo.
Ambos agora carregam `1236` e `1007`, e há um caso que entra pela régua real
para provar que a guarda de largura está viva em produção — não só na chamada
direta. Prova invertida: com o argumento errado de volta, os dois reprovam.
Corrigido também o comentário que afirmava que "os dois defeitos que a recusa
de 08/09/2026 expôs passam a ser vistos antes do banco". Só os do favorecido
são. A agência do cedente, apontada no mesmo laudo nas colunas 053-057 dos três
headers, segue validada apenas por forma — é a #1006, fora deste diff por causa
da #819. Registro que afirma mais do que o código faz é o defeito que o
CLAUDE.md nomeia, e estava sendo introduzido aqui.
Refs #816
Refs #1006
Assisted-by: claude-code:claude-opus-5
GabrielAderaldo
added a commit
that referenced
this pull request
Sep 8, 2026
chore(release): integra a dev na main — DV da agência do favorecido (#1008)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
O que motivou
O Validador Universal recusou a remessa de 08/09/2026 com sete erros. A hipótese de partida era "o Pix
funciona e o TED quebra" — e o diff posicional entre o arquivo recusado e o aceito a derrubou em um passo:
A linha do TED é byte-idêntica nos dois arquivos. Um registro idêntico não distingue recusado de aceito.
Também não havia Pix: as formas eram
41(TED) e01(crédito em conta).O que quebrava era agência e conta, sempre com a mesma forma: o DV grudado dentro do campo numérico e a
posição do DV em branco. Provado por aritmética — a agência do favorecido era
NNNNseguido exatamente dodígito que o módulo 11 produz para
NNNN.Por que o TED escapou: o favorecido dele é de outro banco. O algoritmo do DV é do Bradesco, e ele não o
aplica a conta de terceiro. O recorte real é domicílio Bradesco × domicílio de terceiro, nunca a forma de
lançamento.
O que muda
Fonte primária (Manual 4008-523-0096 v16, p. 30): a seção do cálculo abre com "Agência Bradesco: Módulo 11
Universal, com pesos 2 a 7" e exemplifica com a AGÊNCIA (
9999 → 6); só depois estende o critério à conta.O algoritmo sempre foi dos dois campos — a #734 implementou o consumidor da conta, e o nome da função ficou por
ela.
account-check-digit.ts—verifyAgencyCheckDigitdelega ao núcleo existente, eagencyHasEmbeddedCheckDigitprova por aritmética o campo que traz o dígito colado no número. Sem segundacópia do módulo 11.
payee-account.ts—readAgencyrecebe o banco e produz lacuna nomeada:malformedpara o DV embutido,check-digit-mismatchpara o DV declarado que não fecha.checkDigitGapsganha o campo por parâmetro: apolítica é uma só para os dois.
1234-5, e5nunca foi o dígito de1234. A mesma classe de defeito quechegou ao banco, morando na suíte. O comentário de
bank-code-extraction.test.ts:30-31já previa esse modo defalha para a conta; faltava a agência entrar na régua.
O que NÃO muda, de propósito
12343é1234-3não autoriza escrever a agência derivadanas posições 024-028 — um dígito errado ali não falha o arquivo, paga a conta de outra pessoa. O que sai é
lacuna, e a decisão volta ao operador.
pelo silêncio: apontou as colunas da agência e nunca a posição do DV, em branco nas três linhas.
not-verifiable, como já era para a conta.O segundo commit, e por que ele importa mais que o primeiro
O
/code-reviewachou um defeito na própria correção, e ele era pior que o problema original.readAgencypassava ao detector a agência já compadStart(5, '0'). Com o argumento sempre em cinco caracteres,a guarda de largura nunca disparava em produção, e uma agência legítima de quatro dígitos
abcdera lida como0abc+d— recusada sempre queDV('0abc') === d. 900 das 9000 agências de quatro dígitos, uma em cadadez (
1007,1236,1015…). E a correção que a lacuna sugeria era separar1236em123-6, o que escreveria00123no destino: o gate recusava cadastro correto e ensinava a corromper o pagamento.A suíte ficou verde sobre isso porque as duas guardas eram exercitadas de formas incompatíveis — o teste unitário
chamava o detector sem padding (caminho que produção nunca toma) e os três valores do loop não pertenciam à
classe do falso positivo. Ambos agora carregam
1236e1007, mais um caso que entra pela régua real.Prova invertida: com o argumento errado de volta, os dois testes reprovam (
fail 2). Sem isso eles nãoprovariam nada.
Escopo deliberadamente fora
Só o favorecido. A agência do cedente — apontada no mesmo laudo, colunas 053-057 dos três headers — continua
validada por forma. É a #1006, e está fora daqui porque a #819 pede para não entregar a verificação do
cedente sozinha: com o cadastro travado por histórico (
edit-cedente-account.ts:64-68), o operador ficaria entreuma remessa recusada e uma tela que não deixa corrigir, e a única saída restante seria
UPDATEem produção.O comentário que afirmava cobertura ponta a ponta foi corrigido no segundo commit — registro que afirma mais do
que o código faz é o defeito que o CLAUDE.md nomeia.
Gate
typecheck+format:check+lint+ 11811 testes, 0 falhas (11810 antes; +1 do caso de produção).Issues
Assisted-by: claude-code:claude-opus-5