# Onboarding e dropout

Descubra em qual tela do seu onboarding os usuários estão desistindo. O SDK rastreia cada step e o dashboard monta o funil automaticamente.

## Como funciona

Em cada tela do seu fluxo de onboarding você marca um step, informando um nome e a posição dele no funil. Quando o usuário termina, chama `complete()`. Quem detecta o abandono é o servidor: você não precisa fazer nenhuma chamada para isso. O dashboard usa esses eventos pra montar o funil de conversão e mostrar onde o dropout está acontecendo.

O universo do funil são os **installs do período**, não só quem mandou evento de onboarding. Todo usuário instalado entra na base, e a posição dele no funil vem do step mais avançado que ele reportou. Quem nunca reportou step nenhum fica parado no step 0.

## Antes de começar

-   O SDK precisa estar inicializado antes de qualquer chamada de onboarding. Chamar antes disso lança `ONBOARDING_NOT_INITIALIZED`. Se quiser garantir, aguarde `waitUntilReady()` antes do primeiro `step()`.
-   Se o SDK ainda não tem um `distinctId` (init em andamento), o evento é descartado em silêncio. Mais um motivo pra esperar o `waitUntilReady()` em telas que aparecem logo no boot do app.
-   O dashboard fica em `/dropout` e precisa da feature flag _dropout_ ativa no seu app (fala com a gente se não tiver).

## Marcando steps

Em cada tela do onboarding, chama `step(nome, order)` com um nome único e o `order` da tela no fluxo (0, 1, 2...). O segundo argumento é **obrigatório**: sem ele o backend não consegue montar a sequência do funil corretamente. Pode chamar dentro de um `useEffect` no mount da tela. Chamar de novo pro mesmo step (usuário voltou uma tela, por exemplo) não tem problema: o backend guarda o progresso mais avançado.

```typescript
import { onboardingManager } from "@virex-tech/paywallo-sdk";

// chama em cada tela do seu onboarding, passando o order da tela
await onboardingManager.step("welcome", 0);
await onboardingManager.step("age_selection", 1);
await onboardingManager.step("goal", 2);
```

## Teste A/B

A forma recomendada de modelar variantes A/B é passar um `variantKey` (string) junto com o step. O `order` permanece um inteiro indicando a posição no funil. **Não misture posição e variante no mesmo número**. Passe o mesmo `variantKey` em `complete()` para fechar o funil identificado pela variante correta.

No Flutter, `onboardingStep()` não aceita `variantKey`. A variante se identifica pelo `order` decimal (explicado logo abaixo nesta seção), e o `variantKey` só existe em `onboardingComplete()`.

```typescript
import { onboardingManager } from "@virex-tech/paywallo-sdk";

// Forma recomendada para teste A/B: order inteiro (posição no funil) + variantKey (string)
await onboardingManager.step("pricing", 2, { variantKey: "control" });
await onboardingManager.step("pricing", 2, { variantKey: "variant_b" });

// Opcional: tempo gasto na tela anterior (em segundos)
await onboardingManager.step("goal", 2, { variantKey: "control", timeOnPrevS: 12 });
```

No funil de dropout, steps com o mesmo `order` e `variantKey` diferente colapsam no step pai e aparecem como uma única barra. A comparação granular entre variantes fica por conta da tela de Testes A/B.

O campo `order` também aceita decimais (ex: `2.1`, `2.2`) como forma alternativa de identificar variantes: a parte inteira define a posição; o decimal, a variante. Prefira o `variantKey` pra novos fluxos: é mais legível e não sofre com arredondamento de ponto flutuante.

## Passos que se dividem

Às vezes o onboarding ramifica: telas diferentes aparecem para usuários diferentes na mesma posição do fluxo. Um app de parar de fumar mostra "cigarros por dia" para fumantes e "puffs por dia" para quem usa vape; um app de peptídeos mostra uma tela para quem já tem protocolo e outra para quem não tem. Se você numerar cada tela com um `order` inteiro sequencial, o funil "esparrama": a mesma etapa vira várias linhas, cada uma com só uma fração dos usuários.

A solução é dar o mesmo `order` inteiro à posição e um decimal a cada variante. O servidor arredonda o `order` para baixo, então todas as variantes entram na mesma etapa do funil, e o decimal registra qual tela cada usuário viu (visível no detalhamento do passo).

```typescript
// Ramificacao: cada usuario ve UMA das telas na mesma posicao.
// Mesmo inteiro, decimal por variante.
await onboardingManager.step("cigsPerDay", 8.1);  // fumante
await onboardingManager.step("puffsPerDay", 8.2); // vape
// No funil ambos contam como "passo 8"; o decimal aparece no detalhamento.
```

Não confunda com o `variantKey` de teste A/B: o `variantKey` é para a **mesma tela** testando conteúdos diferentes (ex: dois preços). O decimal é para telas genuinamente diferentes que ocupam a mesma posição por causa de uma ramificação do fluxo.

Use decimais só para telas alternativas: o usuário vê uma ou outra, nunca as duas. Telas que todos veem em sequência devem ter `order` s inteiros distintos.

## Ramificação por resposta

Opcional: se o seu onboarding faz uma pergunta que muda o caminho (ex: "qual é o seu objetivo?"), registre a resposta com `branch`. Cada chamada é um par pergunta → resposta; chamando a mesma pergunta de novo, a **última resposta vence**. Apps com onboarding linear não precisam disso.

```typescript
import { onboardingManager } from "@virex-tech/paywallo-sdk";

// logo depois que o usuário responde a pergunta, antes dos steps do caminho dele
await onboardingManager.branch("objetivo", "perder peso");
```

Chame logo depois que o usuário responde, antes dos steps do caminho escolhido. Limites: a pergunta (`key`) aceita letras, números e `_ . -`, de 1 a 64 caracteres; a resposta (`value`) é texto livre de 1 a 64 caracteres (espaços, acentos e emoji valem, ex: "ganhar músculo"); até 20 perguntas por usuário. Os valores `__none__`, `__other__` e `__all__` são reservados. Chamada inválida é ignorada com um aviso no console, nunca lança erro.

Não confunda com os outros dois recursos: o `variantKey` é para teste A/B da **mesma tela**, e os decimais de "Passos que se dividem" só separam telas alternativas dentro de um passo. O `branch` é por resposta do usuário e é ele que habilita filtrar e quebrar o funil no painel.

No painel, em Onboarding dropout, use o seletor **Quebrar por pergunta** para ver um funil por resposta, e os filtros para olhar só quem respondeu algo. A escolha fica na URL, então dá para compartilhar o link. "Antes da pergunta" reúne quem desistiu antes de responder; "Outros" agrupa as respostas além das 10 mais comuns.

## Finalizando o fluxo

Quando o usuário chega no fim do onboarding (última tela, paywall, home), chama `complete()`. Isso marca o fluxo como concluído pra esse usuário, fecha o funil dele e alimenta o card de taxa de conclusão. Sem o `complete()` a taxa de conclusão fica em 0% mesmo com os steps chegando.

```typescript
import { onboardingManager } from "@virex-tech/paywallo-sdk";

// quando o usuário termina o fluxo (chegou na última tela ou no paywall)
await onboardingManager.complete();

// Em teste A/B, passe o mesmo variantKey usado nos steps
await onboardingManager.complete({ variantKey: "variant_b" });
```

## Abandono (automático)

Não existe mais `drop()` no SDK. O abandono é inferido pelo servidor por tempo: se o usuário iniciou o onboarding, não chamou `complete()` e ficou **mais de 1 hora sem avançar** (sem novo `step()`), o servidor marca o fluxo como abandonado automaticamente. Fechar o app não conta como abandono: só o tempo sem avançar. Se o usuário voltar e mandar um novo `step()`, ele sai do estado de abandono e o fluxo continua normalmente.

## Nomes de step

-   Precisa ser uma string não vazia (senão a chamada lança `ONBOARDING_INVALID_STEP_NAME`).
-   O agrupamento ignora maiúsculas e minúsculas: `introHome` e `introhome` caem no mesmo step. Renomear de verdade (trocar a palavra) quebra o histórico, então mantenha os nomes **consistentes entre releases**.
-   Use nomes descritivos da tela (`"age_selection"`, `"goal"`), não genéricos tipo `"step1"`. É esse nome que aparece no funil.

## Exemplo completo

Um fluxo de 3 telas (welcome, age, goal):

```typescript
import { useEffect } from "react";
import { onboardingManager } from "@virex-tech/paywallo-sdk";

function WelcomeScreen({ navigation }) {
  useEffect(() => {
    onboardingManager.step("welcome", 0);
  }, []);

  return (
    <View>
      <Text>Bem-vindo!</Text>
      <Button title="Continuar" onPress={() => navigation.navigate("Age")} />
    </View>
  );
}

function AgeScreen({ navigation }) {
  useEffect(() => {
    onboardingManager.step("age_selection", 1);
  }, []);

  return (
    <View>
      <Text>Qual sua idade?</Text>
      <Button title="Continuar" onPress={() => navigation.navigate("Goal")} />
    </View>
  );
}

function GoalScreen({ navigation }) {
  useEffect(() => {
    onboardingManager.step("goal", 2);
  }, []);

  return (
    <View>
      <Text>Qual seu objetivo?</Text>
      <Button
        title="Finalizar"
        onPress={async () => {
          await onboardingManager.complete();
          navigation.navigate("Paywall");
        }}
      />
    </View>
  );
}
```

Você também pode usar o hook `useOnboarding()`, que expõe os mesmos `step` e `complete` com referências estáveis:

```typescript
import { useOnboarding } from "@virex-tech/paywallo-sdk";

function AgeScreen() {
  const { step, complete } = useOnboarding();

  useEffect(() => {
    step("age_selection", 1);
  }, [step]);

  // ...
}
```

## Quando o funil aparece

A mensagem **"dados insuficientes"** aparece no lugar do funil quando ele volta com no máximo uma etapa **e nenhum usuário chegou ao onboarding no período**. É o jeito de não exibir um "1 etapa · step_0 100%" enganoso. Não é sinal de amostra pequena: é sinal de que nenhum evento de step entrou nesse recorte. As causas de sempre são três: o período selecionado não cobre a build que você testou, o filtro de environment está em Production e a build é Sandbox, ou o app ainda não está emitindo os steps. Confira com `debug: true` que o `step()` sai do app, e revise os dois filtros antes de concluir que falta volume.

## Validando a integração

-   Inicialize o SDK com `debug: true` numa build de teste: cada evento aceito loga `[Paywallo:Onboarding] event emitted` no console, e eventos descartados (sem distinctId) logam `skipped`.
-   No dashboard, confira se o filtro de environment no topo da página bate com o ambiente da build que você está testando. O SDK detecta sandbox e production automaticamente, e evento marcado como sandbox não aparece com o filtro Production ativo.
-   Se o funil mostra **um único step_0** com 100% dos usuários e conclusão 0%, o app não está enviando os eventos de onboarding. Como o universo são os installs, o funil nunca fica vazio: sem eventos, todo mundo aparece empilhado no step 0 com um nome genérico.
-   O nome real de um step aparece no funil quando ele acumula **massa mínima** no histórico do app. O piso é adaptativo (0,07% do total de steps, com teto de 20) e cai até 1 em app novo ou de baixo volume, justamente pro funil não travar em nome genérico. O `step_N` genérico sobra, na prática, pra passo raro em app de volume alto.

## Pegadinhas

-   Mande o step pro Paywallo mesmo que você já rastreie onboarding em outra ferramenta (PostHog, Firebase, etc). Evento custom com outro nome não alimenta o funil: só a família canônica emitida por `step()` e `complete()` conta.
-   `complete()` marca o fluxo como concluído. Chame no máximo uma vez por usuário.
-   Os dados não são retroativos. Usuários que passaram pelo onboarding antes da integração continuam aparecendo no step 0, e o funil só reflete os steps reais pra quem instalou uma build com o SDK chamando `step()`.
