# Análise de Problema — Geração de PDF Travando

## Histórico do Relato

1. Ao criar um arquivo e apertar "visualizar" ou "salvar", a tela fica travada carregando o PDF
2. Ao sair da página, o PDF é salvo de maneira incompleta, e não há como reverter
3. Ao entrar no CMS em um diário já criado, ele começa a carregar novamente (regeneração)
4. Ao interagir rapidamente (salvar/visualizar) em um já existente, dois processos ocorrem simultaneamente

---

## Análise de Causa Raiz

### 🔴 Problema 1 — Geração Síncrona Bloqueia a Request

**Local:** `DiarioController@store`, `DiarioController@update`, `DiarioController@refresh_PDF_preview`

```php
// DiarioController.php:102 — Chamada síncrona dentro do HTTP request
$this->repository->generatePDF($id, true);
```

```php
// create.blade.php:74 — Fetch síncrono para preview
fetch(`${protocol}//${hostname}/refresh-diario-preview/${id}`)
```

**Causa:** `generatePDF()` é chamado **diretamente no ciclo da requisição HTTP**. Para diários com muitas páginas, PDFs complexos, ou conversão de imagens via Imagick, a geração pode levar **30 segundos ou mais**. O navegador fica aguardando a resposta, resultando em tela congelada.

### 🔴 Problema 2 — Sem Proteção de Concorrência (Race Condition)

**Local:** `DiarioOficialRepository.php:352`

```php
Storage::put($caminho, $pdf->Output($pdf_name, 'S'));
```

**Causa:** Não há lock/mutex. Se o usuário clicar "salvar" e "visualizar" rapidamente, **dois processos `generatePDF` rodam simultaneamente para o mesmo diário**, ambos escrevendo no mesmo arquivo (`diario-oficial/content/{id}/pdf/diário-{edicao}.pdf`). O resultado é um arquivo corrompido.

**Cenário de corrida:**
```
Processo A (salvar)      |  Processo B (visualizar)
--------------------------|-----------------------------
Storage::put(caminho, X)  |  
                          |  generatePDF começa (flag forceCreate=false)
                          |  Storage::exists(caminho) → false (A ainda não terminou)
                          |  Gera Y completo
                          |  Storage::put(caminho, Y) ✓
Storage::put(caminho, Z)  |  ← SOBREESCREVE com X (parcial/corrompido)
```

### 🔴 Problema 3 — Sem Rastreamento de Status

**Local:** Em todo o fluxo

**Causa:** Não há coluna de status no banco. O sistema não sabe se um PDF:
- Ainda não foi gerado
- Está sendo gerado
- Foi gerado com sucesso
- Falhou na geração

A única verificação é `Storage::exists()` que não distingue um arquivo completo de um parcial.

### 🔴 Problema 4 — Arquivo Parcial sem Mecanismo de Validação

**Local:** `DiarioOficialRepository.php:96-98`

```php
$hasDiario = Storage::exists($caminho);
if ($hasDiario && !$forceCreate) {
    return $this->streamPdf($directory . $caminho);  // ← Arquivo pode estar corrompido!
}
```

**Causa:** Se a geração é interrompida (timeout, fechamento de aba, crash), um arquivo PDF parcial fica salvo no storage. Quando o usuário tenta visualizar novamente, `Storage::exists()` retorna `true`, e o sistema tenta fazer stream do **arquivo corrompido**.

### 🔴 Problema 5 — Preview Dispara Geração Automaticamente

**Local:** `create.blade.php:74`

```javascript
fetch(`${protocol}//${hostname}/refresh-diario-preview/${id}`)
```

**Causa:** Toda vez que a página de edição carrega, o JavaScript faz uma requisição para `/refresh-diario-preview/{id}` que **regenera o PDF inteiro**. Isso acontece mesmo que o PDF já exista e esteja completo. O usuário vê a tela "processando" sem entender por quê.

---

## Proposta de Solução

Adotar **processamento assíncrono via fila (Queue)** com **controle de status**, **lock de concorrência** e **escrita atômica**.

### Componentes da Solução

| Componente | Descrição |
|------------|-----------|
| **Queue (database driver)** | Processamento assíncrono usando Laravel Queue com driver `database` |
| **Job `GenerateDiarioPdf`** | Job dedicado para geração de PDF, executado em background |
| **Status no banco** | Colunas `generation_status`, `generation_error`, timestamps na tabela `diarios` |
| **Escrita atômica** | Geração em arquivo temporário, rename atômico para o definitivo |
| **Lock de concorrência** | Previne que dois jobs gerem o mesmo diário simultaneamente |
| **Polling de status** | JavaScript consulta status a cada 3s e atualiza a interface |
| **Regeneração manual** | Botão "Gerar PDF" com feedback de progresso |

### Fluxo Novo

```
SALVAR:
1. Controller salva dados do diário
2. Define generation_status = 'pending'
3. Dispatch Job para a fila
4. Retorna response imediato (sem esperar PDF)

VISUALIZAR PREVIEW:
1. Verifica generation_status
2. Se 'completed' → mostra PDF
3. Se 'processing' → mostra "Gerando..." com polling
4. Se 'pending' → mostra "Na fila..." com polling
5. Se 'failed' → mostra erro + botão "Tentar novamente"

QUEUE WORKER:
1. Pega job da fila
2. Define generation_status = 'processing'
3. Gera PDF em arquivo temporário (.tmp)
4. Renomeia atômico para path definitivo
5. Define generation_status = 'completed'
6. Em caso de erro: status = 'failed' + mensagem de erro

REFRESH MANUAL:
1. Botão "Gerar PDF" na listagem
2. Atualiza status para 'pending'
3. Dispatch novo job
4. Badge na tabela mostra status atualizado
```

### Arquivos a Serem Modificados/Criados

| Tipo | Arquivo | Ação |
|------|---------|------|
| **NEW** | `database/migrations/XXXX_XX_XX_create_jobs_table.php` | Tabela de filas |
| **NEW** | `database/migrations/XXXX_XX_XX_add_generation_status_to_diarios_table.php` | Colunas de status |
| **NEW** | `app/Jobs/GenerateDiarioPdf.php` | Job de geração |
| MODIFY | `.env` (QUEUE_DRIVER=database) | Configuração da fila |
| MODIFY | `app/Http/Controllers/Prefecture/DiarioController.php` | Dispatch job + status endpoint |
| MODIFY | `app/Http/Controllers/Portal/DiarioController.php` | Verificação de status |
| MODIFY | `app/Repositories/Eloquent/Prefecture/DiarioOficial/DiarioOficialRepository.php` | Escrita atômica |
| MODIFY | `routes/basepro.php` | Nova rota de status |
| MODIFY | `resources/views/.../index.blade.php` | Badge de status |
| MODIFY | `resources/views/.../create.blade.php` | Polling de preview |
| MODIFY | `public/assets/js/.../datatable.js` | Coluna de status |
| MODIFY | `public/assets/js/.../index.js` | Polling automático |
