# Secretarias — Arquitetura

> Escopo: `v6` (Laravel 5.5.50). Comparações com `v5` em `08-GAPS-E-DIVIDAS.md`.
> Comportamento de conteúdo genérico (notícias, arquivos, destaques) está descrito em
> `../noticias/`; o menu institucional está em `../menus-do-site/` e a agenda em
> `../agenda-de-eventos/`.

---

## 1. Visão geral das camadas

O módulo **Secretarias** é híbrido: três subfuncionalidades independentes
compartilham a mesma tabela `contents` (categoria 7).

```
┌──────────────────────────── CMS (AdminLTE 1.22) ─────────────────────────────┐
│                                                                              │
│  (A) SECRETARIAS — CRUD genérico                                               │
│  rota  GET /cms/conteudos/{slug}  (slug = secretarias)                         │
│        │                                                                     │
│        ▼                                                                     │
│  ContentController@genericIndex/genericCreate/genericEdit  (:96, :168)        │
│        │  returnIndex() / returnData()  →  view prefecture.contents.secretarias│
│        ▼                                                                     │
│  ContentRequest (switch ($slug) case 'secretarias')  (:216-228)                │
│        ▼                                                                     │
│  Content (App\Prefecture\Content) → contents                                   │
│                                                                              │
│  (B) DEPARTAMENTOS — controller próprio                                        │
│  rota  Route::resource("conteudo/secretarias/secretarias-categorias")          │
│        ▼                                                                     │
│  CategorySecretaryController ──► ICategorySecretaryRepository (VAZIA)           │
│        │                                                                     │
│        ▼                                                                     │
│  CategorySecretaryRepository ──► App\Repositories\Repository (base abstrata)   │
│        ▼                                                                     │
│  CategoriasSecretaria (App\Http\Model\Prefecture) → categorias_secretarias    │
│                                                                              │
│  (C) PÁGINAS DE SECRETARIA — controller próprio                                │
│  rota  Route::resource("conteudo/secretarias/secretarias-paginas")             │
│        ▼                                                                     │
│  PagesSecretaryController ──► IPagesSecretaryRepository (VAZIA)                 │
│        │  + FileTrait::insertImage() / insertArchive()                         │
│        ▼                                                                     │
│  PagesSecretaryRepository ──► Repository (base) ──► PaginasSecretaria           │
│        │                                                                     │
│        ├─► App\Prefecture\Archive  (archives.reference_id = pagina id)         │
│        └─► storage/app/public/content/secretarias_paginas/<id>/{destaque,…}     │
│                                                                              │
│  (D) MENU DAS SECRETARIAS — controller próprio                                 │
│  rotas GET cms/conteudo/secretarias/menus/{id}                                  │
│        GET cms/get_categorias/secretarias/{id}                                  │
│        POST cms/order_categorias/secretarias                                   │
│        ▼                                                                     │
│  MenuSecretaryController ──► CategoriasSecretaria (with contentsCategory)      │
└──────────────────────────────────────────────────────────────────────────────┘

┌────────────────────────── PORTAL (Bootstrap 4) ─────────────────────────────┐
│  GET /a-instituicao/secretarias/{slug}                                          │
│        ▼                                                                        │
│  Portal\PrefectureController@getIndex (PrefectureController.php:31)             │
│        │  if ($category->slug == 'secretarias') → view própria                   │
│        ▼                                                                        │
│  portal.components.secretarias.show  (menu inline + destaque + notícias)         │
│                                                                                 │
│  GET /a-instituicao/secretarias/paginas/{slug}/{id}                              │
│        ▼                                                                        │
│  Portal\SecretariaController@show_pages_secretary                                 │
│        ▼                                                                        │
│  portal.components.secretarias.paginas.index                                    │
└──────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────── API (dados abertos) ──────────────────────────┐
│  GET /api/v1/secretarias                                                        │
│        ▼                                                                        │
│  Swagger\V1\Portal\SecretariaController@index                                    │
│        ▼  getContent() (Controller.php:38)                                       │
│  Content → SecretariaResource (+ ArchiveResource)                                │
└──────────────────────────────────────────────────────────────────────────────┘
```

## 2. Fluxo detalhado: página da secretaria no portal

```
GET /a-instituicao/secretarias/saude
  │
  ├─ middleware: web, check.site.manutention      (v6/routes/web.php:78-82)
  │
  ├─ Route::any('a-instituicao/secretarias/{slug}', 'Portal\PrefectureController@getIndex')
  │        nome: 'a-instituicao.index'  ← DUPLICADO com a linha 79 (ver 08)
  │
  ▼
Portal\PrefectureController@getIndex($slug1)              (PrefectureController.php:31)
  │
  ├─ $news_secretary = Content::with('categoryContents','archives')
  │        ->where('secretaria', $slug1)->orderBy('id','DESC')->paginate(6)   (:38)
  │        ← já paginado no SQL (6 por página)
  │
  ├─ $contentNew = getContentDataWith($slug1)  (GetInformationsPrefectureTrait.php:23)
  │        Content::with('archiveHighlight','archiveGallery','categoryContents:id,name,slug')
  │               ->where('slug',$param)->first()
  │
  ├─ $category = CategoryContent::find($contentNew->category_contents_id)
  │
  ├─ if ($category->slug == 'secretarias'):                    (:52)
  │     ├─ $contentNew->secretarias = $contentNew->secretarias
  │     │        ->where('publication_date','<=',$date)   ← hasMany SEM LIMITE
  │     │     (Content::secretarias() = hasMany(Content,'secretaria','slug'))
  │     ├─ $eventsList = CalendarEvent::where('status','1')
  │     │        ->where('final_date','>=',hoje)
  │     │        ->where('secretarias_id',$contentNew->id)
  │     │        ->orderBy('created_at','DESC')->limit(5)->get()   ← limite no SQL (correto)
  │     ├─ $menu = CategoriasSecretaria::where('secretarias_id',$contentNew->id)->get()
  │     └─ return view('portal.components.secretarias.show', [...])
  │
  ▼
portal/components/secretarias/show.blade.php
  ├─ menu inline: @foreach ($menus->sortBy('order') as $item)
  │       └─ @foreach ($item->contentsCategory as $item2)
  │              └─ route('secretarias-pages.index', ['slug'=>…, 'id'=>$item2->id])
  ├─ destaque: $contentNew->archiveHighlight (tamanho Personalizado → width/height)
  ├─ galeria:  @if ($contentNew->galery && !$contentNew->archiveGallery->isEmpty())
  ├─ vídeo:    @if (!empty($contentNew->linksUnique->link)) @include('portal.statics.video')
  └─ "Notícias de {title}":
        @foreach ($contentNew->secretarias->sortByDesc('created_at')->splice(0, 12) as $newSecreatia)
        ← CORTE EM MEMÓRIA (o hasMany acima não tem LIMIT)
```

## 3. Fluxo detalhado: criação de departamento e página (CMS)

```
POST /cms/conteudo/secretarias/secretarias-categorias      (name, secretarias_id)
  ├─ middleware: check.route.permission (CheckRoutePermission.php:23)
  ├─ SecretariasCategoriasRequest
  │     name           => required
  │     secretarias_id => [SelectRule, required]   (SelectRule rejeita o valor "0")
  ├─ CategorySecretaryController@store
  │     $result = $this->repository->upInsert($request)
  │     └─ CategorySecretaryRepository::structureUpInsert()
  │           $fields = array_keys($request); return $this->filter_request($request, $fields)
  │        └─ Repository::upInsert()  (Repository.php:53)
  │             DB::transaction( ... ) → getModel()->updateOrCreate(['id'=>null], $data)
  │             ⚠ catch (\Throwable) { dd($th); }  — stack trace no navegador
  └─ redirect()->route('secretarias-categorias.index') + flash()

POST /cms/conteudo/secretarias/secretarias-paginas       (title, categoria_id, text, archive_highlight, archive[])
  ├─ SecretariasPaginasRequest
  │     title       => required
  │     categoria_id=> [SelectRule, required]
  │     text        => required
  │     ⚠ messages() declara 'name.required' (chave errada) — ver 08
  ├─ PagesSecretaryController@store
  │     $result = $this->repository->upInsert($request)   → $result->id
  │     if ($request->archive)        → insertArchive($value[0], $result->id, $description)
  │     if ($request->archive_highlight) → insertImage($request->archive_highlight, $result->id)
  └─ redirect()->route('secretarias-paginas.index')
```

## 4. Inventário de classes

### 4.1 Controllers

| Classe | Arquivo | Papel |
|---|---|---|
| `App\Http\Controllers\Prefecture\ContentController` | `v6/app/Http/Controllers/Prefecture/ContentController.php` | CRUD **genérico** das secretarias; `genericIndex()` mapeia `secretarias` → `returnIndex('Secretarias', …)` (:96); `genericGenerate()` → `returnData('Secretarias', $slug, null, ['types_sizes' => …], $id)` (:168); `store()` gera slug (:616-669) |
| `App\Http\Controllers\Prefecture\CategorySecretaryController` | `v6/app/Http/Controllers/Prefecture/CategorySecretaryController.php` | CRUD de **departamentos**; popula `$this->secretarias = DB::table('secretarias')->get(['id','titulo as name'])` no construtor (:20) |
| `App\Http\Controllers\Prefecture\PagesSecretaryController` | `v6/app/Http/Controllers/Prefecture/PagesSecretaryController.php` | CRUD de **páginas**; `insertImage()` (:165), `insertArchive()` (:185), `deleteArchive()` (:205) |
| `App\Http\Controllers\Prefecture\MenuSecretaryController` | `v6/app/Http/Controllers/Prefecture/MenuSecretaryController.php` | Tela de menu por secretaria; `returnSecretarias()` (JSON), `orderCategory()` (reescreve `order` 1..N) |
| `App\Http\Controllers\Prefecture\DatatableController` | `v6/app/Http/Controllers/Prefecture/DatatableController.php` | Grades: `get_secretarias()` (:198-202), `get_secretaria_categorias()` (:185-189), `get_secretaria_paginas()` (:191-195) |
| `App\Http\Controllers\Portal\PrefectureController` | `v6/app/Http/Controllers/Portal/PrefectureController.php` | `getIndex()` (:31): portal da secretaria, eventos e menu |
| `App\Http\Controllers\Portal\SecretariaController` | `v6/app/Http/Controllers/Portal/SecretariaController.php` | `show_pages_secretary($slug,$id)`: página de departamento |
| `App\Http\Controllers\Portal\PortalController` | `v6/app/Http/Controllers/Portal/PortalController.php` | `contentList()` (:318) com `case 'secretarias': return redirect(route('a-instituicao-secretarias.index', $slug2));` (:332) → **rota inexistente** |
| `App\Http\Controllers\Swagger\V1\Portal\SecretariaController` | `v6/app/Http/Controllers/Swagger/V1/Portal/SecretariaController.php` | API de dados abertos; `public $category = 'secretarias'` |
| `App\Http\Controllers\Swagger\V1\Portal\Controller` | `v6/app/Http/Controllers/Swagger/V1/Portal/Controller.php` | `getContent()` (:38): monta filtro por categoria + `limit` de `limite` |

### 4.2 Requests / Rules

| Classe | Arquivo | Regras |
|---|---|---|
| `App\Http\Requests\ContentRequest` | `v6/app/Http/Requests/ContentRequest.php:216-228` | `title required|max:120`; `category_contents_id required|numeric`; `text required`; `link max:2999`; `archive_highlight max:20000|mimes:jpg,jpeg,png,gif,webp`; se `type_size_id == 4` → `width`/`height` `required|numeric`. `authorize()` = `true` |
| `App\Http\Requests\SecretariasCategoriasRequest` | `v6/app/Http/Requests/SecretariasCategoriasRequest.php` | `name required`; `secretarias_id [SelectRule, required]` |
| `App\Http\Requests\SecretariasPaginasRequest` | `v6/app/Http/Requests/SecretariasPaginasRequest.php` | `title required`; `categoria_id [SelectRule, required]`; `text required` |
| `App\Http\Requests\Swagger\V1\Portal\SecretariaRequest` | `v6/app/Http/Requests/Swagger/V1/Portal/SecretariaRequest.php` | `titulo nullable|string`; `descricao nullable|string`; falha com JSON 422 |
| `App\Rules\SelectRule` | `v6/app/Rules/SelectRule.php` | Rejeita o valor `"0"` (select vazio) |

### 4.3 Repositórios

| Classe | Arquivo | Observação |
|---|---|---|
| `App\Repositories\Contracts\ICategorySecretaryRepository` | `v6/app/Repositories/Contracts/ICategorySecretaryRepository.php` | **Interface vazia** (`interface ICategorySecretaryRepository { }`) |
| `App\Repositories\Contracts\IPagesSecretaryRepository` | `v6/app/Repositories/Contracts/IPagesSecretaryRepository.php` | **Interface vazia** |
| `App\Repositories\Eloquent\Prefecture\CategorySecretary\CategorySecretaryRepository` | `v6/app/Repositories/Eloquent/Prefecture/CategorySecretary/CategorySecretaryRepository.php` | Estende `App\Repositories\Repository`; implementa `structureUpInsert()` |
| `App\Repositories\Eloquent\Prefecture\PagesSecretary\PagesSecretaryRepository` | `v6/app/Repositories/Eloquent/Prefecture/PagesSecretary/PagesSecretaryRepository.php` | Idem |
| `App\Repositories\Repository` | `v6/app/Repositories/Repository.php` | Base abstrata: `getModel()`, `structureUpInsert()` (abstrato), `upInsert()` (:53), `destroy()` (:74), `destroyAll()` (:89) |

### 4.4 Models

| Classe | Namespace | Tabela | Papel |
|---|---|---|---|
| `App\Prefecture\Content` | `v6/app/Http/Model/Prefecture/Content.php` | `contents` | Secretaria (categoria 7). `secretarias()` = `hasMany(Content::class,'secretaria','slug')` (:72); `orgaos()` (:83); `archives()` (:64); `categoryContents()` (:104); `archiveHighlight()` (:183); `archiveGallery()` (:192); `linksUnique()` (:116) |
| `App\Http\Model\Prefecture\CategoriasSecretaria` | `v6/app/Http/Model/Prefecture/CategoriasSecretaria.php` | `categorias_secretarias` | Departamento. `contentsCategory()` = `hasMany(PaginasSecretaria,'categoria_id','id')`; `SoftDeletes`; `fillable = name, secretarias_id, order` |
| `App\Http\Model\Prefecture\PaginasSecretaria` | `v6/app/Http/Model/Prefecture/PaginasSecretaria.php` | `paginas_secretarias` | Página. `categoria()` (:26) **concatena `'teste'`** no id; `highlight()` (:31) e `archives()` (:36) apontam para `archives.reference_id` |
| `App\Prefecture\Archive` | `v6/app/Http/Model/Prefecture/Archive.php` | `archives` | Arquivo/imagem da página; `typeSize()` (:109) |
| `App\Prefecture\CategoryContent` | `v6/app/Http/Model/Prefecture/CategoryContent.php` | `category_contents` | Categoria (id 7) |
| `App\Prefecture\CalendarEvent` | `v6/app/Http/Model/Prefecture/CalendarEvent.php` | `calendar_events` | Eventos da secretaria (`secretarias_id`) |
| `App\Http\Model\Admin\Modulos` | `v6/app/Http/Model/Admin/Modulos.php` | `modulos` | Chave do módulo (id 18) |
| `App\Prefecture\TypesSizes` | `v6/app/Http/Model/Prefecture/TypesSizes.php` | `types_sizes` | Tamanhos de destaque (Personalizado = id 4) |

### 4.5 Views, Presenter e Resources

| Classe/arquivo | Papel |
|---|---|
| `App\Http\Presenters\MenuItemPresenter` (`v6/app/Http/Presenters/MenuItemPresenter.php`) | `getLink()`: `categoryContent->id == 7` → `url('a-instituicao/'.$categoryContent->slug.$content_slug)` → `/a-instituicao/secretarias/<slug>` |
| `App\Http\Resources\Swagger\V1\Portal\SecretariaResource` | `titulo`, `descricao` (HTML removido), `interacoes` (`clicks`), `slug`, `arquivos` |
| `App\Http\Resources\Swagger\V1\Portal\ArchiveResource` | `titulo`, `descricao`, `tipo`, `url` |

### 4.6 Services / Providers / Traits

| Classe | Arquivo | Papel no módulo |
|---|---|---|
| `App\Providers\MenuServiceProvider` | `v6/app/Providers/MenuServiceProvider.php` | Injeta `$menuHeader` (`menu_items` de `menus_id = 1`) nos layouts do portal; monta o menu do CMS via `menu_orders` + `has_access()` + `modulos.status` |
| `App\Providers\ModulosServiceProvider` | `v6/app/Providers/ModulosServiceProvider.php` | `Cache::rememberForever('modulos')` (slug→status); composer em `prefecture.contents.noticias.form` — **controla a exibição do select "Secretaria"** |
| `App\Http\Traits\GetInformationsPrefectureTrait` | `v6/app/Http/Traits/GetInformationsPrefectureTrait.php` | `getContentDataWith()` (:23), `updateClicks()` (:104), `getTemplateActived()` (:92-102) |
| `App\Http\Traits\FileTrait` | `v6/app/Http/Traits/FileTrait.php` | `saveUpload()` (:50) grava no disk `public` (`storage/app/public`) e comprime a imagem |
| `App\Http\Traits\AppTrait` | `v6/app/Http/Traits/AppTrait.php` | `filter_request()` (:523), `traitRequest()` (:658) usados pelos repositórios |
| `App\Library\Helper\appHelpers` | `v6/app/Library/Helper/appHelpers.php` | `has_access()` (:22) — permissão por `resource.action` |

## 5. Dependências externas (composer)

| Pacote | Uso no módulo |
|---|---|
| `yajra/laravel-datatables-oracle` | Grades do CMS (`DataTables::of(...)->make(true)`) |
| `cartalyst/sentinel` | `Sentinel::check()`, `hasAnyAccess()` no middleware e em `has_access()` |
| `laravelcollective/html` | Helper `Form::text/open` nas views do CMS |
| `intervention/image` | Compressão/recorte das imagens de destaque (`FileTrait::compress`) |
| `zizaco/entrust` (via Sentinel) | Tabela de permissões `permissions` + `roles.permissions` (JSON) |
| `doctrine/dbal` | Necessário para `->change()`/FK em migrações de `contents` |

## 6. Diagrama de dependências (mermaid)

```mermaid
graph TD
  R[Routes web/basepro/api] --> C1[ContentController]
  R --> C2[CategorySecretaryController]
  R --> C3[PagesSecretaryController]
  R --> C4[MenuSecretaryController]
  R --> C5[DatatableController]
  R --> C6[Portal PrefectureController]
  R --> C7[Portal SecretariaController]
  R --> C8[Swagger SecretariaController]
  C1 --> M1[Content]
  C2 --> REP1[CategorySecretaryRepository]
  C3 --> REP2[PagesSecretaryRepository]
  REP1 --> M2[CategoriasSecretaria]
  REP2 --> M3[PaginasSecretaria]
  C3 --> M4[Archive]
  C3 --> FT[FileTrait]
  C4 --> M2
  C6 --> M1
  C6 --> M2
  C6 --> M5[CalendarEvent]
  C7 --> M3
  C7 --> M2
  C8 --> M1
  C8 --> RES[SecretariaResource]
  M2 --> M3
  M3 --> M4
  MS[ModulosServiceProvider] --> NEWS[prefecture.contents.noticias.form]
  M1 -.contents.secretaria.-> NEWS
```

## 7. Acoplamentos com outros módulos

| Módulo acoplado | Ponto de acoplamento |
|---|---|
| Notícias | `contents.secretaria` (string) gravado pelo form de notícias; `Content::secretarias()` (hasMany por `slug`) |
| A Instituição | Mesma rota (`a-instituicao/{slug}`) e mesma view base (`portal.layouts.$model.layout.layout`); categoria 7 e 12 compartilham o `if` do `PrefectureController` |
| Órgãos | Espelho exato do módulo (`orgaos_id` inteiro × `secretaria` string) — ver `../orgaos/` |
| Agenda de Eventos | `calendar_events.secretarias_id` preenchido a partir do `id` da secretaria |
| Menus do Site | `menu_items.category_contents_id = 7` e `menu_items.contents_id` |
| Módulos (CMS) | `modulos.status` controla a visibilidade do menu do CMS e do select "Secretaria" |
| Layouts do portal | `getTemplateActived()` escolhe `modelo1..modelo6`; a view da secretaria **não** é variante por modelo (view única) |
