# Guia de Execução — Diário Oficial (ponta a ponta)

Jornada completa do módulo: certificado → cadastro → matérias/anexos → geração do PDF →
preview no CMS → visualização final personalizada no portal.

---

## 0. Visão geral dos gatilhos

| Ação | Local | O que dispara |
|------|-------|---------------|
| Salvar diário | CMS (`store`/`update`) | **Apenas persiste dados** (sem PDF) |
| "Processar PDF" | Edição do diário | `GET /refresh-diario-preview/{id}?force=1` (síncrono) |
| "Visualizar" | Edição do diário | Modal com iframe do PDF gerado |
| "Regenerar" (lista) | Listagem CMS | `GET /refresh-diario/{id}` → redireciona para a edição |
| Abrir diário no portal | `/diario-oficial/{id}` | Se PDF não existe, marca `pending` e mostra a tela de espera |

> **Importante:** a geração do PDF é **síncrona** dentro do request normal do Laravel
> (controller → repository). Não há job/fila. A tentativa de background foi abandonada por
> crashes do ImageMagick (ver `16-SOLUCAO_FINAL.md`).

---

## 1. Cadastro do certificado digital

1. CMS → **Certificados** (`/cms/certificado`).
2. Enviar arquivo `.pfx`/`.p12` + senha (`CertificadoRequest` exige ambos).
3. `DiarioCertificadosController@store`:
   - Salva o `.pfx` em `storage/app/public/diario-oficial/certificado/{id}/`.
   - Converte para `.crt` via `openssl pkcs12 ... -nodes -passin pass:{senha}`.
   - Extrai `CN` (nome) e persiste `cert` (PEM) e `pkey` (chave) em `diario_certificados`.
4. O certificado pode ser definido como **padrão** nas configurações globais.

> Riscos de segurança dessa etapa: ver `22-ANOMALIAS_E_RISCOS.md` (A1).

---

## 2. Configurações globais (`diario_configs`)

CMS → Diários → **Configurações** (`/cms/diario/configuracoes`).

- `lei_padrao` — texto sugerido em novos diários.
- `certificado_padrao` — certificado usado quando o diário não define um.
- `ativos` — status padrão.
- `logo_position` — `center` / `left` / `right` (define a variação do cabeçalho).
- `header_img` — imagem de cabeçalho (`archives`), comprimida em 1024×500.

Fluxo de salvar: `DiarioController@update_configuration` (`SaveConfigRequest`), que faz
upload + `Archive::updateOrCreate` + compressão. Remoção:
`DELETE /cms/diario/configuracoes/header-img/{id}`.

---

## 3. Cadastro do diário

1. CMS → **Diários** → Cadastrar (`/cms/diario-oficial/create`).
2. `DiarioController@create` envia: `areas`, `subareas`, `edicao` (max+1), `diarios`
   (para complemento), `certificados`, `preinfo` (config).
3. `form.blade.php` coleta: edição, autor, e-mail, certificado, complemento, data de
   publicação, lei validadora, capa, `columns_number`, `has_line`, `line_style`, `line_color`,
   e o checkbox de status (conforme permissão).
4. `store(DiarioOficialRequest)` → `DiarioOficialRepository::upInsert` (sem PDF).
   Se houver capa (`archive_highlight`): cria `Archive` e seta `cover_image_id`.
5. Redireciona para `diario-oficial.edit/{id}`.

**Validação** (`DiarioOficialRequest`): `edicao`, `autor`, `email`, `data_publicacao`
obrigatórios; `id_certificado` via `SelectRule`; `columns_number` ∈ {1,2,3}.

---

## 4. Gestão de matérias (áreas + conteúdo)

Na **edição** (`form.blade.php` inclui `modal/areas/index`), com `areas.js`:

- **Adicionar/editar matéria** — modal `#modal_area`:
  - Título, Área, Subárea.
  - Tipo `text` (Summernote → `conteudo`) ou `file` (upload PDF → `path`).
  - Para tipo `file`, opção **"Arquivo dentro do conteúdo?"** (`file_in`).
- **Persistência**: `POST /cms/area` / `PUT /cms/area/{id}` (multipart), gravando em
  `diario_has_areas`; PDFs em `storage/app/public/diario-oficial/adendos/`.
- **Reordenação**: drag & drop (SortableJS) → `PUT /cms/ajax/diario-oficial/{id}/sort`
  (atualiza `position`).
- **Exclusão**: `DELETE /cms/diario-area/{id}`.
- **Conteúdo textual**: `DiarioAreaController::processContent()` baixa imagens externas e
  comprime imagens do Summernote (`compressImgsFromContent`).

### Comportamento de `file_in` na geração

| `file_in` | Efeito no PDF |
|:---------:|---------------|
| `true` | PDF é convertido em JPEGs e inserido no fluxo de colunas |
| `false` | PDF é mesclado página a página (FPDI) em página cheia |

### Merge de anexos consecutivos

Anexos PDF consecutivos de **mesma área + subárea** são concatenados por `mergePdfs()`
(FPDI) antes do processamento, evitando quebras artificiais. Detalhes em
`17-MERGE_ANEXOS_PDF.md`.

---

## 5. Geração do PDF (passo a passo)

1. O usuário clica em **"Processar PDF"** (`#processPdf`) na edição.
2. JS `processPdf()`:
   ```js
   setButton('processing', 'Gerando PDF...');
   fetch('/refresh-diario-preview/' + ID + '?force=1');
   pollTimer = setInterval(pollStatus, 1000);
   ```
3. `DiarioController@refresh_PDF_preview`:
   - Marca `generation_status = 'processing'`, `generation_started_at = now()`,
     zera `generation_progress` e `generation_error`.
   - Chama `DiarioOficialRepository::generatePdfFile($id)` **sincronamente**.
   - Sucesso → `completed`; falha → `failed` + `generation_error`.
4. `generatePdfFile`:
   - `@set_time_limit(0)`.
   - Busca diário/config/certificado; monta dados de `Configuration`; gera QR Code.
   - Configura `FPdfService`; merge de anexos; capa/página; itera áreas e matérias
     (texto/imagens/merge FPDI); atualiza `generation_progress`.
   - Escrita atômica (`.tmp` → rename).
5. Frontend faz polling em `GET /cms/ajax/diario-generation-status/{id}`:
   | Estado | UI |
   |--------|----|
   | `processing` + páginas | "Anexo 1/2 — Página 5 de 27" |
   | `processing` sem páginas | "Anexo 1 de 2..." |
   | `completed` | Botão verde "PDF OK — Gerar novamente" (habilita Visualizar) |
   | `failed` | Botão vermelho "Tentar novamente" |
6. Ao completar, o iframe `#pdfFrame` aponta para `/cms/diario-oficial/{id}` (stream do PDF).

### Estrutura de arquivos gerada

```
storage/app/public/diario-oficial/
├── content/{id}/
│   ├── pdf/diário-{edicao}.pdf     # PDF final (escrita atômica)
│   └── images/                     # JPEGs temporários do PdfConverter
├── adendos/                        # PDFs originais enviados
├── capas/                          # Imagens de capa
├── headers/{archiveId}/            # Imagem de cabeçalho das configurações
└── certificado/{id}/               # .pfx e .crt
```

---

## 6. Preview no CMS

- Botão **"Visualizar"** (`#preview`, habilitado após `completed`) abre `#pdfModal`.
- `preview-diario.js` carrega `/loading-diario-preview` no iframe, chama
  `refresh-diario-preview/{id}` e então aponta o iframe para `/cms/diario-oficial/{id}`.
- O `DiarioOficialComponent` renderiza o shell do modal
  (`prefecture.contents.components.preview.diario-oficial.index`).

---

## 7. Portal público (visualização final personalizada)

### 7.1 Listagem/filtro — `/diario-oficial`

- View: `portal/layouts/diario-oficial/index.blade.php` (estende o template ativo `$model`).
- Filtros: `#DataINI`, `#DataFIM`, `#Search_termo`, `#Search_edicao`.
- `diario_portal.js` → `POST /diario-oficial/filtro` (`Portal\DiarioController@filtroGET`),
  paginação cliente (10 por página), links `/diario-oficial/{id}` em nova aba.

### 7.2 Viewer — `/diario-oficial/{id}`

- View: `show-pdf.blade.php` (HTML autônomo, CSS `diario-oficial-viewer.css`).
- `Portal\DiarioController@showPDF`:
  - Calcula edição **anterior** e **próxima** (por `data_publicacao`, status=1).
  - Caminho do PDF: `storage/diario-oficial/content/{id}/pdf/diário-{edicao}.pdf`.
  - Se o arquivo não existe: marca `pending` e exibe tela "Gerando PDF..." com polling
    (`/cms/ajax/diario-generation-status/{id}`) e reload ao completar.
  - Se existe: embute o PDF (`<embed id="pdf-embed">`) e exibe a sidebar.
- Sidebar: logo, título, tamanho do arquivo (`fileInfo`), edição, data, assinatura digital
  (autor/e-mail/lei), busca por termo e navegação anterior/próxima.
- `viewer.js` (`DiarioViewer`): `fileInfo` (`/diario-oficial/viewer/{id}/file-info`),
  busca (`/diario-oficial/viewer/search?term=`), navegação entre edições.

### 7.3 Busca

`Portal\DiarioController@search` faz `ILIKE` em conteúdo/título (via `areas`) e em
`edicao`/`lei_validadora`/`autor`; retorna `{exists, results:[{id, edicao, data_publicacao}]}`.

---

## 8. Estados de geração (`generation_status`)

| Valor | Significado | Como entra |
|-------|-------------|------------|
| `null` | Nunca gerado | padrão |
| `pending` | Aguardando (sem consumidor — ver risco B1) | portal `showPDF` / `dispatchGeneration` |
| `processing` | Sendo gerado | `refresh_PDF_preview` antes de gerar |
| `completed` | Gerado com sucesso | `refresh_PDF_preview` após sucesso |
| `failed` | Erro | exceção ou timeout de 10 min em `generationStatus()` |

---

## 9. Checklist de operação/manutenção

- [ ] Certificado cadastrado e (opcionalmente) definido como padrão
- [ ] Configurações globais salvas (lei, logo, header)
- [ ] Diário cadastrado com dados e capa
- [ ] Matérias adicionadas (texto e/ou PDF) e ordenadas
- [ ] "Processar PDF" executado e status `completed`
- [ ] Preview conferido e, se necessário, regenerado
- [ ] Diário publicado (status) para aparecer no portal
- [ ] Portal: listagem, filtro, viewer, busca e download funcionando
- [ ] Verificar logs `storage/logs/laravel.log` em caso de `failed`

> Para diagnóstico de problemas recorrentes de PDF (colunas, merge, timeout, certificado),
> ver `15-ANALISE_PROBLEMA_PDF.md`, `16-SOLUCAO_FINAL.md`, `17-MERGE_ANEXOS_PDF.md`,
> `18-SESSION_MERGE_ANEXOS.md` e `CadeiaDeErros_ColunasPDF.md`.
