# Implementation Plan: Funil de Onboarding Completo (004)

**Branch**: `004-onboard-funnel-events` | **Date**: 2026-06-08 | **Spec**: [spec.md](./spec.md)

## Summary

Completar o funil VM112 → VM122: portal emite 6 tipos de evento adicionais; Ops agrega por `session_id`, expõe API de funil/timeline, e UI Dashboard mostra pipeline activo. Continuação directa da Phase D da feature 001.

## Technical Context

**Language/Version**: Python 3.11+ (portal VM112, Ops VM122)

**Primary Dependencies**: FastAPI, httpx, sqlite3, vanilla JS (frontend existente)

**Storage**: SQLite — novas tabelas opcionais `onboard_sessions` (cache funil) ou query agregada sobre `webhook_events` + `tickets.payload`

**Testing**: `scripts/verify-funnel-webhook.sh` + E2E wizard staging

**Target Platform**: VM112 `:8090` → VM122 `:8080` LAN

**Performance Goals**: Widget funil < 500ms; emissão webhook não adiciona > 200ms perceived latency

**Constraints**: Non-blocking portal; idempotência; LAN-only

## Constitution Check

| Princípio | Status |
|-----------|--------|
| IV. Mail vs Ops | ✅ PASS |
| VII. Spec-Driven | ✅ PASS |
| IX. YAGNI | ✅ PASS — reutilizar `webhook_events`, sem Postgres |

## Project Structure

```text
specs/004-onboard-funnel-events/
├── spec.md
├── plan.md
├── research.md
├── contracts/webhook-funnel-events.md
├── checklists/requirements.md
└── tasks.md

# VM112
backend/app/routers/onboarding.py    # hooks emit_event em 5 endpoints
backend/app/services/ops_webhook.py  # (sem alteração estrutural)

# VM122
api/app/main.py                      # funnel + timeline endpoints; ticket enrichment
frontend/assets/app.js               # widget funil + timeline ticket
frontend/assets/styles.css           # estilos funil
scripts/verify-funnel-webhook.sh     # teste simulado multi-evento
```

## Phase 0: Research Summary

Ver [research.md](./research.md).

## Phase 1: Design

### Mapeamento evento → hook portal

| Evento | Endpoint / momento |
|--------|-------------------|
| `onboarding.started` | `POST /validate-domain` — 1ª vez por sessão |
| `domain.validated` | `POST /validate-domain` — sucesso |
| `dns.applied` | `POST /dns/cloudflare/apply` — `applied.length > 0` |
| `account.created` | `POST /account/create` — ✅ já existe |
| `infra.synced` | após `infrastructure.provision` OK em create_account |
| `onboarding.completed` | fim de create_account (antes do return) |
| `onboarding.failed` | except CarbonioError / HTTP 400 críticos |

### API Ops (novos endpoints)

```
GET /api/v1/onboard/funnel
  → { stages: { started: N, domain_validated: N, ... }, active_sessions: [...] }

GET /api/v1/onboard/sessions/{session_id}/timeline
  → { session_id, domain, events: [{ event_type, created_at, data }] }

GET /api/v1/desk/tickets/{id}  (extend)
  → + timeline: [...]  (eventos mesma session_id do ticket)
```

### Lógica ticket

- `_process_ingress` / `webhook_onboard`: após insert evento, se ticket existente para `session_id`, append nota timeline (JSON em payload ou tabela `ticket_events`).
- `onboarding.completed`: PATCH interno ticket → tag/note `ready_for_ops`.
- `TICKET_EVENTS`: manter `account.created`, `onboarding.failed`; outros só timeline.

## Implementation Phases

### Phase A — Ops API funil (~2h)

1. Função `_session_timeline(session_id)` query `webhook_events`
2. Função `_funnel_summary()` — agrega última fase por sessão (48h)
3. Endpoints GET funnel + timeline
4. Enriquecer `get_ticket` com timeline
5. Actualizar `TICKET_EVENTS_BY_SOURCE` se necessário

### Phase B — Portal emissor (~2h)

1. Helper `_emit_once(session_id, event, ...)` com flag in-memory ou check activity log (preferir idempotência no Ops)
2. Hooks nos 5 pontos do router onboarding
3. `onboarding.failed` no except de create_account

### Phase C — UI Desk (~2h)

1. Dashboard: card funil com barras/contadores por fase
2. Ticket detail: secção timeline vertical
3. Link sessão → filtro eventos

### Phase D — Validação (~1h)

1. Script verify-funnel-webhook.sh
2. Deploy VM112 + VM122
3. Teste wizard domínio teste

## Risk & Mitigation

| Risco | Mitigação |
|-------|-----------|
| Eventos duplicados validate-domain | Idempotência Ops + emit started só 1x via cache sessão portal |
| Payload grande | Truncar data no ticket; payload completo em webhook_events |
| Funil lento com muitos eventos | Limitar active_sessions a 50; índice SQL em session_id (JSON extract ou coluna denormalizada fase 2) |
