Files
DeskcommCRM/docs/specs
webtecnica 0283ca4e0c fix(contacts): excluir contato não destrói o histórico quando a ficha não sai (#752)
Issue #752: o handler apagava `messages` e `conversations` e só então tentava
`contacts`. Contato com registro em `calendar_appointments` (`on delete
restrict`, migração 0177) nunca sai — e a tentativa levava o histórico junto:
`DELETE messages 204`, `DELETE conversations 204`, `DELETE contacts 403`. No
caso relatado, 90 mensagens destruídas para devolver "Erro interno" com a ficha
de pé. E nada em `api_audit_log`: a linha do sucesso só era escrita no fim, e
quem consultasse a auditoria concluía que nada havia sido apagado.

A lista de vínculos `RESTRICT` que este handler NÃO apaga de propósito
(`VINCULOS_RESTRICT_NAO_APAGADOS`) passa a ser conferida ANTES do primeiro
DELETE: conta `calendar_appointments` do contato (e da organização) e, havendo
vínculo, devolve o mesmo 409 do caminho de FK — com o histórico intacto. Quem
recusa continua sendo o banco, com o 23503 do `restrict`; a contagem existe só
para saber disso antes de apagar. Falha da própria contagem não autoriza
seguir: vira 500, que é reversível — mensagem apagada não volta.

O ramo de falha passa a ser auditado: `contact.delete_blocked` com
`metadata.motivo` (`vinculo_restrict` | `falha_ao_apagar`) e
`metadata.apagados`, a lista do que já saiu quando parou. `contact.deleted`
segue sendo a linha do sucesso; as duas juntas separam "ninguém excluiu" de
"tentei, um vínculo barrou e a ficha ficou".

Não vira RPC transacional (sugestão 1 da issue) de propósito: a de menor
alcance que resolve a perda de histórico é a 2+3 — checar antes de apagar e
auditar o desfecho — e é a que este commit faz. Falha no meio dos DELETEs pode
deixar estado parcial; agora ele sai auditado, com o que já tinha saído.

Catraca: tests/unit/contato-delete.test.ts. O caso do compromisso na agenda
exige `chamadas` VAZIO (nada apagado antes do 409) e auditoria
`contact.delete_blocked` com `apagados: []`; o caso do 23503 na ficha exige a
auditoria do parcial com `apagados: ["messages","conversations"]`. Vermelho na
base, verde com o fix.

Fragmento: .changes/excluir-contato-nao-destroi-historico.md (nada_mudou).

O `ctx` do teste é tipado como `HandlerCtx` (lib/api/handlers/types.ts), no estilo
dos vizinhos: sob `tsc --noEmit` estrito o `Actor` é união discriminada e o retorno
inline não atribuía. O `test:unit` passava assim mesmo — o vitest transpila sem
checar tipos; quem pega é o gate `typecheck`.
2026-09-12 18:31:09 -03:00
..