Excluir a conta sem apagar a nota fiscal
O direito de eliminação esbarra na obrigação de guarda fiscal de cinco anos. A saída é anonimização seletiva — e uma matriz que diz o que apagar, o que anonimizar e o que reter.
Parte 3 da série. Nas partes 1 e 2 tratei do diagnóstico e da minimização. Agora o item que mais dá trabalho: fazer o botão “Excluir conta” funcionar de verdade.
No meu site ele existia, desabilitado, com o aviso “disponível em breve”. Este post é sobre por que “em breve” durou tanto — e não foi preguiça, foi um conflito real entre duas leis.
O conflito
A LGPD dá ao titular o direito de eliminação (art. 18, VI). Parece simples: apagar tudo.
Só que a legislação fiscal obriga a guardar documentos por cinco anos. Nota fiscal emitida, valores, itens vendidos. Se eu apagar o pedido de quem pediu para sair, eu descumpro a obrigação fiscal — e quebro a contabilidade da empresa, que tem lançamentos referenciando aquele pedido.
Se eu não apagar, descumpro a LGPD.
A saída está na própria lei
O art. 16, I resolve: os dados podem ser conservados após o término do tratamento para cumprimento de obrigação legal ou regulatória.
Ou seja, não é um conflito de verdade — é uma exceção prevista. A questão vira uma de engenharia: como manter o pedido sem manter a pessoa?
A resposta é anonimização seletiva. O pedido continua existindo com valores, itens e status, mas deixa de identificar alguém.
A matriz
Escrevi uma matriz explícita, porque essa decisão precisa estar registrada e não enterrada num arquivo SQL. Ela vive em matriz de anonimização e diz, para cada tabela, uma de três ações:
ELIMINAR — perfil, endereços, carrinho, conversas de IA, imagens geradas, arquivos enviados, produtos personalizados, orçamentos, cupons.
ANONIMIZAR — pedidos (some nome, e-mail, telefone, endereço e observações; ficam valores, itens e status) e telemetria (perde o vínculo com o usuário, mantém o agregado).
RETER — a nota fiscal, intocada. E aqui um ponto que eu não tinha considerado: o XML da NF-e é um documento assinado digitalmente. Alterá-lo não é só juridicamente errado, é tecnicamente impossível sem invalidar a assinatura. Editar uma nota fiscal é fraude fiscal. Então ela fica como está, e é apagada só quando os cinco anos vencem.
Três armadilhas técnicas
A tabela sem chave estrangeira
O contador de uso do chat tem uma coluna de texto no formato user:<uuid> ou
guest:<uuid>, e nenhuma chave estrangeira para a tabela de usuários.
Isso significa que apagar o usuário não apaga essa linha. Ela vira órfã: um registro guardando o identificador de uma conta que não existe mais, para sempre, sem que nada quebre nem ninguém perceba.
A correção é um DELETE explícito por string, dentro da rotina de eliminação. E,
como rede de segurança, a rotina semanal de retenção também varre órfãos — porque
contas excluídas antes desta correção já deixaram lixo lá.
Essa é a classe de problema mais perigosa em conformidade: nada quebra. Um dado que deveria ter sumido e não sumiu não gera erro, não gera alerta, não gera nada.
A tabela que não existe
Eu ia escrever a anonimização dos itens do pedido supondo uma tabela
order_items. Fui conferir: ela não existe. Os itens são um campo JSON dentro da
própria linha do pedido.
Isso muda completamente a implementação — em vez de um UPDATE numa tabela
relacionada, é preciso iterar o array JSON, remover as chaves pessoais de cada
elemento e reescrever o campo.
Fui salvo por verificar. A lição vale mais que o detalhe: cheque o schema antes de escrever a migração, especialmente num sistema que você mesmo escreveu ao longo de meses e acha que conhece.
A ordem importa mais do que parece
Três ordenações que não são arbitrárias:
Storage antes do banco. Os caminhos dos arquivos vivem nas linhas do banco. Se você apagar as linhas primeiro, os arquivos ficam órfãos no armazenamento e você não tem mais como saber quais eram. Irrecuperável.
A conta de autenticação por último. Se algo falhar no meio, a conta continua existindo e a operação pode ser repetida. Na ordem inversa, uma falha deixaria um usuário sem dados — o pior estado possível, porque ele não consegue nem entrar para pedir de novo.
O vínculo precisa estar vivo na hora de anonimizar. A coluna de usuário no pedido é configurada para virar nula automaticamente quando a conta é apagada. Se eu apagasse a conta primeiro, o vínculo sumiria — e a rotina não teria mais como localizar os pedidos daquela pessoa para anonimizar. Ela rodaria sem erro nenhum e não faria nada.
Por que precisou de privilégio elevado
A eliminação atravessa tabelas que o próprio dono não tem permissão de escrever — a telemetria e os pedidos, por exemplo, são protegidos contra escrita pelo usuário, e devem ser mesmo.
Além disso, a operação precisa ser atômica. Uma falha no meio deixaria o perfil apagado e os dados pessoais vivos dentro dos pedidos: o pior dos dois mundos, com o titular achando que saiu e os dados dele ainda lá.
Então virou uma função no banco com privilégio elevado, com a permissão de execução revogada de todos os papéis da aplicação. Só o servidor pode chamá-la, e só depois de reautenticar a pessoa.
A reautenticação
Exijo duas coisas para excluir: digitar o e-mail exato da conta e informar a senha.
O e-mail impede clique acidental e ataques de requisição forjada — quem forja não sabe o que digitar. A senha resolve outro cenário, mais comum do que parece: a sessão esquecida aberta num computador compartilhado. Sem ela, qualquer pessoa que sentasse naquela máquina poderia destruir a conta.
Dizer o que não é apagado
Essa foi uma decisão de produto tanto quanto jurídica.
Na tela de exclusão, antes de qualquer clique, aparece um bloco explicando que pedidos e notas fiscais permanecem por cinco anos, por obrigação legal, e que deixam de identificar a pessoa.
Poderia estar só na política. Mas alguém que clica em “excluir conta” está tomando uma decisão baseada numa expectativa — e descobrir depois que algo ficou gera exatamente o tipo de desconfiança que a transparência deveria evitar. É mais honesto e, na prática, gera menos suporte.
O bug que apareceu no caminho
Enquanto ligava o botão, descobri que o script daquela página inteira rodava num evento que não dispara quando a navegação acontece dentro do site.
Consequência: quem chegava em Configurações clicando pelo menu encontrava uma página inerte. Nem trocar senha funcionava. Só funcionava se você digitasse o endereço direto ou recarregasse.
Não tem nada a ver com LGPD. É só o tipo de coisa que aparece quando você finalmente abre um arquivo que estava parado há meses.
Como testar
Auditoria não vale sem verificação. Com uma conta descartável, crie pedido, conversa de IA, imagem e orçamento; exclua; e confira que o perfil sumiu, que o contador de uso sumiu, que o pedido continua com o valor mas sem os dados pessoais, e que a nota fiscal está intacta.
E abra a tela administrativa daquele pedido. Ela precisa renderizar sem erro,
mostrando “Removido a pedido do titular” no lugar do nome — em vez de quebrar
tentando ler um campo que não existe mais, ou pior, exibir a palavra undefined
para quem for atender.
Na parte 4: banner de consentimento que não implora, e o aviso de que seu texto está indo para a China.