# Novidades — Regras e Portabilidade

> Regras de negócio não óbvias, acoplamentos e armadilhas do changelog.

---

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

### 1.1 O número da versão é uma **string**, não um número

A coluna passou de `float` para `varchar(20)` em 2025
(`2025_09_26_103213_alter_number_versao_to_string_in_change_logs_table.php`),
o que permite "5.0.1", "6.0-beta" etc. A view, porém, mantém o
`pattern="^[0-9]+(\.[0-9]+)*$"`, que **impede** sufixos não numéricos —
a mudança de tipo não foi acompanhada da validação.

### 1.2 A data é convertida em dois formatos, com fallback silencioso

O controller tenta `d/m/Y H:i:s` e, em falha, `d/m/Y`. O segundo
`catch` está **vazio**: se nenhum dos dois formatos servir, a
`InvalidFormatException` escapa e a requisição termina em **erro 500**.

Como a validação só exige `required` (sem `date`), um valor malformado
passa pela validação e estoura no Carbon.

### 1.3 A exclusão é definitiva

`ChangeLog` **não** usa `SoftDeletes`, embora a coluna `deleted_at`
exista. `destroy()` faz `delete()` físico. Não há como recuperar uma
novidade removida.

### 1.4 A listagem é ordenada por `id`, não por data

`ChangeLog::orderBy('id', 'DESC')->get()`. Se uma novidade for criada
com uma data antiga, ela aparecerá no topo (id maior) — a ordem
cronológica real não é respeitada.

### 1.5 A versão exibida no rodapé vem de outra tabela

`update_logs.version` (última linha) e a tela de Novidades
(`change_logs`) são **independentes**. Nada impede que o rodapé mostre
"6.0.0" e não exista nenhuma entrada 6.0.0 no changelog. O comando
`update:logs` incrementa o patch da última versão automaticamente.

### 1.6 O módulo é de "produto", não de "instalação"

O changelog documenta as funcionalidades **do sistema** (vendas do
produto), não o que está instalado em cada prefeitura. Por isso ele
não é filtrável por município.

---

## 2. Acoplamentos

### 2.1 Dependências de saída

| Dependência | Tipo | Onde |
|---|---|---|
| `App\Admin\ChangeLog` | interno | model |
| `ChangeLogRequest` | interno | validação |
| `Carbon` | Laravel | conversão da data |
| `modulos` (id 10) | interno | registro do módulo |
| `update_logs` | interno (conceito irmão) | rodapé |

### 2.2 Dependências de entrada

| Consumidor | Dependência | Risco |
|---|---|---|
| Menu do AdminLTE | `route('changelog.index')` | item some |
| Rodapé do AdminLTE | `route('changelog.index')` + `update_logs` | link quebrado |
| `Admin\ModulosController` | `modulos` (id 10) | — |
| Portal público | **nada** | — |

> O módulo é praticamente **folha**: ninguém depende dele. Reconstrução
> sem risco de quebra.

---

## 3. Armadilhas

### 3.1 [ALTO] A rota `changelog.show` aponta para um método inexistente

`v6/routes/basepro.php:76` declara `GET /cms/novidades/{id}` ->
`Admin\ChangeLogController@show`, mas o controller **não implementa**
`show()`. O resultado é um erro 500 ("Call to undefined method").

Mitigação: usar o modal (que já cobre o caso) e **remover a rota**.

### 3.2 [ALTO] O módulo não tem recurso em `resources`

Não existe `resources.slug = 'changelog'` (nem `novidades`). Consequência
direta: as permissões `changelog.index`, `changelog.edit` e
`changelog.delete` **não podem ser concedidas** a nenhum perfil, e
`has_access()` só retorna `true` para `super-admin`.

Como o `@if (has_access(['changelog.index']))` envolve o cabeçalho **e**
o botão "Cadastrar Novidades" (mas **não** a timeline), o operador
comum vê a lista e o botão "Saber mais", mas **não** pode criar,
editar nem excluir.

> A view tem ainda um **HTML quebrado**: o `@if` abre na linha 11 e o
> `</div>` da `col-md-12` está **dentro** do bloco, de modo que o HTML
> fica malformado quando o usuário não tem a permissão (e bemformado
> quando tem). Ver `04-VIEWS-E-UX.md`.

### 3.3 [ALTO] `catch (\Exception $e) {}` vazio

`v6/app/Http/Controllers/Admin/ChangeLogController.php:47-52` (store) e
`88-93` (update): o segundo `catch` não trata nada. A exceção do
Carbon **escapa**.

### 3.4 [MÉDIO] Injeção de HTML via `innerHTML`

`v6/public/assets/js/custom/config/novidades/changelog-modal.js` injeta:

- `desc` (HTML do Summernote) em `#content-modal` com `innerHTML`;
- `title` (texto do usuário) em `#modalTitle` com `innerHTML`.

Não há sanitização no servidor. Um título com `<img src=x onerror=...>`
ou uma descrição com `<script>` executam no navegador de quem abrir o
modal. O risco é limitado a quem tem acesso de escrita — hoje, apenas
`super-admin` — mas o título também é renderizado na **timeline** com
`{{ }}` (escapado), creating uma inconsistência de tratamento.

### 3.5 [MÉDIO] Divergência de validação `title` (150 x 120)

Cliente: `maxlength="150"`. Servidor: `max:120`. Títulos de 121-150
caracteres passam no formulário e são rejeitados no POST, com uma
mensagem que o usuário não entende.

### 3.6 [MÉDIO] `numero_versao` sem validação de formato no servidor

Apenas `required`. O `pattern` do HTML5 não é confiável (pode ser
contornado). Nada impede gravar "abc" em `numero_versao` — que é
exibido na timeline e no rodapé.

### 3.7 [MÉDIO] `store` grava campo a campo; `update` usa `update($data)`

O `store` faz `$changelog->title = $data['title']; ...` (ignora o
`$fillable`); o `update` faz `$changelog->update($data)` (respeita o
`$fillable`). Como `data_versao` **está** no `$fillable`, funciona —
mas o `_token` e o resto também são passados a `update()` e são
silenciosamente ignorados pelo `$fillable`. A assimetria é um risco
latente.

### 3.8 [MÉDIO] `str_limit` foi removido do Laravel 6

`str_limit($log->title, 50)` e `str_limit(strip_tags(...), 250)`
(`v6/resources/views/prefecture/configurations/changelog/index.blade.php:44,47`)
não existem no Laravel 11. A v7 precisa usar `Str::limit()`.

### 3.9 [BAIXO] `ChangeLog` sem `$table`

O model não declara `protected $table`; o Eloquent infere `change_logs`
a partir do nome da classe. Funciona, mas é frágil.

### 3.10 [BAIXO] `$dates` removido no Laravel 6

`protected $dates = ['data_versao']` não funciona no Laravel 11 — é
obrigatório usar `casts`.

### 3.11 [BAIXO] Coluna `deleted_at` nunca utilizada

O model não usa `SoftDeletes`; a coluna fica sempre `NULL`. É uma
coluna enganosa.

### 3.12 [BAIXO] `update_logs` com `description` no `$fillable` sem coluna

`v6/app/Http/Model/Prefecture/UpdateLogs.php:11-19` lista `description`,
que não existe em `update_logs`.

### 3.13 [BAIXO] `ChangeLogTableSeeder` trunca a tabela

`DB::table('change_logs')->truncate()` — executar o seeder apaga as
novidades cadastradas manualmente.

### 3.14 [BAIXO] Sem auditoria

Nenhuma chamada a `activity()` no controller: as novidades criadas,
editadas e excluídas **não** são auditadas.

### 3.15 [BAIXO] Sem paginação

`ChangeLog::orderBy('id','DESC')->get()` carrega **todas** as linhas.
Com o seed, são ~28 — hoje não é um problema, mas uma instalação com
centenas de versões carregaria tudo.

### 3.16 [BAIXO] `UpdateVersion` usa `explode` sem validar o formato

`v6/app/Console/Commands/UpdateVersion.php:47-50`: se `version` não
tiver 3 segmentos (`major.minor.patch`), `$explode[2]` gera notice e
`$patch` fica `null` (virando `1` na concatenação).

---

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

O módulo **não existe na v5**. Tudo é novo:

| Item v5 | Situação na v6 |
|---|---|
| — | `Admin\ChangeLogController` (novo) |
| — | `App\Admin\ChangeLog` (novo) |
| — | `ChangeLogRequest` (novo) |
| — | views `prefecture/configurations/changelog/**` (5 arquivos, novos) |
| — | `public/assets/js/custom/config/novidades/changelog-modal.js` (novo) |
| — | migration `2022_04_22_094515_create_changelog_table.php` (nova) |
| — | `ChangeLogTableSeeder` (novo) |
| — | migration `2023_03_22_095658_create_update_logs_table.php` (nova) — conceito irmão |
| — | `UpdateVersion` (novo) — conceito irmão |

---

## 5. Onde a v6 divergiu (e por quê)

| Mudança | Efeito |
|---|---|
| Módulo `novidades` criado (id 10 do registro de módulos) | habilita ligar/desligar o módulo — **mas** o item de menu não tem guarda `$modulos['novidades']` |
| `numero_versao`: `float` -> `varchar(20)` (2025) | permite versões com sufixo; a validação do cliente não foi atualizada |
| `update_logs` (2023) e o comando `update:logs` | versão do sistema mostrada no rodapé, com link para o changelog |

---

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

| Risco | Detalhe | Mitigação |
|---|---|---|
| `deleted_at` não usado | exclusão definitiva já é o comportamento atual | decidir: `SoftDeletes` (recomendado) ou remover a coluna |
| `numero_versao` sem unicidade | duas entradas para a mesma versão | adicionar `unique` após revisar os dados |
| `title` sem limite real | títulos longos | validação de servidor já existe (`max:120`) |
| `update_logs` x `change_logs` | dessincronização de versão | unificar em `release_notes` ou criar um job de sincronização |
| `description` no `$fillable` de `UpdateLogs` | coluna inexistente | remover do `$fillable` |
| Dados do seed (28 versões) | `truncate` apaga o histórico manual | mover para seeder idempotente com `upsert` |
| HTML não sanitizado | XSS no modal | sanitizar título e descrição |

---

## 7. Perguntas em aberto para o negócio

1. **O changelog deve ser público?** Hoje é interno; muitos portais
   apresentam "novidades" ao cidadão. Se for público, é um módulo do
   portal (com layout e rota próprios).
2. **Deve haver um changelog por município/instalação?** Hoje é global
   (do produto).
3. **A versão do rodapé e a do changelog devem ser a mesma?** Hoje são
   duas fontes. Unificar simplifica.
4. **A exclusão deve ser definitiva ou soft delete?**
5. **O ícone deve ser uma lista fechada?** Hoje qualquer string CSS é
   aceita e injetada em `class`.
6. **É preciso anexar documentos (PDF de release notes)?**
7. **Quem é o autor das novidades?** Não há campo de autor nem
   aprovação.

---

## 8. Checklist de correção (priorizado)

| Prioridade | Item | Arquivo |
|---|---|---|
| P0 | Remover a rota `changelog.show` (ou implementar o método) | `v6/routes/basepro.php:76` |
| P0 | Criar o recurso `changelog` no catálogo `resources` (idempotente) | novo seeder |
| P0 | Sanitizar título e descrição (evitar `innerHTML` com HTML arbitrário) | `v6/public/assets/js/custom/config/novidades/changelog-modal.js` + controller |
| P1 | Tratar a exceção do Carbon (não deixar o catch vazio) | `v6/app/Http/Controllers/Admin/ChangeLogController.php:47-52,88-93` |
| P1 | Validar `numero_versao` no servidor (formato e tamanho) | `v6/app/Http/Requests/ChangeLogRequest.php` |
| P1 | Alinhar `maxlength` do título (150 -> 120) | `v6/resources/views/prefecture/configurations/changelog/form.blade.php` |
| P1 | Corrigir o HTML quebrado do `@if` na listagem | `v6/resources/views/prefecture/configurations/changelog/index.blade.php:11-100` |
| P2 | Trocar `str_limit` por `Str::limit` | `v6/resources/views/prefecture/configurations/changelog/index.blade.php:44,47` |
| P2 | Adicionar paginação ou limitar a listagem | `v6/app/Http/Controllers/Admin/ChangeLogController.php:16` |
| P2 | Adicionar `SoftDeletes` (ou remover `deleted_at`) | `v6/app/Http/Model/Admin/ChangeLog.php` |
| P2 | Guardar `$modulos['novidades']` no menu | `v6/resources/views/vendor/adminlte/page.blade.php:310` |
| P2 | Registrar auditoria (`activity()`) | `v6/app/Http/Controllers/Admin/ChangeLogController.php` |
| P3 | Declarar `protected $table` no model | `v6/app/Http/Model/Admin/ChangeLog.php` |
| P3 | Remover `description` do `$fillable` de `UpdateLogs` | `v6/app/Http/Model/Prefecture/UpdateLogs.php:11-19` |
| P3 | Validar o formato da versão em `UpdateVersion` | `v6/app/Console/Commands/UpdateVersion.php:47-50` |
| P3 | Tornar o `ChangeLogTableSeeder` idempotente (`upsert` em vez de `truncate`) | `v6/database/seeds/ChangeLogTableSeeder.php` |
