# Modelo de dados

Para um dev externo, o modelo de dados do Fayz colapsa numa ideia só: **você traz o seu próprio Supabase (BYOS)** e o seu projeto recebe a **mesma forma de schema** que a plataforma usa em escala. Essa forma tem três camadas — a **biblioteca core**, as **tabelas de plugin** (`plg_`) e as **tabelas do seu app** — e uma regra única que faz tudo se encaixar: **`tenant_id` + RLS é a fronteira de isolamento.** Acerte isso e a dúvida clássica — "onde essa tabela mora?" — desaparece.

```
public.*      tabelas-base que todo negócio precisa (tenants, people, tenant_members, role_permissions…)
plg_*         tabelas que um plugin instala e possui (plg_shop_products, plg_courses_*…)
suas tabelas  o que o seu app/incubator cria por cima (mesmas regras: tenant_id + RLS)
```

{% callout type="info" %}
**Arquitetura em transição.** Esta documentação descreve a **forma-alvo** — a biblioteca `core` e as tabelas de plugin com prefixo `plg_` — que está sendo lançada com a wave de *industry pools*. Hoje as tabelas-base não vivem num schema `core.` separado: moram todas em `public.*` (a migração 001 é explícita — *all core entities live directly in the public schema*), e o prefixo `plg_` é uma convenção já adotada por alguns módulos, não uma regra que todos cumprem ainda. O comando e o fluxo já são estes; os rótulos e a nomenclatura migram junto com a wave.
{% /callout %}

## A biblioteca core — as tabelas-base

A biblioteca **core** (antes chamada `saas_core`) traz o que **todo** negócio precisa. Não é um schema separado com regras próprias — as tabelas moram em `public.*`, prontas e isoladas:

| Tabela core | O que guarda |
| --- | --- |
| `tenants` | os clientes do seu SaaS (um salão, uma clínica) — a raiz do isolamento |
| `people` | pessoas do negócio: clientes, contatos, leads |
| `tenant_members` | quem trabalha no tenant (profissionais, atendentes) |
| `role_permissions` | papéis e permissões dentro do tenant |
| `appointments` | agendamentos, reservas, encontros |
| `transactions` | movimentos financeiros: entradas e saídas |

**Toda tabela carrega `tenant_id` e tem RLS.** É esse par — coluna + policy — que garante que a linha de um tenant seja fisicamente invisível para outro. O isolamento **não** vem de separar schemas; vem do RLS chaveado no `tenant_id`. Esse é o ponto que sustenta o resto do modelo, e o detalhe está em [RLS e multi-tenant](/pt-BR/docs/dados/rls).

## Tabelas de plugin — o prefixo `plg_` é a posse

Um plugin instala as **suas próprias tabelas** com o prefixo **`plg_`** (por exemplo `plg_shop_products`, `plg_courses_*`). O prefixo sinaliza posse: se a tabela começa com `plg_`, ela pertence a um plugin, não ao core nem ao seu app. É uma **convenção** — adotada hoje por shop e courses e **recomendada para plugins novos** — não uma regra que todo módulo já cumpre (alguns conectores criam tabelas sem prefixo).

Veja as tabelas `plg_` que cada plugin instala — com as colunas-chave — na ficha do plugin no [catálogo](/pt-BR/docs/plugins).

Além das tabelas que cria, um plugin **reutiliza** as tabelas do core que precisa, em vez de copiá-las. Se o plugin de agenda precisa saber "quem marcou", ele referencia `public.people`; ele nunca guarda uma cópia da pessoa. Uma fonte de verdade por entidade.

Cada plugin embarca as suas migrations, e elas seguem regras que fazem o `db apply` ser reproduzível:

- **ordenadas** — a sequência de aplicação é determinística.
- **versionadas** — cada migration tem uma versão; o runner sabe o que já rodou.
- **fix-forward** — migrations são append-only: nunca edite uma migração já aplicada, escreva a próxima que corrige. Não há `.down.sql`.
- **idempotentes** — rodar de novo não quebra nem duplica.

## As tabelas do seu app

Quando o seu produto precisa de campos ou tabelas que nem o core nem um plugin trazem, o seu app (ou um [plugin incubator](/pt-BR/docs/plugins-proprios/incubator)) cria as próprias tabelas. É onde a riqueza específica do seu domínio vive. A regra não muda: **precisam carregar `tenant_id` e ter RLS**, exatamente como o core e os plugins. Suas tabelas não são exceção ao isolamento — são iguais a todas as outras.

## A regra de ouro: mesma forma, três tiers

A mesma forma de schema — core + `plg_` + suas tabelas, tudo isolado por `tenant_id` + RLS — vale onde quer que o app rode. É isso que faz o seu BYOS ser idêntico ao que a plataforma opera em escala.

{% callout type="info" %}
**Como a plataforma hospeda em escala (contexto — o seu BYOS é a mesma forma).** A plataforma serve três tiers com o **mesmo shape**: ① um **pool por indústria** — um projeto Supabase compartilhado por muitos tenants da mesma indústria, isolados só por `tenant_id` + RLS; ② **objetos customizados** — tabelas extras no mesmo pool via um mecanismo genérico, sem DDL (ainda sem contrato externo estável); ③ **banco dedicado** — um projeto só seu, com o mesmo shape, onde promover é copiar os dados e trocar a string de conexão. Você não precisa disso para construir: o seu BYOS já nasce na mesma forma, e migrar de tier nunca reescreve o schema.
{% /callout %}

## Website e admin: mesmas tabelas, RLS diferente

Um erro comum é criar pares de tabela `public_*` (para o site) e `admin_*` (para o painel). **O Fayz nunca faz isso.** O lado do site e o lado do admin leem as **mesmas tabelas** — o que muda é a **policy de RLS** e o **papel** de quem consulta: a equipe do tenant enxerga as linhas do tenant; o cliente final enxerga só as próprias. Uma tabela, duas visões, garantidas pelo banco — não duas tabelas para manter em sincronia.

## Por que isso importa na prática

Essa forma única é o que faz o cliente do CRM e a reserva da agenda concordarem sobre o que é "uma pessoa": os dois apontam para a mesma `public.people`. Você não precisa montar nada disso à mão — o `fayz db apply` cria as camadas na ordem certa (core primeiro, depois as migrations versionadas de cada plugin, depois os seus incubator) no seu próprio Supabase.

---

Próximo: provisione tudo isso num banco real com BYOS em [Supabase](/pt-BR/docs/dados/supabase).
