0025 — Organização da visualização da análise de crédito¶
| Status | CONCLUÍDA |
| Branch | spec/0025-visualizacao-da-analise-de-credito |
| Criada em | 2026-08-08 |
| Apps afetados | api · web · e2e |
Status:
RASCUNHO→APROVADA→EM IMPLEMENTAÇÃO→CONCLUÍDA
1. Problema¶
A tela de ver uma análise de crédito é onde o analista decide se a garantidora assume o risco. Hoje ela não está organizada em torno dessa decisão — está organizada em torno do cadastro. Quem abre a tela encontra, nesta ordem: inquilino, imóvel, situação financeira, imobiliária parceira, documentos, histórico, observações, parametrização financeira, contrato e, só então, o parecer.
Cinco dores concretas:
-
Não dá para saber onde a análise está sem ler. O estado aparece como um chip solto ao lado do código. Um chip diz onde está; não diz o que já passou nem o que falta. Para responder "essa análise já foi para o analista?" o operador desce até o histórico, no meio da página, e lê a última linha.
-
O veredito da política aparece depois de tudo. A leitura "dentro/fora da política" é um
<Alert>no fim da página (show.tsx:569), e só existe quando a análise está em estado decidível. É a informação que decide o caso, e está atrás de sete painéis. -
A informação do risco está partida em três lugares. A razão renda/Pacote está numa linha do painel "Situação financeira"; a recomendação automática da API (
credit_recommendation) está escondida dentro do painel de parecer, no fim; e o alerta de política está num terceiro ponto. São três leituras da mesma pergunta — "essa renda cobre esse aluguel?" — em três alturas da tela. -
O que falta de documento obrigatório só aparece na hora de aprovar. O checklist completo mostra tudo (entregue, faltando, vencido) misturado por área, e o bloqueio da aprovação (spec 0008, RN9) só se revela como alerta dentro do painel de parecer, quando o analista já está com o dedo no botão. A pendência devia ser a segunda coisa que ele lê, não a última.
-
"Iniciar análise" está enterrado. Para uma análise
SUBMITTED, a única ação possível é começá-la — e ela é um<Button>pequeno no meio do painel de parecer, no fim da página (DecisionPanel.tsx:215), enquanto o topo da tela oferece só "Cancelar".
Além disso, a tela tem dois painéis financeiros separados — "Situação financeira" e a "Parametrização financeira" colapsável — com um painel de imobiliária e um de observações entre eles.
2. Objetivo¶
A visualização passa a ser lida de cima para baixo na ordem da decisão: onde a análise está, se cabe na política, o que falta, quanto custa — depois quem é e o que aconteceu. As ações do momento ficam no topo, no cabeçalho, e não no meio da página.
3. Fora de escopo¶
- A lista e o formulário de análise de crédito. Só a tela de ver (
show.tsx) muda. O formulário de dois passos (spec 0015) fica como está. - A máquina de estados. Nenhuma transição nova, nenhuma regra de transição alterada. A linha do tempo desenha o que já existe.
min_income_ratiocomo parâmetro do tenant. Continua constante no web (MIN_INCOME_RATIO = 3), como a spec 0015 deixou. VirarTenantSettingsé spec própria — e agora com mais motivo, porque o mínimo passa a ser o número mais visível da tela.- A regra de decisão. O que aprovar/reprovar/pendenciar faz, valida e envia não muda. Muda onde os botões ficam e onde os campos que os habilitam são preenchidos.
- Consulta a bureau de crédito / score. Não há campo nem integração. O quadro de política mostra o que o sistema sabe: renda declarada e a recomendação da API.
- Menu de estouro (
⋮) noPageHeader. O teto de três ações da spec 0020 (RN7) fica de pé; a RN9 resolve o único estado que estoura sem criar o menu. - As telas de contrato e cobrança. O painel "Contrato vinculado" só muda de posição.
4. Regras de negócio¶
Linha do tempo (bloco 1)¶
-
RN1. A linha do tempo é um trilho fixo de quatro etapas —
Em edição→Enviado→Em análise→Aprovado—, com a etapa atual destacada, as anteriores marcadas como cumpridas e as seguintes apagadas. Sem datas: a cronologia (quando, quem, por quê) é do histórico, no fim da página, e repeti-la no topo faria a mesma informação aparecer duas vezes com precisões diferentes. -
RN2. Os três estados fora do trilho feliz não ganham nó próprio — ocupam o nó em que a análise de fato está:
PENDING_DOCSocupa o nó 3 com o rótulo "Aguardando documentos" e cor de alerta. É onde a análise está, e ela volta paraEm análisesem andar no trilho.REJECTEDocupa o nó 4 com o rótulo "Reprovado" e cor de erro.-
CANCELLEDocupa o nó 4 com o rótulo "Cancelado", neutro, e todo o trilho fica esmaecido — é o único estado em que nada mais vai acontecer por caminho nenhum. -
RN3. No mobile (375px) os quatro rótulos não cabem lado a lado. O trilho mostra os quatro nós numerados e o rótulo apenas da etapa atual, numa linha abaixo, no formato
Etapa 3 de 4 · Em análise. Sem rolagem horizontal — nem no trilho, nem na página.
Ordem da página¶
- RN4. A visualização passa a ter esta ordem, de cima para baixo:
| # | Bloco | Observação |
|---|---|---|
| 1 | Linha do tempo | RN1–RN3 |
| 2 | Parecer da análise | RN12–RN13, RN16 |
| 3 | Política de contratação | RN5–RN7 |
| 4 | Documentos obrigatórios faltando | RN8 |
| 5 | Resumo financeiro | RN9 |
| 6 | Inquilino · Imóvel · Imobiliária | painéis de hoje, só reposicionados |
| 7 | Anexos · Checklist completo · Observações · Contrato vinculado | idem |
| 8 | Histórico | RN14 |
O parecer subiu para o bloco 2 num ajuste posterior à primeira implementação. Ele estava no 7, atrás de seis blocos de leitura — e é o que o analista escreve e o que o gestor abre a tela para ler. Os botões de decisão continuam no cabeçalho (RN10); o que mudou de lugar foi o texto.
Blocos que hoje não aparecem em certos estados continuam não aparecendo (o painel de contrato só existe com contrato; o de parecer só a partir de SUBMITTED). A ordem relativa dos que aparecem é sempre esta — nenhum bloco muda de posição conforme o estado.
Política de contratação (bloco 3)¶
-
RN5. O quadro mostra, sempre nesta ordem: renda declarada total (soma de inquilinos e corresponsáveis), Pacote Locatício, e a razão entre os dois com o mínimo ao lado (
2,3× (mínimo 3,0×)). O veredito DENTRO / FORA fica no cabeçalho do quadro, e érazão ≥ 3,0×. -
RN5a. O título do quadro acompanha o veredito, não só o selo: FORA pinta rótulo e selo de vermelho, DENTRO de verde. Mesma regra do quadro de documentos (RN8a) e pelo mesmo motivo — só o selo tingido deixava o bloco parecendo um painel neutro qualquer numa varredura rápida da página. Sem renda declarada não há veredito a dar, e o título fica neutro.
-
RN6. Quando existe
Decision(isto é, deIN_ANALYSISem diante), o quadro mostra também o chip da recomendação automática da API (credit_recommendation:Aprovação recomendada/Análise manual/Rejeição sugerida) com orecommendation_notes. Esse chip sai do painel de parecer, onde vive hoje (DecisionPanel.tsx:231) — a recomendação é leitura da política, não parte do parecer. SemDecision(DRAFT,SUBMITTED), o quadro mostra só a razão local. -
RN7. A razão da API passa a incidir sobre o Pacote Locatício. Hoje
compute_credit_summaryrecebecredit_analysis.rental_value— só o aluguel — nos dois pontos que a chamam (decision_service.py:115ecredit_analysis_participant_service.py:214), enquanto o web calcula sobre o Pacote (aluguel + condomínio + IPTU), como a spec 0006 definiu para todo o resto da precificação. O serviço passa a usarfinancial_rental_package, com fallback pararental_valuenas análises antigas sem snapshot. Os limiares (3× e 2×) não mudam. Decidido em 2026-08-08 (§9): alinhar.
Por que não dá para deixar como está: as RN5 e RN6 colocam as duas contas no mesmo quadro, e o divisor da API é sempre menor — a razão dela é sempre a mais otimista. Numa análise com aluguel R$ 2.500, condomínio R$ 450, IPTU R$ 150 e renda declarada de R$ 8.000, o quadro exibiria o veredito FORA (2,6×, sobre o Pacote de R$ 3.100) logo acima do chip "Aprovação recomendada" da API (3,2×, sobre o aluguel). A faixa em que os dois se contradizem é 3 × aluguel ≤ renda < 3 × Pacote — aqui, toda renda entre R$ 7.500 e R$ 9.300.
Qual das duas está certa: a do Pacote. É sobre ele que incidem mensalidade, LMI e Custos de Saída (pricing.py), e é ele que a garantidora cobre no sinistro. Medir o risco sobre uma base menor do que a que se cobre subestima a exposição. A API ficou para trás porque credit.py é anterior à spec 0006 e não foi revisitada — as docstrings ainda dizem "regra 3× aluguel"; o web foi ajustado na spec 0015 (RN8) e a API não.
Consequência: análises com encargos informados podem mudar de recomendação ao serem reabertas — de APPROVE para MANUAL_ANALYSIS, por exemplo. Como a recomendação é recalculada só na criação da Decision e na atualização de renda, as já decididas mantêm o valor gravado. Não há dado em produção (ver CLAUDE.md, "Estágio do produto").
-
RN8a. O título do quadro acompanha o tom do estado, não só o selo: com pendência, rótulo e selo ficam vermelhos. Só o selo tingido deixava o bloco parecendo um painel neutro qualquer numa varredura rápida da página.
-
RN8. O quadro de documentos lista apenas os obrigatórios não satisfeitos — o
blockingdo checklist (spec 0008) —, cada um com o nome do documento, de quem é e o motivo (FaltaouVencido). Sem pendência, o quadro mostra o estado positivo ("Nenhum documento obrigatório pendente") em vez de sumir: a ausência do quadro seria ambígua entre "não falta nada" e "o tenant não parametrizou nada". Se o tenant não parametrizou exigência nenhuma (RN15 da spec 0008), o quadro diz isso.
O checklist completo por área — entregues, opcionais, vencidos — continua existindo, no bloco 7, junto dos anexos. O quadro do topo é o resumo do que impede.
Resumo financeiro (bloco 5)¶
-
RN9. Os dois painéis financeiros de hoje viram um: "Situação financeira" (aluguel, encargos, Pacote, prazo, vigência) com a parametrização da spec 0010 — taxa, LMI, Custos de Saída, pagamento inicial, carências — no mesmo quadro. As linhas de renda declarada e razão saem daqui: foram para o quadro de política (RN5) e não se repetem.
-
RN9a. O resumo financeiro não colapsa nesta tela. O recolhimento existe para o formulário no mobile (spec 0007, RN13), onde o resumo empurraria os campos para baixo. Numa tela de leitura ele só escondia a metade mais informativa do bloco — taxa, LMI, custos de saída — atrás de um clique que ninguém dava. O
collapsibledo componente continua existindo, para o formulário; a visualização não o usa. -
RN9c. Nenhuma linha do quadro tem peso próprio. O "Pacote Locatício" chegou a sair em 16px/700 contra os 13px/600 das vizinhas e lia como um bloco preto no meio do quadro. Ele já é o número âncora pela repetição — reaparece no resumo logo abaixo e no quadro de política.
-
RN9b. O quadro fica em destaque, tingido de
primary(navy). É a cor do dinheiro no app — oFinancialSummaryPaneljá tingia o próprio cabeçalho de navy antes desta spec —, então o destaque não inventa uma cor: promove a que já estava lá.
Ações no cabeçalho¶
- RN10. As ações do topo passam a ser, por estado:
| Estado | Principal | Secundária | Terciária |
|---|---|---|---|
DRAFT | Enviar para análise | Editar | Cancelar |
SUBMITTED | Iniciar análise | Cancelar | — |
IN_ANALYSIS | Aprovar | Reprovar | Solicitar documentos |
PENDING_DOCS | Aprovar | Reprovar | Resolver pendência |
APPROVED | Criar contrato (ou Visualizar contrato) | — | — |
REJECTED / CANCELLED | Visualizar contrato, se houver | — | — |
DRAFT e os estados terminais ficam como estão hoje. O que muda é do SUBMITTED ao PENDING_DOCS.
-
RN11.
PENDING_DOCSé o único estado com quatro candidatas (Aprovar, Reprovar, Resolver pendência, Cancelar) e o teto doPageHeaderé três. "Cancelar" sai do topo e passa a viver no rodapé do painel de parecer, como ação de texto de baixo destaque, preservando odata-testid. É a de menor frequência das quatro: cancelar uma análise que já está com o analista, esperando documento, é o caso raro — e a destrutiva é justamente a que menos merece o alvo mais fácil do topo. -
RN12. O botão "Aprovar" do topo abre o diálogo de aprovação, que passa a carregar o que habilita a decisão: taxa de setup arbitrada, a confirmação da cláusula obrigatória e — quando houver bloqueio documental e o usuário for gestor — a justificativa da exceção (spec 0008, RN10). O botão do topo fica sempre habilitado; quem valida é o confirmar do diálogo, que só habilita com a cláusula marcada (e com a justificativa preenchida, quando exigida).
A razão: um botão desabilitado no cabeçalho, com o motivo três telas abaixo, é pior que um clique a mais. Hoje o motivo está ao lado do botão porque os dois estão no mesmo painel; levando o botão ao topo sem levar o motivo, o analista veria "Aprovar" cinza sem saber por quê.
-
RN13. O painel de parecer (bloco 2) fica com o que é redação e registro: parecer do analista, notas internas, "Salvar rascunho", os dados de quem decidiu e quando, e a leitura do que foi decidido nos estados terminais. Sem o chip de recomendação, que foi para a política (RN6).
-
RN13b. As ações do momento se repetem no cabeçalho do painel, contornadas. É duplicação deliberada: o topo é onde elas têm hierarquia (RN10), e aqui elas são a alternativa ao alcance da mão de quem acabou de escrever o parecer e não quer voltar ao topo para decidir. Todas contornadas — inclusive a que é preenchida no cabeçalho —, porque um botão preenchido a meia página disputaria a hierarquia com o do topo. A lista é derivada da mesma que alimenta o cabeçalho, não montada de novo, para as duas filas não poderem divergir quando um estado ganhar ou perder ação; e cada cópia recebe o
data-testiddo original com sufixo-panel, porque dois elementos com o mesmo atributo violam o modo estrito do Playwright. -
RN13a. O painel de parecer fica tingido de
highlight(laranja da marca), para sair da fila de painéis neutros. Com ele no bloco 2, o destaque é o que separa "o que se lê primeiro" de "o que se consulta": a página passa a ter três cores de peso — laranja no parecer, navy no financeiro (RN9b) e o semáforo da política e dos documentos —, e todo o resto em teal neutro. O rótulo usahighlightInk(5,59:1), nãohighlight.main: o laranja vivo é cor de preenchimento e não passa como texto sobre o próprio tingimento. -
RN13c. No mobile as ações do painel são ícone, com alvo de 44px — a mesma medida do topo e da gaveta. Com rótulo, três botões envolviam em duas linhas e empurravam o parecer para baixo logo no segundo bloco da página.
Sem rótulo, a cor deixa de ser enfeite: é ela que separa aprovar de reprovar de pendenciar. Por isso cada ação carrega um tom semântico — success em Aprovar, error em Reprovar, warning em Solicitar documentos —, aplicado por sx para vencer a regra do tema que pinta toda variant="outlined" de teal (design-system/theme/components.ts: "Secundário e ghost da marca são teal"). O tom vale nos dois viewports: teal no desktop e colorido no mobile seria a mesma ação com duas identidades.
A tinta é o token *Ink, não palette[tom].main — a mesma lição da spec 0019: a cor viva é de preenchimento e não alcança contraste como texto ou glifo. Medido sobre o laranja do cabeçalho, o warning.main (#F59E0B) dá 1,94:1 e o ícone de "Solicitar documentos" sumiria; warningInk dá 5,04:1.
O cabeçalho da página não muda. Lá a regra de forma é a da spec 0020 — principal preenchida em highlight, as outras contornadas —, e o tom é campo extra que o PageHeader ignora.
-
RN14. O histórico é sempre o último bloco, em largura inteira. Hoje ele divide um grid de duas colunas com os documentos (
show.tsx:506), o que o espreme no desktop e o joga para o meio da página no mobile. -
RN16. No checklist completo (bloco 7), obrigatório vem antes de opcional, e todo item opcional é pintado em
info— qualquer que seja o status dele. A ordem que vinha da API era a da parametrização, misturando os dois; e um opcional faltando pintado de vermelho competia com o obrigatório que de fato bloqueia a aprovação, sendo que são duas conversas diferentes: uma impede a decisão, a outra não. A ordenação é estável, então a parametrização do tenant segue mandando no desempate dentro de cada grupo. -
RN15. Permissões. As ações de decisão no topo só aparecem para quem tem a permissão correspondente:
decisions:createpara "Iniciar análise" edecisions:updatepara aprovar, reprovar e solicitar documentos — lidas deuser.permissions, que ouseCurrentUserjá traz. Hoje a tela não checa nada e deixa a API recusar; com os botões no cabeçalho, umAGENCY_USERveria "Aprovar" como ação principal da tela. A API continua sendo a autoridade — a checagem no web é sobre o que se oferece.
5. Impacto por app¶
apps/api¶
Uma única mudança de comportamento, sem migration e sem mudança de contrato HTTP:
app/services/decision_service.py— as duas chamadas acompute_credit_summarypassam a receber o Pacote Locatício no lugar derental_value(RN7):credit_analysis.financial_rental_package or credit_analysis.rental_value. O mesmo emapp/services/credit_analysis_participant_service.py, que recalcula o resumo quando a renda de um participante muda.- Docstrings —
app/services/credit.py(módulo e função) e o modelapp/models/decision.pydizem "regra 3× aluguel"; passam a dizer Pacote Locatício, citando a spec 0006. O parâmetrorental_valueda função virapackage_value. - Swagger — nenhum endpoint muda de assinatura. A
descriptiondePOST /credit-analyses/{id}/decisionmenciona o resumo automático; ajustar a base de cálculo no texto. - Testes —
tests/que exercitamcompute_credit_summarycom condomínio/IPTU passam a esperar a razão sobre o Pacote. Um teste novo cobre a diferença: mesma renda e mesmo aluguel, com e sem encargos, recomendações diferentes.
apps/web¶
Toda a mudança é em src/pages/credit-analyses/.
Componentes novos (em components/):
StatusTimeline.tsx— o trilho de quatro etapas (RN1–RN3).PolicyPanel.tsx— o quadro de política (RN5–RN6), absorvendo oCreditRecommendationBadgeque hoje vive dentro doDecisionPanel.BlockingDocumentsPanel.tsx— o resumo do que falta (RN8).ApproveDialog.tsx— o diálogo de aprovação com setup, cláusula e justificativa (RN12).
Componentes alterados:
show.tsx— reordenação (RN4), composição das ações do cabeçalho (RN10–RN11, RN15), fusão dos dois painéis financeiros (RN9), histórico em largura inteira (RN14).DecisionPanel.tsx— perde os botões de decisão, o chip de recomendação e os campos de aprovação; ganha o "Cancelar" dePENDING_DOCS(RN11). Fica com parecer, notas, rascunho e a leitura do decidido (RN13).FinancialSummaryPanel.tsx— deixa de ser painel próprio e vira o corpo colapsável do resumo financeiro (RN9).
data-testid — preservados (o e2e depende): credit-analysis-detail-financial, credit-analysis-financial-summary-toggle, credit-analysis-financial-summary-body, credit-analysis-doc-checklist, credit-analysis-doc-checklist-blocking, credit-analysis-history, credit-analysis-detail-renter, credit-analysis-detail-property, credit-analysis-submit-button, credit-analysis-edit-button, credit-analysis-cancel-button, credit-analysis-create-contract-button, credit-analysis-view-contract-button, decision-approve-button (agora no cabeçalho), decision-approve-setup-input e decision-approve-clause-checkbox (agora dentro do diálogo).
Novos:
data-testid | Onde |
|---|---|
credit-analysis-timeline | o trilho (RN1) |
credit-analysis-timeline-step-{n} | cada nó, 1–4 |
credit-analysis-timeline-current | o nó da etapa atual |
credit-analysis-policy-panel | o quadro de política (RN5) |
credit-analysis-policy-verdict | DENTRO / FORA |
credit-analysis-policy-ratio | a razão calculada |
credit-analysis-policy-recommendation | o chip da API (RN6) |
credit-analysis-blocking-docs | o quadro do que falta (RN8) |
credit-analysis-blocking-doc-{i} | cada pendência |
credit-analysis-detail-documents | contêiner do bloco 7 — âncora de ordem |
credit-analysis-decision-panel | o painel de parecer (RN13) |
credit-analysis-start-analysis-button | "Iniciar análise" no cabeçalho |
decision-reject-button | "Reprovar" no cabeçalho |
decision-pend-button | "Solicitar documentos" no cabeçalho |
credit-analysis-approve-dialog | o diálogo de aprovação (RN12) |
credit-analysis-approve-confirm | o confirmar do diálogo |
decision-approve-justification-input | migra do painel para o diálogo |
O credit-analysis-policy-alert de hoje sai — o <Alert> do fim vira o PolicyPanel do topo. O e2e que o usa é ajustado (ver abaixo).
Sider: sem impacto — não há módulo novo.
Responsividade & PWA: a página já é coluna única no mobile; a reordenação vale para os dois viewports. O ponto novo é o trilho (RN3). Os grids de duas colunas dos blocos 5 e 6 continuam colapsando em uma. Sem impacto no PWA — nenhum asset novo, nenhuma rota nova, nenhum comportamento offline.
apps/e2e¶
credit-analyses.spec.ts— os dois testes de aprovação mudam de caminho: o botãodecision-approve-buttonagora está no cabeçalho e abre o diálogo; a cláusula e a taxa de setup são preenchidas dentro dele, e quem fica desabilitado até a cláusula é ocredit-analysis-approve-confirm. As asserções de resultado (statusAPPROVED,approved_initial_fee_valuepropagando ao contrato) não mudam.credit-analyses-form-steps.spec.ts— a asserção sobrecredit-analysis-policy-alertpassa a mirarcredit-analysis-policy-panel/credit-analysis-policy-verdict.credit-analysis-documents.spec.ts— o checklist completo continua com o mesmo testid, agora no bloco 6; acrescentar asserção sobrecredit-analysis-blocking-docs.- Spec nova
credit-analysis-detail-order.spec.ts— cobre RN1–RN4: o trilho marca a etapa certa em cada estado, e os blocos aparecem na ordem da RN4 (comparando a posição vertical dosdata-testid), nos dois viewports. - Seeds: nenhum novo. Os cenários usam o
setupCreditAnalysisInDecisionque já existe; para o caso da RN7, uma análise comcondo_fee/property_taxpreenchidos.
6. Contrato de API¶
Nenhuma assinatura muda. A única alteração é o valor calculado de dois campos da resposta de GET /api/v1/credit-analyses/{id}/decision:
{
"income_to_rent_ratio": "2.58",
"credit_recommendation": "MANUAL_ANALYSIS",
"recommendation_notes": "Renda total R$ 8.000,00 representa 2.58× o Pacote Locatício (mínimo recomendado: 3×)."
}
Antes da RN7, a mesma análise (aluguel R$ 2.500, condomínio R$ 450, IPTU R$ 150, renda R$ 8.000) devolvia "3.20" e APPROVE — o divisor era só o aluguel. É a inversão de recomendação que a RN7 corrige, e a razão de ela existir. O nome do campo (income_to_rent_ratio) é mantido para não quebrar o web sem ganho; o texto de recommendation_notes passa a dizer "Pacote Locatício".
Erros previstos: nenhum novo. Os da aprovação seguem os da spec 0008 (MISSING_REQUIRED_DOCUMENTS, JUSTIFICATION_REQUIRED, HTTP 403 para o analista) e da spec 0003 (MANDATORY_CLAUSE_NOT_CONFIRMED, RATE_OUT_OF_RANGE) — só passam a ser exibidos dentro do diálogo da RN12, não mais no painel do fim da página.
7. Critérios de aceite¶
- [x] CA1. Dado uma análise em
IN_ANALYSIS, quando abro a visualização, então o primeiro bloco da página é a linha do tempo, com quatro nós, o terceiro destacado como atual e os dois primeiros marcados como cumpridos. - [x] CA2. Dado uma análise em
PENDING_DOCS, então o terceiro nó do trilho aparece com o rótulo "Aguardando documentos" em cor de alerta, e nenhum quinto nó é criado. - [x] CA3. Dado uma análise
CANCELLED, então o trilho inteiro aparece esmaecido e o último nó diz "Cancelado". - [x] CA4. Em 375px, a linha do tempo cabe sem rolagem horizontal e mostra o rótulo só da etapa atual, no formato "Etapa N de 4 ·
"; a página inteira tem scrollWidth - clientWidth == 0. - [x] CA5. Em qualquer estado, a ordem vertical dos blocos é a da RN4 — verificável comparando a posição dos
data-testidna página. - [x] CA6. Dado renda declarada de R$ 8.000 e Pacote Locatício de R$ 3.100 (aluguel R$ 2.500 + condomínio R$ 450 + IPTU R$ 150), então o quadro de política mostra "2,6× (mínimo 3,0×)" e o veredito FORA. É o mesmo cenário de CA18 e CA19 — um fixture só atravessa os três.
- [x] CA7. Dado a mesma análise já em
IN_ANALYSIS, então o quadro de política mostra também o chip da recomendação da API, e esse chip não aparece mais no painel de parecer. - [x] CA8. Dado uma análise em
DRAFT(semDecision), então o quadro de política aparece com a razão e sem o chip de recomendação. - [x] CA9. Dado dois documentos obrigatórios faltando, então o quadro de documentos lista os dois com nome, pessoa e motivo; dado nenhum faltando, o quadro diz que não há pendência em vez de sumir.
- [x] CA10. A página tem um bloco de resumo financeiro, com a parametrização dentro dele, e nenhuma linha de renda ou razão — que estão só no quadro de política.
- [x] CA21. O resumo financeiro aparece aberto, sem controle de colapsar, e o quadro está tingido de navy (RN9a/RN9b).
- [x] CA22. Com pendência documental, o título do quadro de documentos obrigatórios está vermelho, não só o selo (RN8a).
- [x] CA26. As ações do momento aparecem duas vezes — no cabeçalho da página e no cabeçalho do painel de parecer —, cada uma com um
data-testidsó, e a cópia do painel dispara a mesma coisa que a do topo (RN13b). - [x] CA28. Em 375px, as ações do painel de parecer são botões de ícone de 44px, uma única linha ao lado do título, com verde em Aprovar, vermelho em Reprovar e âmbar em Solicitar documentos; no desktop os mesmos tons acompanham os rótulos (RN13c).
- [x] CA27. No quadro de situação financeira, nenhuma linha tem tamanho ou peso diferente das demais (RN9c).
- [x] CA25. Com a análise fora da política, o título do quadro de política está vermelho; dentro, verde; sem renda declarada, neutro (RN5a).
- [x] CA23. O parecer é o segundo bloco da página, logo abaixo do trilho, e está tingido de laranja (RN13a).
- [x] CA24. No checklist completo, os itens obrigatórios aparecem antes dos opcionais, e todo opcional está em
info— inclusive um opcional faltando (RN16). - [x] CA11. Dado uma análise em
SUBMITTED, então "Iniciar análise" é a ação principal do cabeçalho, e o painel de parecer não oferece mais esse botão. - [x] CA12. Dado uma análise em
IN_ANALYSIS, então o cabeçalho traz Aprovar, Reprovar e Solicitar documentos, e o painel de parecer não traz nenhum dos três. - [x] CA13. Dado uma análise em
PENDING_DOCS, então o cabeçalho traz três ações (Aprovar, Reprovar, Resolver pendência) e "Cancelar" aparece no rodapé do painel de parecer. - [x] CA14. Clicar em "Aprovar" no cabeçalho abre o diálogo; o confirmar do diálogo está desabilitado até a cláusula obrigatória ser marcada; marcada, aprova e a API devolve
APPROVED. - [x] CA15. Dado documento obrigatório faltando e usuário
ANALYST, então o diálogo informa o impedimento e não deixa confirmar; dadoADMIN_TENANT, o diálogo pede a justificativa e confirma com ela preenchida. - [x] CA16. Dado um usuário sem
decisions:update, então o cabeçalho não oferece Aprovar, Reprovar nem Solicitar documentos. - [x] CA17. O histórico é o último bloco da página, em largura inteira, em desktop e mobile.
- [x] CA18. Dado aluguel R$ 2.500, condomínio R$ 450, IPTU R$ 150 e renda R$ 8.000, então
income_to_rent_ratiodevolvido pela API é2.58(Pacote R$ 3.100), não3.20, ecredit_recommendationéMANUAL_ANALYSIS, nãoAPPROVE. - [x] CA19. Na mesma análise, o veredito do quadro de política (FORA) e o chip de recomendação da API não se contradizem.
- [x] CA20.
make testpassa;make lintnão ganha apontamento novo nos arquivos tocados.
8. Plano de implementação¶
- [x]
apps/api—decision_service.pyecredit_analysis_participant_service.pypassam o Pacote Locatício acompute_credit_summary(RN7), via o helpercredit_base()novo emcredit.py - [x]
apps/api— migration Alembic: não se aplica (nenhuma mudança de schema) - [x]
apps/api— docstrings (credit.py,decision.py,credit_analysis_participant.py,credit_analysis_participant_repo.py) e adescriptiondo endpoint de decisão dizendo Pacote Locatício - [x]
apps/api— testes:tests/test_credit_ratio_pacote.py - [x]
apps/web—StatusTimeline(RN1–RN3) - [x]
apps/web—PolicyPanel(RN5–RN6), absorvendo o chip de recomendação - [x]
apps/web—BlockingDocumentsPanel(RN8) - [x]
apps/web— fusão dos painéis financeiros (RN9) - [x]
apps/web—ApproveDialoge a limpeza doDecisionPanel(RN12–RN13) - [x]
apps/web— ações do cabeçalho por estado, com permissão (RN10–RN11, RN15) - [x]
apps/web— reordenação doshow.tsxe histórico em largura inteira (RN4, RN14) - [x]
apps/e2e— ajuste decredit-analyses.spec.ts,credit-analyses-form-steps.spec.tsecredit-analysis-documents.spec.ts - [x]
apps/e2e— spec novacredit-analysis-detail-order.spec.ts - [x]
make testemake lint— 216 passam (1 falha intermitente conhecida, alheia); ruff sem apontamento novo nos arquivos tocados;tsclimpo - [x] Validado no browser em desktop e mobile, contra o dev-remote depois do
/devremote-rebuild
9. Decisões em aberto¶
Nenhuma. A única que havia foi decidida:
- [x] RN7 — alinhar a razão da API ao Pacote Locatício. Decidido pelo usuário em 2026-08-08: alinhar. A API passa a calcular
income_to_rent_ratioecredit_recommendationsobrefinancial_rental_package, e a spec deixa de ter duas contas para a mesma pergunta. A alternativa considerada — manter a API sobre o aluguel e o quadro de política exibir só a razão do web — foi descartada: esconderia o sintoma sem corrigir a base da recomendação automática, que é o parecer que o analista lê como sendo o do sistema.
10. Registro¶
Implementada em 2026-08-08. O que divergiu do planejado, e por quê:
Acréscimos não previstos na spec¶
-
credit_base()emapp/services/credit.py. A spec dizia "as duas chamadas passam a receber o Pacote". Foram feitas as duas, mas através de um helper em vez de repetir a expressão nos dois arquivos — foi exatamente "cada ponto escolhe a base por si" que deixou a divergência da RN7 sobreviver desde a spec 0006. Com um helper, o próximo ponto que precisar da base de crédito não tem onde escolher errado. -
_brl()e o texto derecommendation_notesem pt-BR. A RN6 trouxe orecommendation_notesda API para o quadro de política, no topo da página. Ali ele passou a aparecer ao lado de valores formatados pelo web (R$ 8.000,00), e o formato padrão do Python (R$ 8000.00 representa 2.58×) virou erro à vista. No fim da página, onde o texto ficava antes, ninguém comparava. Corrigido junto porque a RN6 é a causa da exposição — não é conserto oportunista de outra coisa. -
data-testid="credit-analysis-detail-documents". O plano usavacredit-analysis-doc-checklistcomo âncora do bloco 6 na aferição de ordem, mas oDocumentChecklistrenderizanullquando o tenant não parametrizou exigência nenhuma (DocumentChecklist.tsx:63). A ordem da página não pode depender da parametrização do tenant para ser aferível, então o contêiner do bloco ganhou testid próprio. Descoberto na validação por screenshot, antes de o e2e rodar. -
tolerate403nogetAnalysis. Não previsto, e é um defeito que a spec criou: ao mover a carga da decisão para a página (para alimentar RN6 e RN13 de uma fonte só), ela passou a acontecer em todos os estados, inclusiveDRAFT. UmOPERATOR, que não temdecisions:read, receberia 403 — e ohandle403age antes de quem chamou, redirecionando a aplicação inteira. Antes o endpoint só era tocado quando o painel de parecer aparecia. A decisão é contexto opcional da tela, como/settingsjá era.
Confirmações da validação no dev-remote¶
Rebuild de api e web, containers recriados, screenshots nos dois viewports:
- RN7 no ar: a API devolveu
income_to_rent_ratio: "2.58"eMANUAL_ANALYSISpara o caso de referência — antes seria3.20eAPPROVE. - RN1/RN2: trilho com quatro nós;
PENDING_DOCSrelabelou o nó 3 para "Aguardando documentos" em âmbar, sem criar um quinto. - RN3/CA4: em 375px,
scrollWidth - clientWidth == 0e só a etapa atual nomeada ("Etapa 3 de 4 · Em análise"). - RN4/CA5: ordem vertical conferida pela posição real dos oito blocos — nenhum fora de lugar, nos dois viewports.
- RN10/RN11:
PENDING_DOCSmostrou no topo exatamente "Resolver pendência · Reprovar · Aprovar", com "Cancelar" ausente do cabeçalho. - Sem erro de console em nenhum dos quatro cenários.
Ferramental¶
make testnão roda no host (semuv); a suíte rodou pordocker exec garantora-devremote-api-1 uv run pytest: 217 passam. Numa execução anterior,test_partner_agency_status_filter.py::test_alcanca_quem_esta_fora_da_primeira_paginafalhou — reproduzido 1 em 3 vezes com o código parado, mesma causa do flake que oCLAUDE.mdjá documenta paratest_partner_agency_search.py(empate decreated_atno SQLite, desempate por UUID aleatório). Vale registrar que o flake tem dois arquivos, não um.- Ruff: nos sete arquivos tocados, 21 apontamentos contra 22 do baseline — nenhum novo, e um a menos (uma linha longa preexistente em
credit.pyfoi quebrada ao ser reescrita).credit.pye o teste novo estão limpos. tsc: limpo. Use./node_modules/.bin/tsc, nãopnpm exec tsc: o corepack desta máquina roda pnpm 11, que ao ser invocado grava umapps/web/pnpm-workspace.yamlsó comallowBuilds:. O arquivo não é do repositório, mas oCOPY apps/web/doweb.Dockerfileo leva para dentro da imagem, e opnpm buildmorre comERROR packages field missing or empty. Custou um rebuild inteiro doweb.
Ajustes pós-implementação (2026-08-08)¶
Cinco pedidos do usuário depois da primeira entrega, todos de apresentação — nenhuma regra de negócio, nenhuma mudança de API:
- Título do quadro de documentos obrigatórios em vermelho junto com o selo (RN8a); depois, a mesma regra para o quadro de política de contratação (RN5a) — os dois quadros com veredito passaram a tingir o rótulo, não só o selo.
- Situação financeira em destaque, tingida de
primary/navy (RN9b). - Resumo financeiro sem colapsar na visualização (RN9a) — o
collapsiblecontinua existindo para o formulário no mobile, que é onde ele foi criado. - Checklist completo com obrigatórios antes dos opcionais, e opcional sempre em
info(RN16). - Parecer no bloco 2, logo abaixo do trilho, tingido de
highlight/laranja (RN13a).
Efeito colateral do 5: a RN4, os cabeçalhos de seção e o ordem do e2e foram renumerados.
Três pedidos depois disso:
- Título do quadro de política acompanha o veredito (RN5a) — vermelho em
FORA, verde emDENTRO, neutro sem renda declarada. - Nenhuma linha com peso próprio na situação financeira (RN9c). A cor já era a mesma em todas (
rgb(26,26,46)); o que destoava era o "Pacote Locatício" em 16px/700 contra 13px/600 — medido no dev-remote, não deduzido. -
As ações do momento repetidas no cabeçalho do painel de parecer (RN13b). Isso reverteu parte da RN13 original ("sem botões de decisão"): eles seguem no topo, com hierarquia, e ganham uma cópia contornada ao alcance de quem acabou de escrever o parecer.
-
No mobile, as ações do painel viram ícone de 44px, com cor semântica (RN13c) — e a cor passou a valer também no desktop.
Sobre a 9, duas coisas medidas e não deduzidas:
- O
colordo descritor é inerte emvariant="outlined": o tema pinta toda ela de teal por regra de marca (design-system/theme/components.ts, "Secundário e ghost da marca são teal"). Era por isso que oReprovarsaía teal apesar decolor: "error". O painel passa a aplicar a cor porsx, que vence a sobrescrita; o cabeçalho da página segue a regra do tema, inalterado. - A primeira versão usou
palette[tom].maine o âmbar mediu 1,94:1 sobre o laranja do cabeçalho — o ícone de "Solicitar documentos" sumia. Trocado pelos tokens*Ink, que a spec 0019 criou justamente para "texto ou glifo colorido": agora 7,10 · 5,93 · 4,55. A medição virou parte do script de validação, para não depender de olhar.
A página passou a ter três pesos de cor: laranja no parecer, navy no financeiro e o semáforo (verde/vermelho) na política e nos documentos; todo o resto segue no teal neutro de painel.
Uma armadilha do ambiente compartilhado¶
Na validação por screenshot destes ajustes, o script parametrizou um documento obrigatório e um opcional para LESSEE_PF — e não desfez. A parametrização documental é do tenant, e o tenant de e2e é compartilhado: a rodada seguinte teve 3 falhas que não tinham nada a ver com o código (as duas aprovações da spec 0003 passaram a esbarrar em documento obrigatório faltando, e a contagem por área da spec 0010 virou "2 documentos"). Restaurada a configuração, 29 de 29 voltaram a passar.
Os specs de e2e já seguem essa disciplina (finally com setRequirement(..., null)); scripts de screenshot precisam da mesma.
Executado¶
A pedido do usuário, a suíte e2e dos quatro arquivos tocados foi executada contra o dev-remote reconstruído: 29 de 29 passaram — os 8 testes da spec nova e os 21 dos três specs ajustados, que cobrem as specs 0003, 0008, 0010 e 0015. Re-executada depois dos ajustes de apresentação: 29 de 29 de novo. Todos os 24 critérios de aceite estão verificados.
A suíte completa não foi executada: os quatro arquivos cobrem o que esta spec toca, mas PageHeader e o cliente da API são compartilhados com outras telas, e uma regressão fora da análise de crédito passaria despercebida.