# 01 - Arquitetura Geral

> O Genesis é um ERP/plataforma de gestão (foco forte em logística) construído sobre o **Galaxia**, um framework PHP proprietário. Este documento explica o framework de dentro para fora: nomenclatura, ciclo de request, autenticação, multi-tenant, ORM, componentes dinâmicos e as duas gerações de arquitetura (V1 e V2).
>
> Relacionados: [00 - Introdução](00-introducao.md), [05 - Banco de Dados](05-banco-de-dados.md), [06 - APIs](06-apis.md), [07 - Front-end](07-front-end.md), [12 - Armadilhas](12-armadilhas-conhecidas.md).

## 1. Visão de 10.000 metros

```mermaid
flowchart TD
    subgraph Entrada
        U[Browser do usuário] -->|/missao/...| IDX[system/index.php\nCoffeeCode Router]
        EXT[Sistemas externos] -->|/api/webhooks/...| IDX
        FE[Frontend Genesis] -->|/api/v1/...| IDX
    end
    IDX -->|páginas web| GX[Galaxia.php\ndispatcher]
    IDX -->|REST| API[Plataforma/Api\nDispatcher + Handlers]
    GX --> GR[GalaxiaRoute\nAPI fluente]
    GR --> SINAL[Controller do Sinal\nMissoes/M/sinais/S/S.php]
    SINAL --> LAB[Laboratórios\nações de negócio]
    API --> LAB
    LAB --> SONDA[Sondas\nModelInteligencia]
    SONDA --> PG[(PostgreSQL\ngx_goldie / gnesis)]
    SONDA -.legado.-> MY[(MySQL\ngoldie_atual)]
```

Camadas, da borda ao centro:

| Camada | O que faz | Onde vive |
|---|---|---|
| Bootstrap/roteador | Sessão, tenant por URL, registro de rotas | `system/index.php`, `system/base_config/` |
| Dispatcher Galaxia | Auth, resolução de Sinal, hooks, render | `controladores/_Controladores/Galaxia.php`, `GalaxiaRoute.php` |
| Controller do Sinal | Prepara a view; roteia ações (dispatcher fino no V2) | `componentes/Missoes/<M>/sinais/<S>/<S>.php` |
| Laboratório | Regra de negócio de uma ação | `<Sinal>/Laboratorios/` |
| Sonda (model) | Acesso a dados com multi-tenant automático | `<Sinal>/Sondas/` ou `componentes/Constelacoes/` |
| Banco | PostgreSQL (Genesis) + MySQL (sistema antigo) | servidor único |

## 2. Nomenclatura: a metáfora astronômica

A tabela completa está no [Glossário](15-glossario.md). O essencial:

- **Missão** (= Constelação nas URLs) → domínio (`Logistica`).
- **Sinal** (= Estrela) → módulo/feature (`Edicaorota`). Controller com o nome do diretório.
- **Lua** → sub-rota, chega em `$data['sinalGxData']`.
- **Laboratório** → ação/use-case. **Sonda** → model V2. **Inteligência** → model V1 (deprecated).
- Namespaces: `GalaxiaMissoes\<Missao>\sinais\<Sinal>` (mapeados no PSR-4 do `composer.json`: `GalaxiaMissoes\ → componentes/Missoes`, `Galaxia\ → controladores`, `GalaxiaConstelacoes\ → componentes/Constelacoes`).

**Por que dois autoloaders?** `autoload.php` (raiz) registra um autoloader manual para as classes legadas de `system/` e depois delega ao Composer. Ambos coexistem; código novo depende só do PSR-4.

## 3. Ciclo de vida de uma request web

A URL `/missao/<Constelacao>/<Sinal>/<Lua?>` percorre este caminho:

```mermaid
sequenceDiagram
    participant B as Browser
    participant I as system/index.php
    participant G as Galaxia.php
    participant V as GalaxiaValidation
    participant R as GalaxiaRoute
    participant S as Controller do Sinal

    B->>I: GET /missao/Logistica/Edicaorota
    I->>I: Customer::getCustomerByUrl (tenant pelo subdomínio)
    I->>G: novaMissaoInterna(estrela, sinal, sinalGxData)
    G->>G: safeIncludeData (funde GET+POST+JSON body)
    G->>V: authorization(JWT do cookie)
    V-->>G: galaxiaUserInfo em sessão
    G->>G: possibleRoutesMission (cascata de override)
    G->>R: new GalaxiaRoute(...)->go()
    R->>R: permissaoDeAcesso (rotas × níveis)
    R->>S: new Sinal($galaxia, $data)
    R->>S: start($data)  // e Observatorio/GalaxiaAutoLoad se ação
    S->>R: visual('html/...', $data) + addResponse
    R-->>B: HTML no tema (página) ou {html, data} (fragmento)
```

Detalhes que importam:

1. **Tenant pela URL** — `Customer::getCustomerByUrl()` extrai o *sign* do cliente do subdomínio (`empresa.dominio` → `empresa`, prefixo `dev-` removido) e grava `customerUrlSign` na sessão. Todo o multi-tenant parte daí.
2. **Rotas explícitas por profundidade** — `/missao/{estrela}/{sinal}`, `/missao/{estrela}/{sinal}/{sinalGxData}` etc. (2 a 5 segmentos declarados um a um em `system/index.php:177-185`). Grupos separados para ações: `/missao/satelite/...` (V1) e `/missao/laboratorio/...` (V2).
3. **Tudo é "modal" internamente** — `novaMissaoInterna` marca `fromRequest='modal'`; a decisão entre página completa (embrulhada no tema via `viewTheme('dashboard', ...)`) e fragmento AJAX acontece no final do `GalaxiaRoute::go()`.
4. **Corpo JSON aceito em POST** — se `$_POST` vem vazio, o framework faz `json_decode(file_get_contents('php://input'))`. Form-urlencoded e JSON funcionam nas mesmas rotas.
5. **Cascata de override por pasta** — `possibleRoutesMission` gera 5 candidatos do mais específico ao genérico: `de/<setor>/<cliente>/<nível>/<usuário>/<Sinal>.php` → ... → `<Sinal>.php`. O primeiro arquivo existente vence (`Galaxia.php:405-483`). **O mesmo endpoint pode executar código diferente por usuário/cliente** — poderoso e traiçoeiro; sempre verifique se há overrides antes de concluir "esse código não roda".
6. **Hooks before/after** — em ações (satélite/laboratório), o Sinal pai é executado **duas vezes**: `GalaxiaAutoLoad`/`Observatorio` com `rota='before'` antes da ação (validar) e `rota='after'` depois (atualizar UI: `reencaminheLuz`, `galaxiaResposta`). Efeitos colaterais nesses hooks rodam 2×.

### O contrato do controller

```php
class Edicaorota
{
    public $galaxiaRoute;  // instância de GalaxiaRoute (não de Galaxia!)
    public $response;
    public $routeId;

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

- `visual()` resolve o caminho **relativo ao arquivo do caller** (via `debug_backtrace()`), renderiza com League Plates e, se existir um `.js` de mesmo nome ao lado da view, injeta `<div data-script=".../arquivo.js?v=<filemtime>">` — cache-bust automático do JS pareado.
- Sinais linkados de dentro do SPA de outro App recebem o **método de render** (ex.: `Html()`) em vez de `start()` — implemente `__call` delegando para `start()` (ver [Armadilha 18](12-armadilhas-conhecidas.md)).

## 4. Autenticação, sessão e permissões

### 4.1 Login e JWT

- Login (`system/login/Api/SignInUser.php`) valida credenciais e grava `$_SESSION.userLogin` + cookies.
- Na primeira request autenticada, `GalaxiaValidation::prepareUserInfo()` gera um **JWT HS256** (payload: `date`, `customer`, `user`, `user_os`, `user_browser`, `user_ip`) assinado com a constante `PASSDECODELOGIN`, salvo em cookie `galaxiaUserToken_<sign>` (30 dias), na sessão e na coluna `token` do usuário.
- `GalaxiaValidation::authorization()` valida o JWT a cada request. **O binding de device (IP/OS/browser) está comentado/desativado** — o token depende só do segredo.
- Existem **tokens de bypass hardcoded** para APIs internas (`goldieApi789574`, `tecnosoft789574`, etc. em `GalaxiaValidation.php`) — tratados como dívida de segurança em [13 - Dívidas Técnicas](13-dividas-tecnicas.md).

### 4.2 O objeto `galaxiaUserInfo`

Montado por `prepareUserInfo()` e cacheado em sessão (reconstruído quando o token muda):

```
galaxiaUserInfo->
  userData          (usuário; ->pathname para override por pasta)
  sectorData        (setor; ->inteligencia = QUAL BANCO usar, pagina_inicial)
  accessData        (nível de acesso; start_missao_page)
  customerData      (cliente/tenant; id → block)
  empresasLiberadas[] / principalEmpresa (empresa → block_empresa_id)
```

Repare: **o banco de dados usado pelas Sondas vem do setor do usuário logado** (`sectorData->inteligencia`). É assim que dev (`dev_gx_goldie`) e produção coexistem no mesmo código.

### 4.3 Sessão particionada e overflow para banco

`SessionGalaxia` particiona `$_SESSION` em grupos: `goldie` (login legado), `galaxia` (`galaxiaRockets`) e `dashboard` (todo o resto, incluindo `galaxiaUserInfo`). O `__get` resolve: sessão-em-banco → `$_SESSION[grupo][chave]` → `$_SESSION[chave]`.

Consequências práticas:
- Bootstrap de teste precisa popular `$_SESSION` **na raiz E em `$_SESSION['dashboard']`** (ver [10 - Guia de Desenvolvimento](10-guia-de-desenvolvimento.md#testes)).
- Valores serializados **> 10 MB** (ou chaves `fdb-`) são gravados na tabela Postgres `gx_sessoes` em vez do arquivo de sessão — objetos grandes em sessão geram writes silenciosos e latência.

### 4.4 Permissões (3 camadas)

1. **Permissão de rota**: `GalaxiaValidation::permissaoDeAcesso()` — monta o código `missao/estrela/sinal/...`, consulta a tabela `routes` e o vínculo página × nível de acesso (`NiveisacessoPaginas`). Sem registro → logout. Administrada no Sinal `Galaxia/Rotas` e `Galaxia/Niveisacesso`.
2. **Permissão por estrela**: tabela `gx_permissoes` (`permissao = nivel|especial` + lista `niveis_ids`), checada em `authorization()`.
3. **Guardas declarativas no controller** (`permitirAcesso`, `permitirGrupoAcesso`): **hoje só liberam de fato para `access_id == 1`** — o resto está com TODO. Não confie nelas para segregação fina.

## 5. GalaxiaRoute: a API fluente

Dentro de um Sinal, `$this->galaxiaRoute` oferece:

| Grupo | Métodos | Uso |
|---|---|---|
| Rota fluente | `setConstelacao/setEstrela/setSatelite/setOpcao` (`setOpcao` é alias de `setSinal`), `setVersao(2)` | Endereçar um Laboratório/ação |
| Render | `visual`, `view`, `visualFormulario`, `viewForm` | Views e formulários (com JS pareado auto-versionado) |
| Ações no front | `acaoElemento` (clique → request Galaxia), `acaoJSElemento` (clique → função JS), `abrirModal`, `proximaRota` | Gerar os `data-*` que o JS do framework entende |
| Chamada interna | `dadosG(...)` | Um Laboratório consumindo outro **no mesmo processo**, sem HTTP |
| Atualização parcial | `luz`, `reencaminheLuz`, `getTagAutoRefresh` | Re-renderizar um componente sem recarregar a página |
| Resposta padronizada | `galaxiaResposta($data)->fecharModal()->chamarFuncao(...)->feito()` | Instruções para o JS do front (ver tabela em [07 - Front-end](07-front-end.md)) |

**Regra de ouro dos Laboratórios:** comunicação entre Laboratórios é **só** via `dadosG()` — nunca `new OutroLaboratorio()`. Isso mantém os hooks, o multi-tenant e a auditoria no caminho.

**Cuidado com refactors:** `visual`, `luz`, `reencaminheLuz` e afins descobrem o Sinal de origem via `debug_backtrace()` do arquivo do caller. Mover código para wrappers/helpers **quebra a resolução silenciosamente**.

## 6. ModelInteligencia: o ORM

Classe abstrata em `controladores/_Controladores/ModelInteligencia.php`. Uma Sonda declara `$entity` (tabela), `$protected` (colunas nunca escritas), `$required`, `$propeties` (flags `customerControl`, `companyControl`, `userControl`, `sectorControl`, `cacheControl`, `db`).

### API essencial e semântica

| Método | Retorna | Pegadinha |
|---|---|---|
| `find(terms, params, cols)` | `$this` (fluente) | **Sempre truthy.** Encadeie `->fetch()`. `params` é query-string (`"id=5&x=y"`). |
| `fetch(all=false)` | 1ª linha (obj) ou array; **`null` se vazio** | Exceção PDO é engolida → `null` com `$this->fail` setado. |
| `findById(id)` | **`[obj]`** (embrulhado) ou `[]` | `findById(5)[0]`. |
| `findAny(terms, params)` | dados direto | Atalho `find()->fetch()`. |
| `create(data)` | `lastInsertId()` | Injeta tenant + `data_cadastro` + `usuario_cadastro` automaticamente. |
| `update(data, terms, params)` | **rowCount** (0 se nada mudou) | Use como guard CAS em transições de status. |
| `delete(terms, params)` | — | **Soft delete**: `UPDATE ... SET block = 0`. Nunca apaga fisicamente. |
| `safe()` | array limpo | Remove protegidas e **chaves vazias** (preserva `0`, `'0'`, `false`); `'null'` vira SQL NULL. |

Outros comportamentos centrais:

- `__get`/`__set` mágicos leem/escrevem em `$this->data` — trabalhe sempre com o **retorno** de `fetch/findAny`, não com o estado do model.
- Colunas JSON **voltam como string** — `json_decode` manual em todo ponto de leitura.
- Toda query gravada passa pela auditoria `QueriesRequestApi` (desligável via `turnOff(['queriesRequest'])`); falhas notificam a equipe via `sendFail()`.
- Cache opcional por query (`cacheControl`) memoizado em chaves `fdb-all-<entity>-<md5>`.

## 7. Multi-tenant: o mecanismo mais importante do sistema

Quatro dimensões de isolamento, todas injetadas **automaticamente** pela Sonda conforme `$propeties`:

| Dimensão | Coluna | Valor vem de |
|---|---|---|
| Cliente | `block` | `galaxiaUserInfo->customerData->id` |
| Empresa | `block_empresa_id` | `galaxiaUserInfo->principalEmpresa->empresa_id` |
| Usuário | `block_usuario_id` | usuário logado |
| Setor | `block_setor_id` | setor logado |

- **Leitura:** `find()` prefixa o WHERE. Para cliente: `(block = :cliente OR block = 7)` — o cliente **7 é o "global"** hardcoded: registros com `block=7` aparecem para todos os tenants.
- **Escrita:** `create()` seta as 4 colunas. **Fallbacks perigosos:** se a sessão não resolve a empresa, cai em `block_empresa_id='3'` hardcoded — você grava no tenant errado sem erro.
- **Soft delete = `block=0`** — por isso todo lookup legítimo exige `block > 0`.
- **Desligar:** `$model->turnOff(['customerControl','companyControl'])` — mas a lista é **estática e zera após uma query**; vale só para a próxima chamada.
- **Migrations:** toda tabela nova precisa das colunas `block_*` **sem `DEFAULT`** — o framework injeta os valores. Ver [05 - Banco](05-banco-de-dados.md).

## 8. Luz e composição de blocos (visão de arquitetura)

O **Luz** é o mecanismo de componentes dinâmicos: o controller expõe `Luz_<Nome>()`, componentes podem ser **registrados globalmente** em `componentes/Sinais/Sinais.php` (qualquer Sinal dispara sem conhecer o caminho físico), e `reencaminheLuz()` re-executa e devolve o HTML empacotado para o front trocar o bloco no lugar (atributos `data-gxload*`). A classe `G` (`controladores/_Controladores/G.php`) compõe layouts com "locais" nomeados (`<!--local:...-->`) — é o motor por trás dos GBlocos. Detalhes de uso no [07 - Front-end](07-front-end.md).

## 9. As duas gerações: V1 vs V2

O sistema carrega dez anos de evolução. Reconheça a geração antes de mexer:

| Aspecto | V1 (legado) | V2 (padrão atual) |
|---|---|---|
| Model | Inteligência | **Sonda** |
| Controller | Lógica de negócio embutida (ex.: `Designacoes.php`, 1.586 linhas) | **Dispatcher fino** que só roteia para Laboratórios |
| Validação | Inline no Laboratório | `Validators/<Acao>Validator.php` |
| Status/tipos | Strings soltas | **Backed enums** com label/cssClass/transições em `Enums/` |
| Migrations | `db/tabelas.sql` sem versão | `Migrations/NNN_*.sql` numeradas |
| Testes | Esparsos | `Testes/<espelho>/*TddTest.php` dentro do Sinal |
| Extensão | Import direto entre módulos | **Registry + Interface** (`registrar(nome, classe)`) e **Adapters/<Dominio>/** |
| Satélites/Traits | Em `Constelacoes/` | Dentro do Sinal; `Constelacoes/` é **só model** |

**Referências canônicas V2** (imite-as, não os sinais antigos):
- `componentes/Missoes/Mensageria/sinais/Otp/` — Sinal "capability": channel registry desacoplado, bootstrap de auto-registro, Sondas puras.
- `componentes/Missoes/Logistica/sinais/Edicaorota/` — Sinal "fluxo de domínio": dispatcher fino, `Adapters/Romaneio/`, callbacks Registry+Interface, migrations versionadas, testes espelhados.
- A comparação formal entre gerações está em `componentes/Missoes/Logistica/sinais/Edicaorota/COMPARACAO_FRAMEWORK.md`.

**Por que Registry em vez de Factory tipada?** O Registry resolve por string em runtime e permite que outros módulos se registrem **sem PR no Sinal core** (extensão sem modificação). A Factory por enum é mais type-safe, mas fecha a lista. A decisão consciente do projeto foi privilegiar o desacoplamento.

## 10. O sistema antigo (`antigo/`)

A pasta `antigo/` contém o sistema pré-Galaxia completo (MVC próprio, `modulos_base/`, MySQL `goldie_atual`), mapeado no autoload como `Antigo\`. Ele **ainda está em operação** e é a razão das integrações com "ERP legado" descritas em [08 - Integrações](08-integracoes.md) (cruzamento por `importacao_id`/`codigomd5`, timestamps UTC). Não escrever código novo lá; ele é consumido/sincronizado, não evoluído.

## 11. Decisões arquiteturais e seus porquês (resumo)

| Decisão | Porquê | Custo aceito |
|---|---|---|
| Framework proprietário com metáfora própria | Controle total do render server-side + multi-tenant embutido em toda query | Curva de aprendizado; este manual existe por isso |
| Multi-tenant implícito na Sonda | Impossível "esquecer" o filtro de cliente em query nova | Fallbacks hardcoded perigosos; `turnOff` estático confuso |
| Soft delete global (`block=0`) | Auditoria e recuperação; integrações que referenciam ids antigos não quebram | Tabelas crescem; todo lookup precisa de `block > 0` |
| Banco escolhido pelo setor do usuário | Dev e prod convivem no mesmo deploy sem flag de ambiente | Ambiente dirigido por hostname/sessão é pouco óbvio |
| `dadosG` em vez de HTTP interno | Zero overhead de rede; hooks e tenant preservados | Acoplamento de processo; sem contrato formal |
| V2: Registry + Adapters | Módulos novos plugam sem tocar no core | Resolução por string (erro só em runtime) |
