0009 — A aprovação pede num modal o que a tela já mostra, e o setup é um campo vazio sem referência¶
| Status | CORRIGIDO |
| Branch | fix/0009-aprovacao-em-modal-duplicado |
| Aberto em | 2026-08-15 |
| Severidade | MÉDIA |
| Apps afetados | api · web · e2e |
| Nota do diagnóstico | 8,5 (tentativa 3 de 3) |
| Nota da correção | 8,3 (tentativa 3 de 3) |
Status:
RASCUNHO→DIAGNOSTICADO→EM CORREÇÃO→CORRIGIDO
O fix atravessa dois portões, cada um com nota de 0 a 10 dada por um subagente verificador de contexto limpo, em até 3 tentativas, passando a partir de 7,0:
- O diagnóstico (seções 1 a 4) é validado na seção 5 (reprodução × causa raiz). É o portão barato: sem ele, o segundo mede o conserto contra um palpite.
- A correção (o diff e os testes) é validada na seção 11 (correção × regressão), com duas perguntas — atacou a causa ou o sintoma, e o que prova que não volta.
1. Sintoma¶
O analista da garantidora abre a análise, trabalha no painel de parecer — o quadro laranja "Parecer da análise", onde ele escreve o parecer e as notas internas e salva rascunho — e então clica em Aprovar. Abre-se um modal que pede outras quatro coisas, num formulário que ele não viu até aqui:
- Taxa setup (R$) — campo vazio, com o auxílio "Opcional — se vazio, usa a taxa de adesão padrão da garantidora";
- Justificativa da decisão (opcional) — texto livre;
- Cláusula obrigatória incluída no contrato de locação — caixa de seleção;
- Justificativa dos documentos, quando há obrigatório faltando e ele é gestor.
Três defeitos, e os dois últimos são o mesmo problema visto de dois lados:
- O modal é um segundo formulário, não uma confirmação. Aprovar é a ação mais irreversível do produto — emite garantia —, e o passo que deveria ser "confira e confirme" é onde a pessoa descobre que ainda falta preencher coisa. Quem revisa não pode estar digitando.
- O campo de setup não diz qual é o valor. Ele é opcional e, vazio, a garantia usa a taxa padrão — mas a tela não mostra qual é essa taxa no momento de decidir. O analista escolhe entre "deixar como está" e "arbitrar", sem ver o número de nenhum dos dois lados. O valor existe e já está calculado logo acima, no resumo financeiro, como "Pagamento inicial" — só não chega ao campo.
- O valor sugerido do setup não pode vir do snapshot da análise. Os campos
financial_*são gravados na criação e só se refazem numa edição que toque valor (credit_analysis_service.py:466-467). Se a garantidora mudar a taxa de adesão depois, a análise parada emIN_ANALYSIScarrega o preço de antes — e sugerir esse número no campo faria o analista arbitrar, sem saber, um valor que a garantidora já trocou.
2. Reprodução¶
- Entrar como analista da garantidora — o botão do topo exige
decisions:update(show.tsx:543). A análise precisa já ter parecer aberto: é o botão "Analisar" do cabeçalho (show.tsx:629), que leva deSUBMITTEDaIN_ANALYSIS. Sem ele o painel mostra "Nenhum parecer registrado" e não há campo nenhum. - Abrir uma análise em
IN_ANALYSIScujo tenant tenha taxa de adesão configurada (initial_fee_typediferente deNONE). - Rolar até o painel laranja "Parecer da análise" e reparar no que ele oferece: parecer do analista, notas internas, salvar rascunho.
- Clicar em Aprovar, no topo.
Observado: abre o modal com Taxa setup vazia, Justificativa da decisão e a caixa da cláusula — nenhum dos três existia na tela até este clique. O resumo financeiro, poucos centímetros acima, exibe "Pagamento inicial" com o valor que o campo vazio usaria.
- Ambiente: dev-remote (
https://dev.garantora.com.br/app). Não depende de dado específico: reproduz em qualquer análise emIN_ANALYSIS. - Superfície: ambos. O modal é o mesmo nos dois viewports; em 375px ele pesa mais, porque o formulário inteiro disputa a tela com o teclado virtual. Não é agravado pelo PWA — é estrutura de componente, não asset cacheado.
- Dados/pré-condições: o segundo defeito só é visível quando a garantidora tem taxa de adesão configurada. Com
initial_fee_type = NONEo valor padrão é zero, e o campo vazio não esconde nada.
Para o terceiro defeito, dois passos a mais, como ADMIN_TENANT: alterar initial_fee_value em Parâmetros e reabrir a análise em IN_ANALYSIS. O resumo financeiro segue mostrando o valor anterior; editar e salvar a análise atualiza.
3. Esperado × Atual¶
-
Esperado (refinado pelo usuário em 2026-08-15, depois da aprovação do diagnóstico):
-
o setup sai do modal e passa a ser um terceiro campo do box amarelo, ao lado de "Parecer do analista" e "Notas internas", já preenchido e editável;
- a "Justificativa da decisão" não muda de lugar — é removida. O box amarelo já tem Parecer do analista, e era ele que a justificativa duplicava. Um campo a menos, não um campo mudado de casa;
- a cláusula obrigatória vai para o box amarelo também. O modal a mostra como item da revisão; não confirmada, o Aprovar do modal fica indisponível e o resumo diz o que falta — que é como o botão do topo deixa de "sempre abrir e não responder";
- o modal deixa de ter entrada, com uma exceção declarada: a Justificativa dos documentos continua nele. Ela só aparece quando há obrigatório faltando e quem aprova é gestor (RN10 da spec 0008) — é declaração do instante da aprovação, não trabalho de análise, e tirá-la poria na página um campo que quase nunca existe. Tudo o mais some do modal, que passa a ser resumo do que foi decidido, com Aprovar e um Cancelar que apenas fecha.
- Atual: o painel tem dois campos, o modal tem outros quatro, o setup não tem valor de referência, e a confirmação e o preenchimento acontecem no mesmo passo.
E, para o terceiro defeito:
- Esperado: o setup é resolvido no momento da aprovação — é ali que ele se fecha. O campo sugere o valor calculado com os parâmetros vigentes agora, e é esse que vai para a garantia se o analista não arbitrar outro.
- Atual: o único valor disponível na tela é o snapshot da criação (
a.financial_summary.initial_payment), que pode estar vencido.
Não há recálculo em massa (decisão do usuário, 2026-08-15). Mudar os parâmetros não dispara nada sobre as análises existentes: elas seguem como estão, e o valor novo só aparece quando alguém for aprovar — que é o momento em que o setup deixa de ser estimativa e vira preço.
4. Causa raiz¶
Onde¶
apps/web/src/pages/credit-analyses/components/ApproveDialog.tsx:117-155— os três campos da decisão (Taxa setup (R$),Justificativa da decisão, a caixa da cláusula) dentro do<DialogContent>.apps/web/src/pages/credit-analyses/components/ApproveDialog.tsx:16-29— o docstring diz por que eles estão lá, e é a peça central deste diagnóstico. Ver o mecanismo.apps/web/src/pages/credit-analyses/components/DecisionPanel.tsx:286-311— o painel laranja tem hoje sóopinionenotes, mais o "Salvar rascunho".apps/web/src/pages/credit-analyses/show.tsx:215—const canReadSettings = isAdminTenant || isAdminGlobal. Para o analista, que é quem reproduz este bug, a tela nem chama/settings(show.tsx:366-369), entãosettingsficanull.apps/web/src/pages/credit-analyses/components/FinancialSummaryPanel.tsx:216— o "Pagamento inicial" que o analista vê nesta tela vem definancialSummary.initial_payment, isto é, dea.financial_summary(show.tsx:928), calculado pela API. O cálculo do cliente (:110-132) existe, mas não executa aqui, porque depende desettings.apps/api/app/services/tenant_settings_service.py:21-28—update()grava as configurações e termina ali, sem efeito sobre as análises em curso. Isso está certo e não muda: registrado aqui para que a correção não seja tentada a mexer nele.apps/api/app/services/credit_analysis_service.py:340e:466-467—_apply_financial_snapshotroda na criação e, na edição, só sobif muda_preco:. Editar o telefone do inquilino não refaz o snapshot; editar o aluguel refaz. O snapshot já se atualiza com parâmetros novos — mas apenas de carona numa edição de valor, nunca pelo gatilho que deveria disparar.apps/api/app/services/guarantee_service.py:140-145—decision.approved_initial_fee_valuesobrepõepricing.initial_fee(app/services/pricing.py). O padrão é conhecido pelo servidor; a tela é que não o oferece ao campo.
Não é acidente, é reversão de decisão. O git show 1bb952f^ mostra os campos decision-approve-setup-input e decision-approve-clause-checkbox dentro do DecisionPanel (linhas 325-344). O commit 1bb952f (spec 0025) os moveu para o ApproveDialog, que nasceu ali.
O mecanismo¶
O primeiro defeito é o preço de uma troca feita de propósito. A spec 0025 levou o botão "Aprovar" para o cabeçalho. Com o botão no topo e os campos que o habilitam no fim da página, o analista veria um botão cinza sem saber por quê — o próprio ApproveDialog registra isso em :19-24: "o botão do topo sempre abre, e quem valida é o confirmar daqui". Os campos foram junto para que a validação tivesse onde acontecer.
Ou seja: a duplicação que se vê hoje não é obra inacabada, é a consequência aceita de resolver o botão-cinza-sem-motivo. O que a 0025 não previu foi que, com o painel de parecer ganhando opinion e notes na mesma spec, a decisão passaria a ocupar duas superfícies — e que a segunda seria descoberta só no clique de aprovar.
Por isso a correção precisa responder o que a 0025 respondia, e não pode fingir que a pergunta não existe: com os campos de volta ao painel, o botão do topo continua sempre abrindo, e a cláusula obrigatória continua sendo condição. A resposta está na seção 10 — o modal deixa de validar entrada e passa a exibir o que foi decidido, então uma cláusula não confirmada aparece ali como pendência a resolver na página, e não como um botão que não responde.
O segundo defeito tem causa própria, e ela não é a que parece. ApproveDialog não recebe parâmetro de preço nenhum (show.tsx:1284-1291 passa só blockingDocuments e canOverrideDocuments), mas o motivo de o campo nascer vazio vai mais fundo: para o analista não existe fonte de preço no cliente. canReadSettings (show.tsx:215) é isAdminTenant || isAdminGlobal, então a requisição a /settings nem sai, settings é null, e o ramo de cálculo do FinancialSummaryPanel (:110-132) não roda.
O número que ele vê na tela vem por outro caminho: a.financial_summary, calculado pela API e renderizado em FinancialSummaryPanel.tsx:216. É a fonte que a correção tem em mãos, e ela já está em show.tsx, a poucas linhas do diálogo.
Mas não é o mesmo número que a garantia vai usar, e a diferença importa. financial_initial_payment é snapshot, gravado em credit_analysis_service.py:844 com as TenantSettings do momento em que a análise foi criada ou recalculada. A garantia recalcula na emissão, com as settings de então (guarantee_service.py:126-144). Mesma fórmula, momentos diferentes: se a garantidora mudar a taxa de adesão entre a criação e a aprovação, o campo sugeriria o valor antigo — e, como o campo preenchido passa a valer como arbitramento (seção 7), o analista aprovaria o número velho sem saber que o arbitrou.
O terceiro defeito é sobre de onde o número vem, e a resposta muda o desenho. O snapshot financial_* é gravado na criação e só se refaz numa edição que toque valor (:466-467) — ele é uma estimativa da época, útil para o resumo, imprópria como preço. Preenchê-lo no campo transformaria estimativa vencida em arbitramento, porque o campo preenchido é, por definição, o valor que o analista arbitrou (guarantee_service.py:140-145).
A decisão do usuário resolve isso pelo momento, não pela propagação: o setup se fecha na aprovação. Enquanto a análise está aberta, ninguém precisa que os números acompanhem os parâmetros — e é por isso que não há recálculo em massa. O que precisa estar atual é o número no instante em que ele deixa de ser estimativa, e esse instante é um só.
Consequência que a correção tem de encarar: o resumo financeiro logo acima continua exibindo o snapshot. Se os parâmetros mudaram, os dois números divergem na mesma tela — e uma tela que mostra "Pagamento inicial R$ 500" e sugere setup R$ 600 sem explicar é pior que qualquer um dos dois sozinho.
Detalhe que envelheceu nesta mesma branch: o comentário de
show.tsx:213-214diz que/settingsé fechado por papel "e não porsettings:read, que não existe". Desde o commitc3cd260, namain, a permissão existe (tenant_settings:read) e todo papel com tenant a tem. O cliente é que não foi atualizado. Isso não é a causa deste bug — usarfinancial_summarycontinua sendo o caminho certo, porque é a mesma conta que a garantia vai usar —, mas explica por que o campo nunca teve como se preencher.
Por isso o problema aparece aqui e não nas outras decisões: reprovar e devolver não têm valor a arbitrar nem condição a confirmar, então os diálogos deles (show.tsx:1203-1210 e o PendDialog) são confirmação de motivo — que é o que uma confirmação deve ser.
O alcance¶
- Só a aprovação.
ApproveDialogtem um consumidor (show.tsx:1284). - Dois specs de e2e quebram, e a lista veio de
Greppelos testids (decision-approve-setup-input,decision-approve-clause-checkbox,credit-analysis-approve-dialog,credit-analysis-approve-confirm):tests/e2e/credit-analyses.spec.ts:114-149etests/e2e/credit-analysis-detail-order.spec.ts:298-299, que clicadecision-approve-button-panele afirma o diálogo visível. Otests/e2e/credit-analysis-documents.spec.tsnão entra: ele chamaPOST /decision/approveporfetch, sem passar pela interface. - A fonte do valor é
a.financial_summary, não o cálculo do cliente. Reusar o ramocalcsseria apontar para código que não executa para o analista — e criaria uma terceira cópia da regra que já vive empricing.py(autoridade) e noFinancialSummaryPanel(exibição, quando hásettings). - Nenhuma análise existente é reescrita. Sem recálculo em massa, o
PATCH /settingssegue sem efeito colateral, e as análises aprovadas ficam intactas por construção — não por um filtro de status que alguém pode errar. - A justificativa de documentos fica no modal. Só existe quando há obrigatório faltando e o usuário é gestor (RN10 da spec 0008): é exceção do instante da aprovação, não trabalho contínuo.
- O gate envelhecido é ocorrência única.
GrepporcanReadSettingsdevolve três linhas, todas emshow.tsx(:215,:366,:374). O formulário de análise busca/settingssem gate nenhum (CreditAnalysisForm.tsx:234), então não há um segundo lugar carregando a suposição velha.
5. Validação do diagnóstico¶
| Tentativa | Reprodução | Causa raiz | Final | Veredicto |
|---|---|---|---|---|
| 1 | 9,0 | 4,0 | 4,0 | REPROVADO |
| 2 | 8,7 | 8,0 | 8,0 | APROVADO |
| 3 | 8,5 | 9,0 | 8,5 | APROVADO |
Resultado: aprovado na tentativa 3 com nota 8,5 (mínimo: 7,0).
A tentativa 3 existiu porque o usuário acrescentou o terceiro defeito depois da aprovação da 2: mudar a taxa de adesão passa a recalcular as análises abertas. Isso trouxe a API para dentro do fix, e um diagnóstico que não descreve a API não serve de régua para uma correção que mexe nela.
A tentativa 1 foi reprovada por hipótese escrita como conclusão — o achado mais grave deste portão. Eu havia escrito que "a tela foi construída em duas etapas e ninguém juntou as duas", com os campos tendo ficado no modal por falta de onde persistir. O
git show 1bb952f^mostra o contrário: os campos estavam no painel, e a spec 0025 os moveu de propósito, com a razão escrita no próprioApproveDialog— o botão subiu para o cabeçalho e passou a sempre abrir, então a validação precisava de casa. Isso muda o que este fix é: não é juntar o que ninguém juntou, é reverter uma decisão de desenho, e a correção tem de dizer o que faz com a razão dela.A mesma rodada mostrou que a fonte do valor que eu apontara (
calcs, do cliente) não executa para o analista, e que a lista de e2e afetados estava errada nos dois itens.
Pendências aceitas: —
6. Correção¶
Preencher com /fix implement.
apps/api¶
- Onde: só o caminho do valor sugerido.
tenant_settings_service.pynão muda. - O quê: o setup calculado com os parâmetros vigentes precisa chegar ao painel. Dois caminhos, e o
implementescolhe com o custo à vista: - o cliente calcular, lendo
/settings— possível desdec3cd260, que deutenant_settings:reada todo papel com tenant; falta só corrigircanReadSettings(show.tsx:215), que ainda filtra por papel. Barato, mas cria a terceira cópia da regra de preço; - a API devolver o valor já calculado na resposta da análise. Não duplica a regra, custa campo novo no contrato.
- O contrato de
POST /decision/approvenão muda: já aceitaapproved_initial_fee_value, e a sobreposição já está emguarantee_service.py.
apps/web¶
- Onde:
DecisionPanel.tsx(recebe os campos da decisão, incluindo o setup),ApproveDialog.tsx(vira resumo + Aprovar/Cancelar),show.tsx(fiação, e é dele que sai o valor:a.financial_summary.initial_payment). data-testid: osdecision-approve-*migram para o painel; o modal ganha os do resumo.- Responsividade & PWA: o painel cresce; conferir 375px.
apps/e2e¶
- Teste de regressão: o fluxo novo — preencher no painel, confirmar no modal — mais a atualização de
tests/e2e/credit-analyses.spec.tsetests/e2e/credit-analysis-detail-order.spec.ts.
7. Risco / impacto colateral¶
Toca dinheiro. O setup vai para Guarantee.initial_fee e compõe o total à vista (guarantee_service.py:137-146). Um valor pré-preenchido que o analista não perceba é pior que um campo vazio: hoje vazio significa "use o padrão", e depois da correção o campo sempre terá número. Se o pré-preenchimento divergir do que a API calcularia, a garantia sai com valor arbitrado sem ninguém ter arbitrado.
Segundo risco: a aprovação deixa de ter formulário no modal, mas não pode deixar de ter confirmação — o modal precisa continuar sendo um passo, agora de revisão.
Terceiro, e é o que a spec 0025 tinha resolvido: o botão do cabeçalho sempre abre. Com a validação de volta ao painel, é preciso garantir que ele não vire um botão que abre um modal que só diz "não dá" — a pendência tem de ser visível na página, antes do clique.
8. Critérios de aceite¶
- [x] CA1. Seguindo os passos da seção 2, o setup aparece no box amarelo junto de parecer e notas internas, e o modal de Aprovar não pede preenchimento nenhum.
- [x] CA2. O campo de setup nasce com o valor dos parâmetros vigentes — que coincide com o "Pagamento inicial" do resumo enquanto ninguém mudar a taxa de adesão, e diverge dele depois (é o CA8/CA10). A redação anterior dizia "o mesmo que o resumo mostra", o que contradizia os dois.
- [x] CA3. O analista pode alterar o valor sugerido, e o valor alterado é o que chega à API.
- [x] CA4. O modal de Aprovar mostra um resumo que permite conferir a decisão e oferece Aprovar e um Cancelar que só fecha.
- [x] CA7. O campo "Justificativa da decisão" não existe mais em lugar nenhum da interface — o parecer do analista é o que cumpre o papel.
- [x] CA5. A justificativa de documentos continua no modal, e continua bloqueando quem não é gestor (RN10 da spec 0008 intacta).
- [x] CA6. O painel não rola na horizontal em 375px.
- [x] CA8. Alterada a taxa de adesão nos Parâmetros, o setup sugerido numa análise em
IN_ANALYSISpassa a ser o valor novo, sem que ninguém edite a análise. - [x] CA9. Nenhuma análise existente é reescrita por mudar os parâmetros — os campos
financial_*das análises abertas e aprovadas ficam como estavam. - [x] CA10. Se o setup sugerido divergir do "Pagamento inicial" do resumo financeiro, a tela não se contradiz em silêncio.
9. Plano¶
- [x] Reproduzir o bug — pelo código e pela tela: os campos no modal, o setup vazio.
- [x] Corrigir a causa raiz — só
web. A API não mudou: o valor sugerido é calculado no cliente com/settings, que o analista já pode ler desdec3cd260. - [x] Teste de regressão que falharia se a correção fosse revertida
- [x]
make preflightlimpo ·make test393 passaram, 0 falharam ·tsclimpo - [x] Validado no dev-remote reconstruído, desktop e mobile, com a divergência forçada (
initial_fee_valuede 100% para 50%, restaurado nofinally): painel e modal sem rolagem horizontal nos dois viewports, zero erro de console, e o campo dizendo "Sugerido pelos parâmetros de hoje (50% do pacote). O resumo financeiro acima mostra R$ 2.000,00, calculado quando a análise foi criada." - [x] e2e — 12 casos verdes em
credit-analyses.spec.tsecredit-analysis-detail-order.spec.ts, incluindo o CA8.
10. Decisões em aberto¶
Respondidas pelo usuário em 2026-08-15, antes da abertura deste registro:
- [x] O setup calculado é sugestão ou trava? Sugestão — o campo nasce preenchido e o analista pode alterar.
- [x] A confirmação some? Não. O modal deixa de ter campos e passa a mostrar o resumo do que foi decidido, com Aprovar e Cancelar.
-
[x] O valor recalcula quando o pacote muda? Sim, sempre — inclusive por cima de uma edição manual.
A consequência, registrada porque a correção tem de lidar com ela: o pacote não é editável na tela de decisão (muda em
/edit, ou por outra pessoa), então o recálculo por cima da edição só acontece quando o analista sai e volta, ou quando alguém altera a análise em paralelo. Se a sugestão for calculada ao montar a tela e não a cada tecla, "sempre recalcula" e "não perde o que ele digitou" convivem sem contradição. Se a implementação encontrar um caso em que os dois se excluem, é[PERGUNTAR AO USUÁRIO], não escolha do implementador. -
[x] Qual valor pré-preencher: o snapshot ou o recálculo? O recálculo, com os parâmetros de agora — decidido pelo usuário em 2026-08-15. É o mesmo número que a garantia usaria se ninguém arbitrasse, então o campo nunca sugere preço vencido.
-
[x] Mudar os parâmetros recalcula as análises existentes? Não — decisão revista pelo usuário em 2026-08-15, depois de considerada e descartada. O setup se fecha na aprovação: enquanto a análise está aberta nada precisa acompanhar os parâmetros, e o valor novo entra no momento em que alguém aprova. Sai da correção o recálculo em massa, e com ele o risco de reescrever a carteira inteira a cada
PATCH /settings.Consequência que a implementação herda: o cliente não tem essa conta hoje para o analista —
canReadSettings(show.tsx:215) impede a chamada a/settings. Há dois caminhos, e oimplementescolhe com o custo à vista: usar a permissãotenant_settings:read, aberta no commitc3cd260para todo papel com tenant (o gate do cliente é que ficou velho), ou expor o valor recalculado na resposta da análise. O primeiro é o mais barato e não inventa contrato; o segundo evita recalcular a regra no cliente, que já existe em duplicidade.Note que isso pode fazer o campo divergir do "Pagamento inicial" exibido no resumo financeiro, que é snapshot. Se divergirem, a tela precisa não se contradizer — é achado para o
implementresolver, não para inventar regra. -
[x] A cláusula obrigatória vai junto para o box amarelo. Decidido em 2026-08-15. O modal a exibe como item do resumo; sem ela marcada, Aprovar fica indisponível e o resumo aponta a pendência.
11. Validação da correção¶
| Tentativa | Correção | Regressão | Final | Veredicto |
|---|---|---|---|---|
| 1 | 7,0 | 6,0 | 6,0 | REPROVADO |
| 2 | 8,3 | 7,7 | 7,7 | APROVADO |
| 3 | 8,3 | 8,7 | 8,3 | APROVADO |
Resultado: aprovado na tentativa 3 com nota 8,3 (mínimo: 7,0).
A tentativa 1 reprovou por três coisas, e duas eram estrago meu: a lista de documentos obrigatórios faltantes virou contagem (perdendo quais faltam, no ponto em que se decide escalar), e o
onCloseperdeu a guarda deloading. A terceira foi cobertura: o CA8 — o coração do terceiro defeito — não tinha teste nenhum, e a asserção do valor sugerido eranot.toHaveValue(''), que passaria igual com o snapshot vencido.A tentativa 2 aprovou em 7,7 com um
[REGISTRO INCOMPLETO]procedente e caro: eu mandavasetupFeesempre, então a sugestão virava arbitramento em toda aprovação — o risco que a seção 7 deste registro havia escrito. Passou a ir só o que o analista tocou; sem isso, o arredondamento e o insumo de meses do navegador chegariam à garantia.A tentativa 3 fechou o resto: um caso rodando como analista (todo o e2e roda como gestor, para quem o gate velho já permitia — a linha que mexe em permissão era revertível com a suíte verde), as asserções de ausência dos campos dentro do modal, e a serialização do arquivo que altera parametrização do tenant.
Defeitos aceitos:
- O CA5 não tem prova pela interface. A lógica de bloqueio documental foi reescrita no diálogo e só a regra do lado da API está coberta. Falta o caso com obrigatório faltando, aberto como analista (confirmar desabilitado) e como gestor (justificativa presente, confirmar liberado só depois de preenchida).
- A janela do
PATCH /settingsno tenant compartilhado. O CA8 alterainitial_fee_valuee restaura nofinally; omode: 'serial'ordena só este arquivo. Nada mais no repositório escreve em/settings, então o risco é baixo — mas a serialização não basta por si, e é isso que fica dito. - "Salvar rascunho" ignora o setup e a cláusula.
DecisionPanelgrava sóopinionenotes; os dois campos novos vivem no estado da página e não são persistidos. Quem digita um setup, salva rascunho e recarrega perde o número — a sugestão volta por cima, sem aviso. É consequência de trazer os campos para o painel, e a decisão de persistir ou não é do usuário; enquanto não for tomada, o comportamento fica declarado aqui. TOTAL_GUARANTEEusa insumo diferente do servidor. O cliente multiplica porcredit_analysis.guarantee_months;guarantee_service.py:124usa os meses entre início e fim da garantia. Com a baseRENT— o padrão, e o que o seed usa — os dois coincidem, e a sugestão é exata. ComTOTAL_GUARANTEEpodem divergir, e como o campo preenchido vira arbitramento, o valor gravado seria o do cliente. Alinhar exigiria calcular no servidor, que é o caminho que este fix não tomou.- Arredondamento.
toFixed(2)no cliente contraROUND_HALF_UPdopricing.py. Divergem no meio-centavo — e, pela mesma razão acima, só no que se lê, não no que se grava. - O gate de
/settingsalargado muda o resumo financeiro para mais gente.OPERATOR,VIEWEReAGENCY_USERpassam a carregar as configurações nesta tela, então o ramo de cálculo doFinancialSummaryPanelfica disponível para eles. É o efeito pretendido pelo commitc3cd260, mas quem lê este fix precisa saber que ele acontece aqui.
13. Acrescentado depois: a cláusula obrigatória sai do produto¶
Pedido do usuário em 2026-08-15, depois de o diagnóstico e a correção já terem passado pelos portões. Entra aqui e não num fix próprio porque a cláusula era um dos dois campos que este fix acabara de mover para o painel — separá-los faria um commit acrescentar o campo e o seguinte apagá-lo, com teste e critério de aceite pelo caminho.
O que ela era. Uma caixa que o analista marcava atestando que o contrato de locação daquele inquilino contém a cláusula que vincula a garantia. Bloqueava a aprovação com 422 MANDATORY_CLAUSE_NOT_CONFIRMED (spec 0003, RN2) e guardava um booleano na decisão.
Por que sai. É regra do contrato da garantidora, não da ferramenta que faz a gestão. O sistema nunca teve como conferir a afirmação — guardar o "eu confirmo" dava aparência de controle sem controle nenhum, e a única coisa entre a aprovação e o clique era marcar uma caixa. Estava misturando a gestão com o contrato de quem usa a gestão.
O que muda. mandatory_clause_confirmed sai do schema de entrada e de saída, da validação, do modelo e do banco (migration x7z9b1e3h5i6, com downgrade recriando a coluna e exercitado contra PostgreSQL). No web saem a caixa, o estado e o item do resumo. A RN2 da spec 0003 fica revogada, e o teste que a afirmava foi reescrito para afirmar o oposto: aprovar sem campo nenhum de cláusula devolve 200, e o campo não volta na resposta.
Não passou por portão. As três tentativas do laço da correção foram gastas antes deste pedido. O que sustenta a mudança é o que dá para medir: suíte da API 393 verdes, e2e 13 verdes nos dois specs afetados, downgrade exercitado, e preflight limpo. Um quarto portão exigiria estourar o limite — se você quiser, rodo assim mesmo, ciente disso.
14. Acrescentado depois: o botão "Atualizar" do PWA que não fazia nada¶
Pedido do usuário em 2026-08-15, e não tem relação com este fix — entra aqui por decisão dele, com a objeção registrada: o fix.md não descreve nada de PWA, e o commit fica misto.
O sintoma. Em desenvolvimento, o botão "Atualizar" do banner de nova versão às vezes não faz nada, e o usuário notou que isso acontece logo depois de um hard reset de cache.
A causa. needRefresh é estado de React e sobrevive ao service worker que o originou. O hard reset remove o worker em waiting, mas o banner continua na tela; clicar chamava updateServiceWorker(true), que procura um waiting que não existe mais — nada acontece e a página não recarrega. No dev-remote isso é frequente porque o rebuild troca o SW várias vezes por sessão.
O conserto. Três degraus em pwa-update-banner/index.tsx: usa o waiting se houver; senão pergunta ao servidor uma vez (registration.update()); senão recarrega. Mais um temporizador de 5s para o caso de o controllerchange nunca chegar, que deixaria o botão preso em "Atualizando…".
Sem prova de regressão automatizada, e isto não é "só visual". O diff mexe em condição. Reproduzir o caminho exige um service worker em waiting (dois builds do dev-remote) e depois destruí-lo pelo navigator.serviceWorker — o spec existente (pwa-update-banner.spec.ts) só afirma a ausência do banner no estado normal, e continua verde. O que se mediu: tsc limpo, os 3 casos do spec verdes, e o banner segue ausente no estado normal nos dois viewports. O caminho consertado não foi exercitado.
12. Registro¶
O que era: dois campos no painel, quatro no modal, o setup vazio prometendo um valor que a tela não mostrava, e o preenchimento acontecendo no passo em que se deveria conferir.
O que virou: setup e cláusula no box amarelo, com o setup sugerido pelos parâmetros vigentes e editável; a "Justificativa da decisão" removida (o parecer já cumpria o papel); o modal como revisão, com resumo, Aprovar e Cancelar.
O que divergiu do plano:
-
A cláusula obrigatória foi removida no meio. Ver a seção 13 — chegou depois dos portões, e é a única parte deste fix que não passou por um.
-
A API não mudou. O plano admitia campo novo no contrato; não foi preciso. O gate
canReadSettingsestava velho desdec3cd260— corrigi-lo destravou a fonte no cliente. - A correção quebrou a tela inteira antes de funcionar. O
useEffectda sugestão entrou abaixo de um retorno antecipado, e React #310 apagou a página. Só a captura contra o dev-remote mostrou —tscestava limpo, e o e2e não chegou a rodar. O hook subiu para antes do primeiro retorno, que é o do esqueleto. setupTocadoexistia e não era consultado. O portão pegou: sugerir não é arbitrar.- O resumo do modal lia o parecer do servidor, então texto digitado e não salvo aparecia como "não preenchido" na conferência. Passou a ler o estado vivo do painel.