# Órgãos — Arquitetura

> Escopo: fluxo da requisição, classes envolvidas, diagrama e dependências externas.
> Referência: `v6` (Laravel 5.5.50). Divergências `v5` no capítulo 8.

---

## 1. Visão geral da arquitetura

O módulo **não possui camada vertical própria** — ele é um *caso particular* do CRUD genérico de `contents`, acionado pelo `slug` da categoria. Três superfícies distintas existem para a mesma entidade:

```
                    category_contents (id 32, slug 'orgaos')
                                  |
        +-------------------------+-------------------------+
        |                         |                         |
   SUPERFICIE CMS            SUPERFICIE PORTAL        SUPERFICIE API
   /cms/conteudos/orgaos      /a-instituicao/orgaos/x   /api/v1/orgaos
        |                         |                         |
   ContentController          PrefectureController      OrgaoController
        |                         |                         |
   prefecture.contents.*      portal.layouts.<modelo>   OrgaoResource
        |
   DatatableController ---> view SQL public.a_orgaos
```

A **entidade** é sempre `App\Prefecture\Content` (`v6/app/Http/Model/Prefecture/Content.php`). A **view SQL** `a_orgaos` é uma projeção materializada por `CREATE OR REPLACE VIEW`, mantida por migrations e consumida apenas pela grade do CMS.

## 2. Fluxo da requisição

### 2.1 CMS — listagem

```
GET /cms/conteudos/orgaos
  -> Route 'listar-conteudo'            v6/routes/basepro.php:235
  -> Prefecture\ContentController@genericIndex   (v6/app/Http/Controllers/Prefecture/ContentController.php:91)
       * if ($slug === 'orgaos') return $this->returnIndex('Órgãos', $slug);   (linha 95)
       * returnIndex(): generateLog($title); menuClicks($slug);
       * return view("prefecture.contents.$slug.index", ['title' => $title]);
  -> prefecture.contents.orgaos.index    (v6/resources/views/prefecture/contents/orgaos/index.blade.php)
       * @include JS assets/js/custom/contents/orgaos/datatable.js
  -> GET /cms/datatable/orgaos
       -> Route 'datatable.orgaos'       v6/routes/basepro.php:416
       -> Prefecture\DatatableController@get_orgaos  (linhas 258-262)
            $orgaos = DB::table("a_orgaos")->get();        // <-- carrega a view INTEIRA
            DataTables::of($this->struct_views($orgaos, 'orgaos'))->make(true);
```

**Autorização (CMS):**

- `ContentController::__construct()` aplica `$this->middleware('check.route.permission')`
  (`v6/app/Http/Controllers/Prefecture/ContentController.php:57`).
- `CheckRoutePermission` (`v6/app/Http/Middleware/CheckRoutePermission.php`) resolve a permissão pelo
  nome da rota e pelo segmento da URL:
  - `/cms/conteudos/orgaos` -> segmento 3 = `orgaos` -> exige `orgaos.index`.
  - `/cms/conteudos/cadastrar/orgaos` -> segmento 3 = `cadastrar` -> exige `orgaos.create`.
  - `/cms/conteudos/editar/orgaos/{id}` -> segmento 3 = `editar` -> exige `orgaos.edit`.
- A rota `datatable.orgaos` cai na lista de rotas abertas (`'datatable'` em `$openRoutes`), logo **não exige
  permissão específica** — apenas estar logado.
- `has_access()` (`v6/app/Library/Helper/appHelpers.php:22-42`) é o predicado real, apoiado em
  `cartalyst/sentinel` (`Sentinel::inRole('super-admin')` sempre passa).

### 2.2 Portal — página do órgão

```
GET /a-instituicao/orgaos/procon
  -> Route 'a-instituicao-orgaos.index'  v6/routes/web.php:82
  -> middleware group 'check.site.manutention'
  -> Portal\PrefectureController@getIndex   (v6/app/Http/Controllers/Portal/PrefectureController.php:30)
       * $news_secretary = $this->content->with(...)->where('secretaria', $slug1)->orderBy('id','DESC')->paginate(6);
       * $contentNew = $this->getContentDataWith($slug1);        // Content + archiveHighlight + archiveGallery + categoryContents
       * $this->updateClicks($contentNew->id);
       * $category = $this->categoryContent->find($contentNew->category_contents_id);
       * if ($category->slug == 'orgaos') { ... }
            $contentNew->orgaos = $contentNew->orgaos->where("publication_date", "<=", $date);   // filtro EM MEMORIA
            $eventsList = $eventsList->where('orgaos_id', $contentNew->id)->limit(5)->get();      // limite no SQL (ok)
            return view('portal.layouts.' . $this->getTemplateActived() . '.informacoes-instituicao.a-instituicao.show', [
                'contentNew' => ..., 'title' => ..., 'linkVideo' => ...,
                'sidenews' => $contentNew->orgaos, 'eventsList' => $eventsList, 'model' => ...
            ]);
  -> portal.layouts.<modelo>.informacoes-instituicao.a-instituicao.show
       -> @component('portal.layouts.<modelo>.informacoes-instituicao.components.components-a-prefeitura.index')
            @slot('text'), @slot('gallery'), @slot('video')
            @include('portal.layouts.<modelo>.layout.sidebar.orgaosNews')   // lista de notícias
```

`getTemplateActived()` (`v6/app/Http/Traits/GetInformationsPrefectureTrait.php:92-102`) lê
`configurations.key = 'layout'` e **aborta 404** se `portal.layouts.<layout>.home.index` não existir.
Logo, apenas `modelo1..modelo6` são alcançáveis; `default` e `newsbit` não têm
`informacoes-instituicao/a-instituicao/show.blade.php` e produziriam erro de view.

### 2.3 API — dados abertos

```
GET /api/v1/orgaos?titulo=...&descricao=...&limite=100
  -> Route 'v1.orgaos.index'   v6/routes/api.php:21-23 (prefix 'v1', as 'v1.', sub-grupo 'orgaos')
  -> middleware 'api' (RouteServiceProvider)
  -> Swagger\V1\Portal\OrgaoController@index(OrgaoRequest $request)
       * $contents = $this->getContent();          // base: Content::query()->with('archives')
       *   - filtra por categoryContents.slug == 'orgaos' (orWhereHas, direto ou aninhado)
       *   - orderByDesc('publication_date')->orderByDesc('clicks')
       *   - limit(request('limite')) se informado
       * if ($title)      { $contents->whereRaw(...); }   // <-- RESULTADO DESCARTADO (bug)
       * if ($description) { $contents->whereRaw(...); }   // <-- RESULTADO DESCARTADO (bug)
       * $contents = $contents->get();
       * return OrgaoResource::collection($contents);
```

## 3. Diagrama ASCII do fluxo (CMS -> View SQL)

```
 ┌───────────────────────────────────────────────────────────────────────────┐
 │ Route 'listar-conteudo'   GET /cms/conteudos/orgaos                       │
 │   v6/routes/basepro.php:235                                                │
 └────────────────────────────────┬──────────────────────────────────────────┘
                                  ▼
 ┌───────────────────────────────────────────────────────────────────────────┐
 │ ContentController@genericIndex('orgaos')                                   │
 │   v6/app/Http/Controllers/Prefecture/ContentController.php:91-125          │
 │   → returnIndex('Órgãos','orgaos') → view prefecture.contents.orgaos.index │
 └────────────────────────────────┬──────────────────────────────────────────┘
                                  ▼
 ┌───────────────────────────────────────────────────────────────────────────┐
 │ Blade index  (v6/resources/views/prefecture/contents/orgaos/index.blade.php) │
 │   <table id="tabela_orgaos">  <tbody vazio>                               │
 │   <script src="assets/js/custom/contents/orgaos/datatable.js">            │
 └────────────────────────────────┬──────────────────────────────────────────┘
                                  ▼  (XHR do navegador)
 ┌───────────────────────────────────────────────────────────────────────────┐
 │ GET /cms/datatable/orgaos   → DatatableController@get_orgaos             │
 │   $orgaos = DB::table("a_orgaos")->get();      ← SEM LIMIT / SEM PAGINA  │
 │   struct_views($orgaos,'orgaos')  → + edit/delete/active/inactivate       │
 │   DataTables::of(...)->make(true)   ← Yajra fatia em memória              │
 └────────────────────────────────┬──────────────────────────────────────────┘
                                  ▼
 ┌───────────────────────────────────────────────────────────────────────────┐
 │ PostgreSQL: VIEW public.a_orgaos                                         │
 │   SELECT row_number() OVER (ORDER BY c.created_at DESC) AS value,        │
 │          c.title AS titulo, c.id, c.galery AS galeria, c.slug             │
 │   FROM contents c JOIN category_contents cc ON cc.id=c.category_contents_id│
 │   WHERE c.deleted_at IS NULL AND cc.slug::text = 'orgaos'::text           │
 └───────────────────────────────────────────────────────────────────────────┘
```

## 4. Classes envolvidas

### 4.1 CMS

| Classe | Caminho | Papel |
|---|---|---|
| `App\Http\Controllers\Prefecture\ContentController` | `v6/app/Http/Controllers/Prefecture/ContentController.php` | CRUD genérico dirigido por slug. `genericIndex` (91), `returnIndex` (121), `genericCreate`/`genericEdit`/`genericGenerate` (140-183), `returnData` (275), `store` (616), `destroy` (1320), `genericDestroy` (1369) |
| `App\Http\Controllers\Prefecture\DatatableController` | `v6/app/Http/Controllers/Prefecture/DatatableController.php` | `get_orgaos` (258-262) monta a grade a partir da view SQL; `struct_views` (504-520) injeta flags de permissão |
| `App\Http\Requests\ContentRequest` | `v6/app/Http/Requests/ContentRequest.php` | Regras por slug; `case 'orgaos': case 'a-cidade':` (205-214) |
| `App\Prefecture\Content` | `v6/app/Http/Model/Prefecture/Content.php` | Entidade do órgão e das notícias; `orgaos()` (83), `eventosOrgaos()` (98), `categoryContents()` (107) |
| `App\Prefecture\CategoryContent` | `v6/app/Http/Model/Prefecture/CategoryContent.php` | Categoria 32; `scopeBySlug()` |
| `App\Prefecture\MenuItem` | `v6/app/Http/Model/Prefecture/MenuItem.php` | Itens de menu; `categoryContent()`, `content()`, `subItens()` |
| `App\Prefecture\MenuOrder` | `v6/app/Http/Model/Prefecture/MenuOrder.php` | Entrada de menu do CMS (slug `orgaos`, `modulo_id = 17`) |
| `App\Http\Model\Admin\Modulos` | `v6/app/Http/Model/Admin/Modulos.php` | Flag `status` do módulo |
| `App\Repositories\Contracts\IContentRepository` | `v6/app/Repositories/Contracts/IContentRepository.php` | Contrato vazio; injetado no controller |
| `App\Repositories\Eloquent\Prefecture\Content\ContentRepositoryDefaultImpl` | `v6/app/Repositories/Eloquent/Prefecture/Content/ContentRepositoryDefaultImpl.php` | Implementação mínima (`upInsert`) |
| `App\Http\Presenters\MenuItemPresenter` | `v6/app/Http/Presenters/MenuItemPresenter.php` | `getLink()`/`getShortLink()` montam `a-instituicao/orgaos/<slug>` para a categoria 32 |
| `App\Http\Presenters\ContentPresenter` | `v6/app/Http/Presenters/ContentPresenter.php` | Formatação de datas e thumbnail de destaque |
| `App\Http\Middleware\CheckRoutePermission` | `v6/app/Http/Middleware/CheckRoutePermission.php` | Autorização por rota/segmento |
| `App\Providers\MenuServiceProvider` | `v6/app/Providers/MenuServiceProvider.php` | Injeta `$menuHeader` nas views `portal.layouts.modeloN.layout.*`; monta o menu lateral do CMS (caso `orgaos` -> `/cms/conteudos/orgaos`) |
| `App\Providers\ModulosServiceProvider` | `v6/app/Providers/ModulosServiceProvider.php` | `Cache::rememberForever('modulos')` e injeção de `$modulos` nas views |

### 4.2 Portal

| Classe | Caminho | Papel |
|---|---|---|
| `App\Http\Controllers\Portal\PrefectureController` | `v6/app/Http/Controllers/Portal/PrefectureController.php` | `getIndex()` (30-99); ramo `if ($category->slug == 'orgaos')` (71-88) |
| `App\Http\Controllers\Portal\OrganizationalStructureController` | `v6/app/Http/Controllers/Portal/OrganizationalStructureController.php` | **Não pertence ao módulo**: resolve `/estrutura-organizacional` por `StaticPage`, não pelos órgãos |
| `App\Http\Traits\GetInformationsPrefectureTrait` | `v6/app/Http/Traits/GetInformationsPrefectureTrait.php` | `getContentDataWith` (23), `getTemplateActived` (92), `updateClicks` (104), `getStaticPageData` (60) |
| `App\Prefecture\CalendarEvent` | `v6/app/Http/Model/Prefecture/CalendarEvent.php` | Eventos da agenda; `agendaEventosOrgaos()` por `orgaos_id` |
| `App\Prefecture\CategoryContent` | `v6/app/Http/Model/Prefecture/CategoryContent.php` | Categoria 32 |

### 4.3 API

Detalhe completo em `09-API-E-DADOS-ABERTOS.md`.

| Classe | Caminho | Papel |
|---|---|---|
| `App\Http\Controllers\Swagger\V1\Portal\OrgaoController` | `v6/app/Http/Controllers/Swagger/V1/Portal/OrgaoController.php` | `index()`; `$category = 'orgaos'` |
| `App\Http\Controllers\Swagger\V1\Portal\Controller` | `v6/app/Http/Controllers/Swagger/V1/Portal/Controller.php` | Base: `getContent()` monta o filtro por categoria; `caseInsensitiveAccentFreeSearch()` monta o `whereRaw` |
| `App\Http\Requests\Swagger\V1\Portal\OrgaoRequest` | `v6/app/Http/Requests/Swagger/V1/Portal/OrgaoRequest.php` | `titulo`, `descricao` nullable; falha com 422 JSON |
| `App\Http\Resources\Swagger\V1\Portal\OrgaoResource` | `v6/app/Http/Resources/Swagger/V1/Portal/OrgaoResource.php` | `titulo`, `descricao`, `interacoes`, `slug`, `arquivos` |
| `App\Http\Resources\Swagger\V1\Portal\ArchiveResource` | `v6/app/Http/Resources/Swagger/V1/Portal/ArchiveResource.php` | `titulo`, `descricao`, `tipo`, `url` |

### 4.4 Camadas ausentes (relevante para a reconstrução)

- **Nenhum model de Órgão.** Nada entre `Content` e o banco representa a entidade "Órgão".
- **Nenhum Repository específico.** O `IContentRepository` é um contrato vazio e a implementação
  `ContentRepositoryDefaultImpl` só oferece `upInsert`.
- **Nenhum Service.** Regras de negócio estão espalhadas entre controller, presenter e view.
- **Nenhuma Policy.** A autorização é feita por `has_access()` + `CheckRoutePermission`, por convenção
  de nome de rota.

## 5. Dependências externas (composer)

Extraído de `v6/composer.json`:

| Pacote | Versão | Uso no módulo |
|---|---|---|
| `laravel/framework` | `5.5.*` | Base |
| `yajra/laravel-datatables` | `^1.5` | `DataTables::of(...)->make(true)` na grade do CMS |
| `jeroennoten/laravel-adminlte` | `1.22` | Layout do CMS e `BuildingMenu` (menu lateral) |
| `laracasts/presenter` | `^0.2.2` | `MenuItemPresenter`, `ContentPresenter` |
| `laracasts/flash` | `3.0.*` | Mensagens de sucesso/erro do CMS |
| `cartalyst/sentinel` | `2.0.*` | Sessão e permissões (`has_access`) |
| `laravelcollective/html` | `^5.5` | `Form::text`, `Form::model`, `Form::open` |
| `intervention/image` | `^2.7` | Recorte da imagem de destaque (`cropper-min.js`) |
| `spatie/laravel-activitylog` | `^2.8` | `generateLog()` em create/edit/delete |
| `diglactic/laravel-breadcrumbs` | `^4.0` | Breadcrumb `Home > A Instituição > Órgãos` |
| `maatwebsite/excel` | `^3.1` | Dependência do Yajra para exportação |
| `dompdf/dompdf` / `tecnickcom/tcpdf` | — | Exportação PDF da grade |
| `laravellegends/pt-br-validator` | `^5.1` | Validações em português |

## 6. Onde o módulo encosta em outros módulos

| Módulo | Ponto de encosto | Tipo |
|---|---|---|
| Notícias | `contents.orgaos_id`; select "Órgãos" em `prefecture.contents.noticias.form` (linhas 76-88) | dado |
| Agenda de eventos | `calendar_events.orgaos_id`; `Content::eventosOrgaos()` | dado |
| Menus do site | `menu_items.category_contents_id = 32`, `menu_items.contents_id`; `MenuItemPresenter` | dado + presenter |
| A Instituição | Comparte a view `portal.layouts.<modelo>.informacoes-instituicao.a-instituicao.show` e a rota `/a-instituicao/*` | view/rota |
| Módulos (CMS) | `modulos.status` (`slug = 'orgaos'`) e `menu_orders.modulo_id = 17` | flag |
| Dados abertos (Swagger) | `Swagger\V1\Portal\Controller::getContent()` compartilhado por secretarias/orgaos/legislacao/noticias/publicacoes | código |
| Arquivos (galeria) | `archives.contents_id`, `archives.highlight` | dado |

> Detalhes de cada acoplamento em `05-REGRAS-E-PORTABILIDADE.md`; behaviour equivalente de
> A Instituição e Secretarias em `../a-instituicao/` e `../secretarias/`.

## 7. Pontos de atenção arquiteturais

1. **A view SQL é a "API de leitura" do CMS**, mas o Eloquent é a "API de leitura" do portal e da API
   pública. Duas fontes de verdade para a mesma entidade, com semântica de exclusão diferentes
   (a view filtra `deleted_at IS NULL`; o Eloquent filtra por SoftDelete automaticamente).
2. **A view SQL não é atualizada por Eloquent.** Ela só muda quando uma migration roda. Se o schema de
   `contents` mudar (colunas renomeadas), as views quebram silenciosamente até a próxima migration.
3. **A rota do portal é `Route::any`**, ou seja, aceita GET/POST/PUT/DELETE na mesma URL.
4. **`getTemplateActived()`** e o `View::exists()` acoplam o módulo a uma view de *home* de outro
   contexto — falha indireta e pouco óbvia.
