# Gemini — Gaps, Dívidas e Comparativo v5 → v6

> Módulo **parcialmente removido na v6**: o caminho de *geração de conteúdo* desapareceu
> por completo; o caminho de *mascaramento LGPD* foi mantido e copiado.

---

## 1. Bugs / erros conhecidos encontrados

### 1.1 Críticos (segurança / custo)

| ID | Descrição | Arquivo:linha | Efeito |
|---|---|---|---|
| **C1** | Rota de geração de conteúdo **pública**, sem `check.logged`, sem `check.route.permission` e **sem `throttle`**. | `v5/routes/web.php:203` | Qualquer visitante anônimo pode consumir a chave paga da prefeitura à vontade (custo + DoS). O CSRF não protege — ele é vinculado à sessão, e qualquer página pública entrega um token válido. |
| **C2** | `GeminiPromptRequest::authorize()` retorna `true` incondicionalmente. | `v5/app/Http/Requests/GeminiPromptRequest.php:16` | Nenhuma autorização sobre a chamada. |
| **C3** | **Chave da API hardcoded no repositório** (literal no arquivo). | `v5/database/seeds/AddToConfigurationsGeminiApiKeyValue.php` | A credencial está no histórico do Git. **Deve ser rotacionada.** |
| **C4** | O seeder **sobrescreve** a chave cadastrada pelo operador. | `v5/database/seeds/AddToConfigurationsGeminiApiKeyValue.php` + `v5/database/seeds/ExtrasSeeder.php:35` | Rodar `ExtrasSeeder` apaga a configuração válida. |
| **C5** | Chave da API **gravada em texto claro no log de atividades**. | `v5/app/Http/Controllers/Prefecture/ConfigurationController.php:200` e `:315` | `activity_log.properties` vira um cofre de segredos. |
| **C6** | Chave da API **na query string** (`?key=`). | `v5/app/Http/Controllers/Prefecture/GeminiController.php:31`, `v5/app/Http/Traits/DataMaskTrait.php:242` | Vazamento em log de acesso, proxy e histórico. |
| **C7** | Mensagem de erro do Guzzle **devolvida ao cliente**, podendo conter a URL com a chave. | `v5/app/Http/Controllers/Prefecture/GeminiController.php:83-86` | Exposição da credencial em caso de falha. |
| **C8** | Chave **exibida em claro** no campo de texto da tela de Configurações (`type="text"`). | `v5/resources/views/prefecture/configurations/configuracoes/loop_config.blade.php` | Shoulder-surfing, impressão, screenshot. |
| **C9** | Prompt do usuário **sem sanitização**, direto para o modelo, em rota pública. | `v5/app/Http/Controllers/Prefecture/GeminiController.php:13, 20` | *Prompt injection* por terceiros. |

### 1.2 Funcionais

| ID | Descrição | Arquivo:linha | Efeito |
|---|---|---|---|
| **F1** | `GEMINI_API_MODEL` é lido e **nunca usado** — a URL do modelo é fixa. | `v5/app/Http/Traits/DataMaskTrait.php:218` (leitura) e `:240` (URL fixa) | Trocar o modelo no `.env` não tem efeito nenhum. |
| **F2** | `GEMINI_API_URL` e `GEMINI_API_KEY` **não existem** em `v5/.env` nem em `v5/.env.example`. | `v5/.env.example` | `env('GEMINI_API_URL')` → `null` → sempre a URL fixa; `env('GEMINI_API_KEY')` → `null` → o banco é a única fonte real. |
| **F3** | `env()` lido **em runtime** (fora de `config/`). | `v5/app/Http/Traits/DataMaskTrait.php:217-220` | Anti-pattern do Laravel; quebra com `config:cache` em versões modernas; não testável. |
| **F4** | Se `GEMINI_API_URL` já tiver query string, a chave **não** é anexada. | `v5/app/Http/Traits/DataMaskTrait.php:241-243` | A API responde 400 e o mascaramento cai no fallback silenciosamente. |
| **F5** | Corte de 150 em **bytes** (`substr`/`strlen`/`strpos`), não em caracteres. | `v5/app/Http/Controllers/Prefecture/GeminiController.php:64-71` | Título pode terminar em caractere multibyte partido (UTF-8 inválido). |
| **F6** | Corpo devolvido começa com `\n\n` (parágrafo vazio inicial no Summernote). | `v5/app/Http/Controllers/Prefecture/GeminiController.php:62` | Ruído visual. |
| **F7** | `promptFeedback.blockReason` / `finishReason` **não são verificados**. | `GeminiController.php:46`, `DataMaskTrait.php:263` | Bloqueio de segurança do Gemini vira "Resposta inesperada da API Gemini." (500). |
| **F8** | **Sem timeout** no cliente Guzzle do caminho A (Guzzle 5.3.4: `timeout = 0` = infinito). | `v5/app/Http/Controllers/Prefecture/GeminiController.php:34` | Requisição pode pendurar indefinidamente e reter o worker PHP. |
| **F9** | Sem `timeout` no `$.ajax`. | `v5/public/assets/js/custom/utilities/helpers/ia-modal.js` | Botão travado em "Gerando...". |
| **F10** | `generationConfig` ausente (sem `maxOutputTokens`, `temperature`, `safetySettings`). | ambos os payloads | Sem controle de custo nem de segurança. |
| **F11** | O `.done()` do JS testa `data.success === false`, campo que o controller **nunca** devolve. | `ia-modal.js` | Código morto. |
| **F12** | O `.fail()` do JS **ignora** `responseJSON.title`/`text`. | `ia-modal.js` | O operador nunca vê "Erro de Configuração". |
| **F13** | Mensagem fixa diz "mínimo 5 caracteres"; a regra é `min:4`. | `ia-modal.js` | Informação incorreta. |
| **F14** | `id="modal-banner"` **duplicado** entre `ia-modal/index.blade.php:17` e `banner-modal/index.blade.php:11`. | as duas views | Colisão de ID caso os componentes coexistam. |
| **F15** | Seletores globais `#title` e `#text`. | `ia-modal.js` | Risco de gravar no campo errado. |
| **F16** | `import` não utilizado `use Illuminate\Http\Request;`. | `GeminiController.php:4` | Ruído. |
| **F17** | `GeminiController` (namespace `Prefecture`) estende `App\Http\Controllers\Admin\Controller`. | `GeminiController.php:5, 9` | Herança de CMS em rota de portal — provável causa da rota ter sido colocada no arquivo errado. |
| **F18** | Nenhum índice em `configurations.key`. | schema | Consulta por chave a cada pedido de e-SIC/Ouvidoria. |
| **F19** | `Log::error('Gemini generateContent non-200', ['body' => $body])` grava o corpo bruto da resposta da IA. | `DataMaskTrait.php:260` | Log pode conter conteúdo derivado de dado pessoal (LGPD). |
| **F20** | `Log::warning('Gemini generateContent unexpected response', ['data' => $data])` idem. | `DataMaskTrait.php:278` | Idem. |

---

## 2. Funcionalidades incompletas ou mortas

| Item | Situação |
|---|---|
| **Geração de conteúdo com IA** | Funcional na v5, mas com a UX quebrada (F11, F12): o operador nunca recebe a mensagem de erro real. |
| **Mensagem "Erro de Configuração"** | Produzida pelo backend, nunca exibida no frontend. |
| **Ramo `data.success === false`** | Código morto. |
| **Variável `$apiModel`** | Lida, não usada. |
| **Variável `$apiKeyForUrl`** | Cópia redundante de `$apiKey`. |
| **Bloco `#btnCancelarIA`** | O botão "Cancelar" apenas fecha o modal via `data-dismiss`; o handler de `#btnGerarIA` não o referencia. Não é problema, mas o id é redundante. |
| **Prompt de "mínimo 5 caracteres"** | Texto morto que confunde. |
| **Modal na tela de edição de Publicações** | O partial `form.blade.php` inclui o modal e o script, mas o botão `#abrirModalIA` só existe em `publicacoes/create.blade.php`. O modal é carregado sem gatilho em `edit.blade.php`. |
| **`resources`/`modulos`** | O slug `gemini` **não existe** na tabela `modulos` (que só existe na v6) — o módulo nunca aparece na listagem de módulos do CMS. |
| **Cobertura de testes** | **Nenhum** teste para o módulo em `v5/tests` ou `v6/tests` (verificado por `grep -rni "gemini" v5/tests v6/tests` → vazio). |

---

## 3. Riscos de dados (migração de schema)

| Risco | Detalhe | Mitigação na v7 |
|---|---|---|
| Chave em `configurations` | O valor pode existir em instalações v5/v6 já em produção | Migration que extraia o valor para o `.env`/vault, com depreciação da linha |
| Chave no histórico Git | O literal está versionado | **Rotacionar a chave** antes de qualquer deploy |
| Chave no `activity_log` | Registros antigos contêm o valor em claro | Limpeza (`UPDATE`/`DELETE`) das linhas afetadas após a rotação |
| Chave em logs de acesso HTTP | `?key=` na query string | Rotacionar + remover o padrão da query string |
| `mask_request` / `mask_description` | Textos mascarados por IA podem ter sido gerados com um modelo diferente do atual | Recalcular sob demanda (job) ou versionar o prompt |
| `order = 70` vs `71` | A v5 usa `order = 70` para `gemini_api_key`; a v6 usa `71` | Ajustar na migration para não colidir com outras configurações da aba "Opções" |

---

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

1. A **geração de conteúdo com IA** foi descontinuada de propósito na v6? Qual foi o motivo
   (custo, política de dados, decisão de produto)? — *sem resposta no repositório*.
2. Se a functionality for reativada na v7, ela deve ficar **exclusivamente no CMS** (com
   autenticação) ou continuar acessível ao portal?
3. Qual o **orçamento mensal** de tokens da API do Gemini e qual o limite de requisições
   por município?
4. O mascaramento por IA é **obrigatório** (compliance) ou apenas um *melhoria* sobre a
   máscara determinística? Se obrigatório, qual o SLA de resposta aceitável?
5. Com o mascaramento assíncrono (fila), o que o relatório público deve mostrar enquanto o
   job não concluiu — o texto original, o texto determinístico ou um aviso?
6. A chave pode ser gerenciada por município (multi-tenant) ou é única por instalação?
7. Os textos gerados pela IA **precisam** passar por aprovação humana antes da publicação?
   (Na v5, o conteúdo é apenas preenchido no formulário; o operador ainda salva manualmente.)
8. Qual o modelo desejado: `gemini-2.0-flash` (geração, como na v5) ou `gemini-2.5-flash`
   (mascaramento, como na v5)? Os dois modelos devem ser unificados?

---

## 5. Regressão v5 -> v6

> Verificação executada com
> `grep -rn "gemini" v6/app v6/routes v6/database v6/resources` (32 ocorrências,
> 8 arquivos) e confronto item a item com a v5 (39 ocorrências, 13 arquivos).

### 5.1 O que **existe** na v6

| Artefato | Caminho na v6 | Situação |
|---|---|---|
| Trait de mascaramento com IA | `v6/app/Http/Traits/DataMaskTrait.php` | **Mantido** — cópia da v5; o `diff` mostra apenas **2 regex** diferentes (padrão de "matrícula funcional" e a lista de termos de gênero/sexualidade). O bloco Gemini (`maskTextWithAi`) é **byte a byte idêntico**. |
| Ponte no controller do e-SIC | `v6/app/Http/Controllers/Portal/InformationController.php:113`, `:321`, `:389` | **Mantido** — `MaskRequestWithGeminiAi()` idêntico. |
| Ponte no controller da Ouvidoria | `v6/app/Http/Controllers/Portal/OmbudsmanController.php:165` | **Mantido** — `$this->maskTextWithAi($request->description ?? '')`. |
| Seeder de metadados da chave | `v6/database/seeds/AddToConfigurationsSeederGeminiKey.php` | **NOVO na v6** — `INSERT` se ausente / `UPDATE` dos metadados se presente; `order = 71` (a v5 usa `70`). |
| Seeder do valor da chave | `v6/database/seeds/AddToConfigurationsGeminiApiKeyValue.php` | **Mantido e corrigido** — agora só grava o literal se `empty($config->value)`; na v5 o `UPDATE` era **incondicional** (bug C4). O literal continua versionado (bug C3 **não** foi corrigido). |
| Seeder principal | `v6/database/seeds/ConfigurationsTableSeeder.php:1108-1120` | **NOVO na v6** — `Configuration::updateOrCreate(['key' => 'gemini_api_key'], [...])`. |
| Orquestração | `v6/database/seeds/ExtrasSeeder.php:34-35` | **Mantida** — chama os dois seeders acima. |
| Campo na tela de Configurações | `v6/resources/views/prefecture/configurations/configuracoes/form.blade.php:157` | **Mantido** — o filtro da aba "Opções" inclui `$item->key === 'gemini_api_key'`. |
| Linha no banco | tabela `configurations` do container v6 | **Mantida** — `id = 74`, `order = 71`, `value` **vazia** (`length = 0`), `category_configurations_id = 1`. |

### 5.2 O que **NÃO existe** na v6 (removido)

| Artefato da v5 | Caminho na v5 | Status na v6 |
|---|---|---|
| Controller de geração | `v5/app/Http/Controllers/Prefecture/GeminiController.php` | **REMOVIDO** — não há nenhum arquivo `GeminiController.php` em `v6/app/Http/Controllers/Prefecture/`. |
| Form Request de geração | `v5/app/Http/Requests/GeminiPromptRequest.php` | **REMOVIDO** — não há nenhum arquivo `GeminiPromptRequest.php` em `v6/app/Http/Requests/`. |
| Rota `gemini.ia` | `v5/routes/web.php:203` | **REMOVIDA** — `grep -rn "gemini" v6/routes` → **nenhuma ocorrência**. |
| Modal da IA | `v5/resources/views/prefecture/contents/components/ia-modal/index.blade.php` | **REMOVIDO** — o diretório `v6/resources/views/prefecture/contents/components/ia-modal` não existe. |
| JS da IA | `v5/public/assets/js/custom/utilities/helpers/ia-modal.js` | **REMOVIDO** — `v6/public/assets/js/custom/utilities/helpers/` **não** contém `ia-modal.js`. |
| Botão "Preencher com IA" | `v5/resources/views/prefecture/contents/noticias/form.blade.php:61` e `.../publicacoes/create.blade.php:11` | **REMOVIDO** — nenhuma ocorrência de `abrirModalIA` em `v6/resources`. |
| Chamada `route('gemini.ia')` | `v5/resources/views/prefecture/contents/noticias/form.blade.php:292`, `v5/resources/views/prefecture/contents/publicacoes/form.blade.php:158` | **REMOVIDA**. |
| Seeder `UpdateConfigurationSeeder` (trecho Gemini) | `v5/database/seeds/UpdateConfigurationSeeder.php:15-31` | **REMOVIDO** — sem equivalente na v6 (substituído por `AddToConfigurationsSeederGeminiKey` + `ConfigurationsTableSeeder`). |
| Seeder `ConfigurationOrderSeeder` (trecho Gemini) | `v5/database/seeds/ConfigurationOrderSeeder.php:504-518` | **REMOVIDO** — sem equivalente na v6. |
| `GEMINI_API_*` no `.env` | `v5/.env`, `v5/.env.example` | **NÃO EXISTE** em nenhuma das duas versões (v5 nem v6). A v6 também **não** declara `GEMINI_API_KEY` / `GEMINI_API_URL` / `GEMINI_API_MODEL` no `.env` nem no `.env.example`. |

### 5.3 Resumo da regressão

```
v5 (funcionalidade completa)                 v6 (funcionalidade parcial)
─────────────────────────────────────────    ───────────────────────────────────────────────
[Caminho A] Geração de conteúdo com IA  ──►  REMOVIDO (controller, request, rota, modal, JS)
[Caminho B] Mascaramento LGPD com IA    ──►  MANTIDO (cópia byte a byte do DataMaskTrait)
Credencial em configurations            ──►  MANTIDA (seeders corrigidos / novos)
Tela de configuração da chave           ──►  MANTIDA (campo sem nenhum consumidor!)
```

**Consequência operacional na v6:** a tela de Configurações **continua pedindo a chave
da API do Gemini** (`v6/resources/views/prefecture/configurations/configuracoes/form.blade.php:157`)
e o seeder **continua populando** `configurations.gemini_api_key`, mas **não existe mais
nenhuma functionality de redação que a consuma**. A chave só é aproveitada pelo
mascaramento LGPD do e-SIC/Ouvidoria. Operadores podem, portanto, configurar uma
credencial achando que ela habilita a redação com IA — **e não habilita**.

**Motivo da remoção:** não documentado no repositório (`[NÃO VERIFICADO]`). As hipóteses
mais prováveis são custo/quota da API e a decisão de não enviar conteúdo institucional
a um serviço externo.

---

## 6. Itens que existem na v5 e foram removidos na v6 (resumo)

1. `GeminiController` (geração de título + corpo).
2. `GeminiPromptRequest`.
3. Rota `POST /preencher-formulario` (`gemini.ia`).
4. Componente Blade `prefecture.contents.components.ia-modal.index`.
5. `v5/public/assets/js/custom/utilities/helpers/ia-modal.js`.
6. Botão "Preencher com IA" em Notícias e Publicações.
7. `UpdateConfigurationSeeder` / `ConfigurationOrderSeeder` (trechos Gemini).

## 7. Itens que existem na v6 e não existiam na v5

1. `v6/database/seeds/AddToConfigurationsSeederGeminiKey.php` — seeder dedicado.
2. `v6/database/seeds/ConfigurationsTableSeeder.php:1108-1120` — declaração da chave por
   `updateOrCreate`.
3. Correção do seeder de valor (não sobrescreve a chave já cadastrada).

---

## 8. Referências

- `v5/app/Http/Controllers/Prefecture/GeminiController.php:11-89`
- `v5/app/Http/Requests/GeminiPromptRequest.php`
- `v5/app/Http/Traits/DataMaskTrait.php:213-287`
- `v5/app/Http/Controllers/Portal/InformationController.php:112, 320, 388-391`
- `v5/app/Http/Controllers/Portal/OmbudsmanController.php:180`
- `v5/app/Http/Controllers/Prefecture/ConfigurationController.php:200, 315`
- `v5/database/seeds/AddToConfigurationsGeminiApiKeyValue.php`
- `v5/database/seeds/UpdateConfigurationSeeder.php:15-31`
- `v5/database/seeds/ConfigurationOrderSeeder.php:504-518`
- `v5/database/seeds/ExtrasSeeder.php:35`
- `v5/routes/web.php:203`
- `v5/resources/views/prefecture/contents/noticias/form.blade.php:61, 273, 287-294`
- `v5/resources/views/prefecture/contents/publicacoes/form.blade.php:135, 153-161`
- `v5/resources/views/prefecture/contents/publicacoes/create.blade.php:11`
- `v5/resources/views/prefecture/contents/components/ia-modal/index.blade.php`
- `v5/resources/views/prefecture/configurations/configuracoes/form.blade.php:274`
- `v5/public/assets/js/custom/utilities/helpers/ia-modal.js`
- `v5/.env.example`
- `v6/app/Http/Traits/DataMaskTrait.php`
- `v6/app/Http/Controllers/Portal/InformationController.php:113, 321, 389`
- `v6/app/Http/Controllers/Portal/OmbudsmanController.php:165`
- `v6/app/Http/Model/Prefecture/Configuration.php`
- `v6/database/seeds/AddToConfigurationsGeminiApiKeyValue.php`
- `v6/database/seeds/AddToConfigurationsSeederGeminiKey.php`
- `v6/database/seeds/ConfigurationsTableSeeder.php:1108-1120`
- `v6/database/seeds/ExtrasSeeder.php:34-35`
- `v6/resources/views/prefecture/configurations/configuracoes/form.blade.php:157`
