# Acesso Rápido — Regras de Negócio e Portabilidade

## 1. Regras não óbvias

| # | Regra | Onde está | Efeito observável |
|---|---|---|---|
| R1 | O bloco só aparece se `modulos.status` do slug `acesso-rapido` for verdadeiro **e** `configurations.links_rapidos == '1'`. | `v6/resources/views/portal/layouts/modelo1/home/acesso-rapido.blade.php` | Desligar qualquer uma das duas flags remove o bloco da home. |
| R2 | Só entram no bloco os conteúdos com `status = true`. | `v6/app/Providers/ConfigHeaderServiceProvider.php:89` | Itens desativados somem do portal (mas continuam na listagem do CMS). |
| R3 | A ordem de exibição é `contents.order` ascendente. | mesmo arquivo, `:90` | O `order` é reescrito pelo drag-and-drop do CMS (`order = índice + 1`). |
| R4 | `/acesso-rapido/ouvidoria` **sempre** redireciona para `/acessos-rapido/solicitar-informacao`, antes de qualquer consulta. | `v6/app/Http/Controllers/Portal/QuickAccessController.php:31-33` | O atalho "Ouvidoria" do CMS aponta para `/pagina/contato`, mas a rota do controller leva ao e-SIC — dois destinos convivem. |
| R5 | Se a categoria do slug não tiver conteúdos, o controller **cai** para a resolução de página estática. | `v6/app/Http/Controllers/Portal/QuickAccessController.php:38-40` | `/acesso-rapido/manual-de-navegacao` funciona mesmo sem nenhum `content` naquela categoria. |
| R6 | Toda visita a uma página estática incrementa `static_pages.clicks`. | `v6/app/Http/Traits/GetInformationsPrefectureTrait.php:110-115` | O contador é por acesso, não por sessão. |
| R7 | `contents.text` e `static_pages.text` são renderizados com `{!! !!}` (HTML cru). | Blades do portal | O conteúdo vem do Summernote do CMS; XSS é possível se o CMS for aberto a operadores não confiáveis. |
| R8 | O `link` do atalho pode apontar para fora do portal; `links.isBlank` define `target="_blank"`. | `v6/resources/views/portal/layouts/modelo1/home/acesso-rapido.blade.php` | Sem validação de domínio ou de protocolo. |
| R9 | O botão "Cadastrar" só aparece para quem tem `acesso-rapido.create`; a exclusão em lote não existe para este módulo (exclusão é individual). | `v6/resources/views/prefecture/contents/acesso-rapido/index.blade.php` | — |
| R10 | A ordenação é persistida na rota `ajust.banners`, compartilhada com o módulo de banners. | `v6/routes/basepro.php:137` | Efeito colateral: qualquer módulo que use `reorder(new Content(), $request->sortable_data)` grava `contents.order`. |
| R11 | A barra de atalhos do cabeçalho (Estrutura A) usa `menu_items` do menu `menus.id = 3`, filtrando `is_active = true` e `menus_items_id IS NULL` (somente itens de primeiro nível). | `v6/app/Providers/MenuServiceProvider.php:438-440` | Um item desativado no CMS some da barra, sem afetar os cards da home. |
| R12 | Itens de menu do tipo 1 (página estática) resolvem para `/acesso-rapido/{slug}`, **exceto** quando `static_pages_id = 4`, que resolve para `/{slug}` (rota raiz `static.show`). | `v6/app/Http/Presenters/MenuItemPresenter.php:15-19` | `/acompanhe-sua-solicitacao` funciona pela rota raiz, que redireciona para `acesso-rapido.index`. |
| R13 | Itens de menu do tipo 2 com `category_contents_id = 12` (Perguntas Frequentes) resolvem para `/acesso-rapido/perguntas-frequentes`. | `v6/app/Http/Presenters/MenuItemPresenter.php:37-38` | É o único vínculo entre o menu e a rota do módulo para conteúdo. |
| R14 | O `slug` da página estática é o que identifica a tela; `static_pages.slug` precisa ser único. | `v6/app/Http/Traits/GetInformationsPrefectureTrait.php:60-63` | O banco atual tem dois `manual-de-navegacao` (bug B8). |

## 2. Acoplamentos

### 2.1 Dependências de outros módulos

| Módulo | Dependência |
|---|---|
| **Perguntas Frequentes** | `/acesso-rapido/perguntas-frequentes` renderiza os `contents` da categoria `perguntas-frequentes` (`category_contents.id = 12`). Ver `../perguntas-frequentes/`. |
| **e-SIC / LAI** | "Solicitar Informação" grava em `informations_requests` via `Portal\InformationController@storeRequestInformacaoAjax`; "Acompanhe sua Solicitação" consulta via `ajax.get.information.by.protocol` e `ajax.get.answer.by.information`. Ver `../requerimentos/`. |
| **Ouvidoria** | "Consultar Solicitação de Ouvidoria" usa `ajax.get.ombudsman.by.protocol` e `ajax.get.answer.by.ombudsman`. |
| **Módulos (habilitação)** | `modulos.status` + `ModulosServiceProvider` controlam a exibição. |
| **Configurações** | `configurations.links_rapidos` e `configurations.layout`. |
| **Layout do portal** | `getTemplateActived()` decide o prefixo das views. |
| **Páginas Estáticas** | "Manual de Navegação" e "Solicitar Informação" são `static_pages`, com `archives` anexos. |
| **Menus do Site** | A Estrutura A depende de `menus.id = 3` e `menu_items`; o CMS "Menus do Site" (`Prefecture\MenuController` / `Prefecture\MenuItemController`) é quem mantém esses registros. |

### 2.2 O módulo **é** dependido por

- `Portal\PortalController@staticShow` e `@contentList` (redirecionam para
  `acesso-rapido.index`).
- O menu do portal, quando o operador cria um item de menu com o slug
  `perguntas-frequentes` / `manual-de-navegacao` / `solicitar-informacao` /
  `acompanhe-sua-solicitacao`.
- A carta de serviços, que usa a coluna `service_letters.menu_acesso_rapido`
  (`v6/database/migrations/2026_01_20_160719_create_service_letters_table.php:54`)
  para classificar o atendimento.

## 3. Armadilhas técnicas confirmadas

| # | Armadilha | Evidência | Impacto |
|---|---|---|---|
| A1 | **Checagem de "não encontrado" depois do uso da variável.** | `v6/app/Http/Controllers/Portal/QuickAccessController.php:47-53`: `$this->updateClickStaticPage($contents->id)` e `Archive::where('static_pages_id', $contents->id)` são executados **antes** do `if (!$contents) { return view("admin.errors.conteudo-nao-encontrado"); }`. | Um slug inexistente gera **erro fatal** (acesso a propriedade de `null`), não a página de erro. A view `admin.errors.conteudo-nao-encontrado` é inalcançável nesse caminho. |
| A2 | **`Route::any` em rotas de leitura.** | `v6/routes/web.php:90,94` | POST/PUT/DELETE nas URLs de acesso rápido executam o mesmo código, incrementando cliques. |
| A3 | **Layout `newsbit` hardcoded.** | `v6/app/Http/Controllers/Portal/QuickAccessController.php:63` | A tela de solicitação de informação é sempre renderizada no layout `newsbit`, enquanto as demais usam o layout ativo — inconsistência visual e de assets. |
| A4 | **Cobertura de views desigual entre layouts.** | `modelo3`/`modelo4` não possuem `solicitar-informacao.blade.php`, `solicitacao-form.blade.php` nem `files-list.blade.php`; `modelo5` não possui `files-list.blade.php`. | Com layout ativo `modelo3`/`modelo4`, a rota `/acesso-rapido/solicitar-informacao` (que não é o slug especial) cai no `getIndexStatic()` e falha com "View not found". |
| A5 | **`newsbit` não tem `home/index`.** | `v6/resources/views/portal/layouts/newsbit/` não contém a pasta `home/`. | Se `configurations.layout = 'newsbit'`, `getTemplateActived()` faz `abort(404, 'view_inexistente')` e marca `pagina_em_manutencao = 1`. |
| A6 | **`struct_views()` chamada com argumentos extras.** | `v6/app/Http/Controllers/Prefecture/DatatableController.php:237` passa 4 argumentos para uma função que aceita 2 (linha 504). | Silenciosamente ignorado; indica código copiado de outro módulo. |
| A7 | **DataTable sem paginação.** | `get_acesso_rapido()` faz `DB::table("acesso_rapido")->get()` e `DataTables::of($this->struct_views($links, 'acesso-rapido', true, false))->make(true)`. | Todo o volume é carregado e filtrado no cliente. |
| A8 | **Rota de ordenação compartilhada e mal nomeada.** | `ajust.banners` -> `ContentController@ajustOrder` -> `reorder(new Content(), $request->sortable_data)`. | Qualquer conteúdo pode ser reordenado por essa rota; o nome sugere exclusividade dos banners. |
| A9 | **Seed não idempotente.** | `v6/database/seeds/AcessoRapidoSeeder.php` usa `insert`/`insertGetId` sem `updateOrCreate`. | Rodar o seed duplica categoria, conteúdos e links. |
| A10 | **`status` e `order` sem validação.** | `v6/app/Http/Requests/ContentRequest.php:193-202`. | Um POST sem `status` grava `null` e o item **some do portal** sem aviso na UI. |
| A11 | **Links relativos no JS do CMS.** | `v6/public/assets/js/custom/contents/acesso-rapido/datatable.js` monta `'editar/acesso-rapido/' + data.id` (relativo). | Funciona apenas porque a página atual é `/cms/conteudos/acesso-rapido`; trocar a URL da listagem quebra o botão de edição. |
| A12 | **`contents.slug` não é usado.** | Os três contents seedados têm `slug` nulo. | Não há URL canônica por atalho; a home depende do `links.link`. |
| A13 | **View composer global.** | `v6/app/Providers/ConfigHeaderServiceProvider.php:86` | A consulta de `$acessos` roda em **todas** as requisições do portal, mesmo em páginas que não mostram o bloco. |
| A14 | **Duplicidade de `static_pages.slug`.** | Duas linhas com `slug = 'manual-de-navegacao'` (ids 6 e 8) na base. | `->first()` devolve sempre a id 6; o contador de cliques exibido (id 8) não corresponde ao conteúdo exibido. |
| A15 | **`isBlank` como string vazia na regra.** | `"isBlank" => ""` no `ContentRequest`. | Regra inválida (ignorada pelo Laravel). Funciona, mas é um código morto que sugere engano. |

## 4. Código duplicado / copiado

| Trecho | Origem | Observação |
|---|---|---|
| `getTemplateActived()` | `v6/app/Http/Traits/GetInformationsPrefectureTrait.php:92` e `v6/app/Http/Controllers/Admin/Controller.php:39` | Implementações idênticas em dois lugares. |
| `updateClickStaticPage()` / `updateClicks()` | `GetInformationsPrefectureTrait.php:105-115` | Idênticos, mudando só a tabela. |
| `files-list.blade.php` | `portal/layouts/modelo{1,2,6}/informacoes-instituicao/acesso-rapido/files-list.blade.php` e `portal/components/view_pdf/files-list.blade.php` | Duas implementações quase iguais de listagem de anexos. |
| `solicitar-informacao.blade.php` / `solicitacao-form.blade.php` | replicados em modelo1, modelo2, modelo5, modelo6, newsbit | 5 cópias com pequenas divergências. |
| `perguntas-frequentes.blade.php` | replicado em 7 layouts | 7 cópias do mesmo markup. |
| `getIndexStatic` | `v5/app/Http/Controllers/Portal/QuickAccessController.php` -> `v6/app/Http/Controllers/Portal/QuickAccessController.php` | Praticamente idêntico; mudou apenas o nome do diretório de views (`informacoes-prefeitura` -> `informacoes-instituicao`). |

## 5. Onde a v6 divergiu da v5

| Aspecto | v5 | v6 | Motivo discoverível |
|---|---|---|---|
| Nome do método de entrada | `getIndex($slug1)` | `index($slug1)` | Padronização com o nome da rota (`acesso-rapido.index`). |
| Pasta das views | `informacoes-prefeitura/acesso-rapido/` | `informacoes-instituicao/acesso-rapido/` | Renomeação global do portal ("prefeitura" -> "instituição"). |
| Fallback de conteúdo vazio | `if (!$contents) return view("admin.errors.conteudo-nao-encontrado");` | `if (!count($contents)) return $this->getIndexStatic($slug1);` | Passa a servir página estática quando não há conteúdo da categoria. |
| Redirect de "ouvidoria" | inexistente | `redirect` para `/acessos-rapido/solicitar-informacao` | Centraliza a ouvidoria no fluxo e-SIC. |
| Prefixo de view | Montado a cada render (`view('portal.layouts.'.$this->getTemplateActived().'.informacoes-prefeitura.acesso-rapido.'.$slug1, compact('contentCategoriesNews','contents','states','cities','archives','view_pdf'))`) | Propriedade `$this->view` montada no construtor (`"portal.layouts.{$this->getTemplateActived()}.informacoes-instituicao.acesso-rapido."`) | Evita repetir a concatenação. |
| `AcessoRapidoSeeder` | Insere também a linha em `resources` | Não insere em `resources` | A v6 passou a gerenciar `resources.modulo_id` via `ResourcesTableSeeder` + tabela `modulos`. |
| `MenuAcessoRapidoSeeder` | Arquivo existe, **todo comentado** (classe morta) | Arquivo **removido** | Código morto retirado. |
| Rotas | Mesmas URIs e mesmos nomes (`acesso-rapido.index`, `acessos-rapido.index`) | idem | Nenhuma mudança de URL pública no módulo. |
| Migrations da view SQL | 3 arquivos | 3 arquivos **byte-idênticos** | A view não mudou desde 2024-01-08. |

## 6. Riscos de migração de dados

| Risco | Detalhe |
|---|---|
| Slugs duplicados em `static_pages` | Duas linhas com `manual-de-navegacao`; a migração precisa decidir qual conservar (a que tem `clicks` é a id 8, mas a que é retornada é a id 6). |
| `contents` usados por vários módulos | A categoria `acesso-rapido` (id 45) e a `perguntas-frequentes` (id 12) são categorias genéricas de `contents`; um novo modelo dedicado exige backfill de `title`, `text`, `icon`, `order`, `status` e `links.link`. |
| `links.contents_id` compartilhado | A tabela `links` é usada por `contents`, `static_pages` e `menu_items` (`v6/app/Http/Model/Prefecture/Link.php:16-25`); extrair só os links de acesso rápido exige filtrar por `contents_id` pertencente ao conjunto de `contents.id` da categoria `acesso-rapido`. |
| Dependência do layout | A view SQL `acesso_rapido` não expõe `icon` nem `link`; uma migração para modelo dedicado precisa recriar a listagem do CMS (a view atual é referenciada apenas por `get_acesso_rapido`). |
| `modulos.status` sem cache próprio | O `Cache::rememberForever('modulos')` é invalidado em `ModulosController@update` (`Cache::pull('modulos')`), ok. |
