# Anomalias e Riscos — Diário Oficial

Catálogo verificado de problemas, código morto e divergências. **Nenhuma correção de código
foi feita** — este documento apenas registra.

Severidade: 🔴 alta · 🟠 média · 🟡 baixa.

---

## A. Segurança

### A1 🔴 Senha do certificado exposta em log e em `shell_exec`

`app/Http/Controllers/Prefecture/DiarioCertificadosController.php`:

```php
$command = "openssl pkcs12 -in $nomeCertPFX -out $nomeCertCRT -nodes -passin pass:$password";
Log::info("Executando comando: $command");   // <-- senha em texto puro no log
$output = shell_exec($command);
```

- A senha do certificado é escrita no log de aplicação.
- `$nomeCertPFX` / `$password` não são escapados — superfície de **command injection**
  (a senha vem do request).
- Corrigir: usar `--passin file:` ou `proc_open` com `array` de argumentos e `escapeshellarg`,
  e remover a senha do log.

### A2 🟠 QR Code gravado em arquivo compartilhado

`DiarioOficialRepository::generatePdfFile()` sempre grava em `public/img/qrcode.png`
(nome fixo). Gerações concorrentes de diários diferentes sobrescrevem o mesmo arquivo,
podendo inserir o QR de outro diário no PDF.

### A3 🟡 `$url` do QR depende de `$_SERVER['SERVER_NAME']`

Host não validado; em ambiente com host manipulado o QR pode apontar para outro domínio.

---

## B. Bugs funcionais

### B1 🔴 Geração "pending" sem consumidor

`DiarioController::dispatchGeneration()` e `Portal\DiarioController@showPDF` apenas marcam
`generation_status = 'pending'`. Não existe **job, comando ou cron** no módulo que consuma
`pending` (não há `app/Jobs`, nem comando `diario:*`). Na prática, um diário visualizado no
portal sem PDF gerado **pode ficar "Gerando PDF..." indefinidamente** até alguém abrir a
edição no CMS e clicar em "Processar PDF". O `generationStatus()` só o marca como `failed`
após 10 min.

### B2 🟠 `TO_CHAR` com formato inválido + variável morta no Portal

`Portal\DiarioController@showPDF`:

```php
$currentMonth = date('Y-m', strtotime($diario->data_publicacao));
$editionsThisMonth = Diario::whereRaw("TO_CHAR(data_publicacao, '%Y-%m') = ?", [$currentMonth])
    ->where('status', 1)->orderBy('data_publicacao', 'asc')->get();
```

Em PostgreSQL o formato correto é `'YYYY-MM'`; `'%Y-%m'` é sintaxe MySQL/SQLite.
Além disso, `$editionsThisMonth` **não é enviada à view** (código morto/possível bug).

### B3 🟠 `createButtonDelete` com assinatura incompatível

`diario-oficial/datatable.js` chama:

```js
createButtonDelete(data.id, `diario-oficial/${data.id}`, data.delete,
    `diario-oficial/${data.id}`, '...', '...', '_self', 'button')
```

Mas `public/assets/js/custom/utilities/table.js` define `createButtonDelete(id, url, permission, del_url)`
(4 parâmetros). Os 4 argumentos extras são ignorados.

### B4 🟠 Permissão de subárea resolvida como área

`DatatableController::get_sub_areas_diario()` chama
`struct_views($data, 'diario-area')`; assim, editar/excluir/ativar subárea valida
`diario-area.*` em vez de `diario-subarea.*`.

### B5 🟠 `viewer.js` chama método inexistente

`public/assets/js/custom/portal/diario-oficial/viewer.js` liga a tecla Enter do `#search-input`
a `searchInPdf()`, que **não está definido**. O parâmetro/objeto `apiUrls` do `show-pdf.blade.php`
também nunca é usado (URLs são hardcoded no JS).

### B6 🟡 `config.js` referencia variável errada

No erro do XHR de remoção da imagem de cabeçalho, usa `if(error)` — a variável do `catch`
chama-se `errorMsg`.

### B7 🟡 `<body` malformado no viewer do portal

`resources/views/portal/layouts/diario-oficial/show-pdf.blade.php` abre `<body ...` sem o
`>` de fechamento.

### B8 🟡 `--}}` órfão em `form.blade.php`

Há um `--}}` sem `{{--` de abertura, que **renderiza como texto literal** na página de edição.

### B9 🟡 `#printPdf` nunca renderizado

`create.blade.php` e o componente de preview tentam ligar o botão `#printPdf`, mas o markup
do preview não o cria — o binding é um no-op.

### B10 🟡 CSS inválido nos templates legados

`diario-oficial/pdf/style.blade.php` contém seletores quebrados (`.pb-10, {`, `.pb-14, {`)
e `.text-left { text-align: center; }`. A cópia órfã em `components/preview/.../style.blade.php`
usa unidade inválida `.title-image { width: 180x; }`.

### B11 🟡 Registro de view do portal prepara dados não usados

`Portal\DiarioController@index` envia `diarios` paginado, mas a view é populada via JS
(`diario_portal.js`); a variável não é utilizada.

---

## C. Código morto / legado

| Item | Detalhe |
|------|---------|
| `app/Http/Model/Prefecture/DiarioCerticado.php` | Nome **com typo**; a classe é `DiarioCertificado`. Depende de classmap otimizado do Composer para carregar |
| `app/Http/Requests/DiarioAdendo/SaveRequest.php` | Sem controller/rota/model — não usado |
| `database` → tabela `diario_adendos` | Sem model e sem código de leitura/escrita |
| `adendo_id` em `DiarioAreaController::store/update` | Coluna **não existe** (removida); `$fillable` não inclui, então é ignorado silenciosamente |
| `pdf/summernote.blade.php` e `components/preview/.../summernote.blade.php` | Bundles CSS (normalize + Bootstrap 3 + Glyphicons) de 7371 linhas; misnomer; o segundo é órfão |
| `components/preview/diario-oficial/style.blade.php` | Órfão |
| `pdf/index.blade.php` + `pdf/style.blade.php` | Template HTML/PDF legado (o PDF real vem de `FPdfService`) |
| `generation-status.js` | Constante `RETRY_INTERVAL_MS` declarada e não usada |
| `index.js` (CMS) | Depende de `.toogle-status`, elemento que a listagem atual (DataTable com `createActiveButton`) não gera |
| Migrations `*_two` (`areas_do_diario_two`, `sub_areas_do_diario_two`) | Recriam as views com SQL **idêntico** — sem efeito prático |
| Imports não usados em `DiarioOficialRepository` | `PSpell\Config`, `Psy\TabCompletion\...`, `use PSpell\Config;` etc. |
| `DiarioOficialComponent` | Recebe `$certificado/$icon/$cidade` no `render()` e a view de preview não os usa |

---

## D. Divergências documentação × código (já corrigidas nos docs)

| Doc antigo dizia | Código real |
|------------------|-------------|
| `DiarioCertificado.php` | `DiarioCerticado.php` |
| `FPdfService` com 581 linhas e métodos próprios | 259 linhas orquestrando 5 sub-serviços em `app/Services/Pdf/` |
| "29 migrations" | **33** (inclui `2025_09_22_203749` e as 3 `generation_*`) |
| Command `DiarioGeneratePdf` | **Não existe** |
| Jobs de geração | **Não existem** |
| `.../contents/areas-diario/index.js` | `.../contents/diario-oficial/areas-diario/index.js` |
| 24 views | **27** (faltavam 3 de certificado) |
| 6 requests | **7** (faltava `CertificadoRequest`) |
| Store/update geram PDF | **Não geram** — geração só sob demanda |

---

## E. Banco de dados / dados

- **`adendo_id` criado e removido**: migration `2025_09_22_022119` adiciona; `2025_09_22_203749`
  (não listada na wiki antiga) adiciona `path` e remove a coluna.
- **Views `*_two`** são recriações idênticas (ponto de confusão, sem ganho).
- **`RolesTableSeeder`** concede `diario-area.*` e `diario-subarea.*`, mas **não** concede
  `diario-oficial.*`. Como o menu exige `diario-oficial.index`, o item "Diários" pode não
  aparecer para o perfil Administrador.
- **`DiarioCaturiteSeeder`** tem nome do módulo mas só migra PDFs legados para
  `contents`/`archives`; **não é chamado** pelo `DatabaseSeeder`.
- **`get_sub_areas_diario`** usa permissão de área (ver B4).
- `diario_has_areas.path` é a coluna usada pelo PDF; `DiarioAreaController` grava em
  `diario-oficial/adendos` e o repositório lê via `public_path($materia->getOriginal('path'))`.

---

## F. Frontend / integrações

- **Asset ausente**: `public/assets/js/custom/RichText/summernote.js` é referenciado por
  `diario-areas/form`, `diario-subareas/form` e `certificado/form`, mas **não existe**
  (o correto é `assets/js/custom/utilities/richtext/summernote.js`, com `u` minúsculo).
  Esses formulários ainda carregam Summernote sem possuir campo Summernote (vestigial).
- **Globais frágeis**: cada `datatable.js` declara `let collums` no escopo global e
  `runDatatables()` **muta** o array com `unshift`; funciona por haver uma tabela por página.
- **`summernote-diario.js`** é o build completo do Summernote 0.8.2 (5277 linhas) com duas
  customizações: largura de imagem forçada para 793px e plugin `clearFormatting`.
  O carregamento do wrapper (`utilities/richtext/summernote.js`) fica próximo de um `--}}`
  órfão no `form.blade.php` (ver B8) — verificar se realmente é renderizado.
- **URLs montadas sem porta** em `create.blade.php`/`preview-diario.js`
  (`location.protocol + '//' + location.hostname`), quebrando deploys em porta não padrão.

---

## G. Recomendações prioritárias

1. **A1** — eliminar senha dos logs e escapar `openssl` (segurança).
2. **B1** — implementar Job/Agendador para consumir `generation_status = 'pending'`
   (ou remover o estado pending e gerar sincronamente no primeiro acesso).
3. **B2** — corrigir `TO_CHAR(..., 'YYYY-MM')` e remover/usar `$editionsThisMonth`.
4. **B3/B4** — alinhar assinaturas JS e corrigir a permissão de subárea.
5. **D/E** — padronizar nomes (`DiarioCerticado`), remover órfãos e atualizar seeders de permissão.
