# Incubator

O incubator é onde os plugins nascem: uma pasta dentro do seu app que segue o **mesmo contrato** dos plugins oficiais. É o caminho para uma capacidade que só existe no seu produto — e é o passo que transforma você de usuário do SDK em autor.

## Gere o plugin

```bash
npx @fayz-ai/cli create plugin fidelidade
```

```
✓ Created app-local plugin "src/plugins/fidelidade"

  Add createFidelidadePlugin() to your app's plugins array (see src/plugins/fidelidade/README.md).
  Run "fayz doctor" to check boundaries.
```

O que nasce em `src/plugins/fidelidade/`:

| Arquivo | Papel |
| --- | --- |
| `index.ts` | O `PluginManifest`: id, navegação, rotas e a página. O mesmo contrato de um `@fayz-ai/plugin-*`. |
| `data/types.ts` | O contrato de dados (`FidelidadeDataProvider`) que todo backend implementa. |
| `data/mock.ts` | Provider mock — o plugin já roda sem banco. |
| `data/supabase.ts` | Provider real, passando pelo boundary do Fayz (`getSupabaseClientOptional`), nunca importando o SDK do Supabase direto. |
| `schema/index.ts` | Onde as migrations do plugin vivem. |
| `README.md` | Como fiar o plugin e a checklist de graduação. |

## O contrato é o mesmo dos oficiais

O `index.ts` exporta uma factory que devolve um `PluginManifest` — id, `navigation`, `routes` e uma página. Ele escolhe o provider automaticamente (Supabase quando há backend, mock caso contrário) via `createSafeDataProvider`:

```ts
export function createFidelidadePlugin(options?: FidelidadePluginOptions): PluginManifest {
  const provider =
    options?.dataProvider ??
    createSafeDataProvider(
      () => createSupabaseFidelidadeProvider(),
      () => createMockFidelidadeProvider(),
    )
  // ...
  return {
    id: 'fidelidade',
    name: 'Fidelidade',
    icon: 'Puzzle',
    version: '0.1.0',
    navigation: [{ section: 'main', position: 50, label: 'Fidelidade', route: '/fidelidade', icon: 'Puzzle' }],
    routes: [{ path: '/fidelidade', component: Page }],
  }
}
```

Para ligá-lo, adicione `createFidelidadePlugin()` ao array de plugins da config do app (o `README.md` gerado mostra onde). Depois confira os limites:

```bash
npx @fayz-ai/cli doctor
```

## O boundary é obrigatório

O `data/supabase.ts` gerado **nunca** importa `@supabase/supabase-js` direto — ele obtém o cliente por `getSupabaseClientOptional()`, o boundary do Fayz, e escopa a query por `tenant_id`:

```ts
import { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'

// ...
const { data, error } = await sb()
  .from('fidelidade')
  .select('*')
  .eq('tenant_id', getActiveTenantId())
```

Se você trocasse isso por um import direto do SDK do Supabase, o `fayz doctor` acusaria a quebra de boundary. Essa é a regra que mantém o seu plugin promovível — o detalhe está em [Padrões](/pt-BR/docs/plugins-proprios/padroes).

## Onde vão as migrations

As tabelas do seu plugin vivem em `schema/index.ts` e são referenciadas no manifesto via `migrations: [...]`:

```ts
migrations: [{ id: 'fidelidade-0001', version: '0.1.0', sql: '<create table ...>' }]
```

No `fayz db apply`, o passo `incubator` (o último da ordem) aplica essas migrations junto com o resto — por isso a ordem `spine → drizzle → seed → plugins → incubator` importa.

## De incubator a oficial

Promover um plugin app-local para um pacote `@fayz-ai/plugin-*` oficial é **empacotamento, não reescrita**: o manifesto não muda, você move a pasta para um pacote próprio. O `README.md` gerado traz a checklist completa de graduação (contrato validado, teste de capacidade, migrations fiadas, permissões declaradas, i18n completo, boundary limpo). O mesmo contrato que você seguiu aqui é o que os plugins do catálogo seguem — não há nada que um plugin oficial faça que o seu não possa.

---

Próximo: cada campo que um plugin pode declarar em [Manifesto do plugin](/pt-BR/docs/plugins-proprios/manifesto).
