Pular para conteúdo

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:

  1. Funnel: Acquisition → Activation
  2. user_registered (já existente)
  3. onboarding_step_completed (qualquer step)
  4. first_transaction_created
  5. Funnel: Activation → Conversion
  6. first_transaction_created
  7. paywall_shown
  8. upgrade_clicked
  9. upgrade_completed
  10. Breakdown sugerido para o segundo funil: por source em upgrade_clicked (split surface).

Dependências e bloqueios

  • Funcionalmente útil quando o billing Asaas estiver live em prod (#521). Antes disso, upgrade_clicked e upgrade_completed raramente disparam (fluxo de checkout não está exposto).
  • Não bloqueia o launch — os eventos onboarding_step_completed, first_transaction_created e paywall_shown já 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