Onboarding
Da conta de família à vaga ativa, com o consentimento antes do cartão (D2) e o teste grátis contado a partir da primeira conexão (D1).
O onboarding é por conta de família. Nada guarda "em qual tela a pessoa está": GET /accounts/{uid}/onboarding deriva o próximo passo dos dados a cada chamada.
Os passos
add_seat ─► fill_dependent ─► send_consent ─► await_consent ─► activate_seat ─► (null: completo)
▲ │
│ ├─ a pessoa recusou ─► consent_declined
└───────────────┴─ link venceu / foi retirado / dados mudaram| Passo | Chamada que avança |
|---|---|
| (antes de tudo) | POST /accounts com { name }. Quem cria vira owner. GET /accounts vazio quer dizer que o onboarding começa aqui |
add_seat | POST /accounts/{uid}/seats, sem corpo. Cria a vaga pending com o preço de hoje |
fill_dependent | PUT /seats/{uid}/dependent com kind (minor ou adult), name, birthDate, relationship, phone (E.164, o WhatsApp que será pareado) e document ({ type, number }, CPF validado) |
send_consent | POST /seats/{uid}/consent-request com { send: "now" } ou { send: "later" } (só owner) |
await_consent | A pessoa responde na página pública (veja Consentimento). SSE consent.given |
consent_declined | Reenviar depois da espera (POST /seats/{uid}/consent-request/resend) ou arquivar a vaga (DELETE /seats/{uid}) |
activate_seat | POST /seats/{uid}/activate com { guardianAttestation: true, campaignCode? } (só owner) |
Como o passo de cada vaga pendente é derivado (seatStep):
- Sem pessoa, ou sem telefone ou documento:
fill_dependent. - Vaga antiga (
activationRequiresConsent: false):activate_seat, na ordem antiga. - Pelo status do consentimento:
givendáactivate_seat,pendingdáawait_consent,declineddáconsent_declined, e nenhum,expiredourevokeddãosend_consent.
No topo, nextStep é o passo da vaga pendente mais antiga (nextSeatUid aponta qual), ou add_seat se não há vaga pendente. É null quando alguma vaga está ativa (complete: true).
Detalhes que mudam o fluxo
- Consentimento antes do cartão (D2). Vagas criadas desde a migração do onboarding v2 têm
activationRequiresConsent: true, e a ativação responde422 CONSENT_REQUIREDaté o sim (ou422 RELATIONSHIP_REFUTED). Leia a flag em vez de supor a ordem. send: "later"cria o pedido e o link (seat.consent.link) sem enviar nada: o responsável copia o link ou chama de novo com"now". O pedido aberto é reaproveitado, então"later"seguido de"now"envia o mesmo link. Pra um link novo, use o resend. A checagem de vínculo roda uma vez por vaga, nesta chamada.- Dados da pessoa. Reenviar os mesmos dados não muda nada. Mudar os dados com o link aberto retira o pedido (
consent.revoked, volta asend_consent). Depois do sim, os dados travam (422 DEPENDENT_LOCKED): pra mudar, arquive a vaga pendente e crie outra. - Um "não" antes da ativação deixa a vaga
pending. Nada é cobrado e a assinatura não é aberta. - Ativação. Grava a atestação do responsável, abre a assinatura em
trialingse a conta não tem uma e ativa a vaga. Em vagas antigas, também manda o link de consentimento e começa a checagem de vínculo. - Teste grátis (D1). Não começa na ativação:
trial.stateficanot_startedaté a primeira conexão de WhatsApp da vaga chegar apaired. Aí sai o SSEtrial.started. O teste é por vaga, e o da conta é o da primeira vaga que conectou. Usetrial.label(oucatalog.trialLabelantes de existir conta) pro texto, e nunca deduza "vencido" de umtrialEndsAtnulo. POST /accounts/{uid}/subscriptionabre a assinatura explicitamente (pra escolher intervalo ou campanha). Não faz parte do onboarding.GET /catalogé público e devolve os serviços com o preço que a pessoa pagaria, maistrialDays,trialStartsOnetrialLabel.
Erros comuns
code | Onde | O que fazer |
|---|---|---|
DEPENDENT_REQUIRED / DEPENDENT_INCOMPLETE | consent-request, activate | Voltar a fill_dependent |
SEAT_NOT_PENDING | consent-request, activate | Vaga ativa: usar o resend |
CONSENT_ALREADY_GIVEN | consent-request | Ir pra ativação |
RESEND_LOCKED | consent-request, resend | Esperar details.retryAt |
CONSENT_REQUIRED / RELATIONSHIP_REFUTED | activate | Voltar a await_consent / mostrar o bloqueio |
DEPENDENT_LOCKED | dependent | Oferecer arquivar e recomeçar |
CAMPAIGN_NOT_FOUND / CAMPAIGN_ALREADY_APPLIED | activate | Tirar o código de campanha |
BIRTH_DATE_OUT_OF_RANGE | dependent | minor precisa ter menos de 18 anos |
sd() { curl -s -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' "$@"; }
sd -X POST "$API/accounts" -d '{"name":"Família Silva"}' # → data.uid (conta)
sd -X POST "$API/accounts/$ACCOUNT/seats" # → data.uid (vaga)
sd -X PUT "$API/seats/$SEAT/dependent" -d '{
"kind": "minor", "name": "Lia", "birthDate": "2014-03-09", "relationship": "child",
"phone": "+55<telefone>", "document": { "type": "cpf", "number": "<cpf>" } }'
sd -X POST "$API/seats/$SEAT/consent-request" -d '{"send":"now"}'
sd "$API/accounts/$ACCOUNT/onboarding" # → nextStep: "await_consent"Em aberto
- A atestação do responsável é gravada na ativação, depois que o link já foi pra pessoa menor de idade. Falta confirmar isso com o jurídico (LGPD, art. 14), como anotado na frente k-onboarding.
- Se alguém dá o sim e nunca conecta, não há teste nem período. Como cobrar essa vaga fica pro G-33/G-34 do
api-gaps.md.
Fontes
backend/src/http/routes/accounts/create.route.ts,list.route.ts,add-seat.route.ts,onboarding.route.ts,subscribe.route.ts,schemas.tsbackend/src/http/routes/seats/dependent.route.ts,consent-request.route.ts,activate.route.ts,archive.route.tsbackend/src/domains/subscriptions/use-cases/onboarding-status.ts,activate-seat.ts,set-dependent.ts,start-trial.tsbackend/src/domains/subscriptions/schemas/dependent.schema.ts,activate.schema.tsbackend/src/http/routes/catalog/list.route.ts- Notas da frente k-onboarding (
k-onboarding-api.md) - AD-006, AD-009, AD-019 (D1, D2)
Autenticação
Login sem senha por código ou magic link, Google e Apple, JWT de acesso curto, refresh em cookie httpOnly e sessões revogáveis no Postgres.
Consentimento
A atestação do responsável, o link de consentimento, a página pública, a evidência por selfie e áudio, a recusa e a parada pela própria pessoa.
