> ## Documentation Index
> Fetch the complete documentation index at: https://developers.googa.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Modelo de dados

> O vocabulário de entidades por trás da API — Subscriber, User, Reader/Listener, o grafo de conteúdo, Entitlement e Event — sem expor o schema SQL completo.

<Info>
  Esta página descreve **conceitos**, não o schema físico. Os nomes de tabela citados (`profiles`,
  `novels`, `subscriptions`...) existem dentro de cada projeto Supabase de produto — o vocabulário
  de API (`Subscriber`, `Entitlement`, `Event`) é a camada de parceiro que se apoia neles. Onde os
  dois convergem, dizemos explicitamente.
</Info>

## Três identidades que parecem uma

O erro mais comum ao integrar é tratar "o assinante" como uma linha única. São três conceitos
diferentes, em três lugares diferentes, e a API de parceiro existe justamente para costurá-los
sem que quem integra precise saber onde cada um mora:

| Conceito                                  | O que é                                                                                                                  | Onde vive                                                                            | Quem o possui                                                        |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
| **Subscriber**                            | A identidade do lado do parceiro — o assinante do plano Algar que inclui o benefício Googa                               | `partner_subscribers` (com `partner_id`), dentro do projeto Supabase de cada produto | O parceiro (Algar conhece esse ID antes de qualquer chamada à Googa) |
| **User**                                  | A identidade Supabase — quem efetivamente autenticou (e-mail/senha, ou SSO via Minha Algar)                              | `auth.users`, dentro do projeto do produto (Novel ou HistorinhAI)                    | O produto                                                            |
| **Reader / Listener / perfil de criança** | O perfil de consumo dentro de um produto — preferências, progresso, o que aquela pessoa (ou aquela criança) já leu/ouviu | `profiles` (NovelAI/NovelAudio) ou `profiles` + `children` (HistorinhAI)             | O produto                                                            |

A relação entre os três é 1:N descendo: um `Subscriber` pode, via SSO, corresponder a um `User`
em cada produto que ele ativa (o `sub` do token da Algar fica gravado em `auth.identities`, o
`subscriber_id` em `app_metadata` — ver [Modelo de autenticação](/architecture/supabase-auth-model));
e um `User` tem exatamente um perfil por produto — exceto no HistorinhAI, onde o `User` é o
responsável (pai/mãe) e não quem consome o conteúdo.

<Tabs>
  <Tab title="NovelAI / NovelAudio">
    Aqui `User` e "Reader/Listener" colapsam na prática: a tabela `profiles` é 1:1 com
    `auth.users`, e a única diferença entre um "Reader" e um "Listener" é a coluna `app_source`
    (`novelai` ou `novelaudio`) — porque os dois produtos compartilham o mesmo projeto Supabase
    (ver [Modelo multi-tenant](/architecture/multi-tenant-model)). "Reader" e "Listener" são
    vocabulário de API para diferenciar a intenção do produto, não duas tabelas diferentes.
  </Tab>

  <Tab title="HistorinhAI">
    Aqui a distinção é real, não só semântica: `profiles` é o responsável — a pessoa que
    autentica, paga, e gerencia a conta (limite de 6 perfis por conta). A criança nunca autentica:
    ela existe como uma linha em `children` (nome, data de nascimento, preferências), sem
    identidade Supabase própria. O equivalente a "Reader" no vocabulário do HistorinhAI é o perfil
    de criança — e é por isso que o relatório mensal (RFP, página 17-19) é agregado por criança,
    não por conta.
  </Tab>
</Tabs>

## Conteúdo: Novel → Chapter → Page

O catálogo tem cinco níveis, com a granularidade pensada para separar o metadado de capítulo
(usado pelo player de áudio) do conteúdo de leitura em si:

```mermaid theme={null}
erDiagram
    CATEGORY ||--o{ NOVEL : organiza
    NOVEL ||--o{ CHAPTER : "tem (drip: 1/dia; final junto com o 7º)"
    CHAPTER ||--o{ PAGE : "tem (7 por capítulo de conteúdo)"
    NOVEL ||--o{ CHOICE : "oferece (no capítulo final)"
```

* **Category** — agrupamento editorial do catálogo (ex: "coreana", "fantasia") — leitura pública,
  sem autenticação.
* **Novel** — a obra em si (título, sinopse, tags) — metadado público; conteúdo exige login.
* **Chapter** — a unidade do drip diário: um capítulo novo é liberado por dia, por leitor, por
  obra (`unlocked_chapter_count`). Guarda metadado (título, duração — usado pelo menu do player de
  NovelAudio).
* **Page** — a unidade de conteúdo dentro do capítulo. **Cada capítulo de conteúdo tem 7
  páginas**, liberadas juntas quando o capítulo é desbloqueado — não uma página por dia. Essa
  granularidade existe para separar "o que o motor de drip libera" (capítulo) de "como o conteúdo
  é paginado na leitura" (página), sem mudar o ritmo do drip nem o preço.
* **Choice** — os finais (tipicamente 4, por obra) vivem em um **capítulo final próprio** (o 8º
  em `novel_chapters`, só metadado — a tela de escolha, sem páginas de conteúdo), liberado **no
  mesmo dia** em que o último capítulo de conteúdo (o 7º) libera. Só ficam visíveis quando esse
  capítulo final libera para aquele leitor.

NovelAudio espelha exatamente esse grafo — mesma obra, mesmos capítulos, mesmos finais — só a
unidade de progresso é diferente: `reading_progress` (NovelAI) guarda o **capítulo** alcançado;
`listening_progress` (NovelAudio) guarda **capítulo + posição em segundos** dentro do áudio
pré-renderizado daquele capítulo. São conceitos de progresso deliberadamente separados, porque a
semântica de "onde você está" é diferente entre ler e ouvir.

HistorinhAI não compartilha esse grafo — seu catálogo é `themes` → `stories`, mais simples, porque
não há ramificação de narrativa nem drip diário: a seleção da história certa por perfil de criança
é decidida pela geração personalizada (RFP, página 17-19), não por um calendário fixo de
capítulos.

## Entitlement: a ponte entre Subscriber e Product

`Entitlement` é o conceito que a API de parceiro expõe (`POST /entitlements`,
`GET /subscriber-status/{id}` — ver [Platform API](/platform/overview)) para responder uma
pergunta: **este Subscriber tem acesso a este Product agora?**

Internamente, o entitlement de parceiro é gravado em uma tabela **própria**, `partner_subscribers`
(chave `(partner_id, subscriber_id, product)`), dentro do projeto Supabase de cada produto:
quando o Partner Gateway (projeto Platform) recebe `POST /entitlements`, ele roteia para a Edge
Function de domínio do produto correspondente (`entitlements-novelai`, por exemplo), que faz o
upsert nessa tabela. Isso é **deliberado**: enquanto o SSO não existe, não há `user_id` real para
ligar o `subscriber_id` do parceiro a uma conta do app — então nada toca a tabela `subscriptions`
do app oficial (que tem chave `(user_id, app)` e coluna `source` distinguindo `app`, `stripe`,
`revenuecat`). Quando o SSO existir, um passo de vínculo preencherá o `user_id` em
`partner_subscribers` e/ou provisionará `subscriptions` com `source = 'partner'` — reaproveitando
a lógica de gating (drip, TTS, streaming) que já lê `subscriptions` hoje, sem que nenhuma tabela
de domínio do leitor precise saber sobre parceiros.

Um Subscriber pode ter zero, um, ou até três Entitlements simultâneos (um por produto) — é isso
que permite ao parceiro ativar "só NovelAI" ou "os três produtos" para o mesmo assinante sem
nenhuma mudança de schema (ver [Modelo multi-tenant](/architecture/multi-tenant-model)).

## Event: a base dos webhooks

`Event` é a abstração por trás de `subscriber.updated` e `subscription.canceled` (ver
[Platform API — Eventos](/platform/overview#eventos-webhooks-de-saída)). Não existe uma tabela
`events` genérica — o mecanismo real é um Database Webhook (trigger Postgres via `pg_net`)
disparando na mudança de linha de `partner_subscribers` e invocando a função intermediária
`webhook-dispatch` (projeto Platform), que assina o payload com HMAC-SHA256 e o entrega ao
`webhook_url` do parceiro em **uma única tentativa** — não há fila própria de reentrega. Do ponto
de vista de quem integra, `Event` é o contrato estável (nome do evento + payload); do ponto de
vista da implementação, é uma consequência direta de uma escrita que já aconteceria de qualquer
forma — não um sistema de mensageria adicional para operar.

## Visão consolidada

```mermaid theme={null}
erDiagram
    PARTNER ||--o{ SUBSCRIBER : possui
    SUBSCRIBER ||--o{ ENTITLEMENT : tem
    SUBSCRIBER ||--o| USER : "vincula-se via SSO"
    USER ||--o| PROFILE : "1:1 (NovelAI/NovelAudio)"
    USER ||--o{ CHILD_PROFILE : "1:N, até 6 (HistorinhAI)"
    ENTITLEMENT ||--o{ EVENT : "gera ao mudar"
    ENTITLEMENT }o--|| PRODUCT : "concede acesso a (NovelAI/HistorinhAI/NovelAudio)"
    PRODUCT ||--o{ NOVEL : "catálogo (NovelAI/NovelAudio)"
    NOVEL ||--o{ CHAPTER : "drip diário"
    CHAPTER ||--o{ PAGE : "7 por capítulo"
```

`PARTNER` (e as credenciais/configuração de webhook) vive no projeto Platform. `SUBSCRIBER` e
`ENTITLEMENT` colapsam, fisicamente, na tabela `partner_subscribers` de cada projeto de produto;
`EVENT` não é uma tabela — é o disparo do webhook na mudança dessas linhas. `USER`, `PROFILE`,
`CHILD_PROFILE`, `NOVEL`, `CHAPTER` e `PAGE` vivem dentro dos projetos Supabase de cada produto —
ver [Modelo multi-tenant](/architecture/multi-tenant-model) para onde exatamente cada um vive.
