17 de nov. de 2025·8 min de leitura

Máquina de estados de onboarding: pare cadastros pela metade

Aprenda como uma máquina de estados de onboarding evita cadastros incompletos, lida com refresh e atrasos de e-mail, e mantém os usuários progredindo.

Máquina de estados de onboarding: pare cadastros pela metade

Por que usuários ficam presos no meio do onboarding

“Cadastros incompletos” acontecem quando seu fluxo de inscrição cria um registro de usuário, mas a pessoa nunca chega a um estado utilizável. Ela pode ver um spinner para sempre, cair numa tela de “verifique seu e-mail” que nunca atualiza, ou voltar para o passo um toda vez que faz login.

A maioria dos fluxos quebra porque assume que o onboarding é uma lista linear e única. Usuários reais atualizam a página quando algo parece lento, pressionam o botão Voltar, abrem o mesmo link no celular depois de começar no laptop, ou voltam no dia seguinte depois que a sessão expirou.

Gatilhos comuns parecem banais, mas custam caro:

  • Um problema de rede faz uma etapa falhar depois que a conta foi criada.
  • A verificação por e-mail chega atrasada, ou o usuário clica num link antigo.
  • Um timeout desloga o usuário no meio do passo e o app perde o rastro.
  • Várias abas submetem a mesma etapa duas vezes e deixam os dados em estado estranho.
  • Um refresh repete uma ação não re-tentável (como cobrar um cartão ou criar um time).

O impacto aparece rápido no suporte: “Não consigo fazer login”, “diz verificado mas ainda bloqueado” ou “não para de pedir para definir a senha de novo”. Mas a perda maior é silenciosa: muita gente não abre chamado. Simplesmente vai embora.

A promessa central de uma máquina de estados de onboarding é simples: aconteça o que acontecer, o usuário sempre pode retomar com segurança. Se uma etapa falha, o app sabe exatamente onde ele está, o que pode fazer a seguir, e o que ainda não é permitido.

Máquinas de estado em linguagem simples

Um estado é só um rótulo claro sobre onde o usuário está no cadastro. Não é um sentimento, e nem o nome de uma página. Pense: account created, email_unverified, profile incomplete, payment pending, onboarded.

Uma checklist linear assume que as pessoas avançam uma vez, em ordem. Na vida real, alguém atualiza durante o pagamento, abre o e-mail de verificação uma hora depois, toca em Voltar, ou tenta de uma segunda aba. Com uma checklist, o sistema muitas vezes não sabe se deve continuar, reiniciar ou bloquear.

Uma máquina de estados de onboarding é um pequeno conjunto de regras que responde a duas perguntas sempre:

  • Em que estado esse usuário está?
  • A partir desse estado, quais movimentos são permitidos (avançar, re-tentar ou voltar)?

Em vez de adivinhar pela “tela atual”, você armazena o estado no registro do usuário e só o atualiza quando uma etapa realmente tem sucesso. Se uma etapa falha, o estado não muda. Se o usuário re-tentar, você roda a mesma etapa de novo com segurança. Se ele voltar depois, você olha o estado armazenado e o manda para o próximo passo válido.

Mantenha simples. Comece com os poucos estados que realmente mudam o que o usuário deve ver a seguir. Evite criar um estado minúsculo para cada clique de botão.

Exemplo: um usuário cria uma conta, depois espera pela verificação de e-mail. Ele atualiza a página, depois tenta entrar de outro dispositivo. Se o estado dele for email_unverified, seu roteamento pode sempre enviá-lo para “re-enviar verificação” e nunca para “completar perfil” até a verificação ser concluída. Uma regra assim evita muitos cadastros presos.

Escolha os estados que importam

Uma máquina de estados funciona melhor quando você registra momentos que mudam o que o usuário pode fazer a seguir. Se você registrar todo clique, gera ruído e casos de borda. Se registrar de menos, não consegue retomar com confiança.

Uma regra prática: crie um estado quando você (1) escreveu algo importante no banco, (2) acionou algo fora do app (como enviar um e-mail), ou (3) a próxima tela deveria ser diferente se o usuário atualizar a página.

A maioria dos fluxos de cadastro pode ser coberta com um pequeno conjunto de estados como:

  • started (conta criada)
  • email_sent (e-mail de verificação solicitado)
  • verified (e-mail verificado)
  • profile_done (campos obrigatórios de perfil preenchidos)
  • completed (onboarding finalizado)

Nem todo estado precisa bloquear o usuário. Decida quais são obrigatórios para avançar e quais são opcionais. A verificação de e-mail pode ser bloqueante por segurança, enquanto adicionar foto de perfil pode ser opcional.

Depois decida o que o app deve mostrar para cada estado. É aí que muitas contas presas são resgatadas.

Se um usuário chegar com state = email_sent, não mostre um erro genérico ou reinicie o cadastro. Mostre uma tela clara de “Verifique sua caixa” com botão de reenvio, opção de alterar o e-mail e uma nota curta sobre possíveis atrasos.

Se state = verified mas o perfil está incompleto, envie-o diretamente para o formulário de perfil e carregue o que já foi salvo.

Defina as transições permitidas

Uma máquina de estados só ajuda se você for rigoroso sobre o que pode acontecer a seguir. Escreva um mapa simples: para cada estado, liste os poucos estados para os quais o usuário pode mover. Mantenha previsível.

Pense em termos de movimentos, não de telas. Uma tela pode mudar sem alterar o estado. Um estado deve mudar apenas quando você tem prova de que a etapa teve sucesso.

Regras que funcionam bem para a maioria dos fluxos:

  • Dê a cada estado de 1 a 3 estados possíveis a seguir.
  • Adicione movimentos de reentrada seguros que não mudam o estado, como “abrir o app de novo” ou “atualizar a página”.
  • Defina estados terminais cedo, como completed (concluído) e disabled (bloqueado por fraude, chargebacks, ou políticas).
  • Trate “esperando” como um estado real, por exemplo email_verification_pending, não apenas uma tela temporária.
  • Decida o que acontece em um movimento inválido (pular etapas, aba antiga submete, duplo clique). Normalmente: ignore a requisição, mostre uma mensagem clara e roteie para o passo correto.

Exemplo: um usuário se inscreve, fica em email_verification_pending e fecha o navegador. Dois dias depois clica no link de verificação. Suas regras devem permitir email_verification_pending -> email_verified, então movê-lo para o próximo passo. Não deve permitir email_verification_pending -> completed só porque um endpoint de “terminar” foi chamado.

Passo a passo: implementar um fluxo de onboarding retomável

Um fluxo retomável faz uma promessa: não importa onde o usuário abandonou (refresh, pagamento falho, e-mail lento), o app sempre pode decidir o que mostrar a seguir.

Comece armazenando uma fonte única de verdade para o progresso. Adicione um campo onboarding_state no registro do usuário, ou crie um registro separado de onboarding se precisar de histórico, re-tentativas ou múltiplas tentativas.

Depois, centralize a lógica de roteamento. Escreva uma função que receba o usuário (e qualquer registro de onboarding) e retorne a próxima tela, como "verify_email" ou "create_workspace". Essa função é o coração da sua máquina de estados de onboarding.

Trate cada etapa como “completa apenas em caso de sucesso”. Se um usuário submete um formulário de perfil, valide e salve primeiro, e só então avance o estado. Se algo falhar, mantenha o estado onde estava para que o usuário possa re-tentar.

Um checklist compacto de implementação:

  • Persista o estado no banco (não só no navegador)
  • Use uma função “decide next step” única em todo lugar
  • Avance o estado apenas depois que a etapa realmente suceder
  • Em cada carregamento do app, roteie com base no estado atual
  • Mantenha um pequeno log de auditoria das mudanças de estado

Esse log de auditoria importa mais do que parece. Quando o suporte vê “Verifiquei o e-mail mas ainda estou bloqueado”, o log deve mostrar se a verificação foi recebida e em que estado a conta está.

Exemplo: Sam se cadastra no mobile, pede verificação por e-mail e depois abre o app no desktop. Se o seu app sempre chamar a mesma função de decisão no carregamento, Sam cai em “Verificado? Sim. Próximo: criar workspace”, em vez de uma página morta ou um loop.

Lidando com refresh, botão Voltar e múltiplas abas

Deixar pronto para produção
A maioria dos projetos fica pronta em 48–72 horas com verificação especializada.

Trate o onboarding como algo que pode ser interrompido a qualquer momento. Pessoas atualizam quando algo parece preso, apertam Voltar quando ficam nervosas e abrem duas abas quando um código não aparece. Se seu fluxo só funciona numa jornada perfeita de aba única, você vai criar contas presas.

Uma regra segura: qualquer página pode recarregar a qualquer momento. Em cada carregamento, pergunte ao servidor onde o usuário está na máquina de estados de onboarding e então renderize a etapa que corresponde àquele estado. Não assuma que a URL é a verdade. URLs são navegação; estado é a fonte de verdade.

Evite manter progresso crítico só no navegador (localStorage, flags em memória, uma variável de estado do React). Isso é fácil de perder em refresh, modo privado ou troca de dispositivo. Armazene o progresso no backend com timestamps, e trate o armazenamento cliente como conveniência.

Múltiplas abas criam problemas de “envio duplo”. Se duas abas tentam completar a mesma etapa, a segunda não deveria quebrar a conta. Responda com “já concluído” e rodeie para frente.

Algumas escolhas de UI também reduzem o pânico:

  • Coloque um ponto de entrada claro “Continuar onboarding” no app após o login.
  • Mostre em que passo eles estão e o que vem a seguir.
  • Se uma etapa está pendente, explique por que e o que resolve.
  • Ofereça “Reiniciar onboarding” apenas se puder fazer isso com segurança sem perda de dados.

Atrasos na verificação por e-mail sem pontos sem saída

Verificação por e-mail é lenta por natureza. Mensagens são atrasadas, vão para spam, ou chegam depois que alguém já fechou a aba. Se seu fluxo assume que a verificação é instantânea, você terá cadastros que nunca se recuperam.

Numa máquina de estados, trate a verificação como trabalho assíncrono. Um padrão limpo separa “enviamos um e-mail” de “o endereço está verificado”. Mantenha um estado como email_sent (ou awaiting_email_verification) e uma flag separada email_verified = true/false. Assim, o usuário pode atualizar, voltar amanhã ou trocar de dispositivo sem cair num beco sem saída.

Na tela de espera, dê ações seguras que não resetem o progresso nem criem duplicatas:

  • Reenviar o e-mail de verificação (limitado por taxa, mesmo usuário, mesmo estado)
  • Rechecar o status de verificação
  • Alterar o endereço de e-mail (com confirmação e novo envio)
  • Usar outro método de login (só se suportado e após confirmar identidade)

Também trate o caso de “e-mail errado” com orientação direta. Uma linha simples como “Usou o endereço errado? Atualize aqui e enviaremos um novo link.” evita muitos tickets.

Cenário: Sam se cadastra no mobile, depois vai ao laptop terminar a configuração. O e-mail de verificação chega atrasado e Sam clica no telefone. Se seu app armazena um estado pendente e checa email_verified no próximo carregamento, Sam pode continuar no laptop na hora. Sem nova conta e sem loop para o passo um.

Torne etapas re-tentáveis e idempotentes

Pare cadastros presos rapidamente
Diagnosticamos por que usuários ficam presos e o que mudar primeiro.

Pessoas clicam duas vezes. Telefones perdem conexão. Navegadores reenviam requisições. Se seu onboarding assume que cada etapa roda exatamente uma vez, você terá contas em estado intermediário.

Re-tentável significa que uma etapa pode ser tentada de novo após uma falha. Idempotente significa que executá-la duas vezes tem o mesmo resultado que executá-la uma vez. Você quer ambos.

Um exemplo simples é “Criar workspace”. Se um usuário toca duas vezes ou a requisição dá timeout e o app re-tenta, você não deve acabar com dois workspaces, dois perfis de cobrança, ou um usuário que não consegue prosseguir porque a segunda requisição falhou.

Um padrão prático é a chave de idempotência. Quando o cliente inicia uma ação (como criar um workspace), gere uma chave única para essa ação e envie com a requisição. No servidor, armazene a chave com o registro resultante. Se a mesma chave reaparecer, retorne o resultado já criado em vez de criar um duplicado.

Regras que previnem a maioria das contas presas:

  • Trate toda ação de “criar” como “criar ou retornar existente”, chaveada por um token de idempotência.
  • Coloque constraints de unicidade no banco (por exemplo, um workspace por usuário durante onboarding).
  • Avance para o próximo estado apenas depois que o banco estiver consistente.
  • Se algo falha no meio da etapa, registre um motivo claro de falha e mantenha o usuário no estado seguro anterior.
  • Torne a UI segura também (desabilite botões durante requisições), mas não dependa só disso.

Um bug comum é atualizar o estado do onboarding primeiro, e depois tentar criar os dados. Se a segunda parte falhar, o usuário parece “além” da etapa mas tem registros faltando.

Erros comuns que criam contas presas

A maioria dos bugs de onboarding preso não é sofisticada. Acontecem quando seu app registra progresso cedo demais, ou confia mais no navegador do que no servidor.

Uma armadilha comum é marcar uma etapa como completa antes que ela realmente seja. Por exemplo, definir payment_complete quando a página de checkout carrega, não quando o provedor de pagamento confirma sucesso. Se a aba atualizar ou o callback falhar, o usuário fica num estado que não pode realmente satisfazer.

Outra causa frequente é confiar em estado do cliente (localStorage, flags em memória, índice de passo no React) para decidir o que vem a seguir. Usuários abrem nova aba, limpam o armazenamento ou trocam de dispositivo, e seu fluxo perde o rumo. Numa máquina de estados, o servidor deve ser a fonte de verdade, e a UI deve refletir isso.

Erros que criam contas pela metade de forma confiável:

  • Progresso codificado em URLs (como /onboarding/step-3) em vez de um status no servidor que o backend aplica.
  • Criar um novo registro de usuário em toda tentativa de verificação falhada, levando a duplicatas e confusão de “conta errada”.
  • Tratamento fraco de atrasos na verificação por e-mail: o app assume que o e-mail será clicado na hora e bloqueia todas as outras ações.
  • Sem caminho de re-tentativa seguro: re-submissão cria um segundo workspace, segunda assinatura ou um perfil quebrado.
  • Sem “escape hatch”: quando algo dá errado, o usuário não tem um jeito claro de pedir ajuda ou resetar.

Exemplo: Sam se cadastra, pede verificação por e-mail, depois atualiza enquanto espera. O frontend avança um contador de passos e assume que Sam está verificado, mas o backend ainda exige verificação. Sam fica redirecionado em círculos.

Verificações rápidas antes de lançar

Antes de liberar, teste o fluxo do jeito que pessoas reais se comportam: atualizam, trocam de dispositivo, clicam Voltar e esperam horas pelo e-mail. Se sua máquina de estados lidar com isso sem confusão, você evitará a maioria dos cadastros presos.

Para cada estado, confirme:

  • Um estado mapeia para uma tela (ou mensagem) clara e uma ação seguinte clara.
  • Atualizar mantém o mesmo progresso e mostra o mesmo “o que vem a seguir”.
  • Re-tentar a mesma etapa duas vezes não cria duplicatas (contas, orgs, pagamentos, convites).
  • Verificação pode ser reenviada, re-checada e completada depois sem reiniciar o cadastro.
  • Suporte pode ver o estado atual e transições recentes (timestamps e erros).

Então faça três testes de “quebre de propósito” num navegador real:

  1. Abra a mesma etapa em duas abas, complete na Aba A, depois recarregue a Aba B. A Aba B deve se atualizar com segurança.

  2. Desconecte a internet no meio de uma etapa, submeta, e tente de novo ao reconectar. Você deve receber o mesmo resultado de sucesso, ou um erro claro com re-tentativa segura.

  3. Atraso na verificação: solicite o e-mail, espere 10 minutos, clique no link e volte ao app. Você deve cair no lugar certo, não numa página em branco ou num loop de “começar de novo”.

Exemplo: resgate realista de um onboarding preso

Reforçar segurança do cadastro
Corrigimos problemas de auth, segredos expostos e gaps comuns de segurança no cadastro.

Maya se inscreve para um trial de SaaS numa tarde corrida. Ela cria a conta, vê “Verifique seu e-mail” e fecha a aba.

No dia seguinte o e-mail de verificação finalmente chega. Ela clica, mas sua sessão original expirou. Num fluxo frágil, isso é um beco sem saída: o app não sabe se ela é nova, verificada ou metade criada.

Com uma máquina de estados, o app consulta o estado atual de Maya e retoma dali. O clique de verificação marca a conta como verificada mesmo se ela estiver deslogada, e então manda para o próximo passo obrigatório.

Minutos depois, Maya abre o app em duas abas. Em uma delas ela preenche o perfil; na outra ainda vê “Complete seu perfil”. Quando ela salva na primeira aba, o estado avança uma vez. A segunda aba atualiza e vê o novo estado em vez de sobrescrever algo.

A experiência de Maya fica consistente:

  • Se inscreve: vê “Verifique seu e-mail” com opção segura de “Reenviar”.
  • Clica num e-mail atrasado: verificação conclui e é solicitada a entrada.
  • Faz login: cai em “Complete o perfil”, não na home.
  • Usa duas abas: a aba atrasada sincroniza com “Já concluído” e segue em frente.

O suporte também vê uma história limpa: estado atual, timestamp da última etapa completa e o último erro (se houver). Isso é a diferença entre consertar em minutos e chutar por horas.

Próximos passos: mapeie seu fluxo e saia do sufoco rápido

Se as pessoas conseguem se cadastrar mas não conseguem terminar de forma confiável, trate o onboarding como um recurso de produto, não apenas um conjunto de telas. Uma máquina de estados simples dá uma fonte única de verdade sobre onde o usuário está e o que pode fazer a seguir.

Comece no papel. Anote cada etapa que seu app espera (criar conta, verificar e-mail, aceitar termos, criar workspace, adicionar pagamento, convidar time). Então agrupe em 5 a 8 estados claros que descrevam progresso, não páginas. Estados devem permanecer estáveis mesmo que a UI mude.

Um plano rápido que costuma compensar:

  • Defina seus estados e seu estado final (por exemplo: SignedUp, EmailPending, Verified, ProfileComplete, Active).
  • Registre cada mudança de estado (quem, de, para, quando e por quê).
  • Acompanhe abandonos por estado, não por página.
  • Adicione uma única regra central de “rotear por estado” para que refreshes e deep links caiam onde devem.
  • Escolha uma etapa frágil (frequentemente atrasos na verificação por e-mail ou pagamento) e torne-a retomável primeiro.

Um hábito pequeno que evita muita dor: quando algo falha, não deixe o usuário em “desconhecido”. Coloque-o de volta em um estado conhecido com uma ação clara a seguir, mesmo que essa ação seja “re-tentar” ou “verifique seu e-mail mais tarde”.

Se você herdou um app gerado por IA onde o signup funciona em demos mas quebra sob re-tentativas, e-mails atrasados e troca de abas, FixMyMess (fixmymess.ai) foca em diagnosticar e reparar esses fluxos: persistência de estado, checagens de transição e lógica de backend que impede que usuários fiquem encalhados no meio do onboarding.

Perguntas Frequentes

O que é uma máquina de estados de onboarding e por que eu usaria uma?

Uma máquina de estados torna o onboarding retomável. Em vez de adivinhar o progresso pela página atual, você armazena um onboarding_state claro no usuário e só o avança quando uma etapa realmente é bem-sucedida, assim atualizações, re-tentativas e troca de dispositivo não prendem as pessoas em loops.

Com quantos estados de onboarding devo começar?

Comece com poucas estados que realmente mudam o que o usuário pode fazer a seguir. A maioria dos produtos cobre o cadastro com cerca de 5–8 estados, como “conta criada”, email_unverified, “perfil incompleto” e “concluído”. Criar um estado para cada clique só adiciona casos de borda sem melhorar a recuperação.

Onde devo armazenar o progresso do onboarding — frontend ou backend?

Persista no backend, normalmente como um campo onboarding_state no registro do usuário. Use armazenamento do navegador só por conveniência — ele some em refresh, modo privado, troca de dispositivo ou em múltiplas abas.

Os usuários podem retomar o onboarding com segurança após fechar a aba ou trocar de dispositivo?

Sim, se você centralizar o roteamento. Em cada carregamento do app após o login, chame uma função “decide next step” que lê o estado armazenado e envia o usuário para a tela correta, mesmo que ele reabra o app amanhã ou em outro dispositivo.

Como evito que atrasos na verificação de e-mail causem pontos sem saída?

Trate a verificação como trabalho assíncrono. Mantenha um estado de espera como email_sent e uma flag separada email_verified, então exiba uma tela “verifique sua caixa” que permita reenvio ou troca de e-mail sem reiniciar a conta.

Como evito que envios duplicados criem duplicatas quando usuários abrem várias abas?

Torne ações críticas idempotentes. Use uma chave de idempotência para operações de “criar” (como criar um workspace ou iniciar cobrança) e imponha unicidade no banco para que um segundo envio devolva o resultado já criado em vez de duplicar ou quebrar o fluxo.

O que deve acontecer se alguém tentar pular etapas ou enviar um formulário antigo?

Não avance o estado até ter prova de que a etapa deu certo, e rejeite ou ignore movimentos inválidos. Se alguém tentar pular etapas, mostre uma mensagem clara e redirecione para o próximo passo correto com base no estado armazenado.

Realmente preciso de um log de auditoria do onboarding?

Sim. Mantenha um pequeno rastro de auditoria de mudanças de estado com timestamps e motivos de falha. Isso transforma “estou verificado mas ainda bloqueado” de um jogo de adivinhação em uma verificação rápida do que aconteceu e em que estado a conta está.

Quais são os testes mais rápidos antes de enviar isso para produção?

Teste o fluxo como usuários reais: refresh no meio da etapa, abrir duas abas completando em uma delas, mudar de dispositivo e atrasar a verificação por e-mail. O objetivo é que todo reload direcione para uma ação válida e re-tentativas não corrompam dados.

Como conserto um fluxo de cadastro que foi construído por uma ferramenta de IA e deixa usuários presos?

Procure por contadores de etapa no frontend, progresso codificado em URLs e atualizações de estado que acontecem antes da escrita no banco ou de uma confirmação externa. Código gerado rapidamente costuma “parecer certo” em demos, mas falha sob re-tentativas, timeouts e callbacks assíncronos; corrigir isso normalmente exige mover lógica de estado e decisão para o servidor e tornar etapas idempotentes.