# 10 - Guia de Desenvolvimento

> Receitas passo a passo para criar as coisas do jeito certo — sempre no padrão V2, imitando as referências canônicas (`Mensageria/Otp` e `Logistica/Edicaorota`). **Nunca** imite Designacoes, Pessoas/Gerenciar ou outros sinais antigos: eles carregam padrões legados.
>
> Relacionados: [01 - Arquitetura](01-arquitetura-geral.md) (por que os padrões são assim), [07 - Front-end](07-front-end.md), [12 - Armadilhas](12-armadilhas-conhecidas.md).
>
> **Dica:** o repositório tem skills de IA em `.claude/skills/genesis-*` (novo-sinal, sondas-db, api-handler, views-i18n, tests, callbacks-adapters, canais-otp) com templates prontos — este capítulo é o resumo humano delas.

## 0. Regras inegociáveis (o checklist do reviewer)

1. Models novos são **Sondas** (nunca Inteligências V1).
2. **Nunca** raw SQL para writes — `create()`/`update()` da Sonda.
3. Controller do Sinal = **dispatcher fino**, nome do diretório (`Edicaorota/Edicaorota.php`, nunca `App.php` para sinal novo).
4. Validação em `Validators/`, não inline no Laboratório.
5. Migration versionada `Migrations/NNN_*.sql`, idempotente, `block_*` sem DEFAULT.
6. `Constelacoes/` só recebe models. Satélites/Traits/views ficam no Sinal.
7. Strings de UI via `show('Texto PT')` + tradução nas 3 línguas.
8. Todo Laboratório novo tem teste (`Testes/<espelho>/*TddTest.php`).
9. Status/tipos como **backed enums** com label/cssClass/transições.
10. Extensibilidade via **Registry + Interface** e **Adapters/<Dominio>/** — sem import direto de domínio externo no core.

## 1. Como criar um Sinal novo

Referência: skill `genesis-novo-sinal`; modelo vivo: `Logistica/sinais/Edicaorota/`.

```
componentes/Missoes/<Missao>/sinais/<MeuSinal>/
├── <MeuSinal>.php        # controller = dispatcher
├── Laboratorios/         # 1 ação = 1 Laboratório (<Acao><MeuSinal>Laboratorio.php)
├── Validators/           # <Acao>Validator.php
├── Sondas/               # <Entidade>Sonda.php
├── Enums/                # <Coisa>Status.php (backed enum)
├── Migrations/           # 001_initial_schema.sql
├── Testes/               # espelha a estrutura, *TddTest.php
├── html/                 # <meusinal>.php (view index) + .js pareado
└── forms/                # inputs de formulário (sem lógica)
```

Controller mínimo:

```php
namespace GalaxiaMissoes\<Missao>\sinais\<MeuSinal>;

class <MeuSinal>
{
    public $galaxiaRoute; public $response; public $routeId;

    public function __construct(object $galaxia, ?array $data = []) {
        $this->galaxiaRoute = $galaxia;
        $this->routeId = $galaxia->routeId;
        $this->bootstrap();               // registrar callbacks/canais (idempotente)
    }

    public function __call($name, $args) { return $this->start($args[0] ?? []); } // SPA render-method guard

    public function start(?array $data = []) {
        $response['html'] = $this->galaxiaRoute->visual('html/<meusinal>', $data, []);
        $this->galaxiaRoute->addResponse($this->routeId, $response);
        $this->response = $this->galaxiaRoute->getResponse($this->routeId);
        return $this;
    }
}
```

Checklist de registro:
- URL: `/missao/<Missao>/<MeuSinal>` funciona sem registro extra (resolução por convenção de pasta).
- Para aparecer no menu/permissões: cadastrar a rota no admin (`Galaxia/Rotas`) e vincular aos níveis de acesso (`Galaxia/Niveisacesso`) — sem isso, `permissaoDeAcesso` redireciona para logout.
- View index = nome do controller em minúsculo.
- Se o Sinal será linkado de dentro do SPA de outro App, o `__call` acima é obrigatório ([Armadilha 18](12-armadilhas-conhecidas.md)).

## 2. Como criar uma Sonda e uma migration

Referência: skill `genesis-sondas-db`; modelo: `Mensageria/sinais/Otp/Sondas/OtpVerificacaoSonda.php`.

**Migration** (`Migrations/00N_minha_tabela.sql`) — idempotente, espelhando `wms_movimentos`:

```sql
CREATE TABLE IF NOT EXISTS minha_tabela (
    id SERIAL PRIMARY KEY,
    -- ... colunas de negócio ...
    -- multi-tenant: SEM DEFAULT — o framework injeta
    block INTEGER,
    block_empresa_id INTEGER,
    block_usuario_id INTEGER,
    block_setor_id INTEGER,
    block_cliente_id INTEGER,
    -- auditoria
    data_cadastro TIMESTAMP DEFAULT NOW(),
    usuario_cadastro VARCHAR(255),
    data_atualizacao TIMESTAMP,
    usuario_atualizacao VARCHAR(255),
    prev_data TEXT,
    block_data TIMESTAMP,
    block_quem VARCHAR(255)
);
CREATE INDEX IF NOT EXISTS idx_minha_tabela_block ON minha_tabela (block);
```

Aplicar manualmente no `dev_gx_goldie` (não há runner — [05 §6](05-banco-de-dados.md#6-migrations)) e **validar com um `create()` de integração** antes de dar por pronta — colunas faltando só quebram aí.

**Sonda:**

```php
namespace GalaxiaMissoes\<Missao>\sinais\<Sinal>\Sondas;
use Galaxia\_Controladores\ModelInteligencia;

class MinhaEntidadeSonda extends ModelInteligencia
{
    private function init(): void {
        self::$entity = 'minha_tabela';
        self::$protected = ['id'];
        self::$required = [];   // vazio! validação fica no Validator (Armadilha 3)
        self::$propeties = ['customerControl' => true, 'companyControl' => true, 'db' => 'gx_goldie'];
        parent::__construct(self::$entity, self::$protected, self::$required, [], self::$propeties);
    }
    // métodos privados + __call, padrão das Sondas existentes
}
```

Lembretes de semântica ([01 §6](01-arquitetura-geral.md#6-modelinteligencia-o-orm)): `find()` é fluente (`->fetch()` obrigatório), `findById()` devolve `[obj]`, `update()` devolve rowCount (guard CAS), `delete()` é soft delete, JSON volta string.

## 3. Como criar um Laboratório + Validator

```php
// Validators/CriarCoisaValidator.php — retorna lista de erros (strings)
class CriarCoisaValidator
{
    public function validar(array $d): array {
        $erros = [];
        if (empty($d['nome'])) $erros[] = 'nome é obrigatório';
        return $erros;
    }
}

// Laboratorios/CriarCoisaLaboratorio.php
class CriarCoisaLaboratorio
{
    public function executar(array $dados): array {
        $erros = (new CriarCoisaValidator())->validar($dados);
        if ($erros) return ['ok' => false, 'erros' => $erros];
        $id = (new MinhaEntidadeSonda())->criar($dados);   // via create() da Sonda
        return ['ok' => true, 'id' => $id];
    }
}
```

- Laboratório consome **outro** Laboratório apenas via `dadosG()` — nunca `new OutroLab()`.
- Transições de status usam guard CAS: `update(['status' => 'novo'], "id = :id AND status = :esperado", ...)` e checar rowCount.
- Efeitos secundários (socket, hooks) **depois** do write, best-effort (write-before-fire, [03 §R24](03-regras-de-negocio.md)).

## 4. Como criar um endpoint da API

Referência: [06 - APIs §6](06-apis.md#6-webhook-inbound-genérico) e skill `genesis-api-handler`. Resumo:

1. Handler em `Plataforma/sinais/Api/Handlers/<Recurso>/<Acao>Handler.php` estendendo `BaseHandler`: ler `$req->body/query/routeParams/jwtClaims`, validar (→ `$this->erro('invalid_input', ..., 422)`), delegar ao Laboratório, responder `$this->ok($req, $data)` ou `adaptarRespostaLegacy()`.
2. Método fino em `Api.php` chamando `$this->dispatcher()->run('POST', '/pattern', Handler::class, $data, $opts)`.
3. Rota em `Routes.php` dentro do grupo `/api/v1`.
4. Opções: GET → `skipValidacaoConteudo`; público → `skipAuth`; docs → `requireDevAccess`.
5. Documentar em `OpenApi/spec.php` e testar em `Testes/Handlers/` (padrão `CriarHandlerIntegrationTest`).

**Se o Laboratório dispara callbacks registrados no controller web:** re-registre-os no caminho da API (padrão `garantirCallbackRegistrado()` do Edicaorota) — a API não instancia o Sinal ([02 §3](02-fluxos-do-sistema.md#3-fluxo-edição-de-rota-pelo-motorista-edicaorota)).

## 5. Como criar uma tela

Referência: [07 - Front-end](07-front-end.md) e skill `genesis-views-i18n`. Resumo:

1. View `html/<nome>.php` + JS pareado `html/<nome>.js` (carregado/versionado automaticamente).
2. Controller: `visual('html/<nome>', $data, [])`. Toda query/regra fica no controller/Laboratório — a view só exibe.
3. Formulário: `visualFormulario('forms/<acao>_<obj>', $data, [])` com `$data['opcoesDoBotao']`; upload exige `filesUpload=true`.
4. Ações: `acaoElemento()` (backend) / `acaoJSElemento()` (JS) / `abrirModal()` — nunca fetch/axios manual no JS do Sinal.
5. Blocos dinâmicos: método `Luz_<Nome>()` + `reencaminheLuz()` no Observatorio; `luzId` único em loops.
6. i18n: `show('Texto em português')`; preencher `linguagens/{pt-br,en,es}.json`; **verificar no browser** (harvest é em runtime).
7. Cores via variáveis do tema (`var(--bg-*)`); testar mobile (largura real 512px) e dark.
8. Callbacks JS globais: expor em `window`.

## 6. Como criar um ponto de extensão (Registry/Adapter)

Referência: skill `genesis-callbacks-adapters`. Decisão:

- **Outros módulos vão plugar comportamento no seu Sinal?** → Interface + Registry (`registrar(nome, classe)`, resolução por string, validação `is_subclass_of`). Modelos: `OtpCanalRegistry`, `EdicaoRotaCallbackRegistry`.
- **Seu Sinal precisa tocar um domínio externo (Romaneio, Coletas, ERP)?** → `Adapters/<Dominio>/` com callback + laboratórios + migrations + testes daquele domínio isolados. O core não importa nada do domínio. Modelo: `Edicaorota/Adapters/Romaneio/`.
- Registro acontece num `bootstrap()` **idempotente** do controller (guard `temRegistro()`), e deve ser garantido também nos caminhos sem controller (API, cron).

## 7. Como criar uma integração externa

Resumo em [08 §10](08-integracoes.md#10-como-criar-uma-integração-nova): inbound via `webhook_endpoints` + Laboratório alvo (tenant vem do `$endpoint`); outbound via Interface+Registry com provider que nunca lança; config `define('GENESIS_<DOMINIO>_*')` com fallback stub; worker cron idempotente com estados por linha e sessão sintética por tenant.

## 8. Como criar um cron

1. Arquivo em `crons/` nomeado `MM-HH-descricao.php` (agendamento pelo nome — [09 §6](09-configuracoes.md#6-agendamento-crons)).
2. Bootstrap: `require __DIR__.'/../autoload.php'`; host via `getenv('CRON_HOST')`; **guard de produção** (abortar se host contém `dev-` quando o cron é production-only) — copie de `dashboard_expedicao_diario.php`.
3. Multi-tenant: sessão sintética por `block` (mesmo padrão do cron de dashboard) antes de usar Sondas.
4. Idempotente (estado por linha ou delete+insert de janela); limite por ciclo; log em stdout.
5. Instalar: rodar `auto.sh` (confira o `CRON_DIR`) e conferir com `crontab -l`.

## 9. Testes

Referência: skill `genesis-tests` e [11 §3](11-guia-de-manutencao.md#3-como-testar-alterações) (bootstrap canônico completo).

- **Unit/TDD:** script standalone `Testes/<espelho>/<Coisa>TddTest.php`, sem banco (mock-then-require), rodado com `php arquivo.php`, saída `[PASS]/[FAIL]`.
- **Integração:** `tests/integration/<Area>/` — host `dev-`, `$_SESSION` **na raiz e em `['dashboard']`**, `Connect` para `dev_gx_goldie` + sanity gate, dados `__test_` + cleanup em `finally`.
- Rode também os testes vizinhos da área antes de entregar.

## 10. Fluxo de trabalho de feature

O caminho completo é o pipeline de agentes ([09 §8](09-configuracoes.md#8-processo-de-desenvolvimento-pipeline-de-agentes)): PRD em `PRD/<slug>/00-prd.md` → `/genesis-feature` (ou etapas soltas `/po`, `/tl`, `/backend`...). Manualmente, a ordem de implementação é a mesma que o pipeline impõe:

**Migrations → Sondas → Validators → Laboratórios → Controller/Enums → Adapters/Callbacks → Handlers de API → Rotas → Views/JS/i18n → Testes → Review.**

`php -l` em cada arquivo; verificação final exercitando o fluxo real no browser dev ([11 §3.4](11-guia-de-manutencao.md#34-verificação-mínima-antes-de-dar-por-pronto)); commit só com pedido explícito.
