SuperdindaDocs

Visão geral do domínio

As peças do backend (conta, vagas, consentimento, pareamento, estado da proteção, alertas e notificações) e como elas se ligam.

Esta seção explica como construir sobre a API da Superdinda. A referência rota a rota fica em API Reference. Aqui ficam os conceitos e os fluxos que amarram essas rotas.

As peças

PeçaOnde viveO que é
Conta de famíliadomains/accountsaccounts + account_members. Quem cria vira owner; convidados entram como guardian
Vaga (seat)domains/subscriptionsUma pessoa protegida dentro da conta. Status pending, active, paused ou archived, com preço congelado no momento em que foi criada
Pessoa protegidadomains/subscriptions (dependents)Os dados por trás da vaga: kind (minor ou adult), nome, nascimento, vínculo, telefone E.164 e documento
Consentimentodomains/consentsO "sim" da própria pessoa (AD-009): link público, selfie e áudio verificados, checagem de vínculo
Pareamentodomains/channelsA ligação do WhatsApp da pessoa com uma sessão do provedor (WAHA), por QR ou código
Pausa e paradadomains/subscriptions (seat_pauses)Pausa feita pela família ou parada feita pela própria pessoa
Estado da proteçãodomains/protectionUma derivação única por pessoa (state, reason, since, action), sem tabela própria. Calculada de vaga, consentimento, pareamento, pausas e alertas
Achados e alertasdomains/analysis, domains/alertsA análise grava achados (risk_findings), que viram alertas (risk_alerts) agrupados
Notificaçõesdomains/notificationsAvisos pros responsáveis, com catálogo de tipos, severidade e preferências por categoria
Eventosdomains/eventsToda mudança de estado vira uma linha em events e uma entrada no stream do Redis da conta

Como se ligam

conta (owner, guardians)
 └─ vaga (pending → active ⇄ paused, archived)
     ├─ pessoa protegida (kind: minor | adult)
     ├─ pedido de consentimento (pending → given | declined | expired | revoked)
     │    └─ checagem de vínculo (confirmed | refuted | inconclusive | failed)
     ├─ canal contratado (seat_channels: whatsapp)
     │    └─ pareamento (starting → challenge → connecting → paired ⇄ degraded …)
     └─ análise → achados → alerta (open | resolved | dismissed)
                                  └─ notificação pros responsáveis
tudo isso → events (ULID) → stream Redis → SSE

Algumas regras que atravessam tudo:

  • Só refuted bloqueia. Uma checagem de vínculo inconclusive vai pra revisão manual e não impede o pareamento (AD-009).
  • Consentimento antes do cartão. Toda vaga criada desde a migração do onboarding v2 tem activationRequiresConsent: true: o link sai antes da ativação, e POST /seats/{uid}/activate responde 422 CONSENT_REQUIRED até o "sim" (AD-019, D2).
  • Pareamento exige vaga ativa, consentimento dado e vínculo não refutado. GET /seats/{uid}/consent diz se pode (pairingAllowed) e o que falta (blockedBy).
  • O número conectado precisa ser o da pessoa. Se o provedor confirmar outro telefone, o pareamento é desfeito (pairing.identity_mismatch).
  • O teste grátis começa na primeira conexão, não na ativação (AD-019, D1). O evento trial.started avisa.
  • Responsável não lê conversa. Quando a análise vê risco, ele recebe um alerta com o trecho mínimo. Push, e-mail e SSE nunca carregam trecho (regra P1 do api-map.md).

Estado da proteção

Toda tela que diz "como está a proteção de alguém" lê a mesma derivação (deriveProtectionState), em vez de juntar status de vaga, consentimento e pareamento na mão. Nada disso é guardado: é recalculado a cada leitura.

stateQuando
waiting_consent / declinedEsperando o sim / a pessoa recusou por enquanto
ready_to_connect / connectingSim dado, falta conectar / conexão em andamento (fila ou pareamento aberto)
warming_upConectado, primeira leitura do histórico em curso (com etaAt quando há estimativa honesta)
protectingConectado e lendo ao vivo
needs_youExiste alerta aberto
stopped / paused / endedProteção parou (com reason) / pausada pela família / vaga encerrada

Junto vêm reason (vocabulário fechado, por exemplo consent_stopped_by_dependent, identity_mismatch, paused_by_guardian; nunca um motivo que a pessoa deu), since e action (o próximo passo sugerido, como send_consent, connect ou resume_pause). O texto fica no front.

  • GET /accounts/{uid}/home: a tela Início numa leitura só (estado de cada pessoa, a semana com dias protegidos e quedas, o aquecimento e o bloco de alertas).
  • GET /seats/{uid}/protection: uma pessoa, com o mesmo cartão e mais consentimento, conexão, pausa, quem recebe os avisos e a semana.
  • Quando a derivação muda, sai seat.state_changed no SSE. O começo e o fim da primeira leitura saem como analysis.warmup.

Papéis e portas de entrada

QuemComo se autenticaMiddleware
Responsável (owner ou guardian)Bearer JWT do apprequireAuth + requireAccount(...) ou requireSeat(...)
Pessoa protegidaO token do link, na URL (rotas públicas /consent-requests/{token}/…)Nenhum. O link é a credencial e tem limite de taxa
Operador do backofficeBearer JWT de uma sessão backofficerequireAuth + requireOperator (AD-018)

Uma vaga de outra família responde 404, nunca 403: a existência dela não é da conta de quem pergunta (backend/src/http/middleware/seat.ts).

Guias desta seção

Usando a referência da API

A API Reference é gerada do openapi.json que o backend exporta (bun run openapi:export). Cada rota tem um playground.

  • Servidores. O padrão é http://localhost:3000 (backend com bun run dev), e https://api.superdinda.com (produção) também aparece na lista. Se a variável DOCS_API_URL estiver definida no app de docs, esse servidor entra em primeiro lugar (por exemplo, um host de homologação).
  • Proxy. O playground não chama a API direto do navegador: passa por /api/proxy do próprio docs, que só encaminha para os servidores dessa lista e recusa qualquer outro destino.
  • Token. As rotas protegidas declaram o esquema Bearer. Pegue um accessToken (veja Autenticação) e cole no campo de autorização do playground. O token vale JWT_ACCESS_TTL_SECONDS (padrão 900 s). Quando vencer, peça outro.
Pegar um token pra colar no playground (local)
curl -s -X POST http://localhost:3000/auth/otp/request \
  -H 'content-type: application/json' \
  -d '{"email":"voce@exemplo.com","delivery":"code"}'
# Em dev o código sai no terminal do backend (MAIL_TRANSPORT=log).
curl -s -X POST http://localhost:3000/auth/otp/verify \
  -H 'content-type: application/json' \
  -d '{"email":"voce@exemplo.com","code":"123456"}'
# → { "success": true, "data": { "accessToken": "<access-jwt>", "expiresAtMs": "…" } }

Fontes

  • backend/README.md
  • backend/src/http/middleware/auth.ts, account.ts, seat.ts, operator.ts
  • backend/src/domains/subscriptions/tables/seats.table.ts, dependents.table.ts
  • backend/src/domains/consents/tables/consent-requests.table.ts, relationship-checks.table.ts
  • backend/src/http/routes/channels/schemas.ts
  • backend/src/http/routes/seats/activate.route.ts, seats/protection.route.ts, accounts/home.route.ts
  • backend/src/domains/protection/dtos/protection.dto.ts, use-cases/state-watch.ts
  • apps/docs/src/lib/openapi.ts, apps/docs/src/app/api/proxy/route.ts
  • .specs/features/redesign/api-map.md (§0.2 regras de privacidade)
  • AD-009, AD-010, AD-018, AD-019

On this page