# Feature Specification: Funil de Onboarding Completo (004) **Feature Branch**: `004-onboard-funnel-events` **Created**: 2026-06-08 **Status**: Draft **Input**: Completar a integração VM112 → VM122 com todos os marcos do wizard de onboarding, timeline por sessão e widget de funil no Dashboard Ops — continuando a feature 001 (Phase D). **Depends on**: `001-webhook-vm112-integration` (MVP `account.created` entregue) ## User Scenarios & Testing *(mandatory)* ### User Story 1 — Visibilidade do funil activo (Priority: P1) A equipa ops vê no Dashboard quantos onboardings estão em cada fase (domínio validado, DNS aplicado, conta criada, concluído) sem abrir o portal VM112. **Why this priority**: Responde directamente à expectativa de um painel com informação útil — mostra onde clientes travam. **Independent Test**: Emitir eventos simulados para 3 sessões em fases diferentes e confirmar contadores correctos no widget funil. **Acceptance Scenarios**: 1. **Given** 2 sessões com `domain.validated` e 1 com `onboarding.completed`, **When** o dashboard carrega, **Then** o widget mostra contagens por fase actual. 2. **Given** sessão sem eventos há > 24h, **When** listada no funil, **Then** aparece marcada como `stale` (inactiva). --- ### User Story 2 — Timeline por sessão no ticket (Priority: P1) Ao abrir um ticket de onboarding, a equipa vê a sequência cronológica de eventos da mesma `session_id` (validação, DNS, conta, infra, conclusão). **Why this priority**: Diagnóstico rápido quando onboarding falha a meio — sem cruzar logs manualmente. **Independent Test**: Enviar 5 eventos com mesmo `session_id` e confirmar timeline ordenada no detalhe do ticket. **Acceptance Scenarios**: 1. **Given** ticket criado por `account.created`, **When** chegam `domain.validated` e `dns.applied` com mesma sessão, **Then** aparecem na timeline do ticket (notas/eventos). 2. **Given** `onboarding.completed` para sessão existente, **When** processado, **Then** actualiza ticket (nota ou tag `ready`) sem criar ticket duplicado. --- ### User Story 3 — Portal emite marcos do wizard (Priority: P1) O portal VM112 emite webhooks para cada marco importante do funil, de forma não-bloqueante (mesmo padrão `ops_webhook.py`). **Why this priority**: Sem emissor completo, o Ops não tem dados para funil nem timeline. **Independent Test**: Percorrer wizard em staging e confirmar eventos no registo Ops por ordem. **Acceptance Scenarios**: 1. **Given** `POST /validate-domain` com sucesso, **When** resposta 200, **Then** emite `domain.validated`. 2. **Given** `POST /dns/cloudflare/apply` com registos aplicados, **When** sucesso, **Then** emite `dns.applied`. 3. **Given** provision infra concluído após criar conta, **When** sucesso, **Then** emite `infra.synced`. 4. **Given** fluxo completo sem erro, **When** resposta final ao cliente, **Then** emite `onboarding.completed`. 5. **Given** falha crítica (ex. CarbonioError em create account), **When** HTTP 400, **Then** emite `onboarding.failed` com motivo em `data.error`. --- ### User Story 4 — Sessão iniciada (Priority: P2) Quando o cliente escolhe/valida domínio pela primeira vez na sessão, o Ops regista `onboarding.started` para contagem de funil desde o início. **Why this priority**: Permite taxa de conversão início → conclusão; não bloqueia MVP do funil. **Independent Test**: Primeira validação de domínio numa sessão nova gera evento `onboarding.started` uma única vez. **Acceptance Scenarios**: 1. **Given** nova `session_id`, **When** primeiro `validate-domain` OK, **Then** emite `onboarding.started` (idempotente — não repete na mesma sessão). --- ### Edge Cases - Eventos fora de ordem (ex. `onboarding.completed` antes de `dns.applied`): Ops aceita e ordena por timestamp na timeline. - Mesmo evento repetido (retry portal): idempotência `event + session_id + domain` — sem duplicar timeline. - Sessão sem `session_id`: evento aceite, funil agrupa em `unknown`. - Domínio com múltiplas sessões: funil mostra sessões separadas; tickets ligados por `session_id`. - Ops offline: portal continua; activity log regista falha webhook. ## Requirements *(mandatory)* ### Functional Requirements - **FR-001**: Portal DEVE emitir: `onboarding.started`, `domain.validated`, `dns.applied`, `infra.synced`, `onboarding.completed`, `onboarding.failed` via `ops_webhook.emit_event`. - **FR-002**: Emissão DEVE ser non-blocking (`BackgroundTasks` ou equivalente) — nunca alterar resposta HTTP ao cliente. - **FR-003**: Ops DEVE persistir todos os eventos em `webhook_events` (já existente). - **FR-004**: Ops DEVE expor `GET /api/v1/onboard/funnel` com contagens por fase e lista de sessões activas (últimas 48h). - **FR-005**: Ops DEVE expor `GET /api/v1/onboard/sessions/{session_id}/timeline` com eventos ordenados. - **FR-006**: Ticket existente (mesma `session_id`) DEVE enriquecer-se com novos eventos — notas internas ou campo `timeline` no detalhe. - **FR-007**: Apenas `account.created` e `onboarding.failed` criam ticket novo; restantes eventos actualizam contexto. - **FR-008**: Idempotência OBRIGATÓRIA: `(event_type, session_id, domain)` — já implementada em 001, manter. - **FR-009**: UI Dashboard DEVE incluir widget **Funil Onboarding** (contadores + link sessões). - **FR-010**: UI Ticket DEVE mostrar timeline de eventos da sessão. - **FR-011**: Comunicação LAN-only (`10.10.10.112` → `10.10.10.122`). ### Key Entities - **Funnel Session**: `session_id`, `domain`, `current_stage`, `last_event_at`, `events[]`. - **Funnel Stage**: ordered enum — `started` → `domain_validated` → `dns_applied` → `account_created` → `infra_synced` → `completed` | `failed`. - **Timeline Entry**: event_type, timestamp, data snapshot, source `vm112-onboard`. ## Success Criteria *(mandatory)* - **SC-001**: 100% dos marcos do wizard (6 tipos) geram evento Ops quando portal e Ops disponíveis. - **SC-002**: Equipa identifica fase actual de um onboarding em < 10s via Dashboard (sem SSH no portal). - **SC-003**: Timeline completa visível no ticket para qualquer sessão com ≥ 2 eventos. - **SC-004**: Zero tickets duplicados por sessão além de `account.created` + opcional `onboarding.failed`. - **SC-005**: Falha Ops não afecta taxa de sucesso do wizard (0 regressões em testes E2E portal). ## Assumptions - Receptor `/api/v1/webhooks/onboard` e `ops_webhook.py` já funcionais (001). - `session_id` disponível via `bind_onboarding_session` em todos os routers de onboarding. - Fases do funil mapeiam 1:1 aos passos actuais do wizard ibytera-mail-portal. - `onboarding.completed` dispara no fim de `create_account` após infra (mesmo request). - UI Desk v2 existente — extensão de dashboard e ticket detail, não rebuild. ## Dependencies - Constitution v1.0.0 (separação VM112/122, LAN-only). - Feature 001 deployada em VM112 + VM122. - Feature 002 não conflita (origens diferentes). ## Out of Scope - Notificações push (ntfy) em mudança de fase. - Auth/RBAC no Desk (feature 003 futura). - Sincronização Ops → Portal. - Expor webhook onboard na internet pública. - Kanban / SLA (feature desk futura).