# 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` **Backlog agregado:** [BACKLOG-2FA-BACKUP-EXPORT.md](BACKLOG-2FA-BACKUP-EXPORT.md) — export códigos 2FA (Roger 2026-06-19) --- ## 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, **Then** vê `admin@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) | | `frontend/src/PortalTotpSetup.jsx` | Modal 2FA + ecrã «Salve estes códigos» | --- ## Backlog agregado (não implementado) | Item | Documento | Status | |------|-----------|--------| | Export códigos backup 2FA (local, Drive, Telegram, WhatsApp, cofre) | [BACKLOG-2FA-BACKUP-EXPORT.md](BACKLOG-2FA-BACKUP-EXPORT.md) | 📋 Backlog P2 | | Tarefas | [tasks.md](tasks.md) | 0/16 concluídas (export) | **Pedido Roger (2026-06-19):** dar ao utilizador opções para guardar os 10 códigos de recuperação além de apenas visualizar no ecrã. --- ## 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.