Pular para conteúdo

0008 — Documentos obrigatórios da Análise de Crédito

Status CONCLUÍDA
Branch spec/0008-documentos-obrigatorios-analise-credito
Criada em 2026-07-31
Apps afetados api · web · e2e

Status: RASCUNHO → APROVADA → EM IMPLEMENTAÇÃO → CONCLUÍDA

1. Problema

Hoje é possível aprovar uma análise de crédito sem um único documento anexado. Nada impede: submit não valida documentos, approve não valida documentos, e DocumentType não tem qualquer noção de obrigatoriedade — só name, group e description. O status PENDING_DOCS existe e o analista tem a ação "Solicitar documentos", mas depende de alguém lembrar de usá-la.

Para uma garantidora é risco de subscrição direto: aprova-se a garantia sem comprovação de renda e o prejuízo só aparece no sinistro.

Dois problemas alimentam o primeiro:

Não se sabe de quem é o documento. A aprovação usa a renda somada de inquilinos e corresponsáveis (total_declared_income → income_to_rent_ratio), e a spec 0003 existe justamente para o corresponsável elevar essa razão. Mas nada relaciona um comprovante de renda à pessoa que o declarou. Uma análise aprovada pela renda de três pessoas pode ter o comprovante de uma só.

O seletor de tipo não separa contexto. A lista de tipos é global do tenant e mistura tudo — são 20 tipos em 7 grupos (Renda, Identificação, Imóvel, Contrato, Jurídico, Residência, Outros). Ao anexar numa análise, o operador vê "Laudo de Vistoria" e "Contrato de Garantia" ao lado de "Comprovante de Renda". É a tela que produz documento classificado errado — e classificação errada torna qualquer conferência inútil.

2. Objetivo

Cada garantidora define, por área (inquilino PF, inquilino PJ, corresponsável, imóvel, adicionais), quais tipos de documento são obrigatórios ou opcionais e por quantos dias cada um vale. A análise mostra um checklist do que falta, e a aprovação é bloqueada enquanto faltar obrigatório — com saída registrada para a exceção legítima.

3. Fora de escopo

  • Coleta guiada dentro do formulário de criação — pedir os documentos ao selecionar inquilino, imóvel e corresponsáveis. É a evolução natural e vai para a 0009. Esta spec entrega a parametrização, o checklist e o bloqueio; o anexo continua pelos painéis de documento que já existem (no inquilino, no imóvel e na análise).
  • Outros contextos de anexo fora da análise de crédito (contrato).
  • Mudar a lista de Tipos de Documento. Continua como está, fonte única em /settings/document-types. Esta spec consome essa lista.
  • Bloqueio no submit. Barra na aprovação. O operador segue podendo cadastrar o caso e juntar documentos depois — é para isso que existe o PENDING_DOCS.
  • Validar conteúdo (OCR, autenticidade, valor declarado). A regra é de presença e validade temporal, não de conteúdo.
  • Notificar o inquilino sobre o que falta.

4. Regras de negócio

Parametrização

  • RN1. A exigência é definida por área. São cinco:
Área A que se refere Onde o documento fica
LESSEE_PF inquilino pessoa física cadastro do inquilino (renter_id)
LESSEE_PJ inquilino pessoa jurídica cadastro do inquilino (renter_id)
CO_RESPONSIBLE corresponsável (sempre PF, ver RN4) cadastro do corresponsável (renter_id)
PROPERTY imóvel da análise cadastro do imóvel (property_id)
ADDITIONAL a análise em si a análise (credit_analysis_id)
  • RN2. Cada par (tipo de documento, área) tem uma exigência: REQUIRED ou OPTIONAL. Ausência de registro = não se aplica — é o padrão. Tipo novo criado em /settings/document-types não passa a ser exigido em lugar nenhum sem alguém dizer.
  • RN3. Cada par tem validity_days (inteiro, opcional). null = não expira (RG, Contrato Social, Matrícula). Preenchido = o documento só satisfaz o requisito se foi anexado há no máximo esse número de dias — é o que impede um holerite de dois anos atrás sustentar uma análise de hoje.

A contagem é a partir da data de anexo (created_at do documento), não da data do documento em si. Consequência assumida: um holerite de março anexado em julho conta como sendo de julho. O sistema não captura a competência do documento, e capturar exigiria um campo novo no anexo — decidido ficar fora (ver seção 9). Na prática o documento é pedido no momento da análise, então anexo e competência costumam andar juntos; quem quiser margem menor usa um validity_days mais curto.

Por que validade e não "documento desta análise": guardar o documento no cadastro da pessoa é o que permite saber de quem ele é — o Document se liga a uma entidade só, então um documento preso à análise perde a autoria, e era exatamente o furo do problema. validity_days resolve a atualidade sem abrir mão da autoria, e é mais expressivo: cobre "extrato vale 30 dias" e "RG não vence" no mesmo campo.

  • RN4. A área CO_RESPONSIBLE não se desdobra em PF/PJ: a API já impede corresponsável PJ (CO_RESPONSIBLE_MUST_BE_PF, RN6 da spec 0003).
  • RN5. Só ADMIN_TENANT parametriza. Os demais papéis leem — o checklist aparece para quem enxerga a análise. Alinhado ao que já vale para document_types:*.
  • RN6. Excluir um tipo (soft delete) remove junto as suas exigências: some do checklist, sem requisito órfão travando aprovação. O diálogo de exclusão, que já obriga a decidir a reatribuição dos documentos, passa a avisar quando o tipo é exigido em alguma área. Não bloqueia — é decisão do admin, mas informada.

Conferência e bloqueio

  • RN7. Um documento satisfaz um requisito quando, cumulativamente: está anexado à entidade da área (RN1), tem document_type_id do tipo exigido, não está soft-deleted, e está dentro da validade (RN3). Arquivo sem tipo classificado não conta para nada.
  • RN8. A conferência é por pessoa. Requisito de CO_RESPONSIBLE precisa ser satisfeito por cada corresponsável da análise — é o que fecha o furo da renda somada. Idem para o inquilino: cada participante LESSEE responde pelos requisitos da sua natureza (PF ou PJ, pelo renter_type de cada um).
  • RN9. POST /credit-analyses/{id}/decision/approve é bloqueado se faltar qualquer obrigatório, salvo a exceção da RN10. Erro MISSING_REQUIRED_DOCUMENTS, detalhando o quê e de quem — "faltam documentos" sem nomear obriga o analista a adivinhar. Documento presente mas vencido aparece como EXPIRED, distinto de ausente: são conversas diferentes com o cliente.
  • RN10. Exceção com justificativa. Um ADMIN_TENANT pode aprovar mesmo faltando obrigatório, informando missing_documents_justification (texto obrigatório, não vazio). ANALYST não tem essa saída — precisa escalar. A justificativa e a lista do que faltava vão para o histórico e para a auditoria.
  • RN11. Snapshot na aprovação. No momento do approve, a decisão guarda os documentos considerados (id, tipo, área, a quem pertencia, data de anexo) e o que faltava. Sem isso, auditar uma aprovação antiga mostraria o cadastro de hoje — e como o documento é reutilizável entre análises (RN3), o cadastro muda.
  • RN12. A conferência acontece no approve, sobre o estado corrente. Documento anexado depois do submit conta normalmente.

Interface

  • RN13. O seletor de tipo no painel de documentos da análise oferece só os tipos da área ADDITIONAL. Nos painéis do inquilino e do imóvel, só os tipos das áreas correspondentes. Os demais contextos seguem com a lista completa.
  • RN14. A tela da análise mostra o checklist agrupado por área e por pessoa: o que é exigido, o que veio, o que falta e o que venceu — visível antes da aprovação, para o bloqueio não ser surpresa no fim do fluxo.
  • RN15. Nada é retroativo. A migration nasce sem exigência nenhuma (RN2), então nenhuma análise em andamento passa a ser bloqueada sem alguém ter parametrizado. Análises já APPROVED não são tocadas.

5. Impacto por app

apps/api

  • Enums novos: DocumentArea (LESSEE_PF, LESSEE_PJ, CO_RESPONSIBLE, PROPERTY, ADDITIONAL) e DocumentRequirement (OPTIONAL, REQUIRED).
  • Modelo novo: CreditAnalysisDocumentRequirement (credit_analysis_document_requirements): tenant_id, document_type_id (FK, ON DELETE CASCADE), area, requirement, validity_days (int, nullable). Unicidade em (tenant_id, document_type_id, area).

O CASCADE não sustenta a RN6 sozinho — descoberto na implementação: a exclusão de tipo de documento é soft delete (deleted_at), então o cascade nunca dispara. A RN6 é garantida em dois pontos: a consulta de exigências ignora tipos soft-deleted, e o DocumentTypeService.delete apaga as exigências explicitamente.

Por que tabela separada e não colunas em document_types: manter a lista de tipos neutra é o que deixa barato estender para contrato depois, e evita que a exclusão de tipo (que já tem regra própria de reatribuição) precise entender de exigência.

  • Snapshot: coluna documents_snapshot (JSON, nullable) em decisions, gravada no approve (RN11), mais missing_documents_justification (Text, nullable) para a RN10. JSON em vez de tabela nova porque é artefato de auditoria, lido inteiro e nunca consultado por partes — e JSON genérico, não JSONB, porque é o que o resto do projeto usa (tenant_settings, channel_integration) e o que funciona no SQLite dos testes.
  • Endpoints:
  • GET /api/v1/settings/credit-analysis-documents — todos os tipos do tenant × 5 áreas com a exigência atual, para a tela não cruzar duas chamadas. Permissão credit_analysis_document_requirements:list.
  • PUT /api/v1/settings/credit-analysis-documents — grava em lote (a tela é uma grade; salvar célula a célula geraria N requisições e estado parcial). Permissão credit_analysis_document_requirements:update.
  • GET /api/v1/credit-analyses/{id}/document-checklist — checklist resolvido: áreas, pessoas, satisfeito/ausente/vencido. Permissão credit_analyses:read.
  • Permissões novas (2) no rbac_seed + migration: ADMIN_TENANT com as duas; demais papéis com list.
  • Service: DecisionService.approve chama o checklist antes de aprovar (RN9/RN10/RN11). AnalysisDecisionRequest ganha missing_documents_justification: str | None — segue o precedente do mandatory_clause_confirmed, que mora nesse payload desde a spec 0003.
  • Migration: tabela + 2 enums + coluna JSONB + 2 permissões e vínculos. Não popula exigência (RN2/RN15). downgrade simétrico.
  • Testes: bloqueio por ausência; liberação ao anexar; documento vencido não satisfaz; PF e PJ com conjuntos distintos; cada corresponsável conferido separadamente (RN8); ADMIN_TENANT com justificativa; ANALYST sem a saída; soft-deleted não conta; sem tipo não conta; snapshot gravado.

apps/web

  • Página nova: src/pages/settings/credit-analysis-documents/ — grade de tipos (agrupados por group, como a tela de Tipos de Documento) × 5 áreas, com exigência e validade por célula.
  • App.tsx + components/custom-sider/index.tsx: item novo em ADMINISTRAÇÃO, ao lado de "Tipos de Documento". Os dois arquivos, conforme o CLAUDE.md do web.
  • DocumentsPanel: aceita a lista de tipos permitidos, para respeitar a RN13 sem afetar os outros usos.
  • Checklist em credit-analyses/show.tsx: por área e por pessoa, distinguindo ausente de vencido.
  • DecisionPanel: quando falta obrigatório, mostra a lista e — só para ADMIN_TENANT — o campo de justificativa que habilita o botão.
  • Aviso na exclusão de tipo (settings/document-types), conforme RN6.
  • i18n: chaves PT e EN.
  • data-testid: ca-doc-requirements-form, ca-doc-requirement-{typeId}-{area}, ca-doc-validity-{typeId}-{area}, ca-doc-requirements-save, credit-analysis-doc-checklist, credit-analysis-doc-missing-{typeId}, credit-analysis-doc-expired-{typeId}, decision-approve-missing-docs, decision-approve-justification-input.
  • Responsividade & PWA: a grade tipos × 5 áreas não cabe em 375px como tabela — no mobile vira lista por tipo, com as áreas empilhadas (padrão das specs 0004/0007). O checklist entra em coluna única. Sem impacto de PWA além do cache normal de assets.

apps/e2e

  • Spec nova credit-analysis-documents.spec.ts: parametrizar obrigatório; aprovar sem ele e ver o bloqueio nomeando o tipo; anexar e aprovar; documento vencido barrando; PF e PJ distintos; dois corresponsáveis, só um com documento (RN8); ADMIN_TENANT aprovando com justificativa; analista@e2e.test sem a saída.
  • Rodar em desktop e mobile — a grade de parametrização é o caso crítico em 375px.
  • Seeds: aproveitar os tipos que o seed já cria; acrescentar inquilino PJ e um segundo corresponsável em tests/fixtures/seeds/ se necessário.

6. Contrato de API

GET /api/v1/settings/credit-analysis-documents
{
  "items": [
    { "document_type_id": "uuid", "name": "Comprovante de Renda", "group": "Renda",
      "areas": {
        "LESSEE_PF":      { "requirement": "REQUIRED", "validity_days": 90 },
        "LESSEE_PJ":      null,
        "CO_RESPONSIBLE": { "requirement": "REQUIRED", "validity_days": 90 },
        "PROPERTY":       null,
        "ADDITIONAL":     null
      } },
    { "document_type_id": "uuid", "name": "RG / Identidade", "group": "Identificação",
      "areas": {
        "LESSEE_PF":      { "requirement": "REQUIRED", "validity_days": null },
        "LESSEE_PJ":      null,
        "CO_RESPONSIBLE": { "requirement": "REQUIRED", "validity_days": null },
        "PROPERTY":       null,
        "ADDITIONAL":     null
      } }
  ]
}
PUT /api/v1/settings/credit-analysis-documents

{ "items": [
    { "document_type_id": "uuid", "area": "LESSEE_PF",
      "requirement": "REQUIRED", "validity_days": 90 }
] }
GET /api/v1/credit-analyses/{id}/document-checklist
{
  "areas": [
    { "area": "LESSEE_PF", "renter_id": "uuid", "renter_name": "José da Silva",
      "items": [
        { "document_type_id": "uuid", "name": "Comprovante de Renda",
          "requirement": "REQUIRED", "status": "SATISFIED",
          "document_id": "uuid", "uploaded_at": "2026-06-20T10:00:00Z" },
        { "document_type_id": "uuid", "name": "RG / Identidade",
          "requirement": "REQUIRED", "status": "MISSING" }
      ] },
    { "area": "CO_RESPONSIBLE", "renter_id": "uuid", "renter_name": "Maria Souza",
      "items": [
        { "document_type_id": "uuid", "name": "Comprovante de Renda",
          "requirement": "REQUIRED", "status": "EXPIRED",
          "document_id": "uuid", "uploaded_at": "2025-01-10T10:00:00Z" }
      ] }
  ],
  "blocking": [
    { "area": "LESSEE_PF", "renter_name": "José da Silva", "name": "RG / Identidade", "status": "MISSING" },
    { "area": "CO_RESPONSIBLE", "renter_name": "Maria Souza", "name": "Comprovante de Renda", "status": "EXPIRED" }
  ]
}

Erros previstos:

Código Quando HTTP
MISSING_REQUIRED_DOCUMENTS Falta (ou venceu) obrigatório, sem justificativa válida 422
JUSTIFICATION_REQUIRED Justificativa vazia ou só espaços 422
FORBIDDEN ANALYST tentando usar a saída da RN10 403

7. Critérios de aceite

  • [x] CA1. ADMIN_TENANT marca "Comprovante de Renda" obrigatório em LESSEE_PF com validade 90, salva, recarrega e a marcação persiste.
  • [x] CA2. ANALYST não altera a parametrização, mas vê o checklist na análise.
  • [x] CA3. Análise de inquilino PF sem o comprovante: aprovar falha com MISSING_REQUIRED_DOCUMENTS, e a mensagem nomeia o tipo e a pessoa.
  • [x] CA4. Anexado o documento com o tipo correto no cadastro do inquilino, a mesma aprovação passa.
  • [x] CA5. Documento do tipo certo, porém anexado há mais dias que a validade, não satisfaz: aparece como EXPIRED e bloqueia. (A contagem é da data de anexo — RN3.)
  • [x] CA6. Inquilino PJ com o conjunto de PJ satisfeito aprova, mesmo faltando documento exigido só de PF.
  • [x] CA7. Com dois corresponsáveis e obrigatório em CO_RESPONSIBLE, ter o documento de um não basta — o checklist aponta o outro pelo nome e a aprovação é bloqueada (RN8).
  • [x] CA8. ADMIN_TENANT aprova faltando obrigatório informando justificativa; ela fica no histórico e na auditoria com a lista do que faltava. Justificativa vazia é recusada.
  • [x] CA9. ANALYST não aprova faltando obrigatório, nem enviando justificativa.
  • [x] CA10. Aprovada a análise, o snapshot registra os documentos considerados; e se depois o documento for trocado no cadastro da pessoa, o snapshot segue mostrando o que sustentou aquela decisão (RN11).
  • [x] CA11. No painel de documentos da análise, o seletor mostra só tipos de ADDITIONAL; no painel do inquilino, só os das áreas de inquilino.
  • [x] CA12. Excluir um tipo exigido avisa o admin e, confirmado, remove a exigência sem deixar a aprovação travada por requisito órfão.
  • [x] CA13. Análises aprovadas antes desta feature seguem íntegras, e nenhuma análise em andamento passa a ser bloqueada sem parametrização (RN15).
  • [x] CA14. No mobile (375px), a tela de parametrização é usável sem scroll horizontal e o checklist aparece em coluna única.
  • [x] CA15. make test verde e e2e passando em desktop e mobile.

8. Plano de implementação

Ordem obrigatória: api → web → e2e.

  • [x] apps/api — enums DocumentArea/DocumentRequirement + modelo CreditAnalysisDocumentRequirement
  • [x] apps/api — schemas → repository → service (parametrização + resolução do checklist por área e por pessoa)
  • [x] apps/api — routers (GET/PUT settings, GET checklist) com Swagger completo
  • [x] apps/api — DecisionService.approve: bloqueio, justificativa e snapshot (RN9–RN11)
  • [x] apps/api — rbac_seed + migration (tabela, enums, JSONB, 2 permissões, vínculos)
  • [x] apps/api — testes
  • [x] apps/web — tela de parametrização (App.tsx e custom-sider), responsiva
  • [x] apps/web — DocumentsPanel filtrado por área, checklist na análise, diálogo de aprovação, aviso na exclusão de tipo
  • [x] apps/web — i18n PT/EN
  • [x] apps/e2e — spec nova, desktop e mobile
  • [x] make test e tsc limpos; sem apontamento novo de lint nos arquivos tocados
  • [x] Validado no browser em desktop e mobile

9. Decisões em aberto

Resolvidas com o usuário (2026-07-31):

  • [x] Escopo da parametrização — tela própria da Análise de Crédito, consumindo a lista de Tipos de Documento; por área (inquilino PF/PJ, corresponsável, imóvel, adicionais).
  • [x] PF vs PJ — a exigência varia por natureza do inquilino.
  • [x] Onde bloqueia — na aprovação, não no envio.
  • [x] Exceção — ADMIN_TENANT aprova com justificativa registrada.
  • [x] Corresponsável PJ — não existe; a API já impede (CO_RESPONSIBLE_MUST_BE_PF). Decisão encerrada por fato do código.
  • [x] Tipo excluído com exigência — a exigência cai junto, com aviso no diálogo de exclusão. Bloquear a exclusão fugiria do padrão já estabelecido para tipos.
  • [x] Conferência por pessoa — sim (RN8). Viabilizada por guardar o documento no cadastro da pessoa.
  • [x] Atualidade do documento — resolvida por validity_days por tipo/área (RN3), e não por prender o documento à análise. Prender à análise custaria a autoria do documento, que é o que fecha o furo da renda somada.
  • [x] Auditoria — snapshot no momento da aprovação (RN11).
  • [x] Fatiamento — 0008 entrega parametrização, checklist e bloqueio; a coleta guiada no formulário de criação vai para a 0009.

  • [x] Validade conta de quando? Da data de anexo (created_at). Sem campo "data do documento" no anexo — ficaria mais preciso, mas mexeria no DocumentsPanel e no modelo de Document por um ganho pequeno: na prática o documento é pedido no momento da análise. A imprecisão está registrada na RN3.

Nenhuma decisão em aberto. A spec está pronta para APROVADA.

10. Registro

API (2026-07-31)

Entregue como planejado: enums, modelo, migration (validada nos dois sentidos), schemas, repository, service com resolução por área e por pessoa, três endpoints com Swagger, bloqueio na aprovação com justificativa e snapshot, permissões no rbac_seed. 14 testes novos, todos passando.

Divergências do planejado:

  • ON DELETE CASCADE não sustenta a RN6. A spec afirmava que o cascade da FK garantiria que excluir um tipo levasse junto a exigência. Errado: a exclusão de tipo é soft delete, então o cascade nunca dispara — o teste test_excluir_tipo_remove_a_exigencia pegou isso. A RN6 passou a ser garantida em dois pontos: a consulta de exigências ignora tipos soft-deletados (vale para qualquer caminho) e o DocumentTypeService.delete apaga as exigências explicitamente (higiene de dados).
  • JSON em vez de JSONB para o snapshot, seguindo a regra do CLAUDE.md da API: tipos específicos do PostgreSQL quebram os testes, que rodam em SQLite.
  • BusinessRuleError ganhou details (opcional, retrocompatível). A RN9 exige que o erro nomeie o que falta e de quem; sem isso a mensagem seria genérica e o analista teria que adivinhar.

O espelho de permissões do conftest, de novo

Os testes novos falharam 12 de 14 na primeira rodada, todos com 403 ao criar tipo de documento. Causa: o tests/conftest.py mantinha cópias manuais do catálogo de permissões e do mapa papel→permissões do rbac_seed, e essas cópias não tinham document_types — nem nunca tiveram. Ou seja, aquela superfície inteira jamais foi exercida por teste.

É a segunda vez que esse espelho custa caro: na spec 0007 ele estava com o recurso decisions no singular, deixando a suíte em 167/173 sem que ninguém percebesse.

Em vez de remendar acrescentando as permissões novas, o conftest passou a importar _PERMISSIONS e _ROLE_PERMISSIONS do rbac_seed. A divergência deixa de ser possível: um papel só tem em teste o que tem em produção. Isso resolveu 8 das 12 falhas.

Vale registrar o que o espelho escondia: como ele era um subconjunto, um papel podia perder permissão real sem nenhum teste falhar — e, pior, podia passar em teste com permissão que não tem em produção.

Bugs meus, encontrados pelos próprios testes: um assert ... or True que nunca falharia (mascarava uma aprovação indevida antes do delete) e um Document.id == str onde a coluna é UUID.

Web (2026-07-31)

Divergência de desenho, aprovada pelo usuário: a spec pedia uma grade tipos × 5 áreas. Implementando, isso dava ~200 controles numa tela (20 tipos × 5 áreas × 2 campos) — ilegível no desktop e impossível em 375px. Virou uma aba por área: escolhe-se a área e a lista mostra os tipos com exigência e validade. Resolve o mobile por construção e casa com o trabalho real, que é configurar uma área por vez. Custo assumido: não dá para comparar áreas lado a lado.

Entregue: tela de parametrização (rota, item em ADMINISTRAÇÃO via nav-groups, 23 chaves i18n em PT e EN), checklist na tela da análise distinguindo ausente de vencido, painel de documentos filtrado por área, bloqueio no DecisionPanel com o campo de justificativa só para gestor, e o aviso na exclusão de tipo.

Duas decisões de implementação que a spec não previa:

  • Sem parametrização de ADDITIONAL, o seletor de anexo continua completo. Filtrar para uma lista vazia impediria anexar qualquer coisa num tenant que ainda não configurou nada — o oposto da RN15.
  • documents-file-input ganhou data-testid. O input era oculto e sem identificação; o CLAUDE.md do web exige testid em elemento interativo, e sem ele não há como exercer o seletor de tipo (que só aparece depois de escolher arquivo).
  • O seletor de tipo do DocumentsPanel não tinha nome acessível. Ao tentar localizá-lo pelo papel ARIA, o snapshot do Playwright mostrou combobox sem nome: o InputLabel não estava vinculado ao Select (faltavam id/labelId). Na prática, um leitor de tela anunciava um campo mudo. Corrigido, mais o data-testid. Foi um defeito pré-existente que só apareceu porque o teste tentou usar a árvore de acessibilidade em vez de um seletor frágil.

E2E

credit-analysis-documents.spec.ts, 5 cenários + 1 mobile.

O tenant de e2e não tem tipos de documento. Os 20 tipos padrão nascem na ativação do tenant, e o seed_e2e não passa por ela — a primeira versão do teste dependia do seed e falhou com "nenhum tipo de documento no tenant". Os testes agora criam o tipo de que precisam (idempotente por nome), o que também os torna independentes do ambiente.

Os testes limpam a exigência no finally: a parametrização é global do tenant, e deixar um obrigatório configurado quebraria os outros specs, que aprovam análises sem anexar documento nenhum.

Verificação

  • API: 187 testes (173 anteriores + 14 novos), zero falhas.
  • Migration: aplicada e revertida no PostgreSQL real; o downgrade zera tabela, tipos e permissões sem deixar resíduo.
  • Permissões no banco: list para os seis papéis, update só para ADMIN_GLOBAL/ADMIN_TENANT — conferido por query, não por leitura do seed.
  • Lint: zero apontamento nos seis arquivos novos. Nos arquivos existentes tocados, 78 → 60 (a remoção do espelho de permissões do conftest levou junto várias linhas longas); nenhum apontamento novo.
  • tsc do web: limpo.
  • e2e: 6 cenários da 0008 e 54 passed / 2 skipped na suíte inteira, sem falha.
  • Browser real: tela de parametrização e checklist a 1440px e 375px, scrollWidth - clientWidth == 0 nas quatro combinações.