Negociação no mockup — calculadora IBFT dentro do drawer — Plano de implementação
TLDR: extrair o motor da calculadora para um módulo JS testado, inliná-lo no bundle do mockup, e reconstruir o
Negociacao Drawerem duas colunas pré-preenchido pelo débito, com gerar/aprovar em dois estágios.
Spec:
.project/docs/specs/20260930014047_negotiation_calculator_mockup.mdBranch:feat/negotiation-calculator-mockup
Arquitetura: o motor de cálculo vira um módulo ES puro em tasks/mockup/negotiation_engine.js,
sem DOM e sem estado global — é a fonte única e a única parte testável automaticamente. A
ferramenta tasks/mockup/bundle.py desempacota o charges_mockup.html, inlina o motor no
template.html e reempacota. As mudanças de layout do drawer são HTML dentro do bundle, com
verificação manual no navegador conforme a lista da spec.
Stack: Python 3 (stdlib: json, base64, gzip) para o bundle; JavaScript ES2022 e o test
runner nativo do Node 22 (node --test), sem dependência nova.
Restrições globais
- Nenhuma dependência nova:
node --testé nativo do Node 22 e a ferramenta de bundle usa só stdlib. - O motor não acessa DOM nem data do sistema:
hojeerefsempre entram como parâmetro, senão o teste é não-determinístico. - Valores monetários truncam com
floor2, nunca arredondam — é o comportamento do Asaas. - Nenhum arquivo de
modules/backendoumodules/frontendé tocado. - Negativadas não existem em lugar nenhum do motor (D8 da spec).
Mapa de arquivos
| Arquivo | Responsabilidade |
|---|---|
tasks/mockup/bundle.py |
Desempacotar e reempacotar o charges_mockup.html, inlinando o motor |
tasks/mockup/negotiation_engine.js |
Motor: aritmética, agregados, quitModel, gerarTexto, pré-preenchimento, validações |
tasks/mockup/negotiation_engine.test.js |
Testes do motor |
charges_mockup.html → template.html |
Estado, fluxo de negociação, botão do modal de detalhes, motor inlinado |
charges_mockup.html → Negociacao Drawer.dc.html |
Layout em duas colunas e todas as seções |
Task 1: Ferramenta de bundle
Files:
- Create: tasks/mockup/bundle.py
- Test: tasks/mockup/bundle_roundtrip.sh
Interfaces:
- Produces: python3 tasks/mockup/bundle.py unpack <html> <dir> e python3 tasks/mockup/bundle.py pack <dir> <html>
- [ ] Step 1: Teste que falha —
bundle_roundtrip.shdesempacota, reempacota, desempacota de novo e exige que as duas árvores sejam idênticas (diff -r). - [ ] Step 2: Rodar —
sh tasks/mockup/bundle_roundtrip.sh→ FAIL (bundle.pynão existe). - [ ] Step 3: Implementar — linha 375 é o manifest, 379 os
ext_resources, 387 o template JSON. Assetscompressedpassam porgzip(commtime=0para determinismo); o nome do arquivo sai doext_resourcesquando existe, senão é o uuid. Nopack, senegotiation_engine.jsexistir emtasks/mockup/, seu conteúdo é inlinado notemplate.htmlentre os marcadores/* ENGINE:START */e/* ENGINE:END */. - [ ] Step 4: Rodar → PASS.
- [ ] Step 5: Commit —
chore: ferramenta de bundle do mockup
Task 2: Motor — aritmética base
Files:
- Create: tasks/mockup/negotiation_engine.js
- Test: tasks/mockup/negotiation_engine.test.js
Interfaces:
- Produces: floor2, parseV, jurosRate, diasAtraso, addDays, addMonths, jurosParcelaBruto, descJurosParcela, valorAtualizado, e as constantes JUROS_MES = 0.02, MULTA = 0.02, PISO = 500
Casos de teste, todos derivados das fórmulas da calculadora real:
| Caso | Entrada | Esperado |
|---|---|---|
floor2 trunca |
2.3976 |
2.39 |
parseV pt-BR |
'1.206,28' |
1206.28 |
jurosRate |
184 dias |
0.12266… |
diasAtraso |
venc 2026-03-10, ref 2026-09-10 |
184 |
diasAtraso futuro |
venc 2026-12-01, ref 2026-09-10 |
0 |
addMonths clamp |
2026-03-31 +1 / +2 / +3 |
2026-04-30 / 2026-05-31 / 2026-06-30 |
| juros bruto | 206.28, 184 dias |
25.30 |
| multa | 206.28 |
4.12 |
valorAtualizado |
206.28, 184 dias, sem desconto |
235.70 |
valorAtualizado |
idem, desconto de juros 50% | 223.05 |
- [ ] Step 1: Escrever os testes acima (todos RED).
- [ ] Step 2:
node --test tasks/mockup/negotiation_engine.test.js→ FAIL (Cannot find module). - [ ] Step 3: Implementar as funções, portadas 1:1 da calculadora.
jurosRate(d) = (JUROS_MES/30)*d.valorAtualizado(p, ctx) = orig + floor2(orig*MULTA) + (jurosBruto − descJuros). - [ ] Step 4: → PASS.
- [ ] Step 5: Commit —
feat: aritmetica base do motor de negociacao
Task 3: Motor — agregados e vincSplit
Interfaces:
- Consumes: Task 2
- Produces: pAtrasoTable, crossedVinc, pAtraso, aVencer, vincSplit, atrasoOriginal, atrasoMultaV, atrasoJurosV, descJurosTotal, atrasoComEnc, totalAtraso, totalVinc, totalGeral
| Caso | Entrada | Esperado |
|---|---|---|
| soma do original | 3 parcelas de 206.28 |
618.84 |
| multa por parcela | idem | 12.36 (3 × 4.12, não floor2(618.84×0.02)) |
vincSplit |
ref 2026-09-10, 1ª a vencer 2026-07-15, qtd 3 |
2 vencidas (07-15, 08-15), 1 a vencer (09-15) |
pAtraso inclui as cruzadas |
idem, com 1 em atraso na tabela | 3 em atraso |
| a vencer no total | atraso 618.84 + a vencer 412.56 |
total inclui as duas |
A multa por parcela é o caso que separa o motor correto do mockup atual — 3 × floor2(206.28×0.02) = 12.36, enquanto floor2(618.84×0.02) = 12.37.
- [ ] Step 1–5: mesmo ciclo. Commit —
feat: agregados de parcelas do motor de negociacao
Task 4: Motor — quitModel e piso
Interfaces:
- Consumes: Tasks 2–3
- Produces: quitModel(input) → { A, V, jurosRem, multaRem, vincDesc, dv, dg, geralDesc, subtotal, total, bruto, descTotal, abaixoDoPiso }
| Caso | Entrada | Esperado |
|---|---|---|
| isenção de juros | 3 × 206.28 em atraso, retJuros |
jurosRem = juros cheio; total sem ele |
| isenção de multa (só total) | retMulta em quitação parcial |
multaRem = 0 |
| clamp das a vencer | descVincPerc = 25 |
aplica 10 |
| clamp do geral | descGeralPerc = 150 |
aplica 100 |
| piso | total resultante < 500 |
abaixoDoPiso = true |
| piso, limite | total resultante = 500 |
abaixoDoPiso = false |
| sem negativada | qualquer entrada | nenhum campo neg* no retorno |
abaixoDoPiso é o D9 da spec: o piso passa a valer sobre o total da quitação e rejeita a
simulação, não arredonda. Quem consome decide como mostrar; o motor só sinaliza.
- [ ] Step 1–5: commit —
feat: motor de quitacao com piso de 500
Task 5: Motor — gerarTexto do reparcelamento
Interfaces:
- Consumes: Tasks 2–4
- Produces: gerarTexto(input) → string
| Caso | Esperado no texto |
|---|---|
| sem desconto, 2 grupos | itemiza atraso e a vencer; 📌 O *total* fica em |
| sem desconto, 1 grupo | não itemiza; sufixo (N parcelas em atraso) |
| com desconto de juros | 💵 *Valor atual da dívida:*, ✅ *Descontos aplicados:*, 💚 *Você economiza* |
| parcelado | 💡 *Importante:* dividir em mais vezes *não gera juros* |
| 1º reparcelamento | CTA termina com *boleto* da primeira parcela |
| 2º reparcelamento | CTA cita *formulário rápido* e contrato de confissão de dívida |
| acesso 12m vigente | expira no dia |
| acesso 12m expirado | expirou no dia |
| extensão > 0, não expirado | estendendo seu acesso até |
| vitalício | Seu acesso ao material desse produto é *vitalício* |
| restrições | bloco Enquanto houver um reparcelamento ativo: presente |
- [ ] Step 1–5: commit —
feat: texto da proposta de reparcelamento
Task 6: Motor — gerarTexto da quitação
| Caso | Esperado no texto |
|---|---|
| quitação total sem benefício | abertura neutra, sem “condição especial” |
| quitação total com desconto | 📌 *Valor final para quitação:* e 💚 *Você economiza* |
| quitação parcial | título Quitação parcial; 🔎 Nosso acordo é *exclusivamente financeiro*. sem bloco de acesso |
| CTA | termina com Posso já gerar o seu *PIX* |
| sem bloco de restrições | Enquanto houver um reparcelamento ativo ausente |
fraseProdutos com 2 |
referente aos seus cursos de *A* e *B* |
fraseProdutos com 3 |
*A*, *B* e *C* |
- [ ] Step 1–5: commit —
feat: texto da proposta de quitacao
Task 7: Motor — pré-preenchimento pelo débito
Interfaces:
- Produces: buildFromDebit(debit) → { atrasos, aVencer, produtos, acesso, expiracao }
| Caso | Entrada | Esperado |
|---|---|---|
| separa por status | installments overdue e upcoming |
duas listas |
| ignora pagas | uma paid |
fora das duas |
| produtos | 2 product_debits |
nomes na ordem recebida |
| acesso vitalício | lifetime: true |
'vital' |
| acesso 12m | lifetime: false, expires_on |
'12m' com a data |
| livro | lifetime: false, sem expires_on |
'livro' |
| divergência | linha editada | divergente: true; contador { debito: 3, proposta: 2 } |
- [ ] Step 1–5: commit —
feat: pre-preenchimento da negociacao pelo debito
Task 8: Motor — validações de R-001
Interfaces:
- Produces: validar(input) → { bloqueios: [], alertas: [], reclassificar: 'agendamento' | null }
| Regra | Caso | Esperado |
|---|---|---|
RN-REPARC-1 |
payment_type = repayment_second |
bloqueio: sem novo reparcelamento |
RN-REPARC-1 |
repayment_first |
livre |
RN-REPARC-2 |
13 parcelas | bloqueio |
RN-REPARC-3 |
1ª vence em 8 dias | reclassificar = 'agendamento' |
RN-REPARC-3 |
1ª vence em 7 dias | null |
| extensão | > 90 dias |
alerta |
RN-QUIT-4 |
total < 500 |
bloqueio |
- [ ] Step 1–5: commit —
feat: validacoes das regras de negociacao
Task 9: Inlinar o motor e reescrever o estado no template.html
Files:
- Modify: template.html (dentro do bundle)
Substituir o bloco simAtrasoCalc / simValorAtrasoSum / simJurosSum / simMultaSum /
simTotal por chamadas ao motor inlinado. Novo estado negPgIdx guardando o débito em
negociação. Fluxo de dois estágios: gerar proposta leva o débito a negociacao sem executar;
aprovar executa; recusar devolve a pendente; cancelar e expirar idem. Rascunho não altera o
status. A proposta guarda a composição inteira (D12), não só o texto.
Verificação: manual, itens 5, 6, 7, 12 e 13 da spec.
- [ ] Commit —
feat: motor de negociacao no mockup
Task 10: Botão do modal de detalhes por status
Files:
- Modify: template.html
pendente e expirado → “Negociar”. negociacao → “Continuar negociação”, reabrindo em modo
edição. pago e cancelado → sem botão.
Verificação: item 1 da spec.
- [ ] Commit —
feat: botao de negociacao por status da cobranca
Task 11: Drawer — card “Dados do débito”
Files:
- Modify: Negociacao Drawer.dc.html
Card sem número no topo da coluna esquerda: cliente, CPF, produtos separados por vírgula, e as parcelas em atraso somente leitura. Contador de divergência.
Verificação: itens 2 e 9 da spec.
- [ ] Commit —
feat: card de dados do debito na negociacao
Task 12: Drawer — tabelas de atraso e a vencer
Files:
- Modify: Negociacao Drawer.dc.html
Remover os três campos geradores. Tabela de atraso editável por linha, com marca de divergência e checkbox de seleção. Parcelas a vencer viram tabela com os mesmos controles. Remover coluna de negativada, “marcar todas” e o botão associado.
Verificação: itens 2, 5, 8 e 9 da spec.
- [ ] Commit —
feat: tabelas de parcelas da negociacao
Task 13: Drawer — descontos e acesso
Files:
- Modify: Negociacao Drawer.dc.html
Descontos da quitação: isenção de juros, isenção de multa, a vencer (≤10%), geral. Sem controles de negativada. Acesso: “Expiração do acesso” pré-preenchida, mais “Extensão (dias)” e o alerta de > 90.
Verificação: itens 6 e 7 da spec.
- [ ] Commit —
feat: descontos e acesso na negociacao
Task 14: Drawer — duas colunas, resumo e proposta
Files:
- Modify: Negociacao Drawer.dc.html
Layout em duas colunas: formulário à esquerda; resumo itemizado, grade 2x–12x e proposta fixos à
direita. Preview com *negrito* renderizado, edição manual com aviso e botão de regerar. Rodapé
com rascunho, gerar, aprovar (com confirmação), recusar e cancelar. Somente leitura quando
aprovada.
Verificação: itens 6, 10, 11 e 12 da spec.
- [ ] Commit —
feat: layout em duas colunas da negociacao
Task 15: Verificação ponta a ponta
Percorrer os 13 itens de “Como verificar” da spec no navegador e registrar o resultado. O item 3 (juros pró-rata de ~12% numa parcela com seis meses de atraso) é o que prova que o motor foi portado, e o item 10 (texto igual ao da calculadora real para a mesma entrada) é o que prova que o gerador de texto está fiel.
- [ ] Commit —
chore: verificacao da negociacao no mockup
Cobertura da spec
| Requisito | Task |
|---|---|
| D5 paridade 1:1 do motor | 2, 3, 4 |
| D9 piso sobre o total | 4 |
| D8 sem negativadas | 2–4 (ausência testada) |
| D4/D17 texto e edição manual | 5, 6, 14 |
| D6/D7/D10 pré-preenchimento | 7, 12, 13 |
| D11 divergência | 7, 11, 12 |
| D14 a vencer como tabela | 3, 12 |
| D15 quitação parcial | 4, 12 |
D16 validações de R-001 |
8 |
| D18/D20/D21 fluxo de estados | 9, 10, 14 |
| D19 rascunho não altera status | 9 |
| D12 composição guardada | 9 |
| D13 duas colunas | 14 |
| Verificação da spec | 15 |