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:
- 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.
- 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.
- O logo está quebrado em toda a aplicação. Todas as telas referenciam
/logo.svg, mas o app é servido sobbase: "/app/". O caminho absoluto resolve paraorigin/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 otenant_purgedeADMIN_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(viauseCurrentUser). - 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 responde422 ONBOARDING_ALREADY_COMPLETEDe 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_iddo token. - RN7. Logo. Toda referência a
/logo.svgpassa a resolver sob obasedo app (/app/logo.svg), viaimport.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. Responde204 No Contentno 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_codesdele - 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>" (douseCurrentUser) + botão Cancelar cadastro. Ao confirmar num diálogo, chamaDELETE /onboarding, limpa a sessão (ologoutdo authProvider) e navega para/login.providers/onboardingoupages/onboarding/api.ts— funçãocancel()que chama o endpoint novo.- Componente
<Logo>novo (src/components/logo/index.tsx) que renderiza oimgcomsrc={${import.meta.env.BASE_URL}logo.svg}. Substituir os 10src="/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
imgresponde 200, não 404/redirect). - Precisa de um usuário em onboarding não-ativado. O
seed_e2ecria 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¶
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 responde422 ONBOARDING_ALREADY_COMPLETEDe 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 dotenant_purge_servicepara 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 /onboardingno router, com Swagger completo - [x]
apps/api— testes: cancelar com tenant, cancelar sem tenant, bloquear ativado - [x]
apps/web— componente<Logo>e troca dos 10src="/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 testemake lintsem 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:
-
Criar a garantidora já ativa (
create_tenantsetaonboarding_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). -
Logo via constante
LOGO_SRC, não só o componente<Logo>. Entreguei os dois (components/logo/index.tsxexportaLOGO_SRCeLogo), mas apliquei a constante nos 10 sites com<img>já estilizados — trocarsrc="/logo.svg"porsrc={LOGO_SRC}preserva exatamente o estilo de cada um, com menos risco que reescrever cada<img>como<Logo>. -
e2e chega ao onboarding via API, não pela UI. As telas de register/verify-email não têm
data-testidnos 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 nolocalStoragee 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) onamedo register temmin_length=2. -
Reliability do e2e sob a latência do ambiente. Os logins levam 6–9s (proxy + Tailscale + auth check). Ajustes:
auth.setupusawaitForURL(nãoexpect().not.toHaveURLde 5s) e timeout de 120s para os 8 logins; o teste destrutivo de senha (spec 0001) ganhoutest.setTimeout(90s)ewaitForURL.
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.