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ça | Onde vive | O que é |
|---|---|---|
| Conta de família | domains/accounts | accounts + account_members. Quem cria vira owner; convidados entram como guardian |
| Vaga (seat) | domains/subscriptions | Uma pessoa protegida dentro da conta. Status pending, active, paused ou archived, com preço congelado no momento em que foi criada |
| Pessoa protegida | domains/subscriptions (dependents) | Os dados por trás da vaga: kind (minor ou adult), nome, nascimento, vínculo, telefone E.164 e documento |
| Consentimento | domains/consents | O "sim" da própria pessoa (AD-009): link público, selfie e áudio verificados, checagem de vínculo |
| Pareamento | domains/channels | A ligação do WhatsApp da pessoa com uma sessão do provedor (WAHA), por QR ou código |
| Pausa e parada | domains/subscriptions (seat_pauses) | Pausa feita pela família ou parada feita pela própria pessoa |
| Estado da proteção | domains/protection | Uma derivação única por pessoa (state, reason, since, action), sem tabela própria. Calculada de vaga, consentimento, pareamento, pausas e alertas |
| Achados e alertas | domains/analysis, domains/alerts | A análise grava achados (risk_findings), que viram alertas (risk_alerts) agrupados |
| Notificações | domains/notifications | Avisos pros responsáveis, com catálogo de tipos, severidade e preferências por categoria |
| Eventos | domains/events | Toda 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 → SSEAlgumas regras que atravessam tudo:
- Só
refutedbloqueia. Uma checagem de vínculoinconclusivevai 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, ePOST /seats/{uid}/activateresponde422 CONSENT_REQUIREDaté o "sim" (AD-019, D2). - Pareamento exige vaga ativa, consentimento dado e vínculo não refutado.
GET /seats/{uid}/consentdiz 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.startedavisa. - 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.
state | Quando |
|---|---|
waiting_consent / declined | Esperando o sim / a pessoa recusou por enquanto |
ready_to_connect / connecting | Sim dado, falta conectar / conexão em andamento (fila ou pareamento aberto) |
warming_up | Conectado, primeira leitura do histórico em curso (com etaAt quando há estimativa honesta) |
protecting | Conectado e lendo ao vivo |
needs_you | Existe alerta aberto |
stopped / paused / ended | Proteçã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_changedno SSE. O começo e o fim da primeira leitura saem comoanalysis.warmup.
Papéis e portas de entrada
| Quem | Como se autentica | Middleware |
|---|---|---|
Responsável (owner ou guardian) | Bearer JWT do app | requireAuth + requireAccount(...) ou requireSeat(...) |
| Pessoa protegida | O token do link, na URL (rotas públicas /consent-requests/{token}/…) | Nenhum. O link é a credencial e tem limite de taxa |
| Operador do backoffice | Bearer JWT de uma sessão backoffice | requireAuth + 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
Autenticação
Onboarding
Consentimento
Pareamento
Pausar e parar
Alertas
Tempo real (SSE)
Envelope e erros
Limites de taxa
Paginação
Webhooks e eventos
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 combun run dev), ehttps://api.superdinda.com(produção) também aparece na lista. Se a variávelDOCS_API_URLestiver 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/proxydo 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 umaccessToken(veja Autenticação) e cole no campo de autorização do playground. O token valeJWT_ACCESS_TTL_SECONDS(padrão 900 s). Quando vencer, peça outro.
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.mdbackend/src/http/middleware/auth.ts,account.ts,seat.ts,operator.tsbackend/src/domains/subscriptions/tables/seats.table.ts,dependents.table.tsbackend/src/domains/consents/tables/consent-requests.table.ts,relationship-checks.table.tsbackend/src/http/routes/channels/schemas.tsbackend/src/http/routes/seats/activate.route.ts,seats/protection.route.ts,accounts/home.route.tsbackend/src/domains/protection/dtos/protection.dto.ts,use-cases/state-watch.tsapps/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
