# Glossário — Regras e Portabilidade

> Regras não óbvias, acoplamentos e armadilhas observadas no código da `v6`
> (e, quando pertinente, na `v5`).

---

## 1. Regras de negócio não óbvias

| # | Regra | Onde está | Efeito observável |
|---|---|---|---|
| R1 | O verbete é modelado como `contents`: `title` = termo, `text` = definição | `v6/resources/views/prefecture/contents/glossario/form.blade.php:5-24` | Não há model/tabela de verbete; o glossário herda semântica de conteúdo genérico |
| R2 | A listagem pública não é server-side: a view só desenha A–Z e o JS busca os dados | `v6/resources/views/portal/layouts/modelo1/transparencia/glossario.blade.php:28-71` | Sem JS, a página fica sem verbetes |
| R3 | `all` significa "sem filtro": o controller troca a letra por string vazia | `v6/app/Http/Controllers/Portal/GlossaryController.php:29-31` | A primeira carga traz o glossário inteiro |
| R4 | O filtro é por **início do título**, não por ocorrência | `GlossaryController.php:35` (`where('title','like',$letter.'%')`) | "Tributação" só aparece na letra T |
| R5 | O CMS lê a view SQL `glossario`; o portal lê `contents` via Eloquent | `DatatableController.php:224` vs `GlossaryController.php:32-34` | Divergência de conjunto/ordem entre as duas telas |
| R6 | A view SQL não filtra `status` nem `publication_date` | `2023_04_10_151502_create_view_to_glossarios.php:16-25` | Verbetes "desativados" (campo herdado) continuam visíveis |
| R7 | A exclusão é soft delete (`contents.deleted_at`) | `v6/app/Http/Model/Prefecture/Content.php:16` | A view `glossario` exclui `deleted_at IS NOT NULL`; o Eloquent também |
| R8 | O módulo do CMS é `glossario` (id 24) e a categoria de conteúdo também | `2024_11_01_150206_create_modulos_table.php:167-171` e `CategoryContentTableSeeder.php:76-80` | Coincidência de slug entre `modulos` e `category_contents` |
| R9 | O índice alfabético só cobre A–Z ASCII | `GlossaryController.php:20` (`range('A','Z')`) | Termos iniciados por número ou letra acentuada (ex.: "Água") ficam fora do índice |

## 2. Acoplamentos

| Depende de | Onde | Natureza |
|---|---|---|
| `category_contents` (categoria 11) | filtro do portal e view SQL | O glossário só existe enquanto a categoria existir |
| Tabela `contents` + colunas genéricas (`status`, `order`, `clicks`, `galery`) | `Content` | Herança do conteúdo genérico; campos não usados pelo glossário |
| `App\Http\Filter\FilterSearchAllSite` | `PortalController@searchContents` / `@searchingContents` | O glossário participa da busca global |
| `Portal\PortalController@getViewToBeReturned` | busca | Mapa `11 => 'glossario.show'` determina a view do resultado |
| `Portal\QuickAccessController` / menus do portal | navegação | O acesso ao glossário depende do menu montado por `MenuServiceProvider` |
| Layouts do portal (modelo1..modelo6, newsbit, default) | render | Uma cópia do JS por layout |

## 3. Armadilhas

| Armadilha | Evidência | Impacto |
|---|---|---|
| `LIKE` sensível a maiúsculas no PostgreSQL | `where('title','like','A%')` | Termo salvo como "alíquota" não aparece na letra A |
| Sem normalização de acentos no índice | idem | Termos iniciados por acento não são alcançáveis pelo índice |
| JSON expõe o model completo | `response()->json(['letterActive' => $letter, 'contentGlossary' => $contentsGlossary])` | Vazamento de campos internos (slug, autor, contadores, ids de categoria) |
| HTML montado por concatenação no JS | `listGlossary += \`<li><h5>${item['title']}</h5><p>${item['text']}</p></li>\`` | XSS se o `text` (HTML do CMS) tiver script; o portal não escapa |
| URL relativa no AJAX | ``url: `glossario-ajax/${letter}` `` | Quebra se o portal for publicado em subpasta |
| `view('portal.layouts.{template}.transparencia.glossario')` sem fallback | `GlossaryController.php:20-25` | Layout `default` não tem essa view -> erro 500 em `/glossario` |
| `case 'glossario': return redirect(route('glossario.index'));` | `PortalController.php:321` | `/portal/glossario/{letra}` perde a letra selecionada — sempre volta ao índice completo |
| `struct_views` chama `has_access()` por linha | `DatatableController.php:504-520` | N+1 de verificações de permissão por item do DataTable |
| Bloco `catch (\Throwable $th) {}` no `contentList` | `PortalController.php:367` | Falhas são engolidas e a resposta fica vazia (HTTP 200 sem conteúdo) |
| `text` não validado no servidor | `ContentRequest.php:308-314` | Verbete pode ser salvo sem definição, apesar do campo ser obrigatório no formulário |

## 4. Código duplicado entre v5 e v6

| Arquivo | v5 | v6 | Diferença |
|---|---|---|---|
| `Portal/GlossaryController.php` | idêntico | idêntico | Nenhuma (mesmo `range('A','Z')`, mesmo `like`) |
| `Prefecture/DatatableController.php` | `get_glossario` idêntico | idêntico | Nenhuma |
| View `2023_04_10_151502_create_view_to_glossarios.php` | presente | presente | Mesma SQL |
| JS `assets/js/custom/contents/glossario/datatable.js` | presente | presente | Mesma estrutura |

A única diferença estrutural entre v5 e v6 para este módulo é ** indireta**: a v6 mudou
a nomenclatura das pastas do portal (`informacoes-prefeitura` -> `informacoes-instituicao`)
e incluiu os layouts `modelo6`/`newsbit`; o glossário não usa essas pastas, mas os demais
módulos do portal usam.

## 5. Onde a v6 divergiu da v5 (visível no glossário)

| Aspecto | v5 | v6 |
|---|---|---|
| Layouts do portal | modelo1..modelo6 + newsbit (glossário idêntico) | idem |
| `GlossaryController` | mesmo código | mesmo código |
| View SQL | mesma SQL | mesma SQL |
| Busca global | não investigada em detalhe nesta documentação | `glossario` incluído em `searchContents` |

Nenhuma divergência funcional **própria do glossário** foi identificada entre v5 e v6.

## 6. Onde o módulo depende de decisões de outros módulos

- **Busca global**: o recorte de busca depende de `FilterSearchAllSite` e do mapa
  `getViewToBeReturned` (`PortalController.php:391-402`). Alterar ids de `category_contents`
  quebra o glossário silenciosamente.
- **Layouts**: o glossário é um dos poucos módulos com cópia de view por layout; qualquer
  novo layout exige nova cópia (ou um componente).
- **Permissões**: `has_access(['glossario.*'])` — o nome do slug do módulo é a chave de
  permissão; renomear o slug quebra as permissões já gravadas.

## 7. Pontos de atenção para a v7

1. Normalizar o índice alfabético (`unaccent`/`lower`) ou manter duas colunas
   (`term`, `normalized_term`).
2. Devolver um recurso JSON enxuto (`id`, `term`, `definition`) em vez do model completo.
3. Extrair o JavaScript de letra para um componente Blade único.
4. Tornar o `text` obrigatório no FormRequest.
5. Cachear o glossário completo (Redis/APCu) em vez de uma consulta por clique de letra.

## 8. Checklist de portabilidade

| Item | Estado na v6 | Situação na v7 (decisão) |
|---|---|---|
| Slug do módulo e da categoria | `glossario` (id 24 / id 11) | Manter `glossario` como chave de permissão e de rota |
| Rotas públicas | `/glossario`, `/glossario-ajax/{letra}` | Manter as duas; a segunda vira `GET /glossario/letra/{letra}` |
| Filtro por inicial do termo | `title LIKE 'X%'` | Manter, com normalização de acentos e maiúsculas |
| Resposta JSON | model `Content` completo | `GlossaryTermResource` com `id`, `term`, `definition` |
| Origem dos dados no CMS | view SQL `glossario` | Consulta Eloquent (ou view mantida) sobre a tabela nova |
| Exclusão | soft delete via `contents.deleted_at` | `SoftDeletes` na tabela `glossary_terms` |
| Verbos na busca global | sim | Manter (a categoria `glossario` entra no filtro) |
| Exportação | botões Excel/PDF do DataTable (client-side) | Manter botões de exportação no DataTable do CMS |
| Layout por template | 8 cópias de HTML/JS | Componente Blade único |
| API | inexistente | `GET /api/v1/glossario` com filtros `letra` e `q` |

## 9. Regras que NÃO podem mudar (equivalência funcional)

1. O índice alfabético **filtra** a lista de verbetes; não é apenas navegação decorativa.
2. A letra `all` (ou equivalente) lista todos os verbetes de uma vez.
3. O termo é o **título** do verbete e a definição é o **corpo** do texto.
4. O verbete pertence a uma **categoria de conteúdo** — a mesma categoria alimenta a busca global.
5. O módulo continua acessível por `/glossario` sem autenticação.
6. A edição pelo CMS continua exigindo permissão específica do módulo (`glossario.edit`).
