0010 — Parametrização de documentos por área: botões e seleção do tipo¶
| Status | CONCLUÍDA |
| Branch | spec/0010-parametrizacao-documentos-por-area |
| Criada em | 2026-08-01 |
| Apps afetados | api (sem impacto) · web · e2e |
Status:
RASCUNHO→APROVADA→EM IMPLEMENTAÇÃO→CONCLUÍDA
1. Problema¶
A tela Documentos da Análise de Crédito (/settings/credit-analysis-documents) apresenta, dentro de cada aba de área, a lista inteira dos tipos de documento cadastrados no tenant — hoje 20 tipos vindos da ativação, agrupados em 7 grupos — cada um com um seletor "Não se aplica / Opcional / Obrigatório".
Três dores vêm daí:
- A tela mente sobre o que está configurado. Um tenant recém-ativado, sem exigência nenhuma, mostra 20 linhas preenchidas com "Não se aplica". O gestor rola uma lista longa procurando os poucos itens que realmente valem, e não consegue responder de relance "o que eu exijo do inquilino PF?".
- O trabalho é de exclusão, não de inclusão. Configurar 3 documentos exige passar por 20 seletores. A lista é fixa e imposta; o gestor queria escolher o tipo e dizer se é obrigatório ou opcional — não percorrer o catálogo inteiro.
- As abas não comunicam estado. Cinco abas idênticas não dizem qual área já foi parametrizada e qual está vazia. Só clicando uma a uma se descobre.
Some-se que o gestor precisa clicar Salvar no rodapé para gravar: a tela acumula pendências e, ao trocar de aba, não é óbvio que o que ficou para trás ainda está por salvar.
2. Objetivo¶
Trocar as abas por botões de área que mostram quantas exigências cada uma tem, e transformar o painel de configuração em uma lista do que foi cadastrado — vazia até que o gestor adicione, escolhendo o tipo de documento e o grau (obrigatório ou opcional). Cada alteração grava na hora.
3. Fora de escopo¶
Foi cogitado e fica de fora:
- Mudar o contrato da API.
GET/PUT /api/v1/settings/credit-analysis-documentscontinuam como estão (ver §5apps/api). Enxugar oGETpara devolver só as exigências configuradas foi avaliado e recusado: mexeria em service, testes e no aviso de exclusão dedocument-types, sem ganho para o usuário. - Criar tipo de documento a partir desta tela. O gestor continua indo a Tipos de Documento. A tela só ganha uma mensagem apontando para lá quando não há tipo disponível para adicionar.
- Reordenar exigências (drag-and-drop, prioridade). A ordem é derivada de grupo + nome do tipo.
- Regra de conferência, bloqueio na aprovação, justificativa do gestor e snapshot — tudo o que a spec 0008 definiu segue idêntico. Esta spec não toca em
decision_service,build_checklistnem noDocumentChecklist. - Copiar exigências entre áreas ("usar o mesmo do Inquilino PF no PJ").
- Confirmação ao remover uma exigência. Remover é reversível re-adicionando; um diálogo por remoção atrapalharia mais do que protege.
4. Regras de negócio¶
- RN1. As cinco áreas (
LESSEE_PF,LESSEE_PJ,CO_RESPONSIBLE,PROPERTY,ADDITIONAL) são apresentadas como botões-cartão, não como abas. Cada botão exibe o rótulo da área e a contagem de exigências configuradas nela (ex.: "Inquilino PF · 3 documentos", "Inquilino PJ · nenhum documento"). - RN2. Ao carregar a tela nenhuma área está aberta — só os botões aparecem. Clicar num botão abre o painel daquela área; clicar no botão da área já aberta fecha o painel. Uma área aberta por vez. Onde o painel entra depende da largura:
- desktop (grade de 2 ou 3 colunas): abaixo da grade inteira;
- mobile (coluna única): logo abaixo do botão clicado, como acordeão — no fim da lista ele abriria a três ou quatro cartões de distância, fora da tela.
- RN3. O painel de uma área lista apenas as exigências já configuradas nela. Um tipo sem registro naquela área não aparece — é a leitura literal da RN2 da spec 0008 ("ausência de registro = não se aplica"). A lista fixa de todos os tipos deixa de existir na tela.
- RN4. Área sem nenhuma exigência mostra um estado vazio explícito ("Nenhum documento exigido nesta área. Nada será cobrado do gestor na aprovação."), seguido da linha de adição.
- RN5. A linha de adição fica ao final do painel e tem três campos: tipo de documento (obrigatório), exigência (
Obrigatório|Opcional, padrãoObrigatório) e validade em dias (opcional, inteiro ≥ 1; vazio = não vence). O botão Adicionar fica desabilitado enquanto não houver tipo escolhido. - RN6. O seletor de tipo da linha de adição oferece somente os tipos ativos ainda não configurados naquela área, agrupados pelo
groupdo tipo. Um tipo já presente na lista some do seletor — não há como cadastrar o mesmo tipo duas vezes na mesma área (oUniqueConstraintdo banco já garante; a UI impede antes). - RN7. Esgotados os tipos disponíveis, a linha de adição é substituída pela mensagem "Todos os tipos de documento já estão configurados nesta área." Se o tenant não tem nenhum tipo cadastrado, a mensagem aponta para Tipos de Documento (comportamento atual do
ca-doc-no-types, preservado). - RN8. Cada alteração grava na hora, com uma chamada
PUTde um item só: - adicionar → grava ao clicar em Adicionar;
- editar → grava ao confirmar a linha (ver RN16);
- remover → grava ao clicar no ícone de remoção (envia
requirement: null).
O botão Salvar global e o contador de "alterações não salvas" do rodapé deixam de existir: o que grava é sempre a ação de uma linha, nunca um lote acumulado. - RN16. A exigência configurada é exibida como valor, não como formulário: nome do tipo, grau (Obrigatório em destaque, Opcional discreto), validade (vence em N dias / não vence) e grupo, mais os botões editar e remover. Os controles só aparecem na linha que o usuário mandou editar, e ela ganha confirmar e cancelar próprios. Uma linha em edição por vez. Cancelar descarta o rascunho sem chamar a API; Esc cancela e Enter confirma. - RN9. Enquanto uma gravação está em curso, a linha afetada fica em estado de espera (controles desabilitados). Falha na gravação reverte o estado local para o que a API devolveu por último e mostra o erro — a tela nunca fica mostrando algo que não foi gravado. - RN10. A resposta do PUT é a grade completa e substitui o estado local: é ela quem manda, não o otimismo da UI. - RN11. Validade inválida (não numérica, 0 ou negativa) não é enviada: o campo mostra erro e o valor anterior é restaurado. O ge=1 do schema segue como segunda barreira. - RN12. Permissões inalteradas em relação à spec 0008 — credit_analysis_document_requirements:list para ler, :update para gravar (só ADMIN_TENANT e ADMIN_GLOBAL). Novo: quem não tem :update (ANALYST, OPERATOR, VIEWER, AGENCY_USER) vê a tela em modo leitura — sem linha de adição, sem botão de remover, seletores desabilitados e um aviso "Somente o gestor pode alterar as exigências". Hoje esses papéis veem tudo editável e só descobrem o 403 ao salvar. - RN13. A contagem no botão da área e a lista do painel refletem o mesmo estado — gravou, o número no botão muda junto, sem recarregar a página. - RN14. Nada muda no dado. Exigências já parametrizadas continuam valendo e aparecem na área correspondente; não há migration nem alteração de contrato.
5. Impacto por app¶
apps/api¶
Sem impacto. Nenhum model, schema, service, router ou migration é alterado. O contrato existente já sustenta a tela nova:
GET /api/v1/settings/credit-analysis-documentsdevolve todos os tipos ativos comareasmapeando cada área para a célula ounull. A tela usa a mesma resposta de duas formas: as células não nulas viram as linhas do painel (RN3), e os tipos com célula nula naquela área alimentam o seletor de adição (RN6).PUT /api/v1/settings/credit-analysis-documentsjá é upsert parcial por célula: células não enviadas ficam como estão, erequirement: nullapaga. Umitemsde um elemento só é exatamente o que a RN8 pede.
Nenhum teste de tests/test_credit_analysis_documents.py precisa mudar.
apps/web¶
- Página:
src/pages/settings/credit-analysis-documents/index.tsx— reescrita da camada de apresentação.api.tsfica inalterado. - Componentes internos (mesma pasta, se o arquivo passar de ~250 linhas):
AreaButtons.tsx(os cinco botões-cartão com contagem) eAreaPanel.tsx(lista + linha de adição). - Estado: o
edits: Record<string, CellValue>acumulado morre com o botão Salvar. Ficarows(verdade da API),openArea: DocumentArea | nullesavingKey: string | nullpara o estado de espera por linha (RN9). - Aproveitar:
AREA_LABELSeAREA_HELPseguem como estão (oAREA_HELPvira o subtítulo do painel aberto).useCurrentUser()(src/shared/hooks/useCurrentUser.ts) forneceisAdminTenant/isAdminGlobalpara o modo leitura da RN12. data-testid:
| Testid | Onde | Situação |
|---|---|---|
ca-doc-area-buttons | container dos cinco botões | novo |
ca-doc-area-button-{AREA} | botão-cartão da área | novo (substitui ca-doc-area-tab-{AREA}) |
ca-doc-area-count-{AREA} | contagem dentro do botão | novo |
ca-doc-requirements-form | painel da área aberta | mantido (era o container da lista) |
ca-doc-area-empty | estado vazio da área | novo |
ca-doc-requirement-row-{typeId} | linha de exigência configurada | mantido |
ca-doc-requirement-label-{typeId}-{area} | grau em leitura (RN16) | novo |
ca-doc-validity-label-{typeId}-{area} | validade em leitura (RN16) | novo |
ca-doc-edit-{typeId}-{area} | botão editar da linha | novo |
ca-doc-requirement-{typeId}-{area} | seletor Obrigatório/Opcional — só em edição | mantido |
ca-doc-validity-{typeId}-{area} | validade — só em edição | mantido |
ca-doc-save-{typeId}-{area} | confirmar edição da linha | novo |
ca-doc-cancel-{typeId}-{area} | cancelar edição da linha | novo |
ca-doc-remove-{typeId}-{area} | botão remover da linha | novo |
ca-doc-add-type | seletor de tipo da linha de adição | novo |
ca-doc-add-requirement | seletor de grau da linha de adição | novo |
ca-doc-add-validity | validade da linha de adição | novo |
ca-doc-add-submit | botão Adicionar | novo |
ca-doc-add-exhausted | mensagem da RN7 | novo |
ca-doc-read-only | aviso do modo leitura (RN12) | novo |
ca-doc-requirements-saved | feedback de gravação | mantido (vira feedback por ação) |
ca-doc-requirements-error | erro de gravação/carga | mantido |
ca-doc-no-types | tenant sem nenhum tipo | mantido |
ca-doc-area-tabs | — | removido |
ca-doc-area-tab-{AREA} | — | removido |
ca-doc-requirements-save | — | removido (não há mais Salvar) |
- Sider: sem mudança. A página já está registrada em
src/App.tsx(rota/settings/credit-analysis-documents) e emsrc/components/navigation/nav-groups.tsx(grupo ADMINISTRAÇÃO, item "Documentos da Análise"). Módulo não é novo. - Responsividade: os botões-cartão fluem em grade — 2 ou 3 colunas no desktop, coluna única em 375px, cada botão ocupando a largura toda. No mobile o painel é intercalado logo após o botão aberto (RN2); quem decide a posição é o
AreaButtons, que recebe o painel por prop — ouseIsMobile()(< 600px) é o mesmo corte em que a grade vira coluna única, então as duas decisões nunca saem de sincronia. No painel, cada linha de exigência empilha emxs(nome, grau, validade, remover) e virarowa partir desm, como a linha atual já faz. A linha de adição segue o mesmo empilhamento, com o botão Adicionar em largura total no mobile. Sem scroll horizontal (scrollWidth - clientWidth <= 1). O rodapé sticky sai junto com o botão Salvar, devolvendo altura útil ao mobile. - PWA: sem impacto — nenhum asset novo, nenhuma tela offline, nenhuma mudança no service worker. Vale a ressalva de sempre: no dev-remote o bundle é cacheado, então a mudança só aparece após rebuild do container
web.
apps/e2e¶
- Spec existente a atualizar:
tests/e2e/credit-analysis-documents.spec.ts - CA1 ("o gestor parametriza pela tela e a marcação persiste") — o fluxo muda: não há mais linha pré-existente para trocar o seletor nem botão Salvar. Passa a abrir a área pelo botão, usar a linha de adição e conferir via API.
- CA14 (usabilidade em 375px) — troca
ca-doc-area-tabs/ca-doc-area-tab-CO_RESPONSIBLEpelos botões novos. - CA3/CA4, CA8/CA9, CA11 não tocam nesta tela pela UI (parametrizam via API) — seguem sem alteração.
- Cenários novos (mesmo arquivo):
- área vazia mostra o estado vazio e nenhuma linha de exigência;
- adicionar grava sem clicar em Salvar e a contagem do botão sobe;
- o tipo recém-adicionado some do seletor de adição;
- remover apaga a exigência e a contagem do botão desce;
- papel sem
:update(analista) vê a tela em modo leitura; - a linha nasce em leitura e os controles só aparecem ao editar (RN16);
- estado interceptado com
page.route(), para o que não dá para montar no tenant compartilhado: catálogo de tipos vazio (CA10),PUTfalhando (CA12) e validade inválida sem chamada à API (CA7). - Seeds: nenhum novo. O helper
ensureDocTypee a limpeza nofinally(setRequirement(..., null)) do arquivo atual continuam valendo — e ficam ainda mais necessários, já que a tela agora reflete exatamente o que está gravado.
6. Contrato de API¶
Inalterado. Reproduzido aqui só para deixar claro como a tela nova usa o que já existe.
Carga da tela — uma chamada, que serve tanto à lista quanto ao seletor:
{
"items": [
{
"document_type_id": "8f1c…",
"name": "CPF",
"group": "Identificação",
"areas": {
"LESSEE_PF": { "requirement": "REQUIRED", "validity_days": null },
"LESSEE_PJ": null,
"CO_RESPONSIBLE": null,
"PROPERTY": null,
"ADDITIONAL": null
}
}
]
}
Na área LESSEE_PF esse tipo é uma linha do painel; em LESSEE_PJ ele é uma opção do seletor de adição.
Adicionar / alterar (RN8) — um item por chamada:
PUT /api/v1/settings/credit-analysis-documents
Authorization: Bearer <token>
{ "items": [ { "document_type_id": "8f1c…", "area": "LESSEE_PF",
"requirement": "REQUIRED", "validity_days": 90 } ] }
Remover:
{ "items": [ { "document_type_id": "8f1c…", "area": "LESSEE_PF",
"requirement": null, "validity_days": null } ] }
A resposta dos dois é o mesmo corpo do GET (grade completa), e substitui o estado local (RN10).
Erros previstos:
| Situação | Resposta | Tela |
|---|---|---|
Sem permissão :update | 403 | Não deve acontecer — RN12 esconde os controles. Se ocorrer, alerta em ca-doc-requirements-error e reversão (RN9) |
validity_days < 1 | 422 | Barrado antes na UI (RN11) |
| Tipo inexistente/soft-deletado | 404 / erro do service | Alerta + recarga da grade |
| Rede/500 | — | Alerta + reversão do estado local (RN9) |
7. Critérios de aceite¶
Marcação: [x] = verificado de fato — pela suíte e2e executada, pelo browser, ou pelo git (CA14). Todos os quinze estão marcados.
- [x] CA1. Dado um tenant sem exigência nenhuma, quando o gestor abre a tela, então vê os cinco botões de área, todos com contagem zero, nenhum painel aberto e nenhuma lista de tipos.
- [x] CA2. Dado que o gestor clica no botão "Inquilino PF", então o painel dessa área abre abaixo dos botões, mostrando o texto de ajuda da área, o estado vazio e a linha de adição.
- [x] CA3. Dado o painel aberto, quando o gestor escolhe o tipo "CPF", mantém "Obrigatório" e clica em Adicionar, então a exigência aparece na lista sem que ele clique em Salvar (não existe botão Salvar na tela) e a contagem do botão "Inquilino PF" passa a 1.
- [x] CA4. Dado que "CPF" já está na lista da área, então ele não aparece mais entre as opções do seletor de adição daquela área — mas continua disponível nas outras áreas.
- [x] CA5. Dado que o gestor clica no botão da área já aberta, então o painel fecha; e clicar em outra área fecha a anterior e abre a nova (uma por vez).
- [x] CA6. Dado uma exigência configurada, quando o gestor troca "Obrigatório" por "Opcional", então a alteração é gravada na hora e persiste após recarregar a página.
- [x] CA7. Dado uma exigência configurada, quando o gestor digita
90em "Validade (dias)" e confirma, então grava; recarregando a página o valor continua90. Digitar0mostra erro no campo e não dispara chamada. - [x] CA8. Dado uma exigência configurada, quando o gestor clica em remover, então a linha some, a contagem do botão desce e o
GETdeixa de trazer a célula. - [x] CA9. Dado que todos os tipos do tenant já estão configurados na área, então a linha de adição dá lugar à mensagem de "todos já configurados".
- [x] CA10. Dado um tenant sem nenhum tipo de documento cadastrado, então a tela mostra o aviso apontando para Tipos de Documento (comportamento atual preservado).
- [x] CA11. Dado um usuário
ANALYST, quando abre a tela, então vê as exigências em modo leitura — sem linha de adição, sem remover, seletores desabilitados — e um aviso de que só o gestor altera. - [x] CA12. Dado que a API falha ao gravar, então a tela mostra o erro e volta ao estado anterior — não fica exibindo a alteração que não foi gravada.
- [x] CA13. Dado o viewport de 375px, então os botões de área ficam em coluna única, a linha de adição empilha e não há scroll horizontal (
scrollWidth - clientWidth <= 1). - [x] CA14. Dadas exigências parametrizadas antes desta mudança, então elas aparecem nas áreas corretas e o bloqueio da aprovação (spec 0008) segue idêntico — nenhum teste de
tests/test_credit_analysis_documents.pyprecisou mudar. - [x] CA15. (RN16) Dada uma exigência recém-adicionada, então ela aparece como valor — grau e validade legíveis, sem nenhum controle de formulário. Clicando em editar, e só então, os controles surgem naquela linha; cancelar descarta sem chamar a API e confirmar grava e volta para leitura.
8. Plano de implementação¶
Ordem obrigatória no monorepo: api → web → e2e. Aqui a api não entra.
- [x]
apps/api— sem impacto, confirmado porgit status(ver Registro) - [x]
apps/api— migration: não há - [x]
apps/api— docstrings/Swagger: sem alteração - [x]
apps/web— botões-cartão de área com contagem (RN1, RN2, RN13) - [x]
apps/web— painel da área: lista só do configurado + estado vazio (RN3, RN4) - [x]
apps/web— linha de adição com seletor de tipos disponíveis (RN5, RN6, RN7) - [x]
apps/web— gravação imediata por ação, com estado de espera e reversão (RN8, RN9, RN10, RN11); removidos o rodapé sticky e o botão Salvar - [x]
apps/web— modo leitura para quem não tem:update(RN12) - [x]
apps/web—data-testidda tabela em §5 completos - [x]
apps/e2e— CA1 e CA14 reescritos; cenários novos escritos - [x]
tscdo web limpo;make testnão roda nesta máquina — ver Registro - [x] Validado no browser em desktop e 375px por script de screenshot Playwright
9. Decisões em aberto¶
Nenhuma. As quatro dúvidas levantadas na entrevista foram decididas pelo usuário e estão registradas como regras:
- Área abre em painel expandido abaixo dos botões, não em modal nem sub-rota (RN2)
- Adição por linha inline ao final da lista, não por dialog (RN5)
- Gravação imediata, sem botão Salvar (RN8)
- Só tela — contrato de API inalterado (§5
apps/api)
8b. O que foi entregue¶
| Arquivo | O quê |
|---|---|
apps/web/src/pages/settings/credit-analysis-documents/index.tsx | Reescrita: carga, gravação por ação, permissão, orquestra os dois componentes |
apps/web/src/pages/settings/credit-analysis-documents/AreaButtons.tsx | novo — os cinco botões-cartão com contagem |
apps/web/src/pages/settings/credit-analysis-documents/AreaPanel.tsx | novo — lista da área + linha de adição |
apps/web/src/pages/settings/credit-analysis-documents/labels.ts | novo — AREA_LABELS/AREA_HELP, agora usados por dois componentes |
apps/e2e/tests/e2e/credit-analysis-documents.spec.ts | CA1 e CA14 reescritos; 3 cenários novos; describes separados por spec |
apps/web/.../api.ts | inalterado, como planejado |
apps/api/** | inalterado — git status confirma |
10. Registro¶
Divergências do planejado¶
-
Modo leitura por permissão, não por papel. A §5 dizia usar
useCurrentUser().isAdminTenant/isAdminGlobal. O código usauser.permissions.includes("credit_analysis_document_requirements:update"), que é o padrão do resto do web (persons:delete,properties:delete) e checa exatamente o que a API exige — se um papel novo ganhar a permissão, a tela acompanha sem edição. -
data-testiddos selects foi paraSelectDisplayProps, nãoinputProps. NoSelectdo MUI oinputPropscai no input nativo escondido, que não recebe clique — o e2e da 0008 contornava isso comgetByRole('combobox'). Pondo o testid no display, o atributo passa a valer para o que o usuário de fato clica. Vale paraca-doc-requirement-{typeId}-{area},ca-doc-add-typeeca-doc-add-requirement. Os campos de texto seguem cominputProps, como manda oCLAUDE.mddo web. -
Reversão da RN9 saiu de graça. Como nada é escrito de forma otimista —
rowssó muda com resposta da API — não há o que desfazer no erro. O único estado local que diverge é o rascunho da validade, descartado ao fim da tentativa. Não foi preciso guardar estado anterior. -
ListSubheaderdo seletor de tipos é lido comooption. Descoberto ao dirigir a tela:getByRole('option').first()clica no cabeçalho do grupo, não no primeiro tipo. Não afeta o usuário (o cabeçalho não é clicável) e o spec e2e filtra por nome, então ficou como está — mas fica registrado para quem for escrever teste novo aqui. -
Ajuste pedido depois da primeira entrega: painel colado ao botão no mobile. A primeira versão punha o painel abaixo de todos os botões, nos dois viewports. No desktop está certo; no mobile, com os cinco cartões empilhados, clicar no primeiro abria o painel a quatro cartões de distância — fora da tela, parecendo que o clique não fez nada. O
AreaButtonspassou a receber o painel por prop e a posicioná-lo: intercalado após o botão aberto quandouseIsMobile(), depois da grade quando não. A RN2 foi reescrita para registrar a diferença. Medido depois da correção: 12px entre botão e painel no mobile (é ogapda grade) contra 98px no desktop, que continua com o painel embaixo da grade inteira. -
Espaçamento no mobile. O
gapde 8px (xs: 1) herdado da linha da spec 0008 servia para uma linha com um campo rotulado; com três empilhados, o label flutuante de um encostava na borda do campo de cima. Subiu para 20px na linha de adição (xs: 2.5) e 16px na linha de exigência (xs: 2), com opyacompanhando. O desktop não muda: os valoressmcontinuam os mesmos. No mesmo passo, o botão de remover passou a alinhar à direita no mobile (alignSelf: flex-end) — empilhado e sozinho à esquerda, lia como elemento solto. -
Destaque dos grupos no seletor de tipo. O
ListSubheadersem estilo sai com peso parecido com o das opções, e o usuário lê "Identificação" como um tipo selecionável. Ganhou versalete, corsecondary, fundo tênue e divisor — a mesma linguagem do cabeçalho de grupo do painel — e as opções ganharam recuo (pl: 3). Isso convive com o item 4 acima: o cabeçalho segue exposto comooptionpara a árvore de acessibilidade; o que mudou é só o visual. -
Lista em leitura, edição sob demanda (RN16, pedido depois da entrega). A primeira versão deixava toda linha configurada como formulário aberto — três controles por exigência, o tempo todo. Com poucas exigências já ficava ruidoso: não dava para ler o que estava exigido sem interpretar campos. A linha passou a mostrar valores, com editar e remover; os controles surgem só na linha em edição, que ganhou confirmar e cancelar próprios.
Isso estreita a RN8, e de propósito: a gravação continua imediata e por linha (não há Salvar global), mas dentro da edição existe um passo de confirmação — que é o que devolve o cancelar, impossível quando cada mexida no seletor já gravava. Sumiu junto a gravação da validade no blur, que era a parte mais frágil do desenho anterior.
make testnão roda nesta máquina: não háuv,.venvnempytestno host. A suíte foi executada dentro do container (docker exec garantora-devremote-api-1 uv run pytest) — 173 passed.
Correção. Uma versão anterior deste registro dizia que a imagem era anterior à spec 0009 e que a árvore teria 187 testes, tratando o resultado como sinal fraco. Está errado: a imagem não tem
app/core/i18n.pynem o model de translations, ou seja, é pós-0009 — e a própria 0009 registrou "173 passed". Os 187 doCLAUDE.mdé que estão velhos (medidos antes de a 0009 remover os testes de i18n). O número bate com a árvore, e o resultado vale. -tscdo web limpo (npx tsc --noEmit, zero erros).ruffnão se aplica: nenhum arquivo Python mudou. - Validado no browser de verdade, contra a API do dev-remote, com um Vite temporário proxiando/api(a API de lá só libera CORS paradev.garantora.com.br). O config temporário foi removido ao final. Cinco telas conferidas em 1440px e 375px: painel fechado, área vazia, adição concluída (contagem sobe, snackbar, sem botão Salvar), mobile em coluna única e modo leitura do analista.scrollWidth - clientWidth == 0nos cinco. A exigência criada na validação foi removida ao final, para não contaminar a suíte e2e. - Suíte e2e executada.npx playwright test tests/e2e/credit-analysis-documents.spec.ts --project=chromium→ 9 passed em 35s, de primeira: os 3 cenários da 0008 que seguem valendo, os 4 novos da 0010 e o mobile. Exigiu rebuild do containerwebdo dev-remote antes (docker compose build web && docker compose up -d web), porque lá o front é build, não dev server — o dev-remote está rodando o código desta branch. Reexecutada depois dos ajustes 5–8, com o container rebuildado: 9 passed em 38s, incluindo os cenários novos de leitura/edição (a linha nasce como valor, editar revela os controles, cancelar não grava) e a asserção de folga botão→painel do CA13.
Com os três cenários de interceptação, o arquivo passou a 12 passed em 37s. - Suíte e2e completa (npm run test:fast, chromium — o mesmo motor do gate): 60 passed, 2 skipped em 2,1 min. Os 2 pulados são os semeadores em massa (cadastrar todas as imobiliárias, cadastrar todos os 20 imóveis), já marcados test.skip no repositório por não serem idempotentes contra o banco compartilhado — não têm relação com esta spec. Nenhuma regressão fora da tela alterada.
Firefox e WebKit não rodaram: o gate cross-browser triplica o tempo e, na VPS, o WebKit não tem as dependências de sistema (mesma limitação registrada na spec 0009). - Os três critérios que tinham ficado sem cobertura foram fechados com page.route(). O que os travava era o estado exigido — catálogo vazio, API falhando — e não a dificuldade do cenário: interceptar a rota produz os dois sem tocar no tenant compartilhado e sem deixar resíduo no banco. - CA10 — GET devolvendo {items: []}: a tela mostra o aviso de Tipos de Documento e não desenha os botões de área. - CA12 — PUT devolvendo 500 na remoção: alerta de erro, e a linha continua com grau e validade originais, confirmado também pelo GET seguinte. - CA7 (metade negativa) — validade 0 com contador de requisições: erro no campo e zero PUT; cancelar devolve o valor gravado.
Sobre o CA12, uma precisão: quando a falha acontece durante a edição, a linha permanece em edição com o que o usuário digitou. Isso é deliberado e não contraria o critério — o que a RN9 proíbe é a tela apresentar como salvo algo que não foi, e um formulário aberto não faz essa afirmação. Perder o que a pessoa digitou por causa de um 500 seria pior. - O CA9 segue sem cenário na suíte, mas foi observado no browser: com os dois tipos do tenant configurados em ADDITIONAL, a linha de adição deu lugar à mensagem de "todos já configurados". Automatizá-lo exigiria um tenant com catálogo pequeno e controlado.