Pular para conteúdo

0002 — Onboarding: usuário conectado, cancelamento e logo

Status CONCLUÍDA
Branch spec/0002-onboarding-e-logo
Criada em 2026-07-24
Apps afetados api · web · e2e

Status: RASCUNHO → APROVADA → EM IMPLEMENTAÇÃO → CONCLUÍDA

1. Problema

Três incômodos, dois no onboarding e um transversal:

  1. O onboarding não diz quem está logado. Quem retoma o fluxo (ou senta numa máquina compartilhada) não vê com qual conta está conectado — nome nem e-mail.
  2. Não há como desistir do onboarding. Um cadastro iniciado por engano, ou com o e-mail errado, fica preso: o usuário não consegue apagar e recomeçar sozinho.
  3. O logo está quebrado em toda a aplicação. Todas as telas referenciam /logo.svg, mas o app é servido sob base: "/app/". O caminho absoluto resolve para origin/logo.svg (sem /app), cai no catch-all do Caddy e não carrega. São 10 pontos afetados (login, register, onboarding, accept-invite, verify-email, forgot/reset-password, sidebar-title).

2. Objetivo

No onboarding, o usuário vê "Conectado como Nome \<email>" e tem um botão para cancelar — que apaga o cadastro incompleto e a conta e o devolve ao login. E o logo volta a aparecer em todas as telas.

3. Fora de escopo

  • Cancelar/excluir um tenant já ativado. Cancelamento só vale enquanto o onboarding não foi concluído (onboarding_completed_at IS NULL). Tenant ativo é outra história (envolve o tenant_purge de ADMIN_GLOBAL).
  • Trocar de conta sem cancelar ("not you? logout" que preserva o cadastro). O botão aqui é cancelamento destrutivo; logout puro não entra nesta spec.
  • Redesenho visual do onboarding. Só acrescenta o cabeçalho de identificação e o botão; o resto do layout fica como está.
  • Recuperação do que foi cancelado. Cancelou, apagou — não há lixeira.

4. Regras de negócio

  • RN1. O onboarding exibe o nome e o e-mail do usuário autenticado, obtidos de GET /auth/me (via useCurrentUser).
  • RN2. Cancelar é destrutivo e self-service. Apaga a garantidora em criação e a conta do próprio usuário. Não exige a chave de confirmação do tenant_purge — é o usuário apagando o próprio cadastro incompleto.
  • RN3. Só antes de ativar. Cancelar só é permitido se o tenant do usuário não foi ativado (onboarding_completed_at IS NULL). Se já ativou, a API responde 422 ONBOARDING_ALREADY_COMPLETED e nada é apagado.
  • RN4. Escopo do que se apaga:
  • Se o usuário já criou o tenant (StepTenant feito): apaga todos os dados escopados ao tenant (settings, imobiliárias e usuários de agência criados no fluxo, etc.), todos os usuários do tenant e o tenant. Segue a ordem de FK do tenant_purge_service (billings → … → persons/properties → users → tenant).
  • Se ainda não criou o tenant (user.tenant_id IS NULL): apaga só a conta do usuário e seus códigos de verificação.
  • RN5. Como o usuário apaga a própria conta, a sessão deixa de valer na hora. O frontend limpa os tokens e vai para /login.
  • RN6. Cancelar exige apenas estar autenticado no próprio onboarding — não é uma permissão RBAC concedível a terceiros. Um usuário nunca apaga o tenant de outro: o alvo é sempre o tenant_id do token.
  • RN7. Logo. Toda referência a /logo.svg passa a resolver sob o base do app (/app/logo.svg), via import.meta.env.BASE_URL. Centralizar num componente <Logo> para não repetir o cálculo em 10 lugares.

5. Impacto por app

apps/api

  • Endpoint novo: DELETE /api/v1/onboarding — cancela o onboarding do usuário autenticado. Sem body. Responde 204 No Content no sucesso.
  • Service (onboarding_service.cancel(current_user)):
  • valida RN3 (tenant não ativado) → senão BusinessRuleError("ONBOARDING_ALREADY_COMPLETED")
  • se tem tenant: reusa a sequência de deleção do tenant_purge_service (extrair a parte de exclusão para uma função reutilizável, sem a checagem de chave/ADMIN_GLOBAL)
  • se não tem tenant: apaga o usuário + verification_codes dele
  • Sem migration — nenhuma coluna nova.
  • Docstrings e Swagger — summary, description (deixar explícito que é destrutivo e só antes da ativação), response_description.

apps/web

  • pages/onboarding/index.tsx — cabeçalho novo: "Conectado como Nome \<email>" (do useCurrentUser) + botão Cancelar cadastro. Ao confirmar num diálogo, chama DELETE /onboarding, limpa a sessão (o logout do authProvider) e navega para /login.
  • providers/onboarding ou pages/onboarding/api.ts — função cancel() que chama o endpoint novo.
  • Componente <Logo> novo (src/components/logo/index.tsx) que renderiza o img com src={${import.meta.env.BASE_URL}logo.svg}. Substituir os 10 src="/logo.svg" por ele: login (2×), register (2×), onboarding, accept-invite, verify-email, forgotPassword, resetPassword, components/sidebar-title.

data-testid (obrigatórios):

onboarding-user-info        onboarding-cancel-button
onboarding-cancel-dialog    onboarding-cancel-confirm
onboarding-cancel-cancel    onboarding-cancel-error

apps/e2e

  • Spec cobrindo: (a) o onboarding mostra nome+e-mail do usuário; (b) cancelar apaga e volta ao login; (c) o logo carrega (o img responde 200, não 404/redirect).
  • Precisa de um usuário em onboarding não-ativado. O seed_e2e cria um tenant já ativo; adicionar um usuário/tenant em onboarding dedicado ao teste de cancelamento (destrutivo e isolado — o teste apaga o que criar).

6. Contrato de API

DELETE /api/v1/onboarding
Authorization: Bearer <token>

204 No Content — cadastro incompleto e conta apagados.

Erros:

HTTP Código Quando
422 ONBOARDING_ALREADY_COMPLETED O tenant do usuário já foi ativado (RN3)
401 NOT_AUTHENTICATED / SESSION_EXPIRED Sem token válido

7. Critérios de aceite

  • [x] CA1. No onboarding, o nome e o e-mail do usuário autenticado aparecem (data-testid="onboarding-user-info").
  • [x] CA2. Dado um usuário em onboarding não-ativado, quando confirma o cancelamento, então a conta e o tenant incompleto são apagados e ele é levado a /login.
  • [x] CA3. Após cancelar, o usuário não consegue mais logar com aquele e-mail (a conta não existe).
  • [x] CA4. Dado um tenant já ativado, quando se chama DELETE /onboarding, então a API responde 422 ONBOARDING_ALREADY_COMPLETED e nada é apagado.
  • [x] CA5. O logo carrega (HTTP 200) em login, register e onboarding — não 404 nem redirect.
  • [x] CA6. O diálogo de cancelamento pede confirmação antes de apagar (não apaga no primeiro clique).

8. Plano de implementação

Ordem obrigatória: api → web → e2e.

  • [x] apps/api — extrair a sequência de deleção do tenant_purge_service para uma função reutilizável (sem a checagem de chave/ADMIN_GLOBAL)
  • [x] apps/api — onboarding_service.cancel() com as regras RN3/RN4
  • [x] apps/api — DELETE /onboarding no router, com Swagger completo
  • [x] apps/api — testes: cancelar com tenant, cancelar sem tenant, bloquear ativado
  • [x] apps/web — componente <Logo> e troca dos 10 src="/logo.svg"
  • [x] apps/web — cabeçalho de usuário + botão/diálogo de cancelar no onboarding
  • [x] apps/web — cancel() na api do onboarding; limpar sessão e ir a /login
  • [x] apps/e2e — seed de usuário em onboarding + spec de cancelamento e logo
  • [x] make test e make lint sem apontamento novo nos arquivos tocados
  • [x] validar no browser: logo aparece, cancelamento leva ao login

9. Decisões em aberto

Nenhuma — as três dúvidas de escopo (o que "cancelar" faz, destino, cabeçalho) foram resolvidas na entrevista de 2026-07-24:

  • Cancelar = apagar cadastro incompleto + conta (só antes de ativar).
  • Destino após cancelar = /login.
  • Cabeçalho = nome + e-mail com o botão de cancelar ali.

10. Registro

Implementada em 2026-07-24. Verificação: 150 testes de API (3 novos em test_onboarding_cancel.py), tsc do web limpo, e o e2e completo passando pelo test:fast — incluindo o fluxo real de cancelamento e a checagem do logo no browser.

Divergências e descobertas:

  1. Criar a garantidora já ativa (create_tenant seta onboarding_completed_at). Consequência: pelo fluxo real, a tela de onboarding só existe no estado "verificado, sem tenant". Então o cancelamento na prática cai sempre no caminho "sem tenant → apaga a conta" (RN4, 2º bullet). O caminho de cascade do tenant (RN4, 1º bullet) é defensivo — se um tenant existe, ele está ativado, e o cancelamento retorna 422. Os três casos estão cobertos por teste mesmo assim (o cascade é exercitado montando o estado "tenant não-ativado" à mão).

  2. Logo via constante LOGO_SRC, não só o componente <Logo>. Entreguei os dois (components/logo/index.tsx exporta LOGO_SRC e Logo), mas apliquei a constante nos 10 sites com <img> já estilizados — trocar src="/logo.svg" por src={LOGO_SRC} preserva exatamente o estilo de cada um, com menos risco que reescrever cada <img> como <Logo>.

  3. e2e chega ao onboarding via API, não pela UI. As telas de register/verify-email não têm data-testid nos campos, então dirigir por elas seria frágil e fora do escopo. O teste registra + pega o OTP no Mailpit + verifica pela API, injeta os tokens no localStorage e dirige só a tela de onboarding (que tem os testids). Dois detalhes que só apareceram rodando: (a) o OTP precisa ser extraído após remover as tags do HTML — um color hex como #374151 é 6 dígitos e casava antes do código real; (b) o name do register tem min_length=2.

  4. Reliability do e2e sob a latência do ambiente. Os logins levam 6–9s (proxy + Tailscale + auth check). Ajustes: auth.setup usa waitForURL (não expect().not.toHaveURL de 5s) e timeout de 120s para os 8 logins; o teste destrutivo de senha (spec 0001) ganhou test.setTimeout(90s) e waitForURL.

Nota lateral: test_operations_center::test_overview_tenant_isolation é um teste flaky pré-existente (depende de ordenação) — falhou uma vez sob a nova ordem de testes e passou em todas as demais execuções. Não tem relação com esta spec; fica como dívida a investigar.