# Órgãos — Resumo Executivo

> **Versão analisada:** `v6` (Laravel 5.5.50) com comparativo `v5`
> **Slug do módulo:** `orgaos` (tabela `modulos`, id 17; tabela `category_contents`, id 32)
> **Natureza:** Ambos (CMS administrativo + Portal público + API de dados abertos)
> **Estado:** migrado v5 -> v6, com divergência de nome de view SQL (`orgaos` -> `a_orgaos`)

---

## 1. Objetivo

O módulo "Órgãos" publica e mantém os órgãos públicos municipais (Gabinete do Prefeito, PROCON, Procuradoria etc.) como unidades institucionais do portal. Cada órgão é um **conteúdo** (`contents`) da categoria 32, com texto institucional, imagem de destaque, galeria de fotos e vídeo opcional. O portal usa essa categoria em dois lugares: como página individual do órgão (`/a-instituicao/orgaos/{slug}`) e como agrupador de menu (o item "Órgãos" é pai dos itens de menu de cada órgão). O CMS permite ainda **vincular notícias e eventos de agenda a um órgão**, para que a página do órgão mostre apenas o que lhe pertence.

> **Fato estrutural crítico:** **não existe model Eloquent de Órgão** em `v5` nem em `v6`. Não há nenhuma classe `Organ`/`Orgao` em `v5/app/Http/Model/Prefecture/` ou `v6/app/Http/Model/Prefecture/`, nem tabela `orgaos` no banco. Um "Órgão" é apenas uma linha de `contents` com `category_contents_id = 32`. Toda a modelagem deste dossiê decorre disso.

## 2. Efeito / Impacto

| Faixa | Efeito |
|---|---|
| Cidadão | Acessa a página institucional do órgão (`/a-instituicao/orgaos/procon`), lê texto, galeria e vídeo, e vê as notícias e a agenda de eventos **vinculados** ao órgão. Sem paginação: a lista de notícias é renderizada inteira. |
| Prefeitura (CMS) | Cadastra/edita órgãos em `/cms/conteudos/orgaos`, com grade DataTable, exclusão em lote, galeria de imagens e "Link Permanente". O link do menu é montado por `MenuItemPresenter` a partir do `id` da categoria 32. |
| Transparência e legalidade | Órgãos também são expostos em `/api/v1/orgaos` (dados abertos SIM), com título, descrição, interações, slug e arquivos. |
| Operação técnica | O módulo depende de `modulos.status` (hoje **false**) para o vínculo notícia↔órgão e de `contents.orgaos_id` (hoje **100% NULL**) — ou seja, a feature existe no código mas está inerte no banco atual. |

## 3. Comportamento

- Um órgão é um conteúdo da categoria `category_contents.id = 32` (`slug = 'orgaos'`, `category_contents_id` NULL, `status = true`).
- A listagem do CMS **não** consulta Eloquent: lê a view SQL `public.a_orgaos` (colunas `value`, `titulo`, `id`, `galeria`, `slug`) inteira via `DB::table(...)->get()`.
- O botão "Acessar órgão" da grade monta `/a-instituicao/orgaos/${data.slug}`; o botão de galeria monta `editar/orgaos/${data.id}#galeria`.
- O formulário do CMS exige `title` (máx. 120), `category_contents_id` (numérico), `text`; `link` (vídeo YouTube) até 2999 chars; `archive_highlight` até 20000 KB, mimes `jpg,jpeg,png,gif,webp`.
- A página pública do órgão resolve a view `portal.layouts.<modelo>.informacoes-instituicao.a-instituicao.show`, renderiza a **galeria** (`archiveGallery`), o **vídeo** (`linksUnique`), a **agenda de eventos** (até 5 eventos com `calendar_events.orgaos_id = <id do órgão>`) e a **lista de notícias** (`contents.orgaos_id = <id do órgão>`).
- A lista de notícias do órgão é filtrada por `publication_date <= now()` **em memória** (filtro aplicado sobre a Coleção, não no SQL) e **não é paginada**.
- O breadcrumb do órgão é `Home > A Instituição > Órgãos > <título>` (`v6/routes/breadcrumbs.php:215-221`).
- O menu do portal monta o item de cada órgão via `MenuItemPresenter::getLink()`, que tem um caso **hardcoded** para `categoryContent->id == 32` -> `url('a-instituicao/orgaos/<slug>')`.
- Ao excluir um conteúdo, o CMS remove automaticamente os `menu_items` que apontam para ele (`ContentController::destroy`/`genericDestroy`).
- A API `GET /api/v1/orgaos` aceita `titulo`, `descricao` e `limite`; **os filtros de `titulo`/`descricao` são descartados** (bug), apenas `limite` e a ordenação por `publication_date`/`clicks` valem.

## 4. Como o Usuário Interage

**Operador (CMS)** — tela `/cms/conteudos/orgaos`:

- Cabeçalho "Lista de Órgãos", botão "Cadastrar Órgão" (visível só com `orgaos.create`), botão de exclusão em lote (visível só com `orgaos.delete`).
- Grade DataTable (Yajra + AdminLTE) com colunas: checkbox de lote, `value` (row_number da view), ID, Título, Ações.
- Ações por linha: "Acessar órgão" (nova aba), "Galeria" (desabilitado quando `galeria` é falso), "Editar", "Excluir" — esta última só habilitada com `orgaos.delete`.
- Botões de exportação padrão do helper `runDatatables`: Excel, PDF, CSV, TXT (client-side, via DataTables buttons).
- Formulário (`create`/`edit`): aba "Órgãos" com Link Permanente, Título, Texto (Summernote), Vídeo YouTube, Imagem Principal e (se `galery == 1`) aba Galeria com upload múltiplo.

**Cidadão (Portal)** — `/a-instituicao/orgaos/{slug}`:

- Cabeçalho com título, imagem de destaque, corpo do texto, galeria de fotos, vídeo incorporado e, abaixo, "Agenda de eventos" e "Notícias de <órgão>".
- O bloco de notícias só aparece se o módulo `noticias` estiver habilitado (`modulos['noticias']`) e se a configuração `noticias`/mostrar-notícias permitir; caso contrário, é substituído por uma imagem de aviso.
- Não existe página de **listagem** de órgãos no portal: o cidadão chega ao órgão pelo menu principal (dropdown "Órgãos") ou por busca. O menu do módulo **não** possui um `pub-nav` próprio (ao contrário de `publicacoes`, `atas-e-pontos`, `comite-de-investimentos` e `requerimentos`).

**Consumidor de dados** — `GET /api/v1/orgaos` (JSON, sem autenticação).

## 5. Detalhes e Particularidades Técnicas

- **Sem model dedicado.** Órgão = `Content` + `category_contents_id = 32`. O slug da categoria é o único gancho: `ContentController::genericIndex()` mapeia `slug === 'orgaos'` para a view `prefecture.contents.orgaos.index`; a view SQL também filtra por `cc.slug = 'orgaos'`.
- **Camadas:** Route -> `ContentController` (CMS) / `PrefectureController@getIndex` (Portal) / `OrgaoController@index` (API) -> `ContentRequest` -> `Content` (Eloquent) -> view Blade / `OrgaoResource`.
- **View SQL como API de leitura.** O `DatatableController@get_orgaos` ignora o Eloquent e lê `public.a_orgaos`; a view é recreate por migrations com `DB::statement("CREATE OR REPLACE VIEW ...")`.
- **Acoplamento com `modulos`.** `ModulosServiceProvider` cacheia `Cache::rememberForever('modulos')` (slug -> status) e injeta `$modulos` em `prefecture.contents.noticias.form`, onde `modulos['orgaos']` decide se o select "Órgãos" aparece. Com `modulos.status = false` (estado atual), o operador **não consegue** vincular notícia a órgão pelo CMS.
- **Acoplamento com `menu_items` + `MenuItemPresenter`.** O caso `id == 32` está hardcoded no presenter, junto do caso `id == 7` (a-instituicao).
- **Acoplamento com `calendar_events`.** `calendar_events.orgaos_id` referencia `contents.id`.
- **Dependências:** `yajra/laravel-datatables` (grade do CMS), `laracasts/presenter` (presenters), `cartalyst/sentinel` (permissões), `laravelcollective/html` (`Form::`), `intervention/image` (imagem de destaque), `spatie/laravel-activitylog` (log das operações), `diglactic/laravel-breadcrumbs` (breadcrumb do portal).

## 6. Estrutura de Dados

| Objeto | Tipo | Papel |
|---|---|---|
| `contents` | tabela | Armazena o órgão (id 10 Gabinete do Prefeito, 23 PROCON, 24 Procuradoria no banco atual) |
| `category_contents` | tabela | Categoria 32 = "Órgãos" (`slug = 'orgaos'`) |
| `contents.orgaos_id` | coluna | Auto-FK nullable: liga uma **notícia** ao **órgão** (100% NULL no banco atual) |
| `calendar_events.orgaos_id` | coluna | Auto-FK nullable: liga um **evento de agenda** ao órgão |
| `public.a_orgaos` | view SQL | Projeção para a grade do CMS: `value`, `titulo`, `id`, `galeria`, `slug` |
| `menu_items` | tabela | Item agrupador "Órgãos" (id 16) e itens de cada órgão (ids 17, 30, 31) |
| `modulos` | tabela | id 17, `slug = 'orgaos'`, `status = false` |

**Relacionamentos Eloquent relevantes** (`v6/app/Http/Model/Prefecture/Content.php`):

```php
public function orgaos()
{
    return $this->hasMany(Content::class, 'orgaos_id', 'id');
}

public function eventosOrgaos()
{
    return $this->hasMany(CalendarEvent::class, 'orgaos_id', 'id');
}
```

## 7. Como Testar

| Cenário | Como validar |
|---|---|
| Grade do CMS | `GET /cms/datatable/orgaos` retorna JSON com `value`, `titulo`, `id`, `galeria`, `slug`, `edit`, `delete`, `active`, `inactivate` |
| Página do órgão | `GET /a-instituicao/orgaos/procon` devolve 200 e renderiza título, texto e "Notícias de PROCON" |
| Vínculo notícia→órgão | Preencher `contents.orgaos_id` e recarregar a página do órgão: a notícia deve aparecer na lista lateral |
| Vínculo evento→órgão | Preencher `calendar_events.orgaos_id` e recarregar: o evento deve aparecer em "Agenda de eventos" |
| API | `GET /api/v1/orgaos?limite=2` retorna no máximo 2 itens; `?titulo=PROCON` **não** filtra (bug conhecido) |
| Exclusão | Excluir um órgão vinculado a `menu_items` remove também os itens de menu (flash informativo) |

Detalhamento completo em `07-TESTES.md`.

## 8. Migração para Laravel Moderno

- Substituir a view SQL `a_orgaos` por uma query Eloquent com `paginate()` e `withCount()`.
- Criar um model `Organ` (ou `Content` com scope `->ofCategory('orgaos')`) com `slug` único, `casts` e `media` (Spatie MediaLibrary) no lugar de `archives`.
- Form Request dedicado `StoreOrganRequest`/`UpdateOrganRequest` com `Rule::exists('category_contents','id')` e `Rule::unique('contents','slug')`.
- Policy `OrganPolicy` para `viewAny/create/edit/delete` — hoje a checagem é implícita via `has_access()` e `CheckRoutePermission`.
- API Resource dedicada `OrganResource` com filtros de busca **realmente aplicados** (`whereRaw` reatribuído ou `when()`).
- Componente Blade `<x-organ.news-list :organ="$organ" />` paginado com `paginate(10)`, eliminando o corte em memória.
- Cache do menu (`modulos`) via `Cache::remember` com invalidação explícita em `ModulosController::update` (já existe `Cache::pull('modulos')`).

## 9. Resumo Geral

Órgãos é um módulo "genérico por slug": não tem model, tabela nem controller próprios; tudo acontece sobre `contents` com a categoria 32. No CMS, a listagem é alimentada por uma view SQL (`a_orgaos` na v6, `orgaos` na v5) que carrega a tabela inteira em PHP antes de paginar no cliente. No portal, cada órgão vira uma página institucional que mostra texto, galeria, vídeo, agenda de eventos e as notícias a ele vinculadas — mas a lista de notícias e a grade do CMS não têm paginação no SQL. O menu do portal monta os links de órgão por um `if` hardcoded no `MenuItemPresenter`. A API de dados abertos expõe os órgãos, porém os filtros de `titulo` e `descricao` nunca são aplicados porque o resultado do `whereRaw` não é reatribuído. Por fim, o vínculo notícia↔órgão existe em código (`contents.orgaos_id`, select no formulário de notícias) mas está inerte no banco atual, porque `modulos.status` do slug `orgaos` é `false` e nenhuma das 184 linhas de `contents` usa a coluna.

---

## Índice do dossiê

| Arquivo | Conteúdo |
|---|---|
| `00-RESUMO.md` | Este documento |
| `01-ARQUITETURA.md` | Camadas, classes e o diagrama de fluxo |
| `02-MODELAGEM.md` | Estrutura de dados detalhada |
| `03-ROTAS-E-CONTROLADORES.md` | Rotas, controllers, requests, resources |
| `04-VIEWS-E-UX.md` | Telas, componentes, JS, assets |
| `05-REGRAS-E-PORTABILIDADE.md` | Particularidades e armadilhas |
| `06-MIGRACAO-MODERNA.md` | Plano de reconstrução em Laravel moderno |
| `07-TESTES.md` | Estratégia e casos de teste |
| `08-GAPS-E-DIVIDAS.md` | Pendências, riscos, comparativo v5 vs v6 |
| `09-API-E-DADOS-ABERTOS.md` | Superfície REST de dados abertos (rota, controller, resource, contrato JSON) |
