MVP 1 - PostHog Conversion Funnel¶
Atualizado: 2026-05-19
Objetivo¶
Instrumentação antecipada (ahead-of-launch) dos eventos do funil de conversão no PostHog, de modo que assim que o billing for live, a saved funnel já exista com dados acumulando — sem perder a primeira semana de tráfego.
Issue de origem: #524 OBS-03.
PR de entrega: italofelipe/auraxis-web#905.
Eventos do funil¶
Os 5 eventos abaixo são o contrato estável que o catálogo
AuraxisEvent em app/plugins/posthog.client.ts declara. Mudar nome
sem coordenar quebra a saved funnel no PostHog Cloud — trate este contrato
com o mesmo rigor de uma API pública.
| Evento | Disparado em | Properties | Notas |
|---|---|---|---|
onboarding_step_completed |
app/features/onboarding/components/OnboardingWizard.vue |
step (1-6), total_steps (6), direction (next/skip/complete) |
Emite no step que está sendo deixado, não no que está sendo entrado. Funil mede abandono por passo. |
first_transaction_created |
app/features/transactions/queries/use-create-transaction-mutation.ts |
transaction_type (income/expense/unknown), batch_size |
Deduplicado por browser via localStorage (auraxis.analytics.firstTransactionCreated). SSR-safe: trata server-side como já-emitido pra evitar duplicação. |
paywall_shown |
app/components/paywall/UiPaywallGate.vue |
feature (FeatureKey), gate (ui-paywall-gate) |
Watch em [() => props.feature, hasAccessData, isLoading]. Emite apenas quando hasAccess === false e não durante loading. |
upgrade_clicked |
app/components/subscription/CheckoutButton.vue e app/components/paywall/UpgradeCTA.vue |
source (string, default por componente), plan_slug, billing_cycle, destination |
Capturado ANTES de await client.createCheckout(...) ou router.push(...) — race condition com navegação faria PostHog perder o flush. |
upgrade_completed |
app/pages/checkout/success.vue (em onMounted) |
source (checkout-success-page) |
Espelho client-side. O webhook do Asaas continua sendo a source-of-truth para entitlement state no servidor; este evento existe pra que o time-to-conversion seja computável só pelo PostHog, sem join com server logs. |
Padrões de implementação¶
1. Catálogo de eventos como contrato¶
Todos os eventos vivem no union type AuraxisEvent em
app/plugins/posthog.client.ts. useAnalytics().capture(name, props)
não aceita nomes que não estejam no union. Refactor pra adicionar novo
evento exige editar o type primeiro — TypeScript bloqueia capture com
nome solto.
2. Source labels em CTAs¶
CheckoutButton.vue e UpgradeCTA.vue recebem prop opcional source
(default checkout-button e upgrade-cta). Sempre que um CTA reaproveitar
o componente em surface diferente (banner do dashboard, modal de feature,
etc.), passe source para que a saved funnel possa quebrar conversão por
surface.
<CheckoutButton :plan-slug="premiumSlug" source="dashboard-banner" />
<UpgradeCTA source="tool-page-paywall" />
3. Dedup client-side via localStorage¶
first_transaction_created é o único evento com risco de duplicar caso o
usuário crie várias transações no mesmo browser. A dedup é feita por flag
local (auraxis.analytics.firstTransactionCreated). Em SSR a função
hasEmittedFirstTransaction() retorna true (pessimista) pra evitar emitir
duplicado, e a falha de storage (modo privado, etc.) degrada graciosamente
pra dedup server-side do PostHog.
4. Captura ANTES de navegação¶
Vue Query mutation createCheckout e router.push("/plans") ambos disparam
navegação que pode interromper o flush do PostHog. Sempre capture o evento
antes do await/push:
analytics.capture("upgrade_clicked", { source: props.source, ... });
await client.createCheckout(...); // navegação após emit
5. Watch immediate para gates declarativos¶
UiPaywallGate.vue usa watch([() => props.feature, hasAccessData, isLoading], ..., { immediate: true })
para que ao montar o componente já avalie se deve emitir paywall_shown.
Loading ainda em true é skip; quando entitlement resolve com false,
emite.
Testes¶
Specs afetados usam mock de useAnalytics:
const captureMock = vi.hoisted(() => vi.fn());
vi.mock("~/composables/useAnalytics/useAnalytics", () => ({
useAnalytics: () => ({
capture: captureMock,
identify: vi.fn(),
reset: vi.fn(),
}),
}));
Sem o mock, importar o composable em ambiente vitest acaba chamando
useNuxtApp() que não existe fora do runtime Nuxt.
Saved funnel — configuração no PostHog Cloud¶
Depois do merge de auraxis-web#905, configure no PostHog:
- Funnel: Acquisition → Activation
user_registered(já existente)onboarding_step_completed(qualquer step)first_transaction_created- Funnel: Activation → Conversion
first_transaction_createdpaywall_shownupgrade_clickedupgrade_completed- Breakdown sugerido para o segundo funil: por
sourceemupgrade_clicked(split surface).
Dependências e bloqueios¶
- Funcionalmente útil quando o billing Asaas estiver live em prod
(#521).
Antes disso,
upgrade_clickedeupgrade_completedraramente disparam (fluxo de checkout não está exposto). - Não bloqueia o launch — os eventos
onboarding_step_completed,first_transaction_createdepaywall_shownjá alimentam dados desde o deploy de #905 e dão sinal sobre adoção mesmo sem billing.
Histórico de mudanças¶
| Quando | O que | PR/Issue |
|---|---|---|
| 2026-05-19 | Criação inicial — 5 eventos do funil instrumentados em auraxis-web | #524 / auraxis-web#905 |