obsidian-vault/ligbox-ops-platform/specs/016-onboard-self-service-prefill/spec.md
2026-06-19 17:26:42 +00:00

6.3 KiB

Feature Specification: Pré-preenchimento Self-Service → Wizard (016)

Criado: 2026-06-16
Solicitado por: Roger
Status: Implementação
Prioridade: P0 (regressão UX onboarding)
Sistema: Portal VM112 (ibytera-mail-portal) — wizard /onboard
Relacionado: Spec 012 (ticket no onboarding.started), chat bruto CHAT_BRUTO_ONBOARD_INFRA_SUPORTE_20260603


Resumo

Quando o utilizador preenche o card Self-Service na landing (hero) ou chega via «Criar Meu Servidor Agora», os dados declarados devem propagar automaticamente para o wizard de onboarding, em especial no passo «Conta admin» (criação da conta do administrador no Carbonio).

Regra de ouro: dados do Self-Service têm prioridade sobre estado antigo do wizard guardado em sessionStorage (domínio/localPart de sessão anterior não pode apagar o que o utilizador acabou de declarar na landing).


Origem dos dados (landing)

Campo Self-Service Label UI Chave persistência
E-mail corporativo do administrador admin@suaempresa.com.br localStorage.ligbox_planned_email
Senha campo Senha sessionStorage.ibytera_onboard_admin_password
Login portal telefone/nickname sessionStorage (portal login id — fora do escopo conta admin)

Botões equivalentes: card Self-Service (hero) e CTA «Criar Meu Servidor Agora» (scroll para o mesmo card).

Fluxos que disparam pré-preenchimento:

  1. Registo → 2FA TOTP → finishOnboarding() → redirect /onboard
  2. Login (ou login + 2FA) → redirect /onboard

Destino no wizard (passo Conta admin — step 2)

Ao abrir ou regressar a este passo, três valores devem estar preenchidos:

# Origem Self-Service Campo wizard Exemplo
1 E-mail corporativo completo localPart + domain (parte local + domínio) admin + suaempresa.com.br
2 Domínio extraído do e-mail domain (passo 0 também) suaempresa.com.br
3 Senha password (mascarada, reutilização) via AdminPasswordField

Passo 0 (Domínio): se ligbox_planned_email existir, o campo domínio deve iniciar com o domínio do e-mail e mostrar banner informativo.

Passo 3 (Rever e criar): senha em modo confirm — mascarada, reutilizada; revelar com olho exige re-autenticação portal (2FA).


Comportamento funcional

FR-001 — Persistência imediata no registo

Após registo portal com sucesso (antes do TOTP), gravar:

  • setAdminPassword(password)
  • localStorage.ligbox_planned_email = e-mail corporativo normalizado (lowercase, trim)

FR-002 — Prioridade Self-Service sobre wizard state

Se ligbox_planned_email ou senha em sessionStorage existirem ao montar /onboard:

  • Ignorar domain / localPart / notifyEmail antigos de ibytera_onboard_wizard_state para pré-preenchimento
  • Aplicar valores derivados do Self-Service

FR-003 — Sincronização no mount

useEffect no wizard reaplica pré-preenchimento se o utilizador navegou landing → onboard na mesma aba.

FR-004 — Senha não vai para wizard state JSON

Senha permanece apenas em sessionStorage (onboardPassword.js) — nunca em saveWizardState().

FR-005 — Revelação de senha

Ícone olho → modal re-autenticação portal (PasswordRevealAuth); visível 30s; opção «Definir senha diferente».

FR-006 — Sem Self-Service

Utilizador entra directo em /onboard sem landing: campos vazios ou defaults (admin, domínio manual) — sem regressão.


Critérios de aceitação

  1. Given registo com admin@empresa.com + senha MinhaSenh@8 + TOTP concluído, When abre /onboard passo Conta admin, Thenadmin@empresa.com, domínio empresa.com, senha reutilizada (mascarada).
  2. Given wizard state antigo com domínio outro.com em sessionStorage, When novo registo com admin@novo.com, Then domínio no wizard é novo.com (não outro.com).
  3. Given login com planned_corporate_email da API, When redirect /onboard, Then campos pré-preenchidos.
  4. Given F5 na mesma aba após Self-Service, When wizard recarrega, Then e-mail/domínio/senha mantêm-se (localStorage + sessionStorage).
  5. Given nova aba sem storage, When /onboard directo, Then sem pré-preenchimento (comportamento legítimo).

Implementação (referência código VM112 — /opt/ligbox-wizard)

Ficheiro Função
frontend/src/sessionPersist.js beginOnboardingForEmail(), syncWizardWithPlannedEmail(), applyPlannedEmailPrefill(), loadWizardStateForOnboard()
frontend/src/portalAuth.js setPortalOnboardCredentials()sessionStorage.ligbox_onboard_password
frontend/src/onboardPassword.js alias leitura/escrita na mesma chave ligbox_onboard_password (wizard)
frontend/src/ligbox/components/SelfServiceCard.jsx registo/login/TOTP → beginOnboardingForEmail + credenciais
frontend/src/App.jsx loadWizardStateForOnboard() no init + useEffect de sync
frontend/src/AdminPasswordField.jsx senha mascarada + reveal com verifyStepUp (2FA)

Fora de escopo

  • Enviar senha para VM122 / webhooks / Desk (nunca)
  • Pré-preencher a partir de cookies cross-domain
  • Sincronizar com Carbonio antes de POST /account/create

Regressão conhecida (corrigida nesta spec)

Causas identificadas (2026-06-16):

  1. Wizard state antigo em sessionStorage (ligbox_onboard_wizard_state) mantinha domain/localPart de sessão anterior e bloqueava o e-mail novo do Self-Service.
  2. Senha gravada em chave errada (ibytera_onboard_admin_password em código de dev) enquanto o portal em produção lia ligbox_onboard_password.
  3. E-mail só ia para localStorage após TOTP completo — registo sem beginOnboardingForEmail() deixava o wizard sem âncora.

Fix aplicado:

  • syncWizardWithPlannedEmail() + ligbox_wizard_planned_email como âncora — descarta wizard stale quando o e-mail muda.
  • loadWizardStateForOnboard() aplica sempre domínio/localPart/notify a partir de ligbox_planned_email.
  • SelfServiceCard chama beginOnboardingForEmail() + setPortalOnboardCredentials() no registo, login e fim do TOTP.