# Manual do Sistema — Regras e Portabilidade

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

---

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

### 1.1 Um manual por módulo do sistema, mas sem vínculo formal

O padrão de conteúdo é "um manual por módulo": "Manual de Notícias",
"Manual de Licitações", "Manual do Diário Oficial"... Os títulos seguem os
slugs dos módulos, mas **não existe** FK entre `manuals` e `modulos`. O
vínculo é apenas convencional (humano).

### 1.2 O título é a chave de negócio

Os seeders localizam manuais por título exato:

```php
$manual_noticias = DB::table('manuals')->where('titulo', 'Manual de Notícias')->value('id');
```

O `ManuaisSinergiaSeeder` ainda **renomeia** títulos
(`$oldTitle => $newTitle`). Isso significa que renomear um manual no CMS
**quebra** a associação feita pelos seeders — as áreas órfãs ficam sem
manual. Não há `slug` estável.

### 1.3 A edição e a criação compartilham a mesma view

`ManualController@edit()` renderiza `manual-usuario.create` com `$data`.
Por isso o `form.blade.php` está cheio de ternários `isset($data) ? ... :
...`, e o bloco de áreas só aparece na edição.

### 1.4 A área é o "print" da funcionalidade

Cada área guarda **uma imagem de destaque** (o `archive_id`) e uma
**descrição** em HTML. A imagem é o print da tela que o manual
documenta; a descrição explica o passo a passo. A imagem é opcional
(`archive_id` é nullable), a descrição também.

### 1.5 Os links `url_portal` e `url_cms` são apenas atalhos

São gravados e exibidos na tela de detalhe, mas **nada** no portal público
lê a tabela `manuals`. O "Manual de Navegação" do portal é uma página
estática, sem relação.

### 1.6 A exclusão de um manual é em cascata (no banco)

`manuals` tem FK de `manual_areas.manual_id` **sem** `onDelete`. Ao
excluir um manual (`Manual::destroy($id)` — soft delete), as áreas
permanecem no banco, apontando para um manual "apagado" (soft deleted).
Elas continuam invisíveis na listagem (que parte do manual), mas órfãs no
banco.

### 1.7 O módulo não é auditado

Nenhum `activity()` é chamado neste módulo. Criar, editar e excluir
manuais **não** deixa rastro em `activity_log`.

---

## 2. Acoplamentos

### 2.1 Dependências de saída

| Dependência | Tipo | Onde |
|---|---|---|
| `IManualUsuarioRepository` / `IManualAreaRepository` | interno | controllers |
| `IArchiveRepository` | interno | `ManualAreaRepository` |
| `AppTrait` / `FileTrait` | interno | repositories |
| `check.route.permission` | interno | autorização |
| `archives` | interno | imagem de destaque |
| `resources` / `modulos` | interno | permissão (quebrada) |
| Summernote, lightgallery | asset | descrição e zoom |

### 2.2 Dependências de entrada

| Consumidor | Dependência | Risco |
|---|---|---|
| Menu do AdminLTE | `route('manual-usuario.index')` | item de menu para todos |
| `resources` (ausente) | permissão `manual-usuario.*` | módulo inacessível a não-super-admin |
| Nenhum consumidor do portal | — | — |

> O módulo é **folha**: ninguém depende dele. Isso o torna seguro de
> reescrever.

---

## 3. Armadilhas

### 3.1 [CRÍTICO] O recurso `manual-usuario` não existe em `resources`

O seeder `v6/database/seeds/ManualDoSistemaResourceSeeder.php` criaria o
recurso com `modulo_id = 8`, mas **a linha não está na base**. Como o
catálogo `resources` é a fonte das permissões concedíveis, nenhuma chave
`manual-usuario.*` pode ser atribuída a um perfil.

Efeito combinado com o middleware:

1. `check.route.permission` avalia `hasAnyAccess(['manual-usuario.index', ...])`;
2. a chave não existe em nenhum perfil → **negado**;
3. apenas `super-admin` passa (bypass).

Resultado: o módulo é **inoperante** para qualquer operador que não seja
`super-admin`, embora o item de menu apareça no CMS.

### 3.2 [ALTO] `check.route.permission` libera `store`/`update`/`destroy`

`v6/app/Http/Middleware/CheckRoutePermission.php:48-52,82-90` — o
`in_array($value, $resourceRoutes)` compara com os **valores** do array
(`update`, `store`, `destroy`). Como `$accessArr` são os segmentos do nome
da rota, e toda rota de resource tem nome `xxx.store`/`xxx.update`/
`xxx.destroy`, o middleware retorna `true` **sem checar permissão**.

Consequências específicas deste módulo:

- `POST /cms/manual-usuario` (`manual-usuario.store`) — **liberado**;
  mas usa `ManualSistemaRequest` (valida) e o `filter_request`/`$fillable`
  limitam o que é gravado.
- `PUT /cms/manual-usuario/{id}` (`manual-usuario.update`) — **liberado**;
  e **não valida** nada (`Request` genérico). Um usuário autenticado
  qualquer pode sobrescrever o objetivo de um manual.
- `DELETE /cms/manual-area/{id}` (`manual-area.destroy`) — **liberado**;
  apaga a área (e deixa o arquivo órfão em disco).
- `POST /cms/manual-area` (`manual-area.store`) — **liberado**; cria área.

### 3.3 [ALTO] `ManualController@update` não valida

`v6/app/Http/Controllers/Prefecture/ManualController.php:110` recebe
`Request $request` em vez de `ManualSistemaRequest`. Um `PUT` sem
`titulo`/`objetivo` chega ao banco e viola `NOT NULL` — o
`Repository::upInsert` captura o `Throwable` e chama **`dd($th)`**
(`v6/app/Repositories/Repository.php:64-71`), **vazando a exceção** na
página em vez de retornar um erro treated.

### 3.4 [ALTO] `Repository::upInsert` faz `dd($th)`

```php
public function upInsert($request, int $id = null)
{
    try {
        $data = DB::transaction(function () use ($request, $id) {
            return $this->structureUpInsert($this->dataTrait($request));
        });
        return $this->getModel()->updateOrCreate(['id' => $id], $data);
    } catch (\Throwable $th) {
        dd($th);
    }
    return false;
}
```

`dd()` mata a requisição com um dump da exceção. Não há retorno de erro
nem log. Isso afeta **todos** os módulos que usam o `Repository`
abstrato (conteúdos, licitações, contratos, fornecedores, e-vacina, ...).

### 3.5 [MÉDIO] Exclusão por GET

`GET /cms/manual/delete/{id}` executa `Manual::destroy($id)`. Uma
operação destrutiva por GET é acionada por link, sem confirmação e sem
proteção CSRF (o CSRF protege POST/PUT/DELETE). A view usa
`bootstrap-confirmation` apenas para o botão de exclusão da área.

### 3.6 [MÉDIO] XSS armazenado na descrição da área

`v6/resources/views/prefecture/configurations/manual-usuario/show.blade.php`
renderiza:

```blade
@php echo ($area->descricao) @endphp
```

O conteúdo vem do Summernote (HTML livre) e é impresso **sem escape**.
Qualquer usuário com acesso à edição de área consegue injetar `<script>`
ou `<img onerror=...>` executado no navegador de todos que abrirem o
manual. Como o módulo é restrito ao `super-admin` na prática, o impacto é
menor — mas o risco existe e deve ser eliminado na v7 (sanitização com
`HTMLPurifier` ou `{!! !!}` apenas para HTML já purificado).

### 3.7 [MÉDIO] `filter_request` transforma valores falsos em `null`

`v6/app/Http/Traits/AppTrait.php:523-534`:

```php
if (!$valor) $filter[$campo] = null; else $filter[$campo] = $valor;
```

`ManualUsuarioRepository::structureUpInsert` usa
`array_keys($request)` como lista de campos — ou seja, **todos** os
campos enviados. Resultado: `url_portal` vazio vira `null` (ok), mas
qualquer campo não mapeado no `$fillable` é simplesmente ignorado pelo
Eloquent. O risco real é `objetivo = "0"` (string não vazia, ok) ou
`titulo` vazio, que passaria pelo `required`? Não — o `required` do
FormRequest rejeita string vazia.

### 3.8 [MÉDIO] `manual_areas` sem `onDelete` nas FKs

`manual_areas.manual_id` e `manual_areas.archive_id` não têm
`onDelete`. Soft delete de um manual deixa áreas órfãs; a exclusão de um
`archives` (por outro módulo) deixa `archive_id` apontando para um id
inexistente — a view usa `@isset($area->archive)`, que verifica a
**propriedade** e não a existência do registro, então exibe uma imagem
quebrada.

### 3.9 [MÉDIO] Arquivos órfãos no disco

`ManualAreaController@destroy` apaga a linha de `manual_areas` mas **não**
apaga o registro em `archives` nem o arquivo em
`storage/app/public/manual-usuario/area/{id}/destaque/`.

### 3.10 [BAIXO] `manual-usuario.destroy` e `manual-area.create`/`show` não existem

- `Route::resource('manual-usuario', ...)` gera
  `DELETE /cms/manual-usuario/{id}` -> `ManualController@destroy`
  **inexistente** (o método chama-se `delete`).
- `Route::resource('manual-area', ...)` gera
  `GET /cms/manual-area/create` -> `@create` **inexistente** e
  `GET /cms/manual-area/{id}` -> `@show` **inexistente**.

### 3.11 [BAIXO] `ManualAreaRepository::upInsert` tem retornos inconsistentes

Retorna `true` em dois caminhos, `null` implícito quando o upload do
arquivo falha e `false` quando o `parent::upInsert` falha. O controller
trata como booleano — funcional, mas frágil.

### 3.12 [BAIXO] `str_replace('storage', '', ...)` no `ArchiveRepository`

`v6/app/Repositories/Eloquent/Prefecture/Archive/ArchiveRepository.php:23`
remove **todas** as ocorrências da substring "storage" do caminho, o que
quebraria qualquer diretório com essa substring no nome.

### 3.13 [BAIXO] `manual-area.index` devolve todas as áreas do sistema

`ManualAreaController@index` não filtra por `manual_id` — devolve **todas**
as áreas de todos os manuais. A rota não é usada pelo JS (que usa
`/cms/manual/get/{id}`), mas está exposta.

### 3.14 [BAIXO] Factories apontam para classes inexistentes

`v6/database/factories/ManualFactory.php` usa `App\Manual::class` e
`v6/database/factories/ManualAreaFactory.php` usa `App\ManualArea::class`
— nenhum desses namespaces existe (os models reais são
`App\Http\Model\Prefecture\Manual` e `App\Http\Model\Prefecture\ManualArea`).
As factories também não definem atributos.

### 3.15 [BAIXO] Migration com nome enganoso

`v6/database/migrations/2023_01_27_104631_add_area_to_manual_areas.php`
não altera `manual_areas`: insere "ATOS DA COMISSÃO DE LICITAÇÃO" em
`diario_areas`.

### 3.16 [BAIXO] Dados criados em migration

`v6/database/migrations/2023_02_01_083626_add_itens_to_archives.php`
insere 20+ manuais. Misturar schema e dado torna `migrate:fresh` um
caminho não determinístico e impede o uso de seeders idempotentes.

---

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

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

| Item v5 | Situação na v6 |
|---|---|
| — | `Prefecture\ManualController` (novo) |
| — | `Prefecture\ManualAreaController` (novo) |
| — | `App\Http\Model\Prefecture\Manual` (novo) |
| — | `App\Http\Model\Prefecture\ManualArea` (novo) |
| — | `IManualUsuarioRepository` / `ManualUsuarioRepository` (novo) |
| — | `IManualAreaRepository` / `ManualAreaRepository` (novo) |
| — | `ManualSistemaRequest` (novo) |
| — | views `prefecture/configurations/manual-usuario/**` (novas) |
| — | `public/assets/js/custom/contents/manual-sistema/areas.js` (novo) |
| — | migrations `2023_01_20_162623`, `2023_01_20_163012` (novas) |
| — | seeds `UpdateManualAreas`, `UpdateManualAreasDiarioSeeder`, `ManuaisSinergiaSeeder`, `ManualDoSistemaResourceSeeder` (novos) |

> A v5 **não** tinha o registro de módulos (`modulos`), nem a coluna
> `resources.modulo_id`. O seeder `ManualDoSistemaResourceSeeder` depende
> de `modulo_id`, então só pode rodar depois da migration
> `2024_11_05_104026`.

---

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

| Mudança | Efeito |
|---|---|
| Introdução do registro de módulos (`modulos`) | o `ManualDoSistemaResourceSeeder` passa a existir (precisa de `modulo_id`) |
| `resources.modulo_id` | permite agrupar o recurso na matriz de permissões |
| O menu do AdminLTE deixou de usar `$modulos[...]` para este item | o item aparece mesmo com o módulo desativado (incoerente com "e-Vacina", "Fornecedores" e "Logs") |

---

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

| Risco | Detalhe | Mitigação |
|---|---|---|
| Títulos como chave | o `ManuaisSinergiaSeeder` renomeia títulos; os seeders de área quebram | adicionar `slug` imutável e migrar as áreas por id |
| Áreas órfãs | soft delete de manual não cascade | migrar para FK com `onDelete('cascade')` |
| `archives` órfãos | exclusão de área não limpa arquivo nem registro | adicionar limpeza no `destroy` e job de varredura |
| HTML não sanitizado | risco de XSS | sanitizar na escrita e na leitura |
| Dados em migration | `migrate:fresh` recria manuais com títulos antigos | mover para seeder |
| Recurso ausente | a permissão não pode ser concedida | executar `ManualDoSistemaResourceSeeder` (idempotente) |

---

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

1. **O manual deve ser público no portal?** As colunas `url_portal` e
   `url_cms` sugerem que sim, mas nada foi implementado. Se for, é
   preciso decidir o formato (página por manual, busca, etc.).
2. **Quem deve manter os manuais?** Se apenas `super-admin`, o módulo
   precisa de um perfil dedicado ("documentador").
3. **As imagens de destaque devem ser versionadas?** Hoje, ao reenviar
   uma imagem para a mesma área, o arquivo anterior **não** é removido.
4. **Existe um vinculo com `modulos`?** Se a relação "1 manual por módulo"
   for garantida, uma FK `modulo_id` elimina a necessidade de edição
   manual de títulos.
5. **O conteúdo deve ser versionado (histórico de alterações)?**
6. **A exclusão deve ser definitiva ou soft delete?** Hoje é soft delete,
   mas o botão diz "Deletar" e a área continua no banco.
