Trocar o "Tem certeza?" por um desfazer que funciona de verdade
Decida quando usar desfazer e quando confirmar, implemente exclusão reversível com prazo no banco e escreva uma confirmação que nomeia o que será apagado.
Uma analista arquivou por engano o cliente "Padaria Estrela", que tinha 340 notas fiscais vinculadas. O sistema perguntou "Tem certeza?", como pergunta antes de qualquer ação, e ela apertou Enter, como aperta trinta vezes por dia. A confirmação existia e não protegeu nada.
A resposta curta: para ação reversível, não pergunte, deixe desfazer. Execute na hora e mostre "Cliente arquivado. Desfazer" por alguns segundos, com o desfazer funcionando no servidor. Para ação irreversível, confirme nomeando o objeto e exija um gesto diferente do clique automático. Misturar os dois casos com a mesma caixa genérica é o que treina as pessoas a clicar sem ler.
Por que o "Tem certeza?" para de funcionar
Confirmação é uma interrupção. Na primeira vez, a pessoa lê. Na décima, ela já sabe onde fica o botão e o dedo vai sozinho. Quando toda ação pede confirmação, a caixa deixa de carregar informação, porque aparece tanto no trivial quanto no grave.
O texto genérico agrava. "Tem certeza que deseja continuar?" não diz o que vai acontecer. Se a pessoa clicou no cliente errado, a pergunta não mostra isso. Ela confirma a ação certa no objeto errado.
Desfazer funciona melhor porque não depende de atenção prévia. A pessoa percebe o erro depois, ao ver o resultado, que é quando humanos realmente percebem erros. O sistema só precisa permitir voltar.
Como decidir entre desfazer e confirmar
Faça duas perguntas sobre a ação. Dá para reverter tecnicamente? E o efeito sai do sistema, como um e-mail enviado, um pagamento estornado ou um arquivo enviado para o governo?
- Reversível e interno (arquivar, mover, remover da lista, excluir para a lixeira): execute e ofereça desfazer.
- Irreversível ou com efeito externo (excluir definitivamente, enviar campanha, cancelar nota): confirme com o nome do objeto e a consequência.
- Em massa (arquivar 200 clientes): mostre o número antes de executar e ofereça desfazer depois. Número grande inesperado é o melhor alarme que existe.
Como implementar o desfazer no servidor
O desfazer que só esconde a linha na tela e dispara o DELETE depois de cinco segundos é frágil. Se a pessoa fecha a aba, a ação nunca acontece, ou acontece sem ninguém ver. O jeito robusto é executar de verdade de forma reversível e apagar de vez só depois de um prazo.
- Adicione uma coluna de exclusão lógica, como
arquivado_em, nula enquanto o registro está ativo. - A ação "arquivar" preenche a coluna com a data e hora atuais. Todas as consultas normais filtram
arquivado_em IS NULL. - O desfazer limpa a coluna, mas só para o registro daquela pessoa e dentro do prazo.
- Uma tarefa agendada apaga ou anonimiza o que passou do prazo de retenção definido pelo negócio.
- Registre quem arquivou e quando, para o suporte conseguir reverter sem acesso direto ao banco.
Em MySQL, para a tabela de clientes do exemplo:
ALTER TABLE clientes
ADD COLUMN arquivado_em DATETIME NULL,
ADD COLUMN arquivado_por INT NULL;
-- arquivar
UPDATE clientes
SET arquivado_em = NOW(), arquivado_por = 17
WHERE id = 4821 AND arquivado_em IS NULL;
-- desfazer, só dentro de 30 dias
UPDATE clientes
SET arquivado_em = NULL, arquivado_por = NULL
WHERE id = 4821
AND arquivado_em >= NOW() - INTERVAL 30 DAY;
-- limpeza agendada
DELETE FROM clientes
WHERE arquivado_em < NOW() - INTERVAL 30 DAY;
A limpeza agendada precisa respeitar chaves estrangeiras. Se o cliente tem notas fiscais, você provavelmente não apaga o cliente, só o mantém arquivado. Revise isso antes de rodar o DELETE, e leia apagar um registro sem apagar a tabela para não errar o WHERE justamente na rotina de limpeza.
No front, a mensagem de desfazer chama o endpoint de verdade:
async function arquivarCliente(id, nome) {
const r = await fetch('/api/clientes/' + id + '/arquivar', { method: 'POST' });
if (!r.ok) {
mostrarErro('Não foi possível arquivar ' + nome + '.');
return;
}
removerDaLista(id);
mostrarAviso('Cliente ' + nome + ' arquivado.', {
acao: 'Desfazer',
aoClicar: async () => {
const d = await fetch('/api/clientes/' + id + '/restaurar', { method: 'POST' });
if (d.ok) recarregarLista();
else mostrarErro('Não foi possível restaurar ' + nome + '.');
}
});
}
Dois cuidados. A mensagem fica na tela tempo suficiente para ser lida e tem um botão alcançável pelo teclado. E existe um segundo caminho para restaurar, uma tela "Arquivados", porque quem perdeu o aviso também merece voltar. Os detalhes de tempo e acessibilidade desse aviso estão em aviso que some sozinho.
Como escrever a confirmação que protege
Quando a ação não tem volta, a caixa precisa quebrar o automatismo. Três elementos fazem isso.
- O título nomeia o objeto e a ação: "Excluir definitivamente Padaria Estrela?" e não "Tem certeza?".
- O corpo diz a consequência em números: "As 340 notas fiscais vinculadas perdem o cliente. Isso não pode ser desfeito."
- O botão repete o verbo: "Excluir cliente", não "Sim" nem "OK". O botão de cancelar recebe o foco inicial.
Para o caso mais grave, peça que a pessoa digite o nome do objeto. É um gesto que não sai no automático e confirma que ela olhou o nome. Use com parcimônia: se tudo exigir digitação, vira o mesmo hábito com mais passos.
<dialog id="excluir-cliente" aria-labelledby="titulo-excluir">
<form method="dialog">
<h2 id="titulo-excluir">Excluir definitivamente Padaria Estrela?</h2>
<p>As 340 notas fiscais vinculadas perdem o cliente. Isso não pode ser desfeito.</p>
<label for="confirma-nome">Digite o nome do cliente para confirmar</label>
<input id="confirma-nome" autocomplete="off">
<button value="cancelar" autofocus>Cancelar</button>
<button value="excluir" id="botao-excluir" disabled>Excluir cliente</button>
</form>
</dialog>
Um script habilita o botão só quando o texto digitado é igual ao nome. Abra com showModal() e, ao fechar, devolva o foco para o botão que abriu a caixa, como explica modal que devolve o foco. E valide de novo no servidor: o botão desabilitado é conveniência, não proteção.
Erros comuns
- Confirmar tudo, inclusive o trivial. A pessoa aprende a apertar Enter e a confirmação perde valor justamente no caso grave.
- Desfazer só na tela. Se o DELETE roda depois de um temporizador no navegador, fechar a aba muda o resultado e ninguém sabe o estado real.
- Botões "Sim" e "Não". A pessoa precisa reler a pergunta para saber qual é qual, e em pergunta negativa erra o sentido.
- Foco inicial no botão destrutivo. Um Enter apressado apaga o que a caixa deveria proteger.
- Aviso de desfazer como único caminho de volta. Quem não viu os poucos segundos de aviso fica sem saída e abre chamado.
- Limpeza agendada sem filtro de prazo bem testado. Um erro no
WHEREtransforma o recurso de segurança em exclusão em massa.
Checklist
- Cada ação destrutiva foi classificada como reversível ou irreversível.
- Ações reversíveis executam na hora e oferecem desfazer que chama o servidor.
- Existe uma tela de arquivados ou lixeira para restaurar depois do aviso.
- A confirmação de ação irreversível nomeia o objeto e diz a consequência.
- O botão de confirmar repete o verbo, e o foco inicial fica em cancelar.
- Ações em massa mostram a quantidade antes de executar.
- A limpeza definitiva roda com prazo explícito e foi testada numa cópia do banco.
Escolha a ação mais destrutiva do seu sistema e execute numa conta de teste. Se foi engano, a pessoa consegue voltar sozinha, sem chamar o suporte e sem você abrir o banco?
Referência: https://www.nngroup.com/articles/confirmation-dialog/