# A Cidade — Resumo Executivo

> **Versão analisada:** `v6` (Laravel 5.5.50) e `v5` (versão anterior)
> **Slug do módulo:** `a-cidade` (tabela `modulos`, id 13 — `status = false`)
> **Categoria de conteúdo:** `category_contents.id = 4`, `name = 'A cidade'`, `slug = 'a-cidade'`, `category_contents_id = NULL`
> **Natureza:** CMS (painel administrativo) + Portal (público)
> **Estado:** migrado v5 → v6 (categoria e view SQL mantidas; conteúdo "Aspectos Gerais" movido para A Instituição)

---

## 1. Objetivo

O módulo **A Cidade** é o espaço institucional do portal que descreve *o
próprio município*: história, mapa, hino oficial, fotos e aspectos gerais.
Ele existe para que o cidadão encontre, em endereços estáveis e indexáveis, a
história e a identidade visual da cidade, sem depender de folders, cartazes ou
documentos avulsos. Na prática o módulo é um **content type genérico** — o
legado não tem um controller exclusivo: ele reaproveita o
`Prefecture\ContentController` dirigido por slug e a view SQL `a_cidade`. O
cidadão lê em `/a-cidade/{slug}`; o operador mantém em `/cms/conteudos/a-cidade`.

## 2. Efeito / Impacto

| Faixa | Efeito |
|---|---|
| Cidadão | Acessa `https://<dominio>/a-cidade/<slug>` e lê título, imagem de destaque, texto rico (Summernote), galeria de fotos (lightgallery) e vídeo do YouTube/Vimeo. Cada visita incrementa `contents.clicks`. A página também aparece nos resultados da busca do portal quando a categoria "A Cidade" está selecionada. |
| Prefeitura (CMS) | Mantém uma lista curta de páginas sobre a cidade; o operador cria, edita, define imagem de destaque, ativa galeria, grava o vídeo e exclui (inclusive em lote). O botão "Acessar A cidade" da grade abre a página pública correspondente. |
| Transparência e legalidade | É o canal oficial de identificação simbólica do município (história, hino, mapa). Conteúdo institucional sem dono claro é um passivo de transparência. |
| Operação técnica | Depende de três chaves: o id 4 em `category_contents`, o slug `a-cidade` e a view SQL `a_cidade`. Todas podem divergir entre ambientes, e o módulo **não tem API pública**. |

## 3. Comportamento

- O CMS resolve a tela pelo **slug do módulo**, não por id: `ContentController@genericIndex`
  mapeia `'a-cidade'` para `prefecture.contents.a-cidade.index`
  (`v6/app/Http/Controllers/Prefecture/ContentController.php:94`).
- A listagem do CMS é alimentada pela **view SQL `a_cidade`** (`value, titulo, id, slug`),
  e não pela tabela `contents` diretamente
  (`v6/app/Http/Controllers/Prefecture/DatatableController.php:246-250`).
- O formulário (criar/editar) é o mesmo componente Blade `form.blade.php`;
  "Link Permanente" é o `slug` do conteúdo e só aparece na edição.
- A validação é feita por `ContentRequest`, no `case 'a-cidade'` compartilhado com
  `orgaos` (`v6/app/Http/Requests/ContentRequest.php:205-214`).
- O portal resolve a página por **slug do conteúdo**, sem filtrar por categoria:
  `GET /a-cidade/{slug}` → `Portal\AboutController@getIndex`
  (`v6/app/Http/Controllers/Portal/AboutController.php:28`).
- O controller busca o conteúdo por slug globalmente (`getContentDataWith`), carrega
  `linksUnique`, converte o link em embed (`embedVideo`), incrementa `clicks` e
  entrega `contentNew`, `title` e `linkVideo` à view do layout ativo.
- Quando o conteúdo não existe, `AboutController` devolve a view
  `admin.errors.conteudo-nao-encontrado` — herança de tela do CMS no portal.
- O breadcrumb é registrado por **slug da categoria**
  (`v6/routes/breadcrumbs.php:266-270`) e empurra "A Cidade" com `url('/#')`.
- O menu do portal resolve a URL do item por `MenuItemPresenter::getLink()`,
  que decide por **id de categoria**: o id 4 cai no ramo genérico e produz
  `/a-cidade/<slug>` (`v6/app/Http/Presenters/MenuItemPresenter.php:41-43`).
- A rota legada `GET /portal/a-cidade/<slug>` redireciona para a rota canônica
  (`v6/app/Http/Controllers/Portal/PortalController.php:330`).
- A busca do portal inclui `a-cidade` na lista fixa de categorias pesquisáveis
  (`v6/app/Http/Filter/FilterSearchAllSite.php:29`).

## 4. Como o Usuário Interage

### Operador (CMS)

| Ação | Tela / endpoint | Resultado |
|---|---|---|
| Abrir listagem | `GET /cms/conteudos/a-cidade` | Tabela "Lista de A Cidade" com colunas `#`, `ID`, `Título`, `Ações`. |
| Filtrar/buscar | campo de busca do DataTable | Filtro **no cliente** (o servidor entrega a view inteira). |
| Exportar | botões Excel / PDF / CSV / TXT | Exporta as colunas visíveis, exceto "Ações"; Client-side. |
| Acessar página pública | botão de link na coluna Ações | Abre `/a-cidade/<slug>` em nova aba. |
| Editar | botão de lápis | `GET /cms/conteudos/editar/a-cidade/<id>`. |
| Galeria | checkbox "Ativar Galeria" + aba Galeria | Galeria gerenciada por `prefecture.contents.components.galerias.index`. |
| Excluir | botão de lixeira | `DELETE /cms/conteudo/<id>`; o controller apaga também os `menu_items` apontando para o conteúdo. |
| Excluir em lote | "Excluir Registro(s)" | `POST /cms/conteudo/deleteAll` (`ajax.generic.content.deleteAll`). |
| Cadastrar | botão "Cadastrar Conteúdo" | `GET /cms/conteudos/cadastrar/a-cidade`. |

### Cidadão (portal)

1. Acessa `https://<dominio>/a-cidade/<slug>` (link do menu principal ou do rodapé).
2. Vê o breadcrumb `Home > A Cidade > <título>`.
3. Vê a imagem de destaque, o bloco de compartilhamento (WhatsApp, Facebook, copiar
   link, e-mail, imprimir), o texto rico, a galeria (se `galery = 1`) e o vídeo.
4. Na coluna lateral: transmissão, banners, clima, enquetes, mais populares, VLibras e newsletter.
5. Se usar a busca do portal e filtrar por "A cidade", o conteúdo aparece na lista.

## 5. Detalhes e Particularidades Técnicas

- **Camadas:** `Route` → middleware (`check.route.permission` no CMS, `check.site.manutention` no portal) →
  `Controller` → `ContentRequest` → `Content`/`CategoryContent` (Eloquent direto) → view Blade.
- **Sem model dedicated:** não existe `A cidade` model; o conteúdo é `App\Prefecture\Content`
  filtrado por `category_contents_id = 4`.
- **Menu do CMS oculto:** `modulos.status` do id 13 é **false**, então a entrada "A Cidade"
  **não aparece** no menu lateral do CMS, embora as rotas existam e o papel
  `administrador` tenha as permissões `a-cidade.*`
  (`v6/app/Providers/MenuServiceProvider.php:46`).
- **Bug do modelo3:** `v6/resources/views/portal/layouts/modelo3/informacoes-instituicao/acidade/show.blade.php:24`
  inclui `portal.layouts.modelo3.informacoes-prefeitura.galeria.index`, diretório que
  **não existe** na v6 (foi renomeado para `informacoes-instituicao`).
- **Views órfãs:** `default` e `newsbit` possuem arquivos de A Cidade, mas são
  inalcançáveis (`getTemplateActived()` aborta 404 sem `home/index.blade.php`).
- **Sem API:** `v6/routes/api.php` expõe secretarias, órgãos, legislações, notícias e
  publicações — **nada** de A Cidade.
- **Autorização duplicada:** `ContentRequest::authorize()` devolve `true`; quem protege é o
  middleware `check.route.permission` e o `has_access()` das views.
- **Contador de cliques não atômico:** `updateClicks()` faz SELECT + UPDATE, não `increment`.
- **Slug sem unicidade:** não há índice único em `contents.slug`; a colisão é tratada
  no `store()` com o sufixo `slug-<maxid+1>`.

## 6. Estrutura de Dados

| Objeto | Tipo | Papel |
|---|---|---|
| `contents` | tabela | Textos de A Cidade (`title`, `text`, `slug`, `galery`, `clicks`, `category_contents_id`). |
| `category_contents` | tabela | Categoria id 4 = "A cidade" (`slug = a-cidade`, pai `NULL`). |
| `archives` | tabela | Imagem de destaque (`highlight = 1`), galeria (`highlight IS NULL`) e anexos. |
| `links` | tabela | Link do vídeo (relação 1:1 `Content::linksUnique()`). |
| `menu_items` | tabela | Itens de menu (principal e rodapé) que apontam para conteúdos de A Cidade. |
| `menu_orders` | tabela | Slug `a-cidade` (id 10, `modulo_id = 13`) — contador de cliques do menu. |
| `resources` | tabela | Slug `a-cidade` (id 12, `modulo_id = 13`). |
| `modulos` | tabela | id 13, `name = 'A Cidade'`, `status = false` — oculta a entrada do menu do CMS. |
| `a_cidade` | **view SQL** | `value, titulo, id, slug` — fonte da grade do CMS. |

Estado atual do banco (consulta de leitura): **4 conteúdos** na categoria 4 —
`História`, `Mapa`, `Hino Oficial`, `Fotos da Cidade`. O item de menu
"Aspectos Gerais" aponta para o conteúdo id 7, que **pertence à categoria 6**
(A Instituição) na base atual.

## 7. Como Testar

| Cenário | Como verificar |
|---|---|
| Listagem do CMS | `GET /cms/conteudos/a-cidade` como `administrador` → tabela com as linhas da view `a_cidade`. |
| Grade | `GET /cms/datatable/a_cidade` → JSON com `value, titulo, id, slug, edit, delete, active, inactivate`. |
| Criação | `POST /cms/conteudo` com `title`, `text`, `category_contents_id = 4` → slug por `Str::slug`; colisão vira `slug-<maxid+1>`. |
| Validação | Título vazio → "Por favor, informe um título."; texto vazio → "Por favor, digite algum texto."; imagem > 20 MB → erro de `archive_highlight`. |
| Página pública | `GET /a-cidade/historia` → 200 com título e texto; `contents.clicks` incrementa em 1. |
| Roteamento legado | `GET /portal/a-cidade/historia` → 302 para `/a-cidade/historia`. |
| Slug inexistente | `GET /a-cidade/nao-existe` → view "CONTEÚDO NÃO ENCONTRADO" (HTTP 200). |
| Galeria por layout | `GET /a-cidade/fotos-da-cidade` em cada `modelo1..modelo6`; no **modelo3** o include quebrado estoura. |

Detalhe dos casos em `07-TESTES.md`.

## 8. Migração para Laravel Moderno

Reconstruir como um recurso REST dedicado (`CidadeContent`) em vez de um
`ContentController` genérico dirigido por slug: model `ConteudoCidade` com
`HasFactory`, Form Request dedicada (`StoreCidadeContentRequest` /
`UpdateCidadeContentRequest`), Policy `CidadeContentPolicy` ligando
`a-cidade.index/.create/.edit/.delete` aos papéis, *route model binding* por
`{conteudo:slug}`, API Resource JSON (o módulo hoje **não tem** endpoint de
dados abertos) e Blade em componentes `<x-*>`. O item de menu deve ser resolvido
por **chave de negócio** (`category_contents.slug`), nunca por id; a galeria deve
usar uma galeria por conteúdo, sem pasta compartilhada por módulo. Detalhes em
`06-MIGRACAO-MODERNA.md`.

## 9. Resumo Geral

"A Cidade" é o módulo de identidade do município: um punhado de páginas curtas
(história, mapa, hino, fotos) mantidas pelo CMS e publicadas no endereço
`/a-cidade/{slug}`. Na v6 ele saiu praticamente intacto em relação à v5: a
categoria 4 e a view SQL `a_cidade` mantiveram id e slug; a coluna `slug` da
view foi acrescentada por migration em datas diferentes nas duas versões
(2024-11-29 na v6, 2026-03-09 na v5) e, por isso, o JS da grade perdeu na v6 a
proteção `data.slug ? ... : '#'` que a v5 tinha. O que de fato mudou foi o
conteúdo "Aspectos Gerais", que na v6 passou da categoria A Cidade para A
Instituição — mas o item de menu que o exibe continua classificado na categoria 4.
O módulo carrega três falhas relevantes: o `include` de galeria do modelo3
aponta para um diretório renomeado e derruba a página; as views de `default` e
`newsbit` são órfãs; e não existe paginação nem API — a grade do CMS carrega a
view SQL inteira em memória e fatia no PHP, enquanto a página pública não tem
nenhuma listagem. Na reconstrução, o ganho maior vem de trocar o content type
genérico por um recurso com política, API e paginação de verdade.

---

## Í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, permissões |
| `04-VIEWS-E-UX.md` | Telas, componentes, JS, assets |
| `05-REGRAS-E-PORTABILIDADE.md` | Regras, riscos de performance 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, bugs e riscos de dados |
| `09-MAPA-V5-V6.md` | Mapa v5 → v6, perguntas em aberto e registro de verificações |
