0038 — Emissão da garantia e a espera pela assinatura¶
| Status | CONCLUÍDA |
| Branch | spec/0038-emissao-e-assinatura-da-garantia |
| Criada em | 2026-08-15 |
| Apps afetados | api · web · e2e |
| Revisão do problema | aprovada na rodada 6 (com decisão do usuário) |
| Revisão da experiência | aprovada na rodada 5 (com decisão do usuário) |
| Revisão do detalhamento | aprovada na rodada 5 (com decisão do usuário) |
| Revisão da implementação | api: rodada 2 · web: rodada 8 · e2e: rodada 1 · branch: aprovada na rodada 6 |
Status:
RASCUNHO→APROVADA→EM IMPLEMENTAÇÃO→CONCLUÍDA
A spec tem três partes, escritas em ordem, e quatro portões — cada um revisado por um subagente de contexto limpo, em até 3 rodadas. A máquina de revisão é do plugin superpowers; a régua é a rubrica correspondente em .claude/rubrics/. Achados vêm classificados: qualquer Critical reprova, Important em aberto reprova salvo decisão registrada do usuário, e Minor vira pendência aceita.
- A Parte I diz qual é o problema e como se convive com ele hoje. Revisada na seção 6 (necessidade × jornada de hoje). É o portão mais barato de todos, e o único que pergunta contra que problema isto está sendo construído.
- A Parte II diz como se usa o que vai existir. Revisada na seção 8 (jornada × viewports).
- A Parte III diz como se constrói. Revisada na seção 16 (segurança × clareza técnica) — autorização, isolamento, e se sobrou decisão para quem vai codar.
- A implementação é medida contra as três, na seção 17 (aderência × qualidade × testes) — uma revisão por fase, e uma da branch inteira antes de fechar.
Cada parte só se escreve depois que a anterior passou. Descrever tela sobre problema mal enquadrado, ou detalhar endpoint sobre experiência vaga, é o jeito mais caro de descobrir que era outra coisa.
Parte I — O problema¶
Aqui não entra solução: nem tela, nem endpoint, nem botão. Só o que dói, para quem, e o que se faz hoje. Se você já sabe qual é a tela, guarde para a Parte II.
As três dores desta spec têm uma causa só: nada no sistema move o status de uma garantia. Ele nasce
ACTIVEna emissão e ficaACTIVE— enquanto o contrato não voltou assinado, e ainda depois de a vigência ter terminado. Tudo o que segue decorre disso: a espera invisível, a cobrança sem guarda, o anexo sem efeito e a garantia vencida que continua se apresentando como vigente. A dor da tela pobre é a exceção, e entra junto por ser o mesmo registro — quem abre a garantia para saber em que pé ela está é a mesma pessoa que não encontra ali o que ela cobre.Nota de recorte (o como é da Parte III; aqui só por que as dores vêm juntas): as três primeiras se aplicam no mesmo ponto e na mesma ordem — não se recusa cobrança sem haver estado que a recuse, e a permissão decide quem opera o que as outras criam. Separá-las produziria specs em que as primeiras não entregam nada sozinhas. A dor da tela é a mais solta: o que falta nela já está gravado, então ela entregaria valor sozinha — entra por ser o mesmo registro, e por ser onde o estado novo vai aparecer. A seção 14 planeja a ordem; esta nota registra por que a ordem não pode ser "uma spec cada".
1. Dor¶
A garantia existe no sistema como se já valesse, semanas antes de valer — e continua valendo depois de ter acabado.
Aprovada a análise de crédito, a garantidora emite a garantia e ela nasce ACTIVE. Não por escolha de quem emite: ACTIVE é o único estado que a emissão sabe produzir. Só que, pelo processo que a garantidora pretende operar — a seção 3 marca passo a passo o que é fato do sistema e o que é processo pretendido —, nesse momento o termo acabou de ser redigido e mandado para o inquilino assinar, e volta dias ou semanas depois. Nesse intervalo o sistema afirma duas coisas que não são verdade — que a cobertura está valendo e que o inquilino aceitou. E na outra ponta acontece o simétrico: passada a end_date, nada rebaixa a garantia. Ela segue ACTIVE indefinidamente.
O custo de conviver com isso tem quatro faces:
- A espera é invisível. Não há como perguntar ao sistema "quais garantias estão aguardando assinatura, e desde quando?". Uma garantia parada há três semanas é indistinguível de uma assinada ontem: as duas aparecem
ACTIVE, na mesma lista, com o mesmo selo. Quem cobra o retorno do inquilino cobra de memória, ou não cobra. - Nada impede cobrar fora da vigência. A cobrança é criada à mão, uma a uma, e a API não olha o estado da garantia em momento nenhum ao criá-la: aceita cobrar a que aguarda assinatura e aceita a que já venceu. A tela esconde o botão fora de
ACTIVE, mas isso é aparência — e, como a vencida continuaACTIVE, nem a tela a esconde. Um boleto contra um contrato não assinado é cobrar por cobertura que ainda não se deve; contra uma garantia vencida, por cobertura que já acabou. - Anexar o contrato assinado não muda nada. Há onde guardar o PDF — o painel de documentos da garantia existe e funciona. Mas o arquivo entra como mais um anexo: a garantia já estava
ACTIVEantes, e continua igual depois. O ato que deveria ser o marco da operação — voltou assinado, agora vale — não tem efeito nenhum no sistema. - A garantia vencida se apresenta como vigente. Terminada a vigência, ela continua na lista com o selo de ativa, continua faturável, e só um alerta em outra tela sabe que algo está errado. Quem lê a garantia lê que ela vale.
A segunda dor: quem toca a operação não pode concluí-la. O OPERATOR conduz o processo inteiro — cadastra inquilino e imóvel, monta a análise, acompanha até a aprovação — e para ali: emitir garantia exige guarantees:create, que ele não tem. A garantia fica esperando um repasse ao gestor, e o gestor vira gargalo de agenda num processo cuja decisão de crédito já foi tomada. Isso não quer dizer que a emissão seja carimbo: quem emite escolhe início e fim de vigência e o valor garantido, nenhum deles conferido contra a análise, e o intervalo entra no cálculo do pagamento inicial — a emissão fixa termos financeiros, e os congela. É justamente por decidir dinheiro que conceder essa permissão é decisão de autorização, não formalidade; a Parte III é que a toma. O que a Parte I registra é o custo de hoje: a espera não é do inquilino nem do processo, é de agenda.
A terceira dor é de leitura. A tela da garantia mostra três campos de dado — início da vigência, fim e valor garantido —, mais o selo de status, um campo de notas, um link para a análise, a lista de cobranças e o painel de documentos. A garantidora já guardou na emissão a taxa aplicada, o LMI, o pacote locatício, a mensalidade, a taxa de setup, a carência de sinistro e o prazo para acionar; e sabe quem é o tomador e qual é o imóvel. Nada disso aparece. Para responder "quanto essa garantia cobre?" ou "de quem é?", sai-se da garantia e entra-se na análise de crédito, no inquilino e no imóvel — três telas para montar de cabeça o que uma poderia mostrar. É o registro do objeto que a garantidora vende, e é o que menos diz sobre ele.
Vale uma distinção que a Parte III vai precisar: dos nove dados que faltam, oito já estão na própria garantia e apenas não são exibidos — os sete financeiros mais o tomador (primary_lessee_id). O imóvel é a única exceção: a garantia não guarda property_id, e chegar até ele exige passar pela análise. Não é o mesmo trabalho.
2. Quem sente¶
Quem emite — hoje, o ADMIN_TENANT. É dele a dor do estado: emite, redige o termo, manda assinar, espera, recebe de volta e anexa. É caminho obrigatório — nenhuma garantia chega a valer sem passar por aqui, e não há emissão de exceção. (O ADMIN_GLOBAL também alcança a rota, por receber todas as permissões; é papel de plataforma, não de operação, e a fronteira plataforma × cliente é assunto da seção 10.)
Quem conduz e não conclui — o OPERATOR. Sente a segunda dor: faz o trabalho todo até a aprovação e depende do gestor para o último passo. Caminho obrigatório também — toda garantia originada por ele passa pelo repasse.
Quem lê depois — ANALYST, VIEWER, OPERATOR e ADMIN_TENANT. A terceira dor é de quem abre uma garantia para responder uma pergunta vinda de fora: a imobiliária ligou perguntando até quando a cobertura vale; o inquilino contesta um valor; alguém confere o que cobrar antes de lançar a mensalidade; o analista quer ver como ficou a garantia que nasceu do parecer dele. Todos chegam pela garantia e precisam de dados que estão em outras três telas.
Quem não sente, e é decisão registrada: a imobiliária (AGENCY_USER). Pelo processo pretendido, todo o ciclo é conduzido pela garantidora — ela gera a documentação, envia para assinatura e anexa o retorno; a imobiliária não participa.
O sistema hoje só sustenta metade disso, e a diferença importa. É fato que o AGENCY_USER não tem nenhuma permissão guarantees:* — não emite. Mas anexar documento em garantia ele consegue: tem documents:create, guarantee é um entity_type válido de upload, e a guarda que fecha o dossiê da análise enquanto a bola está com a garantidora não cobre garantia (cai num else: return). O identificador da garantia chega até ele pela própria análise, que ele lê. Isso importaria muito se anexar o assinado fosse, por si, o que faz a garantia passar a valer — a imobiliária moveria o estado da garantia por uma rota que ninguém guardou, que é a classe que a spec 0035 pagou.
Por isso anexar e ativar são atos separados, e é decisão tomada: guardar o arquivo não move estado nenhum; ativar é um ato explícito, e só a garantidora o pratica. A porta do upload deixa de ser um risco de estado — continua sendo assunto da seção 10 decidir se ela se fecha por outros motivos, e esta spec não a fecha nem a alarga.
Quanto: não há volume a medir, e não há como estimar. A produção subiu na spec 0028 e ainda não tem cliente nenhum — o CLAUDE.md da raiz registra isso. Não há operação da qual extrair "quantas garantias por mês", "quantos dias de espera" ou "quantas consultas por garantia", e qualquer número aqui seria inventado. O que sustenta o alcance é o desenho: as duas primeiras dores estão em caminho obrigatório — toda garantia é emitida, espera e vence, e toda garantia originada pelo OPERATOR passa pelo repasse. A terceira é mista: conferir o que cobrar antes de lançar a mensalidade é recorrente, já que cada cobrança é criada uma a uma; os demais gatilhos (ligação da imobiliária, contestação do inquilino) são exceção, e é honesto dizer que ali o alcance é menor.
3. A jornada de hoje¶
Gatilho: a análise de crédito é aprovada.
Sobre a origem desta jornada: os passos 1, 3, 6 e 7 são conferíveis no código e descrevem o que o sistema faz. Os passos 2, 4 e 5 acontecem fora do sistema. Eles são o processo pretendido pela própria garantidora que constrói o produto — quem pediu esta spec —, e não o relato de uma operação em curso: não há cliente (seção 2), então ninguém executou isso ainda. São premissa de desenho, não evidência de campo, e estão marcados um a um para que ninguém os leia como medição. Se a operação real divergir deles, o que esta spec constrói precisa ser reavaliado — é a premissa mais frágil da Parte I, e é honesto dizê-lo.
- O
OPERATORchega ao fim do que pode fazer. Conduziu inquilino, imóvel e análise atéAPPROVED. Emitir exigeguarantees:create, que ele não tem. - (processo pretendido) O repasse ao gestor acontece fora do sistema. Não há aviso, fila nem caixa de entrada: nada no produto diz ao
ADMIN_TENANTque existe uma análise aprovada esperando emissão. OOPERATORavisa por mensagem ou conversa, ou o gestor descobre olhando a lista. É aqui que a espera de agenda acontece. - O
ADMIN_TENANTemite a garantia. Um diálogo na tela da análise, e a garantia nasceACTIVE— sem que exista outro estado possível. Quem emite informa início e fim de vigência e o valor garantido; o sistema congela aí o snapshot de preço (taxa, LMI, mensalidade, setup) e numera a garantia. Não gera cobrança: cobrança é ato manual, depois. - (processo pretendido) A garantidora redige a documentação — fora do sistema. O termo é produzido à parte. Existe um
ContractTemplateno banco que não é usado para isso. É por isso que a emissão precede a assinatura, e não o contrário: o termo precisa do número da garantia e dos valores que só a emissão congela. Emitir é o que dá ao documento o que ele tem a dizer. - (processo pretendido) A garantidora envia para o inquilino assinar — fora do sistema. Por e-mail, por assinatura eletrônica, ou pelo caminho que a operação usar. O sistema não participa e não fica sabendo.
- Espera-se o retorno — e é aqui que quebra. Este passo não existe no sistema. Não há estado, não há data de envio, não há lista. A garantia que espera é, para todos os efeitos da tela, idêntica a uma em pleno vigor. E, porque o sistema não distingue, ele também aceita que se crie cobrança contra ela.
- O assinado volta e é anexado. O painel de documentos aceita o PDF (
Document.guarantee_idexiste desde antes desta spec). O anexo é gravado, e nada mais acontece — a garantia estavaACTIVEantes e continuaACTIVEdepois.
Depois, e sem gatilho de ninguém: passada a end_date, a garantia permanece ACTIVE. Nenhum caminho do sistema atribui EXPIRED — o estado existe no enum e nunca é usado. A garantia vencida continua na lista como ativa e continua aceitando cobrança.
Onde exatamente quebra: entre o passo 3 e o passo 7, o sistema trata como instantâneo um intervalo que dura dias ou semanas, e não representa nem a espera nem o desfecho dela. Antes disso, no passo 2, o processo já tinha parado uma vez por falta de permissão. E depois do 7 ele nunca mais se move.
O que já se tentou:
- O armazenamento foi construído e não bastou. O
Documentaceitaguarantee_ide a tela já tem o painel. Guardar o arquivo não é o mesmo que reconhecer que a garantia passou a valer — resolveu-se o armazenamento e não o estado. - Há um contorno construído para a garantia vencida — código, não prática de ninguém, já que não há operação —, e ele confirma o diagnóstico: o painel de operações levanta um alerta
CRITICALGUARANTEE_EXPIRED_ACTIVE— "garantia com data de fim no passado ainda marcada como ACTIVE". Ou seja, alguém já precisou construir um detector para um estado que o sistema deveria simplesmente ter. É compensação, não solução: o alerta avisa, mas a garantia continua vigente e faturável. - Sobrou estado morto no enum. Além de
EXPIRED, oSUSPENDEDexiste e nenhum caminho do serviço chega nele. A Parte III precisa decidir se os novos estados convivem com esses ou os substituem. - Fora isso não há contorno construído: nem campo de nota marcando "aguardando", nem planilha paralela. E não há contorno humano a relatar, porque ninguém operou isto ainda — o que a garantidora prevê, se nada mudar, é controlar a espera por fora do sistema.
Na terceira dor, a jornada é curta e repetida. Gatilho: uma pergunta que chega de fora — ligação da imobiliária, contestação do inquilino, conferência antes de lançar a mensalidade. Abre-se a garantia, leem-se três campos, não se acha o que se procurava, clica-se no link da análise de crédito; de lá, se a pergunta era sobre a pessoa ou o imóvel, mais um salto. Volta-se para a garantia para ver as cobranças. Nada disso quebra — só custa três telas toda vez, e obriga a pessoa a lembrar de onde vem cada número enquanto alguém espera na linha.
4. Resultado esperado¶
- A garantia só afirma o que é verdade. Enquanto o contrato não voltou assinado, ela se apresenta como esperando; passada a vigência, deixa de se apresentar como vigente. Verificável: emitir uma garantia e conferir que ela não consta como vigente; registrar o assinado e conferir que passou a constar; passar da data de fim e conferir que deixou de constar.
- Não se cobra garantia fora da vigência. Verificável: tentar criar uma cobrança numa garantia que aguarda assinatura é recusado pela API, e o mesmo vale para a encerrada e a vencida; na vigente é aceito. Hoje as três primeiras tentativas também são aceitas. A cobrança legítima não some, muda de momento: o setup e a primeira parcela que o domínio prevê no primeiro vencimento passam a poder ser criados a partir da ativação — que, pelo processo, é quando o inquilino já aceitou. A guarda atrasa a cobrança, não a elimina.
- A espera é uma pergunta que o sistema responde. É possível ver quais garantias estão aguardando assinatura e há quanto tempo cada uma espera, sem perguntar a ninguém. Verificável: a informação existe e distingue duas garantias emitidas em datas diferentes.
- Registrar o contrato assinado tem consequência. Verificável: existe um antes e um depois observáveis na própria garantia — o que hoje não existe, já que anexar não muda nada.
- O alerta
GUARANTEE_EXPIRED_ACTIVEdeixa de encontrar caso. Verificável: o contorno do painel de operações passa a apontar zero, porque não existe mais garantia com fim no passado se apresentando como vigente. É a prova de que a causa foi tratada, e não o sintoma. Como o vencimento passa a ser reconhecido — estado gravado, condição derivada da data, ou outra coisa — é decisão da Parte III; se for derivado, a consulta do alerta (end_date < hoje AND status == ACTIVE) precisa ser ajustada junto, senão ela continua contando as mesmas linhas e o critério mede a coisa errada. - Quem conduz a operação a conclui sem depender de agenda alheia. Verificável: some o repasse do passo 2 da jornada. A forma — conceder
guarantees:createaoOPERATOR, ou outro desenho de autorização — é decisão da seção 10, não deste resultado. - As perguntas sobre a garantia se respondem na garantia. Verificável: "de quem é", "qual imóvel", "quanto cobre", "quanto custa", "até quando vale" e "está paga" deixam de exigir a análise de crédito, o inquilino e o imóvel.
5. Fora de escopo¶
- Geração automática de cobrança. A cobrança continua sendo criada à mão, como hoje. O que muda é que ela passa a ser recusada fora da garantia vigente. Gerar setup e primeira parcela automaticamente ao assinar foi cogitado e ficou de fora por decisão do usuário: é funcionalidade de faturamento nova — exige decidir vencimento, parcelamento do setup e o que fazer quando os parâmetros do tenant mudaram entre a emissão e a assinatura — e o ganho desta spec não depende dela.
- Integração com assinatura eletrônica (D4Sign, Clicksign, DocuSign e afins). A assinatura continua acontecendo fora do sistema, e o sistema recebe o PDF assinado. Cogitado e deixado de fora porque exige contrato com provedor, credencial, retorno assíncrono e tratamento de falha — é uma spec inteira, e a espera visível não depende dela.
- Geração do termo a partir do
ContractTemplate. O modelo existe no banco e não é usado na emissão. Redigir o termo continua sendo feito fora. Fica de fora porque compor e versionar texto de contrato é problema independente do problema de estado que esta spec resolve — juntá-los faria a espera invisível esperar por um editor de contrato. - Outros signatários. O sistema espera apenas a assinatura do inquilino para reconhecer a garantia como vigente. Que o termo seja bilateral entre garantidora e tomador não muda isso: o que a garantidora assina não é o que ela fica esperando. A imobiliária e o proprietário não assinam — o proprietário, aliás, não é parte conhecida pela garantidora (ver o domínio no
CLAUDE.md). - Expiração automática da garantia não assinada. Uma garantia pode aguardar assinatura indefinidamente; ninguém a cancela por decurso de prazo. Cogitado — o mercado costuma ter validade de proposta — e deixado de fora por decisão do usuário: sem operação rodando, qualquer prazo aqui seria chutado. Não confundir com o vencimento da vigência, que entra nesta spec: aquele tem data conhecida desde a emissão.
- Escrita da imobiliária sobre a garantia. O
AGENCY_USERpassa a ler a garantia da própria imobiliária — decisão tomada no portão da experiência, registrada na seção 6 —, e não ganha nenhuma escrita: não emite, não ativa, não encerra. A seção 10 decide o que dos números ela enxerga, já que a tela nova expõe taxa aplicada, LMI, setup e total de entrada. - Fechar a porta de upload da imobiliária. Esta spec não fecha a porta que ele já alcança hoje (seção 2). Ela deixa de importar para o estado porque anexar e ativar são atos separados — a imobiliária não ativa. Fechar o upload em si é mudança de acesso com efeito fora desta spec (o
AGENCY_USERanexa documento em análise e imóvel pelo mesmo caminho), e fica para quem for tratar isso de propósito. - Mudança no fluxo da análise de crédito. A análise segue terminando em
APPROVEDdo jeito que termina hoje. Esta spec começa depois disso. - Régua de vencimento, pagamento e sinistro. Como a cobrança é calculada, quando vence e como é baixada não muda; o que muda é apenas quando ela pode ser criada. Sinistro não é tocado.
6. Revisão do problema¶
Rubrica .claude/rubrics/problema.md, revisor de contexto limpo e novo a cada rodada.
| Rodada | Critical | Important | Minor | Veredicto |
|---|---|---|---|---|
| 1 | 1 | 2 | 2 | reprovada |
| 2 | 0 | 2 | 4 | reprovada |
| 3 | 0 | 2 | 4 | reprovada |
| 4 | 0 | 1 | 3 | reprovada |
| 5 | 0 | 1 | 5 | reprovada |
| 6 | 0 | 1 | 4 | reprovada — achados corrigidos sem 7ª rodada (ver abaixo) |
Resultado: aprovada com decisão registrada do usuário, após a rodada 6.
Por que seis rodadas, e não três. O limite da régua é 3. Ele foi estourado duas vezes, com autorização explícita do usuário nas duas, porque o laço estava convergindo e não preso: 1C/2I → 0C/2I → 0C/2I → 0C/1I → 0C/1I → 0C/1I. Nenhum achado se repetiu entre rodadas — cada uma encontrou coisa nova, e da rodada 4 em diante os Important foram defeitos introduzidos pela correção anterior, não pontos resistentes.
O que as rodadas acharam, e o que isso custou. Três Important eram afirmações minhas sobre o código que não eram verdade, e as três teriam se propagado para o desenho:
- "A emissão gera cobrança" — não gera; cobrança é ato manual. Isso mudou a fronteira da spec (geração automática saiu de escopo) e o critério de aceite.
- "A emissão não decide nada" — decide: quem emite escolhe vigência e valor garantido, sem conferência contra a análise, e o intervalo entra no cálculo do pagamento inicial. Isso transformou a ampliação ao
OPERATORde formalidade em decisão de autorização. - "A imobiliária não anexa" — anexa: tem
documents:create,guaranteeéentity_typeválido e a guarda de janela cai numelse: return. Isso levou à decisão de separar upload de ativação, que é o desenho que fecha a porta sem mexer em permissão.
A rodada 6 apontou um Important de numeração — eu havia inserido um passo na jornada sem atualizar o preâmbulo que diz quais passos são fato de código. Corrigido sem nova rodada, por decisão do usuário de encerrar o laço: é erro factual de uma frase, sem conteúdo de decisão, e registrá-lo como pendência aceita deixaria texto errado no documento. A correção não foi revisada — é a única parte da Parte I nessa condição.
Decisões do usuário registradas neste portão (nenhuma foi inventada):
| Decisão | Escolha |
|---|---|
| Quando a cobrança pode existir | Só em garantia vigente — a API recusa fora disso. Sem geração automática. |
| Quem assina | Só o inquilino (tomador). |
| Como a assinatura chega | Upload do PDF assinado; sem integração com assinatura eletrônica. |
| Garantia não assinada | Espera indefinidamente; sem expiração automática. |
Emissão pelo OPERATOR | Entra — o gestor é gargalo de agenda hoje. |
Garantia vencida ainda ACTIVE | Entra na spec — vencer também precisa mover o estado. |
| Porta de upload da imobiliária | Não se fecha nem se alarga: anexar e ativar viram atos separados, e só a garantidora ativa. |
Pendências aceitas: nenhuma. Todos os achados das seis rodadas foram corrigidos.
Alteração posterior à aprovação. O portão da experiência (seção 8, rodada 4) achou uma inconsistência que só aparecia ao descrever a tela: a Parte II dizia que a imobiliária "vê a garantia e não a ativa", enquanto o AGENCY_USER não tem nem guarantees:read e receberia 404. O usuário decidiu que ela passa a ler a garantia da própria imobiliária, e a fronteira da seção 5 foi corrigida para refletir isso. A Parte I não foi rerrevisada depois desta alteração — é o único ponto dela nessa condição.
Riscos que a Parte I deixa anotados para as partes seguintes:
- A premissa mais frágil é a jornada externa (passos 2, 4 e 5): é processo pretendido pela garantidora que constrói o produto, não operação observada — não há cliente. Se a operação real divergir, o que esta spec constrói precisa ser reavaliado.
- A porta de upload do
AGENCY_USERem garantia continua aberta. Deixa de mover estado pela decisão acima, mas a seção 10 deve olhá-la de propósito. SUSPENDEDeEXPIREDsão estados mortos no enum. A Parte III decide se os novos estados convivem com eles ou os substituem.- O título da spec anuncia metade do que a Parte I abriu — vencimento e guarda de cobrança entraram depois. Vale renomear ao fechar a Parte III.
Parte II — A experiência¶
Não preencher antes da revisão da seção 6 estar aprovada.
7. Jornada pretendida¶
Os quatro estados que a garantia passa a ter. Hoje ela só sabe ser Ativo. Passa a saber dizer em que ponto do ciclo está:
| Estado | Como se chega | Cobra? |
|---|---|---|
| Aguardando assinatura | é o estado de nascimento, na emissão | não |
| Vigente | de Aguardando, quando alguém registra o contrato assinado | sim |
| Vencida | de Vigente, sozinha, quando passa o fim da vigência | não |
| Encerrada | de Vigente ou de Aguardando, por ato humano com motivo | não |
Vencer rebaixa só quem estava vigente. Uma garantia que nunca foi assinada não vira Vencida ao passar do fim da vigência proposta: ela continua Aguardando assinatura, e a faixa de status ganha um aviso — "A vigência proposta terminou em 14/08/2026. Encerre esta garantia." O motivo é que a Parte I decidiu que a espera não tem prazo; rebaixá-la para Vencida esconderia o botão de registrar assinatura e deixaria a garantia inalcançável sem ninguém ter decidido isso. Quem decide é a pessoa, e a saída é encerrar com motivo — registrar a assinatura passa a ser recusado nesse caso (RN8a), porque ativar uma garantia cujo período já acabou a deixaria vigente e sem como faturar.
"Emitir outra" não é saída, e não pode ser. Há uma garantia por análise de crédito, garantido por unicidade no banco — o botão "Emitir garantia" da análise só aparece enquanto ela não tem garantia, e não volta depois de encerrada. Cobrir o mesmo aluguel de novo exige uma análise nova, que é outro caminho e não faz parte desta spec.
O nome do estado é o que a pessoa lê no selo. "Aguardando assinatura" e não "Pendente": pendente já nomeia um estado de análise de crédito, e as duas coisas aparecem na mesma jornada — reusar a palavra faria alguém ler "pendente" em dois lugares querendo dizer coisas diferentes.
Ponto de entrada. Quatro, e nenhum novo no menu:
- Da análise aprovada — o botão "Emitir garantia" que já existe no topo da análise. É o caminho de origem, e continua o mesmo.
- Da lista de garantias — quem quer saber o que está parado entra por aqui e filtra por estado. É o caminho de acompanhamento, e hoje não responde nada porque tudo é
Ativo. - Da própria garantia — quem recebeu o assinado abre a garantia e registra ali.
- Da imobiliária, pelo menu — o item "Garantias" passa a aparecer para o
AGENCY_USER, já que ele é condicionado aguarantees:list, que ela ganha. Ela chega na lista, filtrada à própria carteira, e abre a garantia dos seus contratos. Só lê: não emite, não registra assinatura, não corrige e não encerra — nenhuma ação de escrita aparece para ela.
Jornada, caso feliz. Sete passos, dois deles fora do sistema:
- Aprovada a análise, quem conduz (
OPERATORou gestor) clica em Emitir garantia. - O diálogo mostra o que vai ser congelado — vigência, valor garantido, taxa, mensalidade, setup e total de entrada — já preenchido, e pede confirmação. Quem emite pode ajustar vigência e valor garantido; o resto é consequência e se recalcula à vista.
- Confirmada a emissão, o sistema leva quem emitiu para o detalhe da garantia recém-criada — que é o que o diálogo já faz hoje. Ela nasce Aguardando assinatura, e a faixa de status diz o que falta, no texto que a tabela de 7.1 fixa: "Aguardando o contrato assinado — emitida há 12 dias" (no primeiro dia, "emitida hoje").
- (fora do sistema) A garantidora redige o termo com o número e os valores que a emissão acabou de congelar, e envia ao inquilino.
- (fora do sistema) O inquilino assina e devolve.
- Quem recebeu abre a garantia e clica em Registrar contrato assinado. O diálogo pede o arquivo e a data da assinatura, e avisa o que vai mudar: a garantia passa a valer e a cobrança é liberada.
- A garantia fica Vigente. Um toast confirma, e a partir dali o botão de nova cobrança deixa de recusar.
Anexar não é ativar, e a tela precisa deixar isso óbvio. O painel de documentos continua aceitando arquivo como sempre — inclusive da imobiliária, que já consegue hoje. O que faz a garantia valer é o passo 6, que é ato próprio, com confirmação e com dono. Um arquivo largado no painel não muda estado nenhum, e a tela não deve sugerir que muda.
Como o arquivo entra no passo 6. Dois caminhos, e os dois terminam no mesmo lugar:
- Enviar agora — o seletor de arquivo do diálogo. É o caminho normal.
- Escolher um já anexado — uma lista dos documentos desta garantia, mostrando nome e data de anexo, para quem já subiu o PDF antes de lembrar de registrar. Sem filtro por tipo: exigir que a pessoa tivesse classificado o documento antes transformaria um acerto em pré-requisito. No desktop os dois caminhos são um par de rádio no topo do diálogo — "Enviar arquivo" (marcado por padrão) e "Escolher um já anexado" —, e a lista aparece abaixo quando o segundo é escolhido. Se a garantia não tiver documento nenhum, o segundo rádio vem desabilitado com o motivo ao lado ("Nenhum documento anexado ainda"), em vez de abrir uma lista vazia.
Em qualquer dos dois, o documento escolhido passa a ser o contrato assinado da garantia — é essa marcação que a seção Documentos exibe depois.
Marcar o PDF errado é possível, e por isso tem conserto. A lista não filtra por tipo, e os anexos se parecem entre si. Com a garantia já Vigente, a ação do topo vira "Corrigir contrato assinado" — mesmo diálogo, com o documento e a data atuais preenchidos, trocando um ou outro. Corrigir não mexe no estado: a garantia continua vigente, porque o que estava errado era o papel apontado, não o aceite. Fica registrado quem corrigiu e quando. Em Vencida e Encerrada a ação não aparece — ali o registro está fechado.
A data da assinatura. Vem pré-preenchida com a data de hoje e é editável, porque o PDF costuma voltar dias depois de assinado. Ela não mexe na vigência: início e fim foram congelados na emissão e continuam sendo. Serve de registro e é o que encerra a contagem de espera. Não pode ser futura — se for, o campo recusa ali mesmo: "A assinatura não pode ser depois de hoje." Pode ser anterior à emissão (o inquilino pode ter assinado antes de o PDF chegar), e nesse caso nada mais muda.
As duas datas nunca se confundem na tela, porque dizem coisas diferentes. A faixa de status sempre nomeia as duas: "Vigente de 01/08/2026 a 31/07/2027 · assinada em 09/08". O intervalo é o congelado na emissão — é ele que define a cobertura. A assinatura é quando o aceite chegou. Isso resolve os dois casos em que a ativação cai fora do intervalo:
- Assinou depois do início (o mais comum): a cobertura já corria; a faixa mostra o intervalo inteiro e a data de assinatura ao lado, sem alarde. A garantia não passa a valer "a partir de agora" — ela passa a constar como valendo, pelo período que sempre foi.
- Registrou antes do início (vigência futura): o selo diz Vigente e a faixa acrescenta "a cobertura começa em 01/09". Não se inventa um quinto estado para isso: a garantia está aceita e é irreversível; o que falta é o calendário passar.
Custo de uso. O que o sistema já sabe e não pede de novo: vigência e valor garantido (da análise), taxa aprovada, LMI, pacote locatício, mensalidade, taxa de setup, carência e prazo de sinistro (dos parâmetros da garantidora e do parecer), quem é o tomador e qual é o imóvel. O que ele precisa perguntar: o arquivo do contrato assinado e a data em que foi assinado — dois campos, uma vez, no fim do ciclo, um deles pré-preenchido. Na emissão, nada de novo: os mesmos quatro campos de hoje (início, fim, valor garantido e notas), agora com o resumo do que será congelado ao lado. As notas ficam onde estão, por último e opcionais.
7.1 Desktop¶
A tela da garantia deixa de ser um índice e vira a resposta. Segue o padrão de detalhe do ui-patterns.md — Paper com faixa de status no topo e seções abaixo, Field em grade de três colunas. Ordem das seções, e o motivo de cada posição:
- Faixa de status — o selo do estado e, ao lado, a frase que muda com ele, uma por estado:
| Estado | A frase ao lado do selo |
|---|---|
| Aguardando assinatura | "Aguardando o contrato assinado — emitida há 12 dias" |
| Vigente | "Vigente de 01/08/2026 a 31/07/2027 · assinada em 09/08" |
| Vencida | "Vencida em 31/07/2027" |
| Encerrada | "Encerrada em 14/08/2026 por Ana Souza" — o motivo fica na seção própria |
Três variantes, e são todas: Vigente com vigência ainda futura acrescenta "a cobertura começa em 01/09"; Aguardando com a vigência proposta já vencida acrescenta o aviso descrito em 7.3; e Vigente sem data de assinatura registrada (garantia anterior a esta spec, migrada) omite o "· assinada em" em vez de mostrar vazio.
O tempo de espera fica aqui, não numa seção lá embaixo: é a informação que a pessoa veio buscar quando o estado é o de espera. 2. Cobertura — valor garantido, LMI, pacote locatício, taxa aplicada e mensalidade. Primeiro porque responde "quanto essa garantia cobre?", a pergunta mais frequente da terceira dor. 3. Partes — o tomador (nome, documento e contato), o imóvel (endereço e tipo) e a imobiliária. Cada um com link para a própria tela: resolve o "quem" e o "qual" sem tirar a pessoa da garantia, sem esconder o caminho de quem quer o cadastro inteiro. 4. Vigência e entrada — início, fim, prazo em meses, taxa de setup, primeira parcela e total de entrada. 5. Sinistro — carência e prazo para acionar. Última das seções de dado porque é a menos consultada no dia a dia e a mais consultada no pior dia; abaixo dela só ficam o campo livre e os dois painéis que já existem.
Fora da ordem, e só num estado: a seção Encerramento — motivo, quem encerrou e quando — aparece entre a faixa de status e Cobertura, e só quando a garantia foi encerrada. É o que a spec exige ao tornar o motivo obrigatório: exigir sem exibir seria pedir um dado para não mostrá-lo a ninguém. Fica no topo porque, numa garantia encerrada, é a primeira pergunta. Nos outros três estados ela não existe, e a numeração abaixo segue como está. 6. Notas — o campo livre que a tela já tem, com edição inline, preservado. Editável em Aguardando e Vigente; só leitura em Vencida e Encerrada, onde o registro está fechado. Hoje a edição existe só em
ACTIVE— passa a existir também na espera, que é justamente quando alguém quer anotar "termo enviado em 12/08, cobrado no dia 20". Em 375px a edição inline abre omultilinede três linhas no lugar do texto, com Salvar e Cancelar logo abaixo e dentro da área visível — não é diálogo, e por isso não ganha rodapé ancorado; o que a protege do teclado é a seção rolar para si ao entrar em edição. 7. Cobranças — a tabela que já existe (competência, vencimento, valor, valor pago, status, ações), com o botão de nova cobrança desabilitado e explicado enquanto o estado não é Vigente: "Disponível quando a garantia estiver vigente." Botão cinza sem motivo é a pessoa adivinhando o que a trava. 8. Documentos — o painel que já existe, com o contrato assinado marcado como tal, no topo da lista, e não perdido entre os outros anexos.
A imobiliária vê quase a mesma tela. Ela cobra a garantia junto com o aluguel e repassa, então precisa do valor e do prazo:
| Seção | O que o AGENCY_USER vê |
|---|---|
| Faixa de status | inteira — estado, tempo de espera, vigência, data de assinatura |
| Cobertura | inteira, inclusive taxa aplicada e pacote locatício |
| Partes | inteira — é a carteira dela |
| Vigência e entrada | inteira, inclusive setup, primeira parcela e total de entrada |
| Sinistro | inteira — é o que ela vai acionar |
| Notas | não vê: é o campo interno da garantidora |
| Encerramento | vê que foi encerrada e quando; não vê o motivo, que é registro interno |
| Cobranças | vê a lista e os valores; não cria, baixa nem marca em atraso |
| Documentos | inteira, incluindo o contrato assinado marcado |
Isto mudou no portão do detalhamento, e o motivo é do código. A versão anterior desta tabela escondia dela a precificação (taxa, pacote, composição da entrada). A seção 16 achou que ela já lê esses números hoje, pelo resumo da garantia embutido na resposta da análise de crédito — que a tela dela consome. Esconder aqui o que sai por lá entregaria uma fronteira que não existe. O que sobra de restrição são os dois campos que não vazam por rota nenhuma: as notas e o motivo do encerramento. Ver 10.6 e os riscos aceitos da seção 15.
A seção 10 aplica isso no contrato da API — esconder na tela não protege a rota, e esta é a lição que as specs 0034 e 0035 pagaram.
Ações no topo, no teto de três do PageHeader, e cada uma com o estado em que aparece:
| Ação | Forma | Aparece quando |
|---|---|---|
| Registrar contrato assinado | preenchida | estado é Aguardando assinatura |
| Encerrar | contornada | estado é Aguardando ou Vigente — nunca em Vencida ou Encerrada, onde não há o que encerrar |
| Ver análise de crédito | contornada | sempre |
Vigente a garantia, a primeira sai e o topo fica com duas. Encerrar exige motivo (obrigatório, não recomendado): é ato irreversível, e o registro de por que se encerrou é o que a auditoria vai procurar. A confirmação diz o que se perde — "A garantia deixa de valer e não gera novas cobranças. Não é possível reativá-la."
O número da garantia passa a ser o número da garantia. Hoje a tela mostra dois identificadores fabricados e diferentes entre si — a lista compõe #260815.1432 a partir do created_at e o detalhe mostra os últimos 8 dígitos do id —, enquanto a API já gera e guarda o guarantee_number no formato G202608-00042 desde a emissão. O tipo Guarantee do web sequer o declara. Os dois códigos fabricados saem, e o guarantee_number passa a ser o que aparece no cabeçalho do detalhe, na coluna da lista e no que a busca encontra. Não é higiene: é a jornada que exige — o termo enviado ao inquilino carrega esse número, e a pessoa que atende a ligação digita o que está no papel. Dois números para a mesma garantia é a ligação que não se resolve. Quem for implementar não deve preservar o guaranteeCode do index.tsx achando que é o identificador da casa.
O cabeçalho da página passa a ser Garantia G202608-00042, e o subtítulo, o nome do tomador — a vigência sai dali porque a faixa de status assumiu.
Em 375px o cabeçalho não cabe inteiro, e isso é decisão, não acidente: com três ações de 44px sobram ~169px para o título (medida do ui-patterns.md), e Garantia G202608-00042 não entra. O título vira G202608-00042 — sem a palavra "Garantia", que a tela toda já diz — e o subtítulo continua sendo o tomador. O número não pode truncar: é a única coisa da tela que a pessoa vai ditar ao telefone, e um número cortado é pior que nenhum.
Lista de garantias. Ganha o que responde à espera invisível:
- Busca e filtro no
FilterBar, o recipiente que oui-patterns.mddefine para as listas: oSearchFieldprimeiro e crescendo, buscando por número da garantia e nome do tomador — é o que se tem na mão quando a imobiliária liga —, e ao lado umFilterSelectde estado. São esses dois, e mais nenhum: a lista de hoje está fora do padrão (umSelectsolto acima da tabela) e entra junto, porque replicar o desvio é o que faz um padrão morrer. - Colunas: número, tomador, imóvel, situação, valor garantido, mensalidade, vigência — sete —, mais a de Ações, que já existe e fica. São oito, que é exatamente o teto medido no
CLAUDE.mddo web ("acima de oito colunas a de Ações sai da tela"). Está no limite de propósito, e é o que impede a nona entrar sem alguém decidir o que sai. - Paginação:
ListPaginationno rodapé, nos dois viewports, com ordenação e paginação no servidor — o padrão da spec 0022. A lista de hoje carrega 200 registros e filtra em memória; ordenar "as que aguardam há mais tempo primeiro" só na página corrente seria mentira, e é o defeito que oCLAUDE.mdda API registra. - A coluna Situação funde o selo de estado com o tempo:
Aguardando · 12 diaspara quem espera, o selo sozinho para os demais. Ela substitui a coluna "Vence em" de hoje, que mostra um chip com os dias restantes (vermelho abaixo de 30, amarelo até 90) — e a troca é deliberada: o sinal de urgência passa a ser o da espera, que é o que esta spec resolve, e os dias até vencer seguem legíveis na coluna de vigência. Quem for implementar não deve apagar o chip achando que é resto. - A coluna "Análise de Crédito" sai, e é decisão: o caminho para a análise passa a ser a ação "Ver análise de crédito" do detalhe. Uma coluna de link numa lista de oito colunas gasta a largura que a Situação passou a precisar, e ninguém navega para a análise a partir da lista — chega-se nela pela garantia que se foi ver. Quem for implementar não deve preservá-la achando que é resto.
- O filtro de estado oferece os quatro estados desta spec, e só eles.
SUSPENDEDnão aparece: é estado morto no enum, nenhum caminho do sistema chega nele, e oferecer um filtro que nunca retorna nada é prometer um recorte que não existe. Se a Parte III decidir ressuscitá-lo, o filtro é o primeiro lugar que muda. - Clicar em qualquer ponto da linha abre a garantia. A coluna de Ações de hoje (ver e encerrar por linha) fica: ver vira redundante mas inofensivo, e encerrar por linha é o que evita abrir três telas para encerrar três garantias.
- Ordenáveis: situação, valor garantido, mensalidade e vigência — as quatro que o servidor sabe ordenar. Número, tomador e imóvel não recebem seta: ordenar só a página corrente seria mentira, e é a regra que o
CLAUDE.mdda API já fixa. - Ordenação padrão: por situação primeiro — as que Aguardam no topo, entre si da mais antiga para a mais nova —, e o resto abaixo pela emissão mais recente. Não é ordenar tudo por data, o que jogaria garantias vigentes antigas para cima e afundaria justamente o que a lista passou a existir para mostrar.
Emissão. O diálogo de hoje ganha o resumo do que será congelado, à vista, antes de confirmar: taxa, mensalidade, setup e total de entrada, recalculados quando se mexe na vigência ou no valor. Hoje esses números só aparecem depois de emitida — e são justamente os que a emissão torna imutáveis. Confirmar o que se vai congelar sem ver o que congela é o que a Parte I chamou de "a emissão decide dinheiro".
7.2 Mobile (375px)¶
Quem usa a garantia no celular está lendo, não emitindo. O caso real é atender uma pergunta longe da mesa — a imobiliária ligou, o inquilino contesta. Por isso o mobile prioriza leitura; as duas escritas continuam disponíveis, e as duas estão descritas abaixo.
O detalhe:
- Vira
MobileDetailSectionempilhado, uma seção por bloco, na mesma ordem do desktop. A faixa de status vem antes de tudo e não colapsa: estado e tempo de espera são a primeira coisa na tela, sem rolar. - Colapsam as sete: Cobertura, Partes, Vigência e entrada, Sinistro, Notas, Cobranças e Documentos — abertas por padrão só Cobertura e Partes. As duas últimas são as de altura ilimitada (n cobranças, até 20 documentos), e são justamente por isso que colapsam: abertas, empurram tudo o mais para fora de alcance. O cabeçalho de cada uma traz a contagem (
Cobranças (14),Documentos (3)), para a pessoa saber se vale abrir. Encerramento nunca colapsa: quando existe, é a resposta que a pessoa veio buscar. Em 375px, sete seções abertas viram milhares de pixels de rolagem para achar a mensalidade. OMobileDetailSectionde hoje não colapsa — éPaper+ cabeçalho + linhas —, então isto é comportamento novo do componente compartilhado, não uso do que existe. É de propósito: os dois casos de colapso do app hoje (HistoryTimelineesettings/document-types) resolveram cada um o seu comCollapselocal, e um terceiro local seria o começo de um padrão por acidente. - Cobranças em 375px vira
RecordCard, não tabela: seis colunas não cabem, e a de hoje não tem tratamento de viewport nenhum. Cada cartão traz competência, vencimento, valor e o selo de status; valor pago aparece só quando difere do valor; as ações da linha viram ícone de 44px no cartão. É a mesma conversão que as specs 0022 e 0023 fizeram nas outras listas. - Documentos em 375px: o
DocumentsPaneltambém não tem tratamento de viewport hoje. Cada documento vira linha de uma coluna — nome, tipo e data em cima, ações de 44px embaixo —, e o contrato assinado mantém a marcação e a posição de topo. - As ações do topo viram ícone de 44px, como manda o
PageHeader(specs 0016 e 0020): a de registrar assinatura preenchida, as outras contornadas. Três alvos e o título cabem — é a medida que a spec 0020 fez. - A faixa de status quebra em linhas, não encurta. Ela carrega as frases mais longas da tela ("Vigente de 01/08/2026 a 31/07/2027 · assinada em 09/08"), e é a primeira coisa que se lê: em 375px o selo fica na primeira linha e a frase desce para a segunda e a terceira se precisar. Encurtar o texto no estreito daria duas verdades diferentes para a mesma garantia dependendo do aparelho.
- A data da assinatura usa o seletor nativo do aparelho (
type="date"), não digitação mascarada: é uma data recente e conhecida, e o calendário do sistema resolve em dois toques sem teclado — que é o que mais atrapalha em 375px.
A lista: vira RecordCard com número, tomador, situação e valor garantido — os quatro que decidem se é esta a garantia procurada. Eles caem nos slots code, title, status e amount do componente, sem consumir as três linhas de meta, que ficam livres.
Aqui, sim, coisa some — e é decisão: imóvel, mensalidade e vigência não aparecem no cartão, só no detalhe a um toque. Saem porque nenhum dos três decide qual garantia é esta: o imóvel é longo demais para caber legível em 375px, e mensalidade e vigência são resposta, não triagem. É a única coisa da spec que o celular não mostra, e está dita aqui para ninguém tentar caber sete campos num cartão de três linhas.
A situação no cartão vem inteira — Aguardando · 12 dias, não só o selo. O celular é a tela de triagem, e a espera é o que esta spec existe para tornar visível: cortar o tempo justamente ali seria devolver a lista ao problema.
As ações da linha sobrevivem, no slot de ações do cartão: "encerrar" como ícone de 44px, pelo mesmo motivo do desktop; "ver" some, porque tocar o cartão já abre. A busca e o filtro de estado colapsam em MobileFilterBar/FilterPill, e são os dois que existem — o MobileFilterBar exige a busca, e ela é a operação de quem atende uma ligação com o nome do inquilino na mão. O ListPagination acompanha, na forma compacta da spec 0022.
Os três diálogos em 375px — os três são tela cheia, e os três têm o mesmo problema: com o teclado aberto sobram ~180px (medida da spec 0037). O ConfirmDialog compartilhado é maxWidth="sm" e não é tela cheia hoje; passa a sê-lo em 375px, como o diálogo de devolução da spec 0037 já é — é o mesmo componente servindo o mesmo viewport, e duas respostas para isso seria o começo de mais uma divergência.
- Registrar contrato assinado — dois campos, arquivo e data. O botão de confirmar fica ancorado ao rodapé, não rola com o conteúdo. O seletor de arquivo abre a câmera ou os arquivos do aparelho, que é como o PDF assinado chega a quem está fora do escritório. Os dois caminhos aparecem como dois botões de largura inteira, empilhados — "Enviar arquivo" e "Escolher um já anexado" —, e não como o par de rádio do desktop: em 375px dois alvos de 44px são mais fáceis de acertar que dois rádios de 20px. O segundo vem desabilitado com o motivo abaixo ("Nenhum documento anexado ainda") quando a garantia não tem anexo, e ao ser tocado abre a lista como tela cheia própria, com alvos de 44px — não como um
Selectespremido. - Emitir garantia — é o mais apertado, porque cresce nesta spec: quatro campos mais o resumo do que será congelado. Em 375px o resumo não fica ao lado, fica abaixo dos campos e acima do rodapé ancorado, colapsado por padrão num "Ver o que será congelado" que abre em quatro linhas. Quem emite pelo celular ainda vê os números antes de confirmar — só não os vê ao mesmo tempo que digita, porque não cabe. Exceção obrigatória: se o resumo não puder ser calculado, ele abre sozinho e não colapsa, porque é ali que está o motivo de o botão estar desabilitado — deixá-lo fechado é o "botão cinza sem motivo" que esta spec condena no desktop.
- Encerrar garantia — um campo, e o motivo agora é obrigatório. Tela cheia, rodapé ancorado, e o
Alertde erro acima do campo. É o menor dos três e mesmo assim precisa do tratamento: ummultilinecom teclado aberto num diálogo de 560px de altura empurra o confirmar para fora da tela.
O que não aparece no celular: no detalhe, nada — muda a ordem, o que vem aberto e a forma (tabela → cartão). Na lista, saem três colunas do cartão (imóvel, mensalidade, vigência), pelo motivo dito acima; elas continuam a um toque, no detalhe.
PWA: nenhuma tela nova entra no cacheado offline e nenhum asset muda. A garantia é dado que muda de estado — servir versão velha diria "aguardando" para uma garantia já vigente, que é pior que não abrir. Sem mudança no service worker.
7.3 Estados¶
| Situação | O que a tela mostra | A saída |
|---|---|---|
| Lista vazia | "Nenhuma garantia ainda. Elas nascem da análise de crédito aprovada." | link para a lista de análises |
| Filtro sem resultado | "Nenhuma garantia aguardando assinatura." — e é boa notícia, não erro | botão para limpar o filtro |
| Carregando o detalhe | Skeleton nas seções, mantendo a altura — a tela não salta quando os dados chegam | — |
| Carregando a lista | no desktop, linhas de Skeleton na tabela, como hoje; em 375px, três RecordCard de Skeleton com a mesma altura do cartão real. A barra de busca e o filtro ficam visíveis e utilizáveis durante o carregamento — travá-los faz quem já sabe o que procura esperar duas vezes | — |
| A garantia ou a lista não carrega | Alert de erro no lugar do conteúdo, com o que houve | "Tentar de novo", que refaz a busca sem recarregar a página |
| Sem permissão para emitir | o botão não aparece; nada de botão cinza que ninguém sabe por que está cinza | — |
| Sem permissão para registrar a assinatura | idem — a ação não aparece. É o caso da imobiliária, que passa a ler a garantia da própria carteira e não pratica nenhuma escrita sobre ela | — |
| Sem permissão para encerrar | idem | — |
| Sem permissão para ver a garantia | 404, nunca 403 — confirmar que o identificador existe já é informação (specs 0034/0035) | volta para a lista |
| Erro ao emitir | Alert dentro do diálogo, acima dos campos, e o diálogo não perde o que foi digitado. Não é modal de Erro: o ui-patterns.md reserva o modal para falha de operação já concluída, e aqui a pessoa ainda está no formulário e vai corrigir e reenviar | corrigir e confirmar de novo |
| Análise sem vigência definida | a análise guarda início e fim como opcionais. Sem eles, o diálogo abre com os dois campos vazios e obrigatórios, e o resumo do congelamento fica em branco até que sejam preenchidos — nunca com zero ou com uma data inventada, que é o que faria alguém congelar um valor sem perceber | preencher a vigência |
| Resumo do congelamento indisponível | se os números não puderem ser calculados ao abrir o diálogo (parâmetros da garantidora ausentes ou fora de faixa), o bloco do resumo mostra o motivo no lugar dos valores — "Não foi possível calcular com os parâmetros atuais" — e o botão de emitir fica desabilitado: congelar valor que ninguém viu é o que esta spec existe para não fazer | link para os parâmetros da garantidora |
| Data da assinatura no futuro | validação inline no campo, sem esperar o envio: "A assinatura não pode ser depois de hoje." | corrigir a data |
| Erro ao encerrar | Alert dentro do diálogo, acima do campo; o motivo digitado não se perde. Mesmo tratamento das outras duas escritas | tentar de novo |
| Encerrar sem motivo | erro no campo, com o botão ainda habilitado: "Diga por que esta garantia está sendo encerrada." O ConfirmDialog compartilhado tem essa mensagem cravada em uma frase só — "Diga o que precisa ser corrigido ou anexado.", escrita para a devolução de análise da spec 0036 —, e reusá-la aqui faria quem encerra ler uma instrução sem relação com o que fez. A mensagem passa a vir de quem chama | escrever o motivo |
| Garantia encerrada ou vencida | a seção Encerramento abre o detalhe (só na encerrada), com motivo, quem e quando. Somem as ações de estado — registrar assinatura, encerrar — e o botão de nova cobrança fica desabilitado com o motivo. As ações de cobrança por linha continuam valendo: registrar pagamento e marcar em atraso funcionam em qualquer estado, porque dívida emitida não some porque a garantia acabou. A guarda desta spec é sobre criar cobrança, nunca sobre baixá-la | — |
| Erro ao registrar assinatura | mesmo tratamento; o arquivo escolhido continua escolhido | tentar de novo |
| Cobrança recusada por estado | a tela não deixa chegar lá: o botão está desabilitado com o motivo à vista. Se a recusa vier da API assim mesmo, aparece no painel de cobranças, não numa faixa no topo da página | — |
| Garantia sem número | guarantee_number é anulável, e garantias anteriores à spec 0027 têm prefixo C. Sem número, o cabeçalho mostra Garantia sem número e a coluna da lista fica vazia — nunca um id truncado disfarçado de número, que é o defeito que esta spec veio tirar. A busca por número simplesmente não as encontra | buscar pelo tomador |
| Upload recusado no diálogo de assinatura | o seletor do diálogo herda os limites do painel de documentos — 10 MB, formatos da lista, e o teto de 20 documentos por registro —, com a mesma mensagem de cada caso. O diálogo não fecha e o resto do que foi preenchido fica | escolher outro arquivo |
| Garantia sem imobiliária (carteira própria) | a seção Partes mostra tomador e imóvel, e diz "Sem imobiliária — carteira própria" em vez de campo vazio | — |
| Aguardando com vigência proposta vencida | a faixa mantém Aguardando assinatura e acrescenta: "A vigência proposta terminou em 14/08/2026. Encerre esta garantia." A ação de registrar assinatura fica desabilitada com esse motivo | encerrar — não há "emitir outra": a análise já tem garantia |
Diferença entre viewports: uma só, e é de posição, não de componente. O Alert de erro dos diálogos aparece acima do primeiro campo nos dois; em 375px, onde o diálogo é tela cheia, isso significa colado ao topo da tela — senão fica atrás do teclado.
8. Revisão da experiência¶
Rubrica .claude/rubrics/experiencia.md, revisor de contexto limpo e novo a cada rodada.
| Rodada | Critical | Important | Minor | Veredicto |
|---|---|---|---|---|
| 1 | 0 | 6 | 6 | reprovada |
| 2 | 0 | 5 | 4 | reprovada |
| 3 | 0 | 5 | 5 | reprovada |
| 4 | 0 | 3 | 7 | reprovada |
| 5 | 0 | 3 | 5 | reprovada — achados corrigidos sem 6ª rodada |
Resultado: aprovada com decisão registrada do usuário, após a rodada 5.
Por que parou na 5ª. O limite da régua é 3, estourado com autorização do usuário. A contagem de Important caiu de 6 para 3 e estacionou — a régua manda parar quando a nota não sobe, e foi o que se fez. Os três Important da última rodada foram corrigidos sem nova revisão: dois eram consequência direta de decisões que o usuário tomou durante o próprio laço (a imobiliária passar a ler, e a correção do registro de assinatura), e o terceiro era um erro factual meu que o código resolve sozinho. Essas correções não foram revisadas.
O que este portão achou que a Parte I não tinha como achar. Descrever a tela é o que revela o que a regra esconde:
- A imobiliária não tem
guarantees:read. Eu havia escrito que ela "vê a garantia e não a ativa" — ela receberia 404 e nunca chegaria à tela. Virou decisão de acesso: ela passa a ler a própria carteira, com uma tabela de o que enxerga de cada seção. - A tela mostra dois identificadores fabricados (
#260815.1432docreated_ate o id truncado) enquanto a API já temguarantee_number. A jornada exige o número real — o termo enviado ao inquilino o carrega —, e os dois códigos saem. - "Emitir outra" era impossível. Ofereci essa saída duas vezes; há uma garantia por análise, com unicidade no banco. As saídas reais são registrar assim mesmo ou encerrar.
- Registrar assinatura era irreversível num diálogo que aceita escolher entre 20 anexos sem filtro. Virou "Corrigir contrato assinado", sem mexer no estado.
- Três diálogos, não dois — encerrar ganhou campo obrigatório e não estava descrito em 375px; e o
ConfirmDialogtem a mensagem de motivo obrigatório cravada num texto escrito para a devolução de análise da spec 0036.
Decisões do usuário registradas neste portão:
| Decisão | Escolha |
|---|---|
| Garantia não assinada cuja vigência acabou | Continua Aguardando, com aviso — não vira Vencida |
| Baixar cobrança em garantia vencida/encerrada | Continua valendo — a guarda é só sobre criar |
| Identidade da garantia | O guarantee_number da API; os dois códigos fabricados saem |
| A imobiliária lê a garantia? | Sim, escopada à própria carteira, sem nenhuma escrita |
| O que ela vê dos números | Valor garantido, LMI, mensalidade, vigência e cobranças. Não vê taxa aplicada, composição da entrada, notas nem motivo de encerramento |
| Registro de assinatura errado | Corrigível enquanto Vigente, sem mexer no estado |
Pendências aceitas: nenhuma. Todos os achados das cinco rodadas foram corrigidos.
Decisão revertida depois, no portão do detalhamento. "O que ela vê dos números" foi revisto na seção 16: a imobiliária já lê taxa aplicada, mensalidade e total de entrada pelo resumo da garantia embutido em GET /credit-analyses/{id}. A restrição decidida aqui era inexequível sem mexer numa jornada que esta spec não toca, e o usuário optou por aceitar. Ficam fora só notes e termination_reason, que não vazam por outra rota. A tabela de 7.1 foi corrigida; a Parte II não foi rerrevisada depois dessa correção.
O que a Parte II deixa anotado para a Parte III:
- A tabela de o que a imobiliária vê é decisão de tela; aplicá-la no contrato da API é da seção 10. Esconder na tela não protege a rota (specs 0034 e 0035).
SUSPENDEDsai do filtro de estado por ser estado morto. Se a Parte III o ressuscitar, o filtro é o primeiro lugar que muda.- O colapso do
MobileDetailSectioné comportamento novo do componente compartilhado, não uso do que existe — decidido assim para não criar um terceiroCollapselocal. - O
ConfirmDialogprecisa aceitar a mensagem de motivo obrigatório de quem chama. - A lista de garantias sai do
limit: 200com filtro em memória para paginação e ordenação no servidor.
Parte III — Detalhamento¶
Não preencher antes das revisões das seções 6 e 8 estarem aprovadas.
9. Regras de negócio¶
O ciclo de vida¶
- RN1.
GuaranteeStatuspassa a ter três valores gravados:PENDING_SIGNATURE(Aguardando assinatura),ACTIVE(Vigente) eTERMINATED(Encerrada).EXPIREDdeixa de ser valor gravável e vira estado derivado (RN4).SUSPENDEDsai do enum — nenhum caminho do sistema chega nele desde que existe, e estado morto foi o que criou a confusão que esta spec conserta. - RN2. Toda garantia nasce em
PENDING_SIGNATURE. A emissão não aceitastatusno corpo — quem emite não escolhe o estado. - RN3. Transições, e são as únicas:
PENDING_SIGNATURE → ACTIVE(registrar contrato assinado),PENDING_SIGNATURE → TERMINATED(encerrar antes de assinar),ACTIVE → TERMINATED(encerrar). Não há volta deTERMINATED, e não há transição paraEXPIREDporque ele não é gravado. Transição inválida → 422INVALID_TRANSITION. - RN4. Vencida é derivada, não gravada. O estado efetivo de uma garantia é
EXPIREDquandostatus == ACTIVEeend_date < hoje; nos demais casos é o própriostatus. Uma função única —estado_efetivo(guarantee)— é a única fonte desse cálculo, e é ela que a resposta da API, o filtro da lista, a ordenação e a guarda de cobrança consultam.
Por que derivada e não gravada: não existe agendador neste sistema — nenhum job, nenhum cron, nada que rode sozinho. Um EXPIRED gravado precisaria de alguém para movê-lo, e é exatamente por não ter esse alguém que o EXPIRED de hoje nunca foi usado. Gravar um estado que nada move é reconstruir o defeito com nome novo.
Consequência que não pode ser esquecida: o alerta GUARANTEE_EXPIRED_ACTIVE do painel de operações consulta end_date < hoje AND status == ACTIVE — que com esta regra passa a ser a definição de "vencida", não de "erro". O alerta é removido (RN20).
-
RN4a. Na resposta da API,
statusé o estado efetivo — é ele que vale para quem consome, e é o que o filtro?status=aceita. O valor gravado não é exposto: dois campos de status na mesma resposta seriam duas verdades, e quem consome escolheria a errada. Ao lado dele vaiproposed_period_expired: bool(RN5), que só étrueemPENDING_SIGNATUREcomend_datepassada. -
RN4b. O cálculo existe em duas formas, e é um teste que as mantém iguais. RN4 pede Python (para serializar) e RN18/RN19 pedem SQL (para filtrar, ordenar e paginar no banco) — uma função Python não entra em
WHERE. As duas moram no mesmo módulo, lado a lado:estado_efetivo(guarantee)eestado_efetivo_sql(), esta devolvendo a expressãoCASEcorrespondente. Um teste percorre todas as combinações destatus×end_datee exige que as duas concordem — sem ele, "fonte única" seria só uma frase, e a regra divergiria no primeiro ajuste. -
RN4d. "Hoje" é a data em
America/Sao_Paulo, fixo — não UTC, e não parâmetro do tenant: não há campo de fuso emTenantnem emTenantSettings, e criar um para esta spec seria inventar configuração que ninguém pediu. Num container UTC, comparar sem fuso faria uma garantia comend_datede hoje virarEXPIREDàs 21h de Brasília — e a RN12 recusaria a cobrança legítima do último dia de vigência.
Um helper único (hoje_local()) serve as duas formas do cálculo, e o estado_efetivo_sql() compara contra a mesma data, passada como parâmetro — não contra CURRENT_DATE do banco, que é UTC. A CA17f prova a concordância em status × end_date; falta-lhe a hora de virada, e é por isso que ela ganha um caso às 23h UTC de um dia em que em São Paulo ainda é o dia anterior. - RN4c. A ordem de classificação de estado, quando alguém ordena pela coluna Situação: PENDING_SIGNATURE < ACTIVE < EXPIRED < TERMINATED — do que espera ação ao que está encerrado. - RN5. Passada a end_date de uma garantia em PENDING_SIGNATURE, ela continua em PENDING_SIGNATURE — vencer rebaixa só quem estava vigente. A resposta da API traz proposed_period_expired: true para a tela mostrar o aviso. Não há transição automática, e não há prazo que a encerre.
O registro da assinatura¶
- RN6. Registrar o contrato assinado exige um documento e uma data, e é o único ato que leva a garantia a
ACTIVE. Anexar documento pelo painel — por qualquer papel que o alcance — não muda estado nenhum. - RN7. O documento informado precisa ser um
Documentdesta garantia (guarantee_id == :id), resolvido no escopo do solicitante (seção 10.3, que aplica a RN5 da spec 0034). Documento de outra garantia, de outra entidade ou de outro tenant → 404, nunca 403. - RN8.
signed_atnão pode ser futura → 422SIGNED_AT_IN_FUTURE. Pode ser anterior à emissão: o inquilino pode ter assinado antes de o PDF chegar, e nada mais muda com isso. - RN8a. Não se ativa fora do período. Registrar a assinatura é recusado quando a
end_datejá passou → 422PROPOSED_PERIOD_EXPIRED. Sem isso a garantia nasceriaACTIVEe a RN4 a devolveriaEXPIREDno mesmo instante — e a RN12 proibiria criar qualquer cobrança, deixando setup e primeira parcela impagáveis pelo sistema, contra a promessa da Parte I de que "a guarda atrasa a cobrança, não a elimina". Uma garantia cuja vigência acabou antes de começar não vira vigente: a saída é encerrar.
Consequência na Parte II: o aviso da faixa de status passa a oferecer uma saída, não duas — "A vigência proposta terminou em 14/08/2026. Encerre esta garantia." — e a ação de registrar assinatura fica desabilitada com esse motivo. - RN9. Registrar a assinatura não altera a vigência. start_date e end_date foram congelados na emissão e permanecem. Se signed_at cai fora do intervalo, a garantia fica ACTIVE do mesmo jeito — a tela explica (Parte II), a regra não muda. - RN10. Corrigir o registro troca o documento e/ou a data e não mexe no estado. Só é permitido com o estado efetivo ACTIVE — o que exclui, pela RN4, tanto a encerrada quanto a que já passou da end_date. Fora disso, 422 SIGNATURE_LOCKED. Cada correção é auditada com autor e horário. - RN11. Os campos do registro são novos na Guarantee: signed_document_id (FK → documents, nulo), signed_at (date, nulo) e signed_by (FK → users, nulo). Em PENDING_SIGNATURE os três são nulos; e o caminho novo só chega a ACTIVE preenchendo os três. Não é invariante de banco — sem CHECK e sem NOT NULL condicional: as garantias que já estão ACTIVE quando a migration roda ficam com os três nulos, e inventar um backfill seria gravar uma data de assinatura que ninguém tem. ACTIVE com assinatura nula significa "vigente de antes desta spec": a tela omite o "· assinada em" (Parte II) e a correção do registro (RN10) é o caminho de conserto — ela aceita preencher pela primeira vez. O mesmo vale para terminated_at: as garantias já encerradas quando a migration roda ficam sem data, e a faixa de status omite o "em 14/08/2026" em vez de inventá-la — a updated_at não serve, porque mente na primeira edição posterior. A FK não é a proteção do arquivo — quem protege é a RN13c, porque a exclusão de documento neste sistema é lógica e nunca chega ao banco.
A guarda de cobrança¶
- RN12. Criar cobrança só é aceito com o estado efetivo
ACTIVE. Nos demais —PENDING_SIGNATURE,EXPIRED(derivado) eTERMINATED— a API responde 422GUARANTEE_NOT_ACTIVE, com o estado atual na mensagem. - RN13. Baixar cobrança já emitida — registrar pagamento, marcar em atraso — continua valendo em qualquer estado. Dívida emitida não some porque a garantia acabou, e a Parte I pôs a régua de pagamento fora de escopo. A guarda da RN12 é sobre criar, e só.
- RN13a. Editar cobrança já emitida (
PATCH .../billings/{id}— vencimento, valor) segue a RN13, não a RN12: continua valendo em qualquer estado. Corrigir o vencimento de uma parcela de garantia encerrada é conserto de dado, não cobrança nova. Os três verbos (PATCH, pagar, marcar em atraso) compartilhambillings:updatecom oPOSTque a RN12 trava — a guarda é por operação, não por permissão, e é por isso que ela mora no serviço e não no porteiro.
Escopo de cobrança e de documento¶
- RN13b.
GET /guarantees/{gid}/billings/{bid}passa a verificar o escopo de imobiliária. Hoje ele resolve a cobrança porget_by_guarantee_and_id, que filtra só portenant_id— semwithin_agency, ao contrário dolist. Concederbillings:readaoAGENCY_USERsem essa correção deixaria a Imobiliária B ler a cobrança da A com os dois UUIDs em mãos. Este vazamento seria criado por esta spec, e é ela que o fecha: fora do escopo → 404. - RN13c. O documento marcado como contrato assinado não pode ser excluído enquanto o estado efetivo for
ACTIVEouEXPIRED→ 422SIGNED_DOCUMENT_IN_USE. A vencida entra de propósito: o prazo de acionamento de sinistro corre depois do fim da vigência, e o contrato assinado é o primeiro documento que alguém procura num sinistro. Só emTERMINATEDa exclusão é aceita. A exclusão é irreversível —DocumentService.deletezera ocontentalém de marcardeleted_at. - RN13c-bis. A mesma guarda vale para substituir o conteúdo (
PUT /documents/{id}/content, da spec 0037) do documento marcado como contrato assinado → 422SIGNED_DOCUMENT_IN_USE. OPATCHde metadados continua permitido — classificar o tipo, corrigir o nome e anotar não mexem na prova, e travá-los deixaria sem tipo, por toda a vigência, justamente o documento que a Parte II decidiu não obrigar a classificar antes.
Sem isto, proteger só a exclusão não protege nada. replace_content troca o arquivo preservando o registro — mesmo id, mesmo vínculo, mesmo signed_document_id —, e o AGENCY_USER alcança essa rota na garantia da própria carteira: tem documents:update, e _owned_by_agency inclui Document.guarantee_id, então o documento está no escopo dele. A garantia continuaria ACTIVE e faturável com outra prova por baixo.
Isto não é a porta de upload que a seção 5 aceita deixar aberta: aquela se apoia em "anexar não move estado", verdadeiro para POST /documents. Aqui o estado já se moveu, e o que muda é a prova que o sustenta. O ondelete="RESTRICT" da FK não protege: DocumentService.delete é soft delete e ainda zera o content, então o banco nunca é acionado e a garantia ficaria vigente apontando para um arquivo vazio. A guarda é no serviço de documentos, não na FK.
Notas¶
- RN13d. Editar notas passa a exigir a permissão nova
guarantees:annotate, em vez deguarantees:update. Hojeupdatecarrega notas e encerrar; separar é o que permite oOPERATORanotar "termo enviado em 12/08" sem ganhar o poder de encerrar. - RN13e. Notas são editáveis com o estado efetivo
PENDING_SIGNATUREouACTIVE. EmEXPIREDeTERMINATED→ 422GUARANTEE_CLOSED_FOR_NOTES. A regra é do servidor, não da tela — a Parte II a descreveu como comportamento de interface, e guarda só de interface é o que esta spec condena.
Encerramento¶
- RN14. Encerrar exige motivo não vazio → 422
TERMINATION_REASON_REQUIRED. Hoje o campo é opcional; passa a ser obrigatório, porque é o que a auditoria procura. - RN15. Encerrar é permitido em qualquer estado gravado que não seja
TERMINATED— o que incluiPENDING_SIGNATURE,ACTIVEe a vencida (cujo gravado ainda éACTIVE). EmTERMINATED→ 422INVALID_TRANSITION.
A API permite mais do que a tela oferece, e é de propósito. A tabela de ações da Parte II não mostra "Encerrar" numa garantia Vencida, porque ali não há o que interromper. A API aceita porque encerrar é registro de decisão, não interrupção — e recusar obrigaria a garantidora a conviver com uma vencida que ela quis encerrar formalmente.
O resumo do que será congelado¶
- RN15a. O resumo exibido no diálogo de emissão usa exatamente os valores que a emissão vai congelar: a taxa e a taxa de setup aprovadas na decisão da análise (
approved_guarantee_rate_percent,approved_initial_fee_value), caindo nos padrões da garantidora só quando a decisão não os traz, e o pacote locatício da análise (financial_rental_package). - RN15b.
POST /guarantees/simulatenão serve para isso, e usá-lo seria o defeito. Ele calcula comsettings.guarantee_rate_percente com oinitial_fee_*do tenant — os padrões —, enquantocreatecongela o que foi aprovado. Numa análise aprovada com taxa negociada de 8%, o diálogo mostraria 10% e o sistema gravaria 8%: a tela cumpriria a letra da Parte II e falharia justamente no que ela existe para impedir. - RN15c. O resumo vem de
POST /guarantees/simulateestendido para aceitarcredit_analysis_id: com ele, a simulação resolve a análise no escopo do solicitante e usa a decisão aprovada; sem ele, segue com os padrões do tenant, como hoje. É um campo opcional, não um endpoint novo — e um teste exige que a simulação comcredit_analysis_iddevolva os mesmos números que a emissão daquela análise congela.
Identidade¶
- RN16. O
guarantee_numberjá é exposto pela API (GuaranteeResponse); o que muda é o cliente — o tipoGuaranteedoapps/webpassa a declará-lo, e ele vira o identificador que a interface mostra. Os dois códigos fabricados peloapps/web(guaranteeCodedocreated_ateid.slice(-8)) são removidos. - RN17.
guarantee_numbercontinua anulável (garantias anteriores à spec 0027 têm prefixoC, e o campo é nulo em registros antigos). Nulo → a interface mostra "Garantia sem número" e a busca por número não as alcança. Não se gera número retroativo.
Busca, ordenação e listagem¶
- RN18. A lista aceita busca por
guarantee_numbere por nome do tomador, e filtro por estado efetivo. Busca e filtro são resolvidos no banco, não em memória. - RN18a.
days_waitingconta decreated_at— a data de emissão — até hoje, em dias inteiros. É nulo fora dePENDING_SIGNATURE: emACTIVE,EXPIREDeTERMINATEDnão há espera a medir, e devolver zero seria confundir "não espera" com "espera há pouco". - RN19. Paginação e ordenação são do servidor. A ordenação padrão é:
PENDING_SIGNATUREprimeiro, entre si porcreated_atcrescente (a que espera há mais tempo no topo); depois os demais porcreated_atdecrescente. Ordenáveis: estado, valor garantido, mensalidade e vigência.
Limpeza que esta spec faz¶
- RN19a. "Cobranças" e "Sinistros" não aparecem no menu do
AGENCY_USER, apesar de as permissões novas os condicionarem. É regra de exposição, não de conveniência: são módulos que a garantidora vai operar, e anunciá-los ao canal é a classe que a spec 0035 pagou. A condição é nonav-groups.tsx, além da permissão, e vale também na barra inferior do mobile. - RN20.
GUARANTEE_EXPIRED_ACTIVEé removido dos dois lugares em que existe: o alerta e o item da fila de trabalho (priority=2, "requer atualização de status"), ambos alimentados pela mesma query. Removê-lo só do alerta deixaria a fila mandando a pessoa atualizar um status que passou a se atualizar sozinho. A condição que ele detecta passa a ser a definição de "vencida" (RN4), e um alerta crítico para um estado normal é ruído que treina a pessoa a ignorar o painel. - RN21. Os contadores do painel de operações passam a usar o estado efetivo, e sem isso eles mentem: hoje
total_activeeexpiring_sooncontamstatus == ACTIVEetotal_terminatedconta[TERMINATED, EXPIRED]. ComEXPIREDdeixando de ser gravado, as vencidas seriam contadas como ativas e sumiriam das encerradas. Passa a valer:total_active= efetivoACTIVE;total_terminated= efetivoTERMINATEDouEXPIRED;expiring_soon= efetivoACTIVEcom fim próximo. E há um quarto, fácil de esquecer porque não está no mesmo bloco:in_guarantee, dos contadores de análise, também filtrastatus == ACTIVE— com a garantia nascendoPENDING_SIGNATURE, "análises em garantia" deixaria de contar as recém-emitidas, e passa a contarACTIVEouPENDING_SIGNATURE. Entra ainda um contador novo,total_pending_signature— é o número que a Parte I diz que ninguém consegue perguntar hoje.
10. Autorização e isolamento¶
10.1 Matriz de papéis¶
| Papel | Pode | Não pode |
|---|---|---|
ADMIN_GLOBAL | nada de novo. Continua alcançando as rotas de garantia por receber todas as permissões — é dívida conhecida, tratada em 10.5 | — |
ADMIN_TENANT | emitir, registrar assinatura, corrigir registro, encerrar, ler tudo, criar/baixar cobrança | — |
ANALYST | ler garantia e cobranças da própria garantidora | emitir · registrar assinatura · corrigir · encerrar · criar cobrança |
OPERATOR | emitir (novo), registrar assinatura (novo), corrigir registro (novo), editar notas (novo), ler tudo | encerrar — permanece só do gestor: é irreversível · criar cobrança (não muda) |
VIEWER | ler garantia e cobranças da própria garantidora | toda escrita, inclusive notas |
AGENCY_USER | ler (novo) garantia e cobranças da própria imobiliária, com os campos financeiros que já lhe são visíveis pela análise (10.6) | emitir · registrar assinatura · corrigir · encerrar · criar cobrança · baixar cobrança · editar notas · ver notes, termination_reason e terminated_by |
O negativo que vira teste: o ANALYST recebe 403 ao tentar emitir no próprio tenant; o OPERATOR recebe 403 ao tentar encerrar; o AGENCY_USER da Imobiliária B recebe 404 ao abrir a garantia da Imobiliária A e 404 ao abrir a cobrança dela pelo id; e o AGENCY_USER que abre a própria garantia recebe 200 sem notes e sem termination_reason.
10.2 Permissões¶
| Endpoint | Permissão | Observação |
|---|---|---|
GET /guarantees | guarantees:list | + AGENCY_USER |
GET /guarantees/{id} | guarantees:read | + AGENCY_USER |
POST /guarantees | guarantees:create | + OPERATOR |
POST /guarantees/{id}/signature | guarantees:sign (nova) | ADMIN_TENANT, OPERATOR |
PUT /guarantees/{id}/signature | guarantees:sign (nova) | idem |
POST /guarantees/{id}/terminate | guarantees:update | inalterada — não vai para o OPERATOR |
PATCH /guarantees/{id} (notas) | guarantees:annotate (nova) | ADMIN_TENANT, OPERATOR — sai do update (RN13d) |
POST /guarantees/simulate sem credit_analysis_id | guarantees:read | + AGENCY_USER por consequência. Calcula com os padrões da garantidora, como hoje — nada que ela já não alcance |
POST /guarantees/simulate com credit_analysis_id | guarantees:create | o parâmetro novo exige a permissão de emitir, não a de ler (ver abaixo) |
POST /guarantees/{id}/billings | billings:update | inalterada; ganha a guarda da RN12 |
GET /guarantees/{id}/billings | billings:list | + AGENCY_USER |
GET /guarantees/{id}/billings/{bid} | billings:read | + AGENCY_USER, e ganha escopo de imobiliária (RN13b) |
DELETE /documents/{id} | documents:delete | inalterada; ganha a guarda da RN13c |
GET /operations-center/* | operations_center:read | inalterada; a resposta muda (RN20/RN21) e ganha o campo total_pending_signature. O AGENCY_USER não tem essa permissão e nada disso a alcança |
GET /credit-analyses/{id} | credit_analyses:read | inalterada; o status do resumo da garantia passa a poder ser PENDING_SIGNATURE (11) |
POST /documents | documents:create | inalterada — usada pelo caminho "Enviar arquivo" do diálogo |
GET /documents?entity_type=guarantee | documents:list | inalterada — usada pelo caminho "Escolher um já anexado" |
GET /documents/{id}/content | documents:read | inalterada — download pelo painel que a tela nova reordena |
PUT /documents/{id}/content | documents:update | inalterada; ganha a guarda da RN13c-bis |
PATCH /documents/{id} | documents:update | inalterada e sem guarda nova — metadados não são a prova (RN13c-bis) |
PATCH /guarantees/{id}/billings/{bid} · POST .../pay · POST .../mark-overdue | billings:update | inalteradas quanto ao porteiro; seguem a RN13a (a guarda de estado não se aplica) e ganham o escopo de imobiliária pela correção do _get_billing (RN13b) |
Os dois de documento não abrem nada: ADMIN_TENANT e OPERATOR, os únicos com guarantees:sign, já têm as duas permissões. Estão aqui porque esta tabela é o inventário do que a feature toca, e um endpoint usado e não listado é o que faz a próxima revisão refazer o levantamento.
O credit_analysis_id do simulate exige guarantees:create, e o motivo é uma janela de tempo que quase passou batido. A justificativa fácil seria "ela já vê a precificação pelo resumo da garantia na análise" — e ela é falsa antes da emissão: aquele resumo é null enquanto não há garantia, e o que a análise mostra até lá é o snapshot dos padrões do tenant, não a taxa negociada. A taxa negociada vive na Decision, atrás de decisions:read, que a imobiliária não tem. Sem esta regra, o parâmetro novo entregaria a ela a taxa e o setup aprovados assim que a análise fosse aprovada — e esta spec ainda alarga essa janela, porque a garantia agora nasce aguardando assinatura e pode esperar semanas. Exigir guarantees:create fecha isso sem tocar em mais nada: quem simula com a análise é quem emite.
Permissões novas — duas, e cada uma é exigida por endpoint real (permissão declarada e não usada é pior que ausente, spec 0035):
("guarantees", "sign", "Registrar contrato assinado")— exigida porPOSTePUT /guarantees/{id}/signature. Vai paraADMIN_TENANTeOPERATOR.("guarantees", "annotate", "Editar notas da garantia")— exigida porPATCH /guarantees/{id}. Vai paraADMIN_TENANTeOPERATOR. Sai doupdate, que passa a servir só ao encerrar.
Por que sign e não update: guarantees:update hoje serve para editar notas e para encerrar. Pendurar a ativação nela daria ao OPERATOR, junto, o poder de encerrar — que a matriz nega de propósito.
Mudanças no rbac_seed.py:
| Papel | Ganha |
|---|---|
OPERATOR | guarantees:create, guarantees:sign, guarantees:annotate |
ADMIN_TENANT | guarantees:sign, guarantees:annotate |
AGENCY_USER | guarantees:list, guarantees:read, billings:list, billings:read |
VIEWER e ANALYST não ganham guarantees:annotate — hoje eles também não têm guarantees:update, então a separação não muda nada para eles. É registro para o portão não concluir que a permissão nova alargou alguém por omissão.
10.3 Isolamento por garantidora (tenant)¶
credit_analysis_id(corpo dePOST /guarantees) — resolvido só por tenant hoje (get_by_id(..., tenant_id=...), semwithin_agency). Não é furo hoje — nenhumAGENCY_USERtemguarantees:create, e oOPERATORque esta spec traz não tem imobiliária —, mas também não é ponto fechado, e quem implementar não deve copiá-lo como modelo: a FK nova dosimulate, logo abaixo, é o caso em que o mesmo padrão vazaria.document_id(corpo dePOST/PUT /signature) — FK nova vinda do corpo, e é o risco novo desta spec. Resolvida porresolve_in_scopee verificada contraguarantee_id == :idantes de gravar. Documento de outro tenant, de outra garantia ou de outra entidade → 404. Sem essa dupla verificação, informar o id de um documento alheio responderia "existe" ou "não existe" — leitura por escrita, que é a RN5 da spec 0034.credit_analysis_id(corpo dePOST /guarantees/simulate, novo pela RN15c) — é a segunda FK nova desta spec, e a mais perigosa, porque chega junto com um chamador novo: oAGENCY_USERpassa a alcançar osimulatepor consequência doguarantees:read. Resolvida porresolve_in_scope— tenant e imobiliária, não só tenant —, respondendo 404 fora do escopo.
O precedente citado acima não serve aqui, e é o ponto: o credit_analysis_id do POST /guarantees é resolvido só por tenant hoje (get_by_id(..., tenant_id=...), sem within_agency), o que é inofensivo porque nenhum AGENCY_USER tem guarantees:create. Copiar esse padrão para o simulate deixaria a Imobiliária B informar o id de uma análise da A e receber a taxa e o setup negociados com o cliente da concorrente — dado que hoje ela não alcança, porque CreditAnalysisService._get_authorized aplica within_agency e responde 404. É a mesma classe da RN13b: conceder permissão sem olhar o que ela abre. - Garantia de outro tenant → 404, nunca 403, em todos os endpoints. 403 fica para o que o papel não pode fazer no próprio tenant (o ANALYST emitindo, o OPERATOR encerrando).
10.4 Isolamento por imobiliária¶
- O
AGENCY_USERalcança apenas garantias cujopartner_agency_idé o dele. Qualquer outra → 404. O vínculo vem do usuário, nunca do corpo nem da query. GET /guaranteespara ele é filtrado no banco pelopartner_agency_id, não filtrado depois — contagem e paginação também mentem quando o filtro é de aplicação.- Cobranças herdam o escopo da garantia — nas duas rotas.
GET /guarantees/{id}/billingsjá responde 404 para garantia fora da imobiliária.GET .../billings/{bid}não: ele resolve porget_by_guarantee_and_id, que filtra só portenant_id. Como esta spec dábillings:readaoAGENCY_USER, ela criaria o vazamento se não o fechasse — e fecha (RN13b). É o achado mais importante deste portão, e nasceu de conceder uma permissão sem olhar o que ela abre. - Documentos herdam o escopo do dono (RN13 da 0034): nem metadado, nem download, nem contagem para garantia alheia. O
signed_document_idsó é resolvível dentro do escopo.
A porta que fica aberta, e está declarada: o
AGENCY_USERjá consegue anexar documento em garantia da própria carteira (documents:create+guaranteecomoentity_typeválido + a janela da imobiliária que cai numelse: return). Esta spec não fecha essa porta — decisão registrada na seção 5 — e ela deixa de importar para o estado porque anexar não ativa (RN6). O que precisa ser garantido é que o registro da assinatura permaneça inalcançável para ela:guarantees:signnão entra noAGENCY_USER, e há CA negativo para isso.
10.5 Plataforma × cliente (SaaS admin)¶
- Quando quem chama é
ADMIN_GLOBAL, o que estes endpoints devolvem? Garantia é dado de negócio do cliente, não de plataforma — o SaaS admin não deveria alcançá-la. Hoje ele alcança, porque recebe todas as permissões ({f"{r}:{a}" for r, a, _ in _PERMISSIONS}) e não tem filtro de tenant. Esta spec não conserta isso, e declara: a dívida é anterior, vale para todos os endpoints de negócio (inquilino, imóvel, análise, cobrança), e resolvê-la só para garantia daria a falsa impressão de que a fronteira existe. O que esta spec garante é não piorar: a permissão novaguarantees:signentra noADMIN_GLOBALpela mesma regra automática de hoje, e nenhum endpoint novo aceitatenant_idde fora (RN7 da 0034). - A tela do SaaS admin (
/app/admin/*) não muda. Nenhuma contagem, coluna ou item novo.
10.6 O que a imobiliária recebe da resposta¶
A decisão da seção 8 foi revertida aqui, e o motivo é do código. Lá se decidiu que o AGENCY_USER não veria taxa aplicada nem a composição da entrada. Este portão mostrou que ela já lê esses números hoje, por outra rota: CreditAnalysisResponse embute CreditAnalysisGuaranteeSummary, que traz guarantee_rate_percent, monthly_value, total_upfront, lmi_value e guaranteed_value — e ela tem credit_analyses:read. Omitir na tela da garantia enquanto o mesmo dado sai pela análise entregaria uma fronteira que não existe, e faria o CA passar verde com o dado vazando.
O que vale: o AGENCY_USER recebe a garantia da própria carteira com os mesmos campos financeiros que qualquer papel da garantidora — não há schema reduzido de precificação, e POST /guarantees/simulate (que exige guarantees:read) deixa de ser um problema pela mesma razão.
Dois campos continuam fora, e não é precificação:
| Campo | Por quê |
|---|---|
notes | texto interno da garantidora, escrito para si — não vaza por nenhuma rota hoje, e abri-lo seria exposição nova, não reconhecimento de uma existente |
termination_reason · terminated_by | o motivo pelo qual a garantidora encerrou é registro interno e de auditoria; a imobiliária vê que foi encerrada e quando |
Billing.notes | o mesmo argumento, no objeto vizinho. A cobrança tem campo de notas escrito só por quem tem billings:update — ou seja, só a garantidora —, e ele guarda o que ela anota sobre a operação, às vezes sobre a própria imobiliária ("está segurando o repasse"). Conceder billings:read sem tirar esse campo entregaria isso ao canal |
Esses dois são omitidos na serialização, não escondidos na tela — a lição das specs 0034 e 0035: campo que só some no apps/web continua no JSON, e o JSON é o que vaza.
E omitir é a chave ausente, não o valor nulo. O endpoint escolhe o response_model pelo papel — um schema próprio para o AGENCY_USER —, em vez de anular os campos num schema único: com "notes": null no JSON, o CA15 ("a resposta não contém") passaria verde com o campo presente, e a próxima pessoa a mexer no serializador não teria como saber que o null era proposital.
A dívida que fica declarada: o vazamento da precificação pela análise não é criado por esta spec e não é consertado por ela. Fechá-lo exigiria mexer no
CreditAnalysisGuaranteeSummary, que a tela da análise da imobiliária já consome — é mudança de contrato numa jornada que esta spec não toca. Registrado em Riscos aceitos (seção 15) com a decisão do usuário.
11. Impacto por app¶
apps/api¶
Modelos e migration
Guarantee:signed_document_id(FK →documents,ondelete="RESTRICT", nulo),signed_at(Date, nulo),signed_by(FK →users,ondelete="RESTRICT", nulo) eterminated_at(DateTime(timezone=True), nulo).
terminated_at é coluna nova, e é fácil não perceber: o model tem terminated_by e termination_reason e não tem a data. A Parte II promete "Encerrada em 14/08/2026" na faixa e o "quando" na seção Encerramento, e a 10.6 promete à imobiliária que ela vê que foi encerrada e quando. Reusar updated_at mentiria na primeira edição posterior, e ler o AuditLog para pintar uma faixa de status é caro e frágil. São quatro colunas novas, não três. - GuaranteeStatus (o enum Python): remove SUSPENDED, acrescenta PENDING_SIGNATURE, e mantém EXPIRED — ele deixa de ser gravado mas continua sendo um valor válido do tipo, porque é o que a API devolve como estado efetivo (RN4a). - No PostgreSQL, o tipo guarantee_status fica com PENDING_SIGNATURE, ACTIVE, EXPIRED e TERMINATED: SUSPENDED é removido do tipo, EXPIRED permanece ainda que nenhuma linha o use — tirá-lo obrigaria a recriar o tipo de novo no downgrade, e o ganho de remover um rótulo não gravado é zero. - Migration xxxx_guarantee_signature_and_states: - upgrade: cria as quatro colunas; converte o enum (ALTER TYPE/recriação, conforme o dialeto); migra os dados — SUSPENDED → TERMINATED (não deve haver linhas; se houver, é o destino honesto) e EXPIRED → ACTIVE (passa a ser derivado, e a end_date o reconstrói). - downgrade: real, e exercitado — devolve o enum ao conjunto anterior mapeando PENDING_SIGNATURE → ACTIVE (perde a distinção, que é o preço de voltar) e derruba as quatro colunas. Desde a spec 0028 este é o caminho de volta de um deploy ruim.
Endpoints — ver a seção 12 para request/response.
| Método | Path | Permissão |
|---|---|---|
POST | /guarantees/{id}/signature | guarantees:sign |
PUT | /guarantees/{id}/signature | guarantees:sign |
Os demais são alterações nos existentes: POST /guarantees deixa de aceitar estado e nasce em PENDING_SIGNATURE; GET passam a devolver estado efetivo, guarantee_number e os campos de assinatura; POST /guarantees/{id}/billings ganha a guarda da RN12; POST /terminate passa a exigir motivo.
Regras no service
estado_efetivo(guarantee)emguarantee_service.py— fonte única da RN4, consumida pelo próprio serviço, pelobilling_servicee pela query de listagem.GuaranteeService.register_signature/update_signature— resolvemdocument_idno escopo e contra a garantia (10.3), validamsigned_at(RN8), gravam os três campos e auditam.BillingService.create— passa a consultar o estado efetivo antes de instanciar (RN12). É a única mudança nobilling_service:payemark_overduenão são tocados (RN13).operations_center_service— remove o alertaGUARANTEE_EXPIRED_ACTIVEe a query dele (RN20), e corrige os contadores para o estado efetivo (RN21):total_active,total_terminated,expiring_soone o novototal_pending_signature. Sem isso as vencidas passariam a ser contadas como ativas.document_service.delete— guarda da RN13c: recusa excluir o documento que é o contrato assinado de uma garantiaACTIVE.billing_service._get_billing— passa a aplicarwithin_agency, como o_get_guaranteejá faz (RN13b).schemas/credit_analysis.py—CreditAnalysisGuaranteeSummary.statuspassa a receber o estado efetivo (RN4a), o mesmo da tela de garantia; a descrição do campo, que lista os status antigos, é atualizada. Sem isso a análise mostraria "Ativa" para uma garantia que a tela da garantia mostra "Vencida" — duas verdades para o mesmo objeto, em duas telas que a mesma pessoa abre em sequência. A tela da análise (credit-analyses/show.tsx) renderiza esse status commap={GUARANTEE_STATUS}e precisa do rótulo novo, senão mostra a chave crua.
Testes — o caminho feliz e os negativos que sustentam a seção 10:
- transição: emitir nasce
PENDING_SIGNATURE; registrar leva aACTIVE; registrar duas vezes → 422; encerrar deTERMINATED→ 422 estado_efetivo:ACTIVEcomend_datepassada devolveEXPIRED;PENDING_SIGNATUREcomend_datepassada continuaPENDING_SIGNATUREcomproposed_period_expired- guarda de cobrança: criar em cada um dos quatro estados; baixar em cada um dos quatro (todos aceitos — é o teste que protege a RN13 de ser "consertada" por engano)
- acesso:
ANALYSTemitindo → 403;OPERATORencerrando → 403;AGENCY_USERregistrando assinatura → 403;AGENCY_USERda Imobiliária B lendo garantia da A → 404;AGENCY_USERlendo a própria → 200 sem os campos de 10.6 - isolamento da FK nova:
document_idde outra garantia → 404; de outro tenant → 404
apps/web¶
Páginas e componentes
pages/guarantees/show.tsx— reescrita para o padrão de detalhe doui-patterns.md: faixa de status, Encerramento (condicional), Cobertura, Partes, Vigência e entrada, Sinistro, Notas, Cobranças, Documentos.pages/guarantees/index.tsx—FilterBar+SearchField+FilterSelect, oito colunas,ListPagination, ordenação e busca no servidor. Remove oguaranteeCode(RN16).pages/guarantees/components/SignatureDialog.tsx— novo: registrar e corrigir.pages/guarantees/components/BillingList.tsx— botão de nova cobrança desabilitado com motivo fora deACTIVE; viraRecordCardem 375px.components/shared/DocumentsPanel.tsx— marca o contrato assinado e o põe no topo; linha de uma coluna em 375px.components/shared/MobileDetailSection.tsx— ganha colapso com contagem no cabeçalho.components/shared/ConfirmDialog.tsx— a mensagem de motivo obrigatório passa a vir de quem chama (hoje é uma frase cravada, escrita para a spec 0036); tela cheia em 375px.pages/credit-analyses/components/GuaranteeCreateDialog.tsx— resumo do que será congelado, colapsado em 375px e aberto quando indisponível.pages/guarantees/api.ts— o tipoGuaranteepassa a declararguarantee_number, o estado efetivo,proposed_period_expired, os campos de assinatura e os objetos aninhados; e a lista passa a ler o envelope paginado em vez de uma lista nua.pages/credit-analyses/api.ts— há uma segunda união deGuaranteeStatusaqui, comSUSPENDEDe semPENDING_SIGNATURE, na tela que consome o resumo da garantia. Fácil de esquecer, e otscsó acusa se alguém comparar com o valor novo.components/shared/StatusBadge.tsx—GUARANTEE_STATUSganhaPENDING_SIGNATURE("Aguardando assinatura",warning) e perdeSUSPENDED. O mapa é compartilhado: a tela da análise de crédito também o usa para o resumo da garantia, e sem o rótulo novo ela mostraria a chave crua.
data-testid novos
guarantee-signature-button · guarantee-signature-correct-button · guarantee-signature-dialog · guarantee-signature-file-input · guarantee-signature-existing-choice · guarantee-signature-date-input · guarantee-signature-confirm · guarantee-signature-cancel · guarantee-status-banner · guarantee-section-{cobertura,partes,vigencia,sinistro,encerramento} · guarantees-search · guarantees-status-filter · guarantees-pagination · guarantee-billing-create-disabled · guarantee-terminate-reason-input
Sider — e aqui o sider muda, ao contrário do que parecia. "Garantias" já existe em nav-groups.tsx condicionado a guarantees:list, e passa a aparecer para o AGENCY_USER sem código novo, o que é o desejado. Mas as permissões novas acendem mais dois itens que ninguém pediu:
| Item | Condição hoje | O que acontece |
|---|---|---|
| Cobranças | billings:list, comingSoon | passaria a aparecer para ela |
| Sinistros | guarantees:list, comingSoon | passaria a aparecer para ela |
Os dois ficam escondidos para o AGENCY_USER — condição extra no nav-groups.tsx, além da permissão. É a classe da spec 0035: o menu não vaza dado pela API e ainda assim anuncia ao canal os módulos que a garantidora vai operar. Ela vê as cobranças dentro da garantia, que é onde elas lhe dizem respeito. buildNavTabs (a barra inferior do mobile) segue a mesma regra. App.tsx não muda.
Menu e rota por permissão: quem digitar a URL de uma garantia fora do escopo recebe 404 da API e a tela mostra o estado "sem permissão para ver" — esconder o menu não protege a rota (spec 0035).
Porteiros de tela — nomeados, porque hoje não existe nenhum. Não há um só can( em pages/guarantees/show.tsx nem em index.tsx; o único do fluxo é can("guarantees:create") na tela da análise. Com o AGENCY_USER passando a abrir essas telas, cada ação passa a ser condicionada: registrar e corrigir → guarantees:sign; editar notas → guarantees:annotate; encerrar → guarantees:update; nova cobrança → billings:update. São conveniência, não proteção — a proteção é a da seção 10.
Responsividade e PWA: conforme 7.2 — colapso das sete seções, RecordCard para lista e cobranças, três diálogos em tela cheia com rodapé ancorado, ícones de 44px. PWA sem mudança: nenhuma tela nova entra no cache offline e nenhum asset muda.
apps/e2e¶
Specs (tests/e2e/emissao-e-assinatura-da-garantia.spec.ts):
- emitir → nasce Aguardando assinatura, sem cobrança possível
- registrar contrato assinado → vira Vigente, cobrança liberada
- corrigir o registro → troca o documento, estado inalterado
- encerrar sem motivo → erro no campo; com motivo → Encerrada
- lista: filtro por estado, busca por número e por tomador, ordenação da espera
AGENCY_USER: lê a própria garantia, não vê notas nem motivo de encerramento, não tem nenhuma ação de escrita, e não vê "Cobranças" nem "Sinistros" no menu- 375px: detalhe não rola na horizontal, faixa de status visível sem rolar, diálogo com rodapé ancorado
Seeds: uma garantia em cada estado no tenant de e2e, criadas pelo caminho real (API) e removidas no afterEach — o tenant é compartilhado, e a lição da spec 0025 vale.
12. Contrato de API¶
Registrar o contrato assinado
POST /api/v1/guarantees/{id}/signature
Authorization: Bearer <token> Permissão: guarantees:sign
{ "document_id": "uuid", "signed_at": "2026-08-09" }
Responde o GuaranteeResponse inteiro, com os aninhados — a tela acabou de mudar o estado e precisa repintar a faixa, as ações e o botão de cobrança; devolver um objeto reduzido obrigaria a um segundo GET logo em seguida. signed_by vem no mesmo formato dos demais:
{
"id": "uuid",
"guarantee_number": "G202608-00042",
"status": "ACTIVE",
"signed_at": "2026-08-09",
"signed_document_id": "uuid",
"signed_by": { "id": "uuid", "name": "Ana Souza" }
}
| Erro | Código | Quando |
|---|---|---|
| 403 | — | papel sem guarantees:sign no próprio tenant |
| 404 | — | garantia de outro tenant ou de outra imobiliária; ou document_id que não é desta garantia |
| 422 | INVALID_TRANSITION | garantia não está em PENDING_SIGNATURE |
| 422 | SIGNED_AT_IN_FUTURE | signed_at > hoje |
| 422 | PROPOSED_PERIOD_EXPIRED | a end_date já passou — não se ativa fora do período (RN8a) |
Corrigir o registro
Os dois campos são opcionais e ao menos um é obrigatório — manda-se o que muda. Corpo vazio ou com os dois nulos → 422 NOTHING_TO_CORRECT.
Exceção, e é a que conserta o legado: numa garantia ACTIVE sem assinatura registrada (as anteriores à migration, RN11), os dois campos são obrigatórios — não se preenche pela metade um registro que nunca existiu, e permitir gravar signed_at sem documento produziria exatamente o estado que a RN11 diz ser impossível.
Erros: 403, 404 e SIGNED_AT_IN_FUTURE como no POST; INVALID_TRANSITION não se aplica (a pré-condição aqui é o oposto), e no lugar dele vale 422 SIGNATURE_LOCKED quando o estado efetivo não é ACTIVE (RN10).
Criar cobrança — a guarda nova
| Erro | Código | Quando |
|---|---|---|
| 422 | GUARANTEE_NOT_ACTIVE | estado efetivo é PENDING_SIGNATURE, EXPIRED ou TERMINATED |
Encerrar
| Erro | Código | Quando |
|---|---|---|
| 422 | TERMINATION_REASON_REQUIRED | motivo vazio ou só espaço |
| 422 | INVALID_TRANSITION | já encerrada |
Listar
skip/limit, como todo o resto do repositório — não page/per_page. Chaves aceitas em sort, com - para descendente: status (na ordem da RN4c), guaranteed_value, monthly_value, end_date. Qualquer outra → 422.
GuaranteeListItem — o que cada item traz, e nada além: id, guarantee_number, status (efetivo), days_waiting (nulo fora de PENDING_SIGNATURE), guaranteed_value, monthly_value, start_date, end_date, lessee_name e property_summary. Os dois últimos vêm de join com a análise — o tomador por primary_lessee_id, o imóvel por credit_analysis.property_id —, resolvidos na mesma query, não em N+1.
Resposta paginada. O item de lista não traz notes, termination_reason nem terminated_by para papel nenhum — não cabem numa linha de tabela, e por isso a omissão da 10.6 não muda nada aqui: para o AGENCY_USER o item de lista é idêntico. A redução dela vale no detalhe.
Simular — estendido, e é o endpoint mais sensível desta spec
POST /api/v1/guarantees/simulate Permissão: guarantees:read
{ "credit_analysis_id": "uuid",
"start_date": "2026-08-01", "end_date": "2027-07-31", ← opcionais
"guaranteed_value": "3000.00" } ← opcional
Com credit_analysis_id, as condições (taxa e setup aprovados) e a base (financial_rental_package) vêm da análise.
Corrigido na implementação, por decisão do usuário. Esta seção dizia que
guaranteed_valuesobrepõe o que a pessoa digita — e isso contradiz a emissão, que desde a spec 0006 usa o Pacote Locatício da análise e ignora o valor informado. As duas não cabiam juntas, e a revisão da faseapps/apilevou a escolha ao usuário: o Pacote manda, nos dois. Consequência para a Parte II: o campo "valor garantido" do diálogo não recalcula o resumo, e a tela precisa dizer isso — ou o campo sai. Só a vigência sobrepõe, e ela sobrepõe mesmo, porque o prazo entra no cálculo do setup
A vigência é o campo que sobrepõe, e existe porque a Parte II promete o resumo "recalculado quando se mexe na vigência" — e o prazo entra no cálculo de verdade: com initial_fee_base = TOTAL_GUARANTEE, o setup depende dos meses. Sem a sobreposição, alguém que muda a vigência de 12 para 24 meses veria o setup de 12 e o sistema congelaria o de 24 — o mesmo defeito da RN15b, por outra porta.
A simulação espelha o create, campo a campo: as mesmas datas produzem os mesmos meses, e o mesmo guaranteed_value cai no mesmo financial_rental_package or guaranteed_value. É o que a CA17i mede.
A resposta ganha guarantee_rate_percent — a taxa efetiva (a aprovada, ou o padrão do tenant quando a decisão não a traz). Hoje GuaranteeSimulateResponse não a devolve, e a taxa só é legível pelo parecer, que exige decisions:read — que o OPERATOR, o novo emissor, não tem. Sem esse campo, o resumo que a Parte II exige não teria fonte para o único papel que esta spec traz para a emissão; a alternativa seria conceder decisions:read a ele, que é mudança de acesso feita para pintar um número.
Sem credit_analysis_id, o contrato de hoje segue igual (guaranteed_value e guarantee_months obrigatórios, padrões do tenant).
| Erro | Código | Quando |
|---|---|---|
| 404 | — | análise de outro tenant ou de outra imobiliária |
| 422 | ANALYSIS_NOT_APPROVED | a análise não está APPROVED — não há decisão aprovada de onde tirar taxa e setup |
| 422 | — | guarantee_months junto com start_date/end_date: duas formas de dizer o prazo |
Ler uma garantia — o contrato que a tela nova exige
{
"id": "uuid",
"guarantee_number": "G202608-00042",
"status": "PENDING_SIGNATURE",
"proposed_period_expired": false,
"start_date": "2026-08-01", "end_date": "2027-07-31",
"guaranteed_value": "3000.00", "lmi_value": "36000.00",
"rental_package": "3000.00", "guarantee_rate_percent": "10.00",
"monthly_value": "300.00",
"initial_fee": "3000.00", "first_month_upfront": "300.00", "total_upfront": "3300.00",
"claim_grace_period_days": 30, "claim_trigger_days": 60,
"signed_at": null, "signed_document_id": null, "signed_by": null,
"notes": "texto interno",
"terminated_at": null, "termination_reason": null, "terminated_by": null,
"created_at": "2026-08-03T14:32:00Z",
"days_waiting": 12,
"credit_analysis_id": "uuid",
"lessee": { "id": "uuid", "name": "Maria Silva", "document": "…", "email": "…", "phone": "…" },
"property": { "id": "uuid", "address": "…", "city": "…", "state": "…", "property_type": "RESIDENTIAL" },
"partner_agency": { "id": "uuid", "name": "Imobiliária Alfa" }
}
Quatro decisões de contrato que este bloco toma, e que não estavam em lugar nenhum:
- A resposta passa a aninhar
lessee,propertyepartner_agency. Hoje ela devolve só ids, e a garantia não guardaproperty_id— o imóvel é alcançado pela análise. Sem aninhar, a tela da Parte II faria três chamadas a cada abertura para montar a seção Partes, e o mobile pagaria isso em rede. O serviço resolve o imóvel viacredit_analysis.property_id. rental_packageentra na resposta. Está gravado na garantia desde a spec 0006 e nunca foi exposto; a seção Cobertura da Parte II o mostra.statusé o efetivo (RN4a), eproposed_period_expiredvem ao lado. O gravado não aparece.signed_byeterminated_byvêm com o nome, não só o UUID —{"id": "…", "name": "Ana Souza"}, como os demais aninhados. É obrigatório e não é conveniência: a Parte II promete "Encerrada em 14/08/2026 por Ana Souza", e nem oOPERATORnem oAGENCY_USERtêmusers:read— os dois papéis que esta spec traz para dentro desta tela não conseguiriam resolver o nome. A alternativa seria concederusers:reada eles, que é mudança de acesso que ninguém pediu para pintar um rótulo.
Para o AGENCY_USER, a mesma resposta sem notes, termination_reason e terminated_by (10.6) — e com todo o resto, inclusive os financeiros.
Os objetos aninhados não abrem nada novo: todo papel com guarantees:read já tem renters:read e properties:read no seed, então lessee e property não expõem a ninguém o que ele já não alcançasse por duas chamadas.
Listar devolve PaginatedResponse[GuaranteeListItem] — o envelope {items, total, skip, limit} de schemas/common.py, com skip/limit, que é o padrão do repositório inteiro. Isto é quebra de contrato: GET /guarantees devolve hoje uma lista nua, e o apps/web a consome assim. A troca está no plano da seção 14, e é barata porque o único cliente é o apps/web deste monorepo.
Migration: xxxx_guarantee_signature_and_states — descrita em 11.
13. Critérios de aceite¶
Marcados no fechamento do
/spec implement. A evidência de cada um é: a suíta da API (426 verdes), o spec e2eemissao-e-assinatura-da-garantia.spec.ts(12 verdes contra o dev-remote reconstruído), e a conferência de cobertura da revisão da branch inteira, que leu teste por teste procurando RN sem prova.A rodada 3 da revisão de branch encontrou CA4, CA7 e CA20 marcados sem teste que os segurasse — os três ganharam um, com red-green. A marcação abaixo só vale porque isso foi conferido teste a teste, e não por leitura do código.
Um fica aberto, e está declarado: o
CA17ksó é exercitado pelo lado do admin — falta oAGENCY_USERdono da carteira tentando substituir o contrato assinado. A guarda é no serviço e não olha papel, então o risco é baixo; a pendência está na seção 17.
O ciclo
- [x] CA1. Emitida uma garantia, ela consta como Aguardando assinatura e nenhuma cobrança existe para ela.
- [x] CA2. Numa garantia Aguardando assinatura,
POST /billingsresponde 422GUARANTEE_NOT_ACTIVE. - [x] CA3. Registrado o contrato assinado (documento + data), a garantia passa a Vigente e o mesmo
POST /billingsé aceito. - [x] CA4. Registrar a assinatura não altera
start_datenemend_date. - [x] CA5.
signed_atno futuro → 422SIGNED_AT_IN_FUTURE. - [x] CA6. Registrar assinatura numa garantia já Vigente → 422
INVALID_TRANSITION; corrigir a mesma → aceito, e o estado continua Vigente. - [x] CA7. Uma garantia Vigente com
end_dateno passado é devolvida pela API com estado efetivoEXPIRED, sem que nada tenha rodado. - [x] CA8. Uma garantia Aguardando com
end_dateno passado continua Aguardando e trazproposed_period_expired: true. - [x] CA9. Em garantia Vencida e Encerrada, registrar pagamento de cobrança já emitida é aceito — só a criação é recusada.
- [x] CA10. Encerrar sem motivo → 422
TERMINATION_REASON_REQUIRED.
Autorização — um negativo por papel afetado
- [x] CA11.
OPERATORemite garantia com sucesso (novo) e recebe 403 ao tentar encerrar. - [x] CA12.
ANALYSTrecebe 403 ao emitir e ao registrar assinatura. - [x] CA13.
VIEWERrecebe 403 em toda escrita e 200 na leitura. - [x] CA14.
AGENCY_USERda Imobiliária B recebe 404 ao abrir a garantia da A. - [x] CA15.
AGENCY_USERabre a própria garantia com 200, e a resposta não contémnotes,termination_reasonnemterminated_by— e contém os campos financeiros, que ela já lê pela análise (10.6). - [x] CA16.
AGENCY_USERrecebe 403 ao tentar registrar assinatura na própria garantia. - [x] CA17.
document_idde outra garantia (mesmo tenant) → 404; de outro tenant → 404. - [x] CA17a.
AGENCY_USERda Imobiliária B, com os dois UUIDs em mãos, recebe 404 emGET /guarantees/{id-da-A}/billings/{billing-da-A}. Este é o teste que prova a RN13b — sem ele, a permissão nova abriria a carteira alheia. - [x] CA17b. O item "Cobranças" e o item "Sinistros" não aparecem no menu do
AGENCY_USER, nem na barra inferior em 375px, apesar de ele ter as permissões que os condicionam. - [x] CA17c.
OPERATORedita as notas com sucesso (guarantees:annotate) e recebe 403 ao tentar encerrar (guarantees:update).VIEWERrecebe 403 nas duas. - [x] CA17d. Editar notas de garantia encerrada → 422
GUARANTEE_CLOSED_FOR_NOTES— pela API, não só pela tela desabilitada. - [x] CA17e. Excluir o documento marcado como contrato assinado de uma garantia
ACTIVE→ 422SIGNED_DOCUMENT_IN_USE. Depois de encerrada, a exclusão é aceita. - [x] CA17g.
AGENCY_USERda Imobiliária B chamaPOST /guarantees/simulatecomcredit_analysis_idde uma análise da Imobiliária A → 404. É o teste que impede esta spec de vazar a taxa negociada da concorrente. - [x] CA17h. Registrar assinatura numa garantia cuja
end_datejá passou → 422PROPOSED_PERIOD_EXPIRED, e a garantia continua Aguardando assinatura. - [x] CA17i. A simulação com
credit_analysis_iddevolve exatamente os números que a emissão daquela análise congela — taxa e setup aprovados, não os padrões do tenant. - [x] CA17l.
AGENCY_USERabreGET /guarantees/{id}/billingsda própria garantia e a resposta não contémnotesda cobrança. - [x] CA17m.
AGENCY_USERchamaPOST /guarantees/simulatecomcredit_analysis_idda própria análise aprovada → 403 (exigeguarantees:create). Sem o parâmetro, 200 com os padrões do tenant. - [x] CA17n. Garantia de outro tenant → 404 em todos os endpoints novos e alterados (o CA14 cobre só entre imobiliárias do mesmo tenant).
- [x] CA17j.
AGENCY_USERrecebe 403 ao criar cobrança e ao registrar pagamento na própria garantia — a leitura não carrega escrita junto. - [ ] CA17k. Substituir o conteúdo (
PUT /documents/{id}/content) do documento marcado como contrato assinado de uma garantiaACTIVE→ 422SIGNED_DOCUMENT_IN_USE, inclusive quando quem chama é oAGENCY_USERdono da carteira. - [x] CA17f.
estado_efetivoem Python e em SQL concordam em todas as combinações destatus×end_date(passada, hoje, futura) — é o teste que sustenta a "fonte única" da RN4b.
A tela
- [x] CA18. A tela da garantia responde, sem sair dela: de quem é, qual imóvel, quanto cobre, quanto custa, até quando vale e está paga.
- [x] CA19. A lista mostra
G202608-00042, e não#260815.1432nem id truncado. - [x] CA20. Filtrando por Aguardando assinatura, a lista mostra há quantos dias cada uma espera, e a mais antiga vem primeiro.
- [x] CA21. O botão de nova cobrança está desabilitado com o motivo à vista fora de Vigente.
- [x] CA22. Em 375px, o detalhe não rola na horizontal, a faixa de status é visível sem rolar, e o confirmar dos três diálogos fica na viewport com o teclado aberto.
- [x] CA23. Em 375px, o cartão da lista mostra número, tomador,
Aguardando · 12 diase valor garantido. - [x] CA24. O alerta
GUARANTEE_EXPIRED_ACTIVEnão existe mais no painel de operações. - [x] CA25. No painel de operações, uma garantia com fim no passado conta em encerradas, não em ativas; e uma aguardando assinatura conta no novo
total_pending_signaturee não em ativas. - [x] CA26. Na tela da análise de crédito, o resumo da garantia recém-emitida mostra "Aguardando assinatura", e não a chave crua
PENDING_SIGNATURE.
14. Plano de implementação¶
- [ ]
apps/api— enum e três colunas no model;estado_efetivono service - [ ]
apps/api— migration comdowngradeexercitado (up, down, up) - [ ]
apps/api—register_signature/update_signature+ resolução da FK no escopo - [ ]
apps/api— guarda da RN12 nobilling_service(e não tocarpay/mark_overdue) - [ ]
apps/api— motivo obrigatório noterminate; remover o alerta do painel de operações - [ ]
apps/api—rbac_seed.py:guarantees:signeguarantees:annotatenovas + as concessões de 10.2 - [ ]
apps/api—within_agencyno_get_billing(RN13b) — é a correção de segurança que a permissão nova exige, e vai antes de concederbillings:read - [ ]
apps/api— guarda da RN13c/RN13c-bis emdocument_service.delete,replace_contente noPATCHde metadados - [ ]
apps/api— contadores do painel de operações pelo estado efetivo (RN21) - [ ]
apps/api— resposta de leitura comlessee,propertyepartner_agencyaninhados, e a lista no envelope paginado (quebra de contrato registrada em 12) - [ ]
apps/api— omissão denotes/termination_reason/terminated_byda garantia e denotesda cobrança paraAGENCY_USER(10.6), porresponse_modelpróprio — chave ausente, não valor nulo - [ ]
apps/api— busca, filtro, ordenação e paginação no banco - [ ]
apps/api— docstrings + Swagger (summary,description,response_description) e os quatro textos que cravam a lista de status antiga:TAGS_METADATAemmain.py("Status possíveis:ACTIVE·SUSPENDED· …"), odescriptiondoGET /guarantees, a descrição deGuaranteeResponse.statuse a docstring de classe doGuaranteeService. OCLAUDE.mdda API torna oTAGS_METADATAobrigatório quando o comportamento do grupo muda - [ ]
apps/api— testes, o teste da RN antes do código (test-driven-development) - [ ]
apps/web—api.ts, detalhe, lista,SignatureDialog, painéis e diálogos compartilhados;data-testidem tudo; desktop e 375px no mesmo passo - [ ]
apps/e2e— spec e seeds - [ ]
make testemake lintsem apontamento novo nos arquivos tocados - [ ] Validado no dev-remote em desktop e 375px
15. Decisões em aberto¶
Nenhuma. As sete decisões do portão do problema e as seis do portão da experiência estão registradas nas seções 6 e 8, e a Parte III não abriu nenhuma nova.
Riscos aceitos, com o porquê:
- O
ADMIN_GLOBALcontinua alcançando dado de negócio do cliente (10.5). Dívida anterior a esta spec, que vale para todos os endpoints de negócio; consertá-la só aqui daria falsa impressão de fronteira. Esta spec garante não piorar. — aceito pelo usuário que pediu a spec, durante a redação da Parte III; registrado na seção 16 ao fechar o portão. - A porta de upload do
AGENCY_USERem garantia continua aberta (10.4). Deixa de importar para o estado porque anexar não ativa; fechá-la tem efeito fora desta spec. — aceito pelo usuário no portão do problema, ao decidir separar upload de ativação. downgradeperde a distinçãoPENDING_SIGNATURE×ACTIVE(11). É o preço de voltar um deploy: sem o estado novo, não há onde guardar a diferença.- A imobiliária lê a precificação da garantia (taxa aplicada, mensalidade, total de entrada). Não é vazamento criado aqui — ela já lê pelo resumo embutido em
GET /credit-analyses/{id}, que a tela dela consome. Decisão do usuário: aceitar, em vez de esconder na tela da garantia uma coisa que sai pela análise. Fechar exigiria mexer noCreditAnalysisGuaranteeSummary, numa jornada que esta spec não toca. notesetermination_reasonficam fora da resposta dela, e essa é a única redução — são os dois campos que não vazam hoje por rota nenhuma, e abri-los seria exposição nova em vez de reconhecimento de uma existente.
16. Revisão do detalhamento¶
Rubrica .claude/rubrics/detalhamento.md, revisor de contexto limpo e novo a cada rodada.
| Rodada | Critical | Important | Minor | Veredicto |
|---|---|---|---|---|
| 1 | 2 | 7 | 5 | reprovada |
| 2 | 0 | 6 | 9 | reprovada |
| 3 | 1 | 3 | 7 | reprovada |
| 4 | 0 | 3 | 5 | reprovada |
| 5 | 0 | 2 | 8 | reprovada — achados corrigidos sem 6ª rodada |
Resultado: aprovada com decisão registrada do usuário, após a rodada 5.
Por que cinco rodadas. O limite da régua é 3, estourado com autorização do usuário. Este é o portão que a régua manda medir com mais rigor, e foi o que mais rendeu: três Critical ao todo, e os três eram vazamento de dado entre imobiliárias. Os Important caíram de 7 para 2 e os dois últimos foram fechados por decisão do usuário.
Os três Critical, e o que cada um teria custado:
- A promessa de esconder a precificação da imobiliária era ficção (rodada 1). Ela já lê taxa, mensalidade e total de entrada pelo resumo da garantia embutido na resposta da análise. O CA passaria verde com o dado vazando. Levou à reversão da decisão da seção 8.
GET /guarantees/{id}/billings/{bid}não tem escopo de imobiliária (rodada 1) — ele resolve a cobrança só por tenant. Concederbillings:readaoAGENCY_USER, como esta spec faz, abriria a carteira da concorrente para quem tivesse os dois UUIDs. Este vazamento seria criado por esta spec, e é ela que o fecha (RN13b), com a correção antes da concessão no plano.- O
simulateestendido entregaria a taxa negociada da concorrente (rodada 3) — eu havia acrescentadocredit_analysis_idao corpo e, na mesma spec, dado o endpoint à imobiliária. Mesma classe do anterior, um passo além: permissão nova e parâmetro novo chegando juntos.
E um quarto, que só a rodada 5 pegou: a justificativa "ela já vê a precificação mesmo" tem uma janela de tempo. O resumo da garantia na análise é null antes da emissão; até lá a taxa negociada vive na Decision, atrás de decisions:read, que ela não tem. E esta spec alarga essa janela, porque a garantia passa a esperar assinatura por semanas. Por isso o credit_analysis_id no simulate exige guarantees:create.
Decisões do usuário registradas neste portão:
| Decisão | Escolha |
|---|---|
| O que a imobiliária vê da precificação | Tudo — a restrição da seção 8 era inexequível; só notes e termination_reason ficam fora |
Notas da garantia para o OPERATOR | Permissão própria guarantees:annotate — anota sem poder encerrar |
| Ativação depois do fim da vigência | Recusada (422) — evita a garantia vigente e impagável |
| Menu da imobiliária | Só "Garantias"; "Cobranças" e "Sinistros" ficam escondidos |
| Notas da cobrança | Também ficam fora da resposta dela |
simulate com credit_analysis_id | Exige guarantees:create — quem simula com a análise é quem emite |
Pendências aceitas: nenhuma. Os achados das cinco rodadas foram corrigidos; os da rodada 5 sem nova revisão, por decisão de encerrar o laço. Essas correções não foram revisadas.
Riscos aceitos estão na seção 15, com o decisor nomeado nos três de acesso.
17. Revisão da implementação¶
Rubrica .claude/rubrics/implementacao.md, revisor de contexto limpo e novo a cada rodada. Uma revisão por fase, e uma sobre a branch inteira no fim.
Fase apps/api¶
| Rodada | Critical | Important | Minor | Veredicto |
|---|---|---|---|---|
| 1 | 1 | 5 | 6 | reprovada |
| 2 | 0 | 0 | 3 | aprovada |
O Critical da rodada 1 era GET /guarantees devolvendo 500 em toda chamada — self.db nunca atribuído no __init__ e PaginatedResponse montado com skip/limit em vez de page/page_size/pages. Nenhum teste exercitava o endpoint, que é como ele chegou ao portão. Dois defeitos apareceram fora do revisor, ao exercitar de verdade: o CASE de estado_efetivo_sql() misturando o enum com um bind varchar (invisível no SQLite, erro no PostgreSQL) e o Billing.notes que vazaria para a imobiliária na mesma spec que lhe concede billings:read.
Fase apps/web¶
| Rodada | Critical | Important | Minor | Veredicto |
|---|---|---|---|---|
| 1 | 1 | 11 | 10 | reprovada |
| 2 | 0 | 6 | 12 | reprovada |
| 3 | 0 | 3 | 7 | reprovada |
| 4 | 0 | 1 | 7 | reprovada |
| 5 | 1 | 0 | 3 | reprovada |
| 6 | 0 | 1 | 4 | reprovada |
| 7 | 0 | 1 | 5 | reprovada |
| 8 | 0 | 0 | 7 | aprovada |
Oito rodadas, e o limite da régua é três. Elas não repetiram achado: cada rodada trouxe coisa nova e a contagem caiu monotonicamente até a 4ª. O que justificou seguir foi isso — a régua manda parar quando a rodada nova traz os mesmos achados, e não foi o caso.
O Critical da rodada 5 foi criado pela correção da rodada 4. Um useRef acrescentado para rolar a seção de notas ao entrar em edição ficou abaixo dos dois return antecipados de show.tsx: na primeira renderização o hook não era chamado, na segunda era, e o React derruba a tela com "Rendered more hooks than during the previous render". O tsc não pega, o preflight não pega, e a tela era a central da spec. É o argumento mais forte a favor de revisar de novo depois de corrigir — e o motivo de a rodada 6 ter existido.
O Critical da rodada 1 era o resumo do congelamento simulando com o prazo da análise enquanto a emissão congela o do formulário: a tela mostrava o setup de 12 meses e o sistema gravava o de 24. Mesma classe do defeito que a §12 descreve, entrando pela porta da tela.
O Important da rodada 7 era a correção da rodada 6 aplicada no lugar errado. O erro de carregamento devia ocupar o lugar do conteúdo; a guarda entrou na barra de filtros, que tem a mesma abertura {isMobile ? (. Efeito: a lista continuava dizendo "Nenhuma garantia ainda" sob o aviso de erro, e quem filtrou perdeu o filtro. Duas rodadas seguidas em que o defeito novo nasceu da correção anterior — é o custo de corrigir em lote sem reler o ponto de aplicação, e o argumento de por que estas rodadas extras se pagaram.
Padrão que se repetiu, e vale registrar: três dos seis Important da rodada 2 eram afordâncias que contrariavam a Parte II já escrita — "Encerrar" aparecendo em garantia Vencida, a ação de registrar sumindo em vez de ficar desabilitada, a busca sem debounce. A Parte II estava certa; o código é que não tinha sido medido contra ela.
Pendências aceitas da fase apps/web:
- A lista usa
<Table>do MUI, não oDataGriddo padrão da casa. Oapps/web/CLAUDE.mdfixa@mui/x-data-grid+garantoraDataGridPropscomo o padrão e diz que "as demais listas devem convergir para cá". Esta lista foi reescrita mantendo<Table>, como as outras quatro. Converter é trabalho de outra spec, e fazê-lo aqui misturaria a migração de padrão com a feature. - Os sete
Minorda rodada 8 foram corrigidos sem uma 9ª revisão. São eles: os chips de filtro aplicado na lista (sem eles oonClearAllera prop morta), a classificação do erro de cálculo peloApiError.codeem vez do texto da mensagem, o fallback para mensagem vazia — que travava o resumo em "Calculando…" com o botão cinza e sem motivo —, o cabeçalho do resumo deixando de ser botão quando não há o que colapsar, e três docblocks colados na declaração errada.
Fase apps/e2e¶
| Rodada | Critical | Important | Minor | Veredicto |
|---|---|---|---|---|
| 1 | 0 | 5 | 7 | reprovada — achados corrigidos, sem 2ª rodada |
O spec novo (tests/e2e/emissao-e-assinatura-da-garantia.spec.ts) rodou antes do portão, como manda o Passo 4c: 10 testes, 10 verdes, 39s. Rodar cedo não pega o que o portão pega — dos cinco Important, dois eram testes verdes que não protegiam a regra que nomeavam:
- O teste da RN10 afirmava "corrigir troca o documento" e só conferia que o estado seguia
ACTIVE. Tirar oonClickque escolhe o anexo deixaria o teste verde: oescolhidojá vinha pré-preenchido com o documento errado, a API responderia 200 e a garantia continuaria vigente. Agora ele confere osigned_document_ide a etiqueta na tela. - O teste do filtro por estado limpava a busca antes de filtrar. Assim que o tenant acumulasse mais de uma página de garantias aguardando, a linha não apareceria de qualquer jeito e o
toHaveCount(0)passaria com o filtro quebrado — mediria paginação. A busca agora fica preenchida, isolando a linha, e o teste também confere que ela volta com o filtro certo: sem isso, um filtro que zera tudo passaria.
O terceiro que valeu a rodada: o helper hoje() calculava em UTC. Das 21h de Brasília em diante ele preencheria o campo com amanhã, a tela recusaria inline pela RN4d e o caminho feliz ficaria vermelho três horas por dia — a mesma armadilha que o SignatureDialog já tinha evitado, repetida no teste.
Pendências aceitas da fase apps/e2e:
- O spec não limpa o que cria, e não há como. A seção 11 prometeu remoção no
afterEach. Não existeDELETE /guaranteesnemDELETE /credit-analysesna API, e apagar inquilino, imóvel ou imobiliária é recusado enquanto houver vínculo (RENTER_HAS_ACTIVE_LINKSe equivalentes) — que é exatamente o caso depois de emitir. O que dava para fazer foi feito: a imobiliária do spec passou a ser uma só, reusada entre execuções, em vez de uma por teste. O cenário concreto que o revisor levantou — o inquilino novo empurrarLocatário da Imobiliária E2Epara fora da primeira página emisolamento-imobiliaria.spec.ts— não se sustenta, e isso foi medido, não suposto: os inquilinos criados aqui têmpartner_agency_idnulo (SELECTno banco do dev-remote: 9 de 9), então não entram na carteira que aquele teste lê. Nenhum outro spec afirma sobre a primeira página de/guarantees. - Os achados desta rodada foram corrigidos sem uma 2ª revisão, e o spec foi reexecutado depois delas: 11 verdes em 35,6s, contra o dev-remote reconstruído.
Revisão da branch inteira¶
| Rodada | Critical | Important | Minor | Veredicto |
|---|---|---|---|---|
| 1 | 1 | 1 | 6 | reprovada |
| 2 | 0 | 3 | 4 | reprovada |
| 3 | 0 | 2 | 6 | reprovada |
| 4 | 0 | 1 | 6 | reprovada |
| 5 | 0 | 1 | 3 | reprovada |
| 6 | 0 | 0 | 4 | aprovada |
O Critical valeu a revisão sozinho, e nenhuma das três fases o veria. O simulate empurrava o setup arbitrado pelo analista para dentro do motor de preço (initial_fee_value=), enquanto o create o trata como valor final em reais — que é o que o Decision documenta. Em app/services/pricing.py esse parâmetro só significa reais quando initial_fee_type == FIXED; em PERCENTAGE ele é lido como percentual.
Num tenant PERCENTAGE com Pacote de R$ 3.000 e setup arbitrado em R$ 500, o diálogo mostrava R$ 15.000 de taxa de setup e a emissão congelava R$ 500. Trinta vezes, no número que esta spec existe para mostrar antes de congelar — a RN15b entrando por uma terceira porta, depois das duas que as fases já haviam fechado.
Ele sobreviveu às oito rodadas do apps/web e às duas do apps/api porque cada fase olha um lado: a API estava certa isolada, a tela estava certa isolada, e o defeito vive na costura. E o teste que declarava provar a CA17i não o alcançava — a fixture _settings_factory fixava initial_fee_type=FIXED, o único tipo em que os dois tratamentos coincidem por acidente. A CA17i estava marcada como coberta e não estava.
O red-green foi feito: com a correção revertida, o teste novo falha com assert '15000.00' == '500.00'; restaurada, passa. A fixture ganhou **sobrescritas para que um teste possa sair do FIXED.
O Important: LesseeRef.phone e PropertyRef.zip_code eram campos mortos. O Renter tem phone_landline, phone_mobile e phone_whatsapp — nenhum chamado phone; o Property tem cep, não zip_code. Com from_attributes e default declarado, o pydantic não levanta: os campos saíam null sempre. A seção Partes imprimia CPF · email e o telefone do tomador não aparecia em viewport nenhum, com os três preenchidos no cadastro — o oposto exato da terceira dor da Parte I, que é não ter o contato à mão. Resolvido com um model_validator que escolhe celular → WhatsApp → fixo, e um AliasChoices para o CEP.
Os Minor corrigidos: o terminate com corpo {} devolvia o 422 genérico do pydantic em vez de TERMINATION_REASON_REQUIRED (o reason ganhou default vazio, e os três casos passaram a responder igual); _count_guarantees sem chamador foi removido; e guarantee_status saiu de app/services/ para app/domain/ — o credit_analysis_repo importava de serviço, invertendo a seta do Router → Service → Repository → Model. O módulo é regra pura, sem sessão e sem I/O, e serviço e repositório precisam da mesma verdade sobre o estado efetivo.
Ao remover o código morto eu cortei junto dois métodos vivos que ficavam entre ele e o próximo —
_count_por_estado_efetivoe_count_encerradas_ou_vencidas—, e a suíte devolveu trêsAttributeError. Restaurado e refeito cirurgicamente. Fica registrado porque é a terceira vez nesta spec que uma correção em lote nasce com defeito próprio.
A rodada 2 confirmou as correções da 1 — conferiu que o model_validator novo não perde campo, que o AliasChoices resolve por atributo do ORM e por dict, e que o simulate espelha o create nos quatro caminhos (taxa, base, meses, setup). Nenhum Critical, e nenhum defeito nascido da correção — o que quebrou a série de três.
Os três Important dela eram de cobertura e de contrato, não de comportamento:
- O Swagger de quatro endpoints alterados não acompanhou o código. O painel ainda anunciava o alerta
GUARANTEE_EXPIRED_ACTIVEe o item de fila de prioridade 2 que a RN20 removeu; oPOST /billingsnão mencionava o422 GUARANTEE_NOT_ACTIVE, que é a guarda central desta spec; e oPUT /contente oDELETEde documentos listavam os erros um a um e omitiam justamente oSIGNED_DOCUMENT_IN_USE. OCLAUDE.mdda API trata isso como regra, e opreflightsó cobre endpoint novo. - A FK nova do
simulatenão tinha negativo de escopo. O código estava certo — o que faltava era o teste. O irmão que existia provava o 403 doAGENCY_USER, que dispara no router antes de qualquer resolução de escopo, e por isso não alcançava o que a CA17g diz proteger. O teste novo alcança, e o red-green mostrou o vazamento inteiro: trocando oresolve_in_scopepeloget_by_id(..., tenant_id=...)que ocreateainda usa, a resposta vira 200 com"guarantee_rate_percent":"13.00"— a taxa negociada da garantidora vizinha atravessando a fronteira. - A correção de
phone/zip_codeda rodada 1 não tinha teste que a segurasse, e o modo de falha é silencioso: comfrom_attributese default declarado o pydantic entreganullsem levantar nada. Coberto agora; revertida a correção, o teste falha comassert None == '(11) 98765-4321'.
Os Minor corrigidos: responses={200: ...} em endpoints que devolvem 201 — o schema ficava sob um código que nunca é retornado, e o 201 aparecia sem corpo no OpenAPI (conferido no app.openapi() depois); e o docstring de Guarantee.end_date, que ainda dizia "após esta data a garantia vai para EXPIRED", como se houvesse transição, enquanto a docstring da classe logo acima já dizia o contrário. O wiki é gerado daí.
A rodada 3 achou a quarta correção com defeito próprio, e ela era minha. Ao consertar o Swagger da rodada 2, renumerei a tabela de prioridades da fila de trabalho tirando a linha do GUARANTEE_EXPIRED_ACTIVE — mas o priority é campo devolvido ao cliente e impresso na tela pelo WorkQueueItem. O código seguia em 1, 3, 4, 5, 6 e o Swagger passou a dizer 1, 2, 3, 4, 6: documentação de endpoint alterado que não descreve o código, que é exatamente a classe que a rodada 2 tinha ido corrigir. O 2 voltou a ficar vago, com o motivo escrito — renumerar trocaria o significado de um campo público para economizar um buraco.
O outro Important: três regras centrais sem teste que as segurasse, duas delas com o critério de aceite já marcado como atendido. Todas agora têm, e o red-green foi feito nas três:
- RN4a/CA7 — apagando
dados["status"] = efetivodo_to_response, uma garantia vigente comend_datede ontem voltava a sairACTIVE, e os 426 testes seguiam verdes. Nenhum teste de rota exercitavaACTIVE+ vigência passada: o de estado efetivo media a função isolada. O teste novo passa pelo detalhe e pela lista, porque divergirem entre si é o pior sintoma possível. - RN19/CA20 — a ordem padrão. Trocando-a por
created_at desc, a garantia que espera há três semanas afundava sob as emitidas hoje — literalmente a dor da Parte I — e nada ficava vermelho. - RN4c — a ordem de classificação por Situação, que só era exercitada de raspão.
O teste da ordem nasceu instável, e eu escrevi o defeito que este repositório documenta. As três garantias eram criadas na mesma chamada: empatavam no
created_at(o SQLite guarda segundo) e o desempate éid descsobre UUID aleatório. Falhava em 3 de 5 execuções. Só apareceu porque oCLAUDE.md— corrigido nesta mesma spec — passou a mandar rodar cinco vezes em vez de uma. Corrigido empurrando ocreated_atde uma delas 21 dias para trás, o que também é o cenário real: a espera se mede em dias, não em ordem de inserção. 5 de 5.
Os Minor corrigidos: o aviso da RN20 estava dentro da tabela de alertas, quebrando a renderização da última linha no Swagger; o CA4/RN9 (assinar não mexe na vigência) estava marcado sem asserção nenhuma e ganhou teste; o rótulo CA23 aparecia em dois testes e2e, um deles apontando para o critério errado; o approve do segundo tenant não era conferido, e a falha dele apareceria trinta linhas adiante como 404 != 200; a concordância Python×SQL do estado efetivo só era medida com o relógio no default — agora também às 02h UTC, que é a hora em que um fuso errado se manifesta; e o painel de operações comparava datas em UTC enquanto os contadores de estado usam São Paulo, então entre 21h e meia-noite uma garantia que termina hoje contava como ativa e sumia da janela de "vence em 30 dias".
A rodada 4 procurou a quinta correção-com-defeito e não achou. Ela conferiu as cinco correções da rodada 3 uma a uma — a numeração da fila contra os priority= do serviço, a troca UTC → hoje_local() ponto a ponto (todos os de calendário convertidos, todos os de duração deixados em UTC, porque 48h e 72h são intervalo e não data), e se o teste que altera o modelo no banco depois de chamar a API poderia passar por engano pelo cache de sessão. Não podia: o client compartilha a sessão do teste e a factory é expire_on_commit=False, então o objeto mutado é o que o repositório devolve.
O Important foi o mesmo padrão da rodada 3, num lugar novo: a RN15 sem teste. As onze chamadas a /terminate da suíte encerravam sempre uma garantia aguardando ou vigente, uma vez só — nenhuma encerrava uma vencida, e nenhuma encerrava duas vezes. Removida a guarda de TERMINATED, o segundo encerramento responde 200 e sobrescreve terminated_by, terminated_at e termination_reason: o registro que a RN14 tornou obrigatório "porque é o que a auditoria procura" some sem rastro, num ato que a spec chama de irreversível. O red-green mostrou o apagamento por extenso — "termination_reason":"outro motivo, de outra pessoa" no lugar do original.
Os Minor corrigidos: o GET /guarantees/{id}/billings declarava um objeto onde devolve um array (o list[...] se perdeu quando o response_model virou responses para não recoagir); signed_by/terminated_by ficaram de fora do lote de _carregar_relacionados e faziam um SELECT por item — numa página de 200 encerradas eram 4 + 400 consultas, e a docstring prometia quatro; o rótulo CA25 aparecia em dois testes e2e, os dois apontando para o critério errado (é a CA22 — o mesmo defeito que a rodada 3 corrigiu no CA23, sobrevivendo noutra linha); ANALYSIS_NOT_APPROVED, NO_CALCULATION_BASE e SIGNATURE_INCOMPLETE ganharam teste; e o comentário do caso das 02h UTC passou a dizer o que o bloco de fato prova (que o lado SQL honra o parâmetro em vez de cair no relógio do banco), e não o que ele parecia prometer sobre o fuso.
O
NO_CALCULATION_BASEsó é alcançável pela análise antiga, sem o snapshot do Pacote: corpo vazio para no schema e devolveVALIDATION_ERROR. O teste encena o caso real zerando a coluna, e confere também que oguaranteed_valuede fallback volta a fazer a conta — que é o que torna a emissão dessas análises alcançável pela tela.
A rodada 5 procurou a quinta correção-com-defeito nas mudanças da rodada 4 e também não achou. A única que mexeu em código de produção — os dois usuários entrando no lote de _carregar_relacionados — foi conferida nas três hipóteses que interessavam: o cache não pode existir sem a chave (há um construtor só, e um chamador só), nenhum outro lugar monta o dict, e usuário apagado ou de outro tenant se comporta igual ao caminho antigo, porque nem o get_by_id nem a consulta em lote filtram deleted_at/tenant_id.
O Important foi o mesmo padrão pela terceira rodada seguida — e desta vez sobre a razão de ser da spec. O teste chamado test_operador_emite_e_registra_mas_nao_encerra nunca emitia: a garantia vinha do _emitir, que usa o ADMIN_TENANT, e o OPERATOR só era criado depois, para exercitar assinatura, nota e encerramento. Apagar guarantees:create do OPERATOR no seed deixava as 434 verdes e o e2e também — porque os doze testes de lá logam como gestor. Em uso, o papel que esta spec existe para habilitar tomava 403 na emissão, e o botão sumia da tela da análise. Corrigido com uma emissão de verdade, com o token dele, e red-green isolado.
O primeiro red-green saiu errado e teria mentido. Cortei
guarantees:createprocurando a linha doOPERATORde trás para frente e acertei a doADMIN_TENANT: os dois testes ficaram vermelhos pelo mesmo motivo, e um deles não era o que eu queria provar. Refeito um de cada vez — o do lote falha comTypeError, o da permissão com 403. Red-green que não isola qual regra caiu prova menos do que parece.
Os Minor corrigidos: a consulta em lote de usuários foi reescrita inline quando user_repo.get_many já existia desde a spec 0037, criada exatamente contra isso — seria a terceira cópia da mesma select(User); o ramo de cache usava .get("usuarios", {}) enquanto os três vizinhos acessam por chave, e a assimetria devolveria nomes nulos em toda a lista sem levantar nada; esse ramo agora tem teste, que confere também que lista e detalhe entregam o mesmo objeto; e o OPERATOR_PERMS do test_rbac_security foi atualizado. Não o chame de espelho: a rodada 6 mostrou que ele não é comparado com o rbac_seed em teste nenhum — só alimenta um papel OPERATOR_TEST montado à mão, e já diverge do seed noutro ponto (credit_analyses:resume). Quem protege o seed do OPERATOR é test_operador_emite_e_registra_mas_nao_encerra, que usa o papel real. A constante foi atualizada para não induzir quem a lê a concluir a política errada, e não porque ela guardasse alguma coisa.
A rodada 6 aprovou a branch inteira — 0 Critical, 0 Important, 4 Minor. É a primeira aprovação em seis rodadas, e o revisor disse por extenso que não via motivo para uma sétima: o código está correto e a costura entre os apps também.
O saldo das seis rodadas, e vale registrar como método: elas acharam zero defeitos de comportamento em código de produção. O que acharam, seis vezes, foi a mesma coisa — regra correta sem teste que a segurasse (RN4a, RN19, RN4c, RN15, a permissão de emitir do OPERATOR, e agora a quarta cláusula da RN21). Três delas estavam com o critério de aceite marcado como atendido. A revisão de branch inteira não é redundante com as de fase: as de fase olham um app de cada vez e não conseguem perguntar "se esta regra sumisse, alguma coisa ficaria vermelha?", porque a resposta mora em outro app ou em outro arquivo de teste.
Os quatro Minor da rodada 6 foram corrigidos, os três primeiros com red-green isolado: a quarta cláusula da RN21 (in_guarantee contando também quem aguarda assinatura — revertida, o painel diria 2 onde há 3, e uma garantidora que emitiu esta semana veria zero); a descrição Swagger desse mesmo campo, que continuava dizendo "garantias ativas"; e o estado efetivo no resumo da garantia dentro da análise, que é a terceira porta para o mesmo objeto — revertido, a tela da garantia diz "Vencida" e a da análise diz "Vigente", em duas telas que a mesma pessoa abre em sequência. O quarto era a frase deste registro sobre o OPERATOR_PERMS, corrigida acima.
Pendências aceitas da revisão da branch:
- A §12 define um
GuaranteeListItemque o código não implementa — a lista devolve oGuaranteeResponseinteiro (ou oGuaranteeAgencyResponse, para a imobiliária). Não é vazamento: os papéis da garantidora leem os mesmos campos no detalhe, e o recorte da imobiliária tem teste. É payload a mais e uma promessa de contrato não cumprida. - O
ADMIN_GLOBALdeixou de emitir pelo diálogo. OisValidpassou a exigir o resumo, e osimulateresolve parâmetros portenant_id— nulo para ele, logoSETTINGS_NOT_FOUND. A direção está de acordo com a 10.5 (o SaaS admin não opera dado de negócio do cliente), mas aconteceu em silêncio, sem ser decisão declarada. - A fórmula de meses existe em três lugares —
_months_betweenna API,mesesEntreno diálogo eprazoEmMesesno detalhe. As três conferem hoje (ceil(dias/30), mínimo 1) e nenhum teste as amarra. - O
GuaranteeSimulateRequestnão aceitastart_date/end_date, que a §12 descreve no corpo — a tela converte paraguarantee_monthsantes de chamar. O422do conflito entre os dois, documentado na §12, é portanto inalcançável. É desvio de contrato, não defeito de comportamento, e mudá-lo agora seria mexer no que está coberto e verde. - A CA17k só é exercitada pelo lado do admin — falta o
AGENCY_USERdono da carteira. A guarda é no serviço e não olha papel, então o risco é baixo. - Os achados da rodada 6 foram corrigidos sem uma 7ª revisão, e são de teste e de texto: três asserções em testes que já montavam a fixture, mais uma descrição de campo.
Registro¶
Entregue nos três apps, na ordem do monorepo. O que a feature faz: a garantia deixa de nascer vigente e passa a esperar o contrato assinado, que é o ato que a ativa e libera a cobrança; ganha tela própria, com tomador, imóvel, imobiliária, cobertura, vigência e sinistro na mesma página; e a lista passa a buscar, filtrar, ordenar e paginar no servidor, dizendo há quanto tempo cada uma espera.
As suítes, no fechamento:
| O quê | Resultado |
|---|---|
pytest da API | 426 verdes, 1 vermelho — o intermitente conhecido de test_partner_agency_search, que passou na rodada anterior |
emissao-e-assinatura-da-garantia.spec.ts | 12 verdes, 38,9s, contra o dev-remote reconstruído |
| Suíte e2e completa | 239 verdes, 10 vermelhos, 2 pulados — ver abaixo |
tsc do web | limpo |
make preflight | limpo |
As 10 falhas da suíte e2e, uma a uma. Duas são desta branch por decisão: menu-por-permissao para a imobiliária, cujo teste codificava a política anterior — a spec dá guarantees:read ao canal e põe "Garantias" no menu dele. Atualizado, com "Cobranças" e "Sinistros" virando proibidos nomeados.
As outras oito não são regressão, e a evidência é de três tipos:
- Registro de spec anterior.
credit-analysis-documents:365,isolamento-imobiliaria:76eproperties:156constam como confirmadas pré-existentes com baseline rodado namainno Registro da spec 0037; oadmin-interfaceestá lá registrado como instável. - Medição no banco. As outras duas do
isolamento-imobiliariacaem porque a carteira da Imobiliária E2E tem 15 inquilinos e o do seed sai da primeira página. Dos 15, 14 foram criados entre 12 e 15 de agosto — nenhum é desta spec, e os que este spec cria têmpartner_agency_idnulo (9 de 9), logo não entram naquela carteira. - Medição no contrato. No
properties:156, oselectAgencyprocuraE2Ee a busca da API casaname ILIKE '%E2E%'ou o CNPJ contendo qualquer dígito do termo — o "2" de "E2E". São 61 resultados para um teto de 50, e a "Imobiliária E2E" está na posição 61. Remover a única imobiliária que este spec cria a deixaria em 60.
O que mudou fora do escopo da spec, e por quê: o CLAUDE.md da raiz dizia que a assinatura dos dois testes intermitentes é "falha na suíte e passa sozinho". É meia verdade, e ela custou uma investigação aqui: como o desempate é sorteado, eles falham sozinhos também. Medido — 3 verdes e 2 vermelhos em 5 execuções isoladas. O texto agora diz que o que assina é o cara-ou-coroa, e manda rodar cinco vezes.
Nota de deploy. guarantees:sign e guarantees:annotate só chegam ao banco quando o RBAC seed roda. No dev-remote isso já aconteceu — o entrypoint chama init_db a cada recriação do container. Em produção é o /ship que precisa fazer, e sem isso o ADMIN_TENANT toma 403 ao registrar assinatura ou editar notas.
O que ficou aberto está nas Pendências aceitas da seção 17: o CA17k sem o lado da imobiliária, o GuaranteeListItem que a §12 define e o código não implementa, o start_date/end_date ausentes do simulate, a fórmula de meses em três lugares, o ADMIN_GLOBAL que deixou de emitir pelo diálogo, a lista em <Table> em vez de DataGrid, e o spec e2e que não limpa o que cria — porque não há DELETE para garantia nem para análise, e apagar inquilino, imóvel ou imobiliária é recusado enquanto houver vínculo.