# Avisos — Rotas e Controladores

> **Portal** = `v6/routes/web.php` | **CMS** = `v6/routes/basepro.php` (prefixo `/cms`) |
> **API** = `v6/routes/api.php` (sem endpoint de avisos).

---

## 1. Rotas do Portal

| Método | URI | Nome | Controller@ação | Observação |
|---|---|---|---|---|
| ANY | `/` | `home` | `Portal\PortalController@index` | **Única** exibição do aviso (modal na home) |

Não existe rota pública de listagem de avisos: o grep por `anuncio`/`avis` em
`v6/routes/web.php` não retorna nenhuma ocorrência. O aviso também não aparece na
busca global, pois `perguntas-frequentes`, `glossario`, `noticias`, `a-cidade`,
`a-instituicao`, `publicacoes` e `legislacao` são os únicos slugs buscáveis
(`v6/app/Http/Controllers/Portal/PortalController.php:251`).

## 2. Rotas do CMS (`/cms`)

| Método | URI | Nome | Controller@ação |
|---|---|---|---|
| GET | `/cms/conteudos/anuncios` | `listar-conteudo` | `Prefecture\ContentController@genericIndex` |
| GET | `/cms/conteudos/cadastrar/anuncios` | `cadastrar-conteudo` | `Prefecture\ContentController@genericCreate` |
| GET | `/cms/conteudos/editar/anuncios/{id}` | `editar-conteudo` | `Prefecture\ContentController@genericEdit` |
| POST | `/cms/conteudo` | `conteudo.store` | `Prefecture\ContentController@store` |
| PUT/PATCH | `/cms/conteudo/{id}` | `conteudo.update` | `Prefecture\ContentController@update` |
| DELETE | `/cms/conteudo/{id}` | `conteudo.destroy` | `Prefecture\ContentController@destroy` |
| DELETE | `/cms/conteudo/delete/{id}` | `generic.conteudo.delete` | `Prefecture\ContentController@genericDestroy` |
| POST | `/cms/conteudo/deleteAll` | `ajax.generic.content.deleteAll` | `Prefecture\ContentController@genericDeleteAll` |
| POST | `/cms/set-status` | `set.status` | `Prefecture\ContentController@setStatus` |
| GET | `/cms/datatable/anuncios` | `datatable.anuncios` | `Prefecture\DatatableController@get_avisos` |

Fontes: `v6/routes/basepro.php:235-237`, `:174`, `:223`, `:141`, `:347`, `:429`.

## 3. `Portal\PortalController@index` — onde o aviso é selecionado

Arquivo: `v6/app/Http/Controllers/Portal/PortalController.php`

```php
// linhas 155-159
$anuncio = $this->content->with('categoryContents', 'archives', 'linksUnique')
    ->whereHas('categoryContents', function ($query) {
        $query->where('slug', '=', 'anuncios');
    })->where('status', 1)
    ->first();
```

E na lista de variáveis passadas à view (linhas 220-241): `'anuncio'` (linha 227).

| Aspecto | Comportamento |
|---|---|
| Filtro | Categoria `anuncios` **e** `status = 1` |
| Quantidade | `first()`: apenas **um** aviso é exibido |
| Eager loading | `categoryContents`, `archives`, `linksUnique` (evita N+1 nos três acessos da view) |
| Sem aviso | `$anuncio` fica `null`; as views testam `!empty($anuncio)` |

## 4. `Prefecture\ContentController` — CMS

| Método | Linha | Comportamento específico de `anuncios` |
|---|---|---|
| `genericIndex($slug)` | 88-119 | `if ($slug === 'anuncios') return $this->returnIndex('Avisos', $slug);` (linha 95) — o rótulo do CMS é "Avisos", não "Anúncios" |
| `genericGenerate($slug, $id)` | 160+ | `if ($slug === 'anuncios') return $this->returnData('Avisos', $slug, null, [], $id);` (linha 167) |
| `store` | ~600-900 | Grava `Content`; se `status == 1`, desativa os irmãos (linhas 672-674); grava `Link` (linhas 717-728); grava `Archive` de destaque e recomprime para 700x405 (linhas 840-860) |
| `update` | ~900-1300 | Mesma lógica: desativa os irmãos (linhas 994-996), atualiza/cria o `Link` (linhas 1062-1100), recomprime a imagem (linhas 1258-1272) |
| `setStatus` | ~1545-1590 | Alterna `status`; ao ativar, desativa os demais avisos da categoria (linhas 1565-1567) |

Trecho da regra de exclusividade (criação):

```php
// v6/app/Http/Controllers/Prefecture/ContentController.php:672-674
$slug = CategoryContent::find($category_contents_id)->slug;
if ($slug === 'anuncios' && $request->get('status') == 1) {
    Content::where('category_contents_id', $category_contents_id)->where('status', true)->update(['status' => false]);
}
```

E da alternância pelo botão da listagem:

```php
// v6/app/Http/Controllers/Prefecture/ContentController.php:1563-1569
if ($content->status == 1) {
    $content->status = 0;
} else {
    if ($slug === 'anuncios') {
        $this->table->where('category_contents_id', $content->category_contents_id)->where('status', true)->update(['status' => false]);
    }
    $content->status = 1;
}
```

## 5. `Prefecture\DatatableController@get_avisos`

```php
// v6/app/Http/Controllers/Prefecture/DatatableController.php:228-232
public function get_avisos()
{
    $avisos = DB::table("anuncios")->geT();
    return DataTables::of($this->struct_views($avisos, 'anuncios'))->make(true);
}
```

Observações:

- A grafia `->geT()` (T maiúsculo) funciona em PHP porque os nomes de método são
  case-insensitive — é apenas uma inconsistência de estilo.
- `struct_views($dados, 'anuncios')` injeta as flags de permissão `anuncios.edit`,
  `anuncios.delete`, `anuncios.restore`, `anuncios.inactivate`
  (linhas 504-520).

## 6. Form Request e validação

`v6/app/Http/Requests/ContentRequest.php`, `case 'anuncios'` (linhas 167-179):

```php
case 'anuncios':

    $rules = [
        "title" => "required|max:120",
        "category_contents_id" => "required|numeric",
        "link" => "required|max:2999",
    ];
    if (Route::getCurrentRoute()->getName() == "conteudo.store") {
        $rules += [
            "archive_highlight" => "max:20000|mimes:jpg,jpeg,png,gif,webp|required",
        ];
    }

    break;
```

| Regra | Efeito |
|---|---|
| `title` required, max 120 | Título do aviso é obrigatório |
| `link` required, max 2999 | O destino é obrigatório (a tabela `links` sempre é criada) |
| `archive_highlight` required (apenas no `store`) | Na criação a imagem é obrigatória; na edição é opcional (trocar só o texto mantém a imagem) |
| `mimes:jpg,jpeg,png,gif,webp`, max 20000 | Formato e tamanho da imagem |

> Observação: o `link` é validado com `max:2999`, mas o formulário do CMS impõe
> `maxlen = 200` — divergência entre markup e servidor.

## 7. Upload e recompressão da imagem

| Etapa | Código | Resultado |
|---|---|---|
| Caminho | `storage/content/anuncios/{id}/destaque/{nome}` | `ContentController.php:840-856` |
| Recompressão (criação) | `$this->compress(storage_path("app/public/$aux"), null, 700, 405, true);` | imagem limited a **700x405** |
| Recompressão (edição) | `$this->compress(storage_path("app/public/$aux"), null, 700);` | largura máxima **700**, altura livre |
| GIF | `if ($size && $ext !== 'gif')` | GIF não é recomprimido (preserva animação) |

## 8. Permissões exigidas

| Permissão | Onde |
|---|---|
| `anuncios.create` | Botão "Cadastrar Aviso" (`v6/resources/views/prefecture/contents/anuncios/index.blade.php:11`) |
| `anuncios.edit` | Botão de edição (`v6/public/assets/js/custom/contents/avisos/datatable.js:20`) |
| `anuncios.delete` | Exclusão individual e em lote (`index.blade.php:16`) |
| `anuncios.restore` / `anuncios.inactivate` | Injetadas por `struct_views`; `inactivate` controla o botão de status (`datatable.js:19`) |
| `check.route.permission` | Middleware aplicado no construtor de `ContentController` (linha 59) |

## 9. API

Não há rota de avisos em `v6/routes/api.php`. Para a v7, sugere-se
`GET /api/v1/avisos` (com o aviso ativo) e `POST /api/v1/avisos/{id}/ativar`.
