# Spec 025 — Onboarding contínuo (sem gaps)

**Criado:** 2026-06-17 · **Roger**  
**Sistema:** Wizard VM112 + Desk VM122 + Traefik CT114 + Carbonio VM112  
**Prioridade:** P0  
**Relacionado:** Spec 012 (ticket/lead), Spec 017 (purge), Spec 022 (ACCOUNT_EXISTS ops)

---

## Problema

O wizard trata cada passo como linear e «virgem». Na prática o cliente (ou um técnico) pode:

- Abandonar no meio e voltar dias depois
- Ter conta já criada no Carbonio mas portal/Traefik/DNS incompletos
- Receber `ACCOUNT_EXISTS` no passo 3 e ficar **bloqueado** sem caminho

O Desk Spec 022 resolve o caso **para técnicos**; o cliente no wizard continua preso.

---

## Princípio — «Estado real, não passo cego»

Cada etapa consulta o **estado efectivo** no servidor antes de mostrar UI ou falhar:

| Camada | Fonte de verdade | Acção se já feito |
|--------|-------------------|-------------------|
| Domínio Carbonio | `domain_exists` | Avançar |
| DNS mail | `dns_verify` | Mostrar OK + continuar |
| Conta admin | `account_exists` + `domain_registry` | **Reconciliar** (não falhar) |
| Portal admin | `is_portal_admin` | Registar se em falta |
| Webmail gate | Traefik dynamic | `sync_webmail_gate` |
| Infra completa | `infrastructure.get_status` | Painel «pendente» com checklist |

**Regra de ouro:** `POST /account/create` é **idempotente** — conta existente → reconciliação + passos pós-criação, HTTP 200 com `reconciled: true`.

---

## Fluxo alvo (cliente)

```mermaid
flowchart LR
  A[Domínio] --> B[DNS]
  B --> C{Dados conta}
  C -->|não existe| D[Confirmar + criar]
  C -->|já existe| E[Continuar activação]
  D --> F[Reconciliar pós-criação]
  E --> F
  F --> G{Infra OK?}
  G -->|sim| H[Concluído — webmail]
  G -->|não| I[Checklist infra + polling]
  I --> H
```

### Passo 2 (UI)

- Consultar `GET /onboarding/account/status?domain=&local_part=`
- Se `exists: true` → banner **«Conta já existe — vamos concluir a activação»** (não «NÃO existe»)
- Botão: **«Continuar activação →»** em vez de só «Rever e criar»

### Passo 3 (UI)

- Se conta existe → **«Continuar activação»** (mesmo endpoint, reconcilia)
- Opcional: actualizar senha via `zmprov sp` se utilizador confirmou nova senha

### Erros que **não** bloqueiam

| Situação | Comportamento |
|----------|---------------|
| ACCOUNT_EXISTS | Reconciliar → 200 |
| DNS incompleto | Passo 4 com `finishPendingInfra` (já existe) |
| Traefik/LE pendente | Polling infra + guia no card Resumo |
| API lenta (>30s) | Timeout frontend 120s + mensagem clara (feito VM122) |

### Erros que **bloqueiam** (com saída clara)

| Situação | Comportamento |
|----------|---------------|
| Domínio inválido / blacklist | Mensagem + voltar passo 1 |
| Carbonio indisponível (OOM) | «Servidor ocupado — tente em 2 min» + webhook ops |
| Senha &lt; 8 chars | Validação local |

---

## API VM112 (novo / alterado)

### `GET /onboarding/account/status`

```json
{
  "email": "admin@exemplo.com",
  "exists": true,
  "portal_admin": true,
  "domain_registered": true
}
```

### `POST /onboarding/account/create` (idempotente)

Resposta quando conta já existia:

```json
{
  "email": "admin@exemplo.com",
  "reconciled": true,
  "account_verified": true,
  "needs_review": false,
  "infrastructure": { "ready": false, "steps": [...] }
}
```

Webhook: `account.reconciled` (novo) ou `account.created` com `reconciled: true`.

---

## Desk / Ops (sem duplicar Spec 022)

| Evento | Desk |
|--------|------|
| `account.reconciled` | Nota no ticket — «Cliente retomou conta existente» |
| `onboarding.failed` + ACCOUNT_EXISTS | Spec 022 — só se reconciliação **impossível** (conta órfã de outro email) |
| `onboarding.completed` + infra não ready | Ticket mantém `crm_track: infra_pending` |

---

## Fases de entrega

### Fase 1 — P0 (esta spec, 2026-06-17)

- [x] Spec documentada
- [ ] Backend: idempotência `create_account` + `GET account/status`
- [ ] Frontend: banners dinâmicos passos 2–3
- [ ] Deploy VM112 + smoke `exuberanti.com.br`

### Fase 2 — Resiliência

- [ ] Sessão wizard: ao reabrir URL, `resume` consulta estado e salta passos feitos
- [ ] VM112 RAM → 16 GB (Proxmox)
- [ ] Validação Traefik YAML (router sem `rule` = build fail)

### Fase 3 — Processo comercial completo

- [ ] Spec 012 abandonos → lead
- [ ] Spec 023 billing no Desk
- [ ] Wizard dedicado VM124 (Spec 018 fase 3)

---

## Critério de aceite (exuberanti.com.br)

1. Cliente com `teste001@exuberanti.com.br` já no Carbonio abre wizard no passo 2 → vê «conta já existe»
2. Clica «Continuar activação» → passo 4 sem erro 400
3. Portal admin registado; gate Traefik sincronizado
4. Ticket Desk recebe evento; Bloqueios Carbonio **não** aparece para este caso

---

## Passo Concluído — CTAs (UI)

**Adicionado:** 2026-06-19 · correção bug rota `/admin`

No passo **Concluído** (índice 4), três acções **independentes**:

| CTA | Comportamento | Não confundir com |
|-----|---------------|-------------------|
| **Abrir webmail** | Nova aba → `https://mail.{domínio}/` | — |
| **Portal de gerenciamento** | Nova aba → `/admin` (**DomainAdmin**) | ≠ wizard Concluído |
| **Finalizar** | Redirect `ligbox.com.br/#self-service` + `clearOnboardingSession()` | ≠ `/admin` |

### Build wizard-only (`onboard.ligbox.com.br`)

O entry **`main-wizard.jsx`** DEVE registar `/admin` → `DomainAdmin` (não só `RouterApp.jsx` da landing).

**Regressão conhecida (corrigida 2026-06-19):** sem esta rota, `/admin` re-renderizava `<App />` e o utilizador via o passo Concluído outra vez.

**KB:** `docs/anais-referencia/20260619_WIZARD_PORTAL_ADMIN_ROUTE_FIX.md`  
**Mirror código:** `deploy/vm112-wizard/frontend/src/main-wizard.jsx`

