# Gemini — Modelagem de Dados

> O módulo **não possui tabela própria**. Toda a persistência está na tabela genérica
> `configurations`, compartilhada com as demais configurações do CMS.

---

## 1. Tabela `configurations` (v5)

Estrutura obtida de `\d configurations` na base `prefeituras_v5` (PostgreSQL 10):

| Coluna | Tipo | Nullable | Default | Papel para o módulo |
|---|---|---|---|---|
| `id` | `integer` | not null | `nextval('configurations_id_seq')` | PK — `id = 77` para `gemini_api_key` na v5 |
| `title` | `character varying(191)` | not null | — | `"Chave da API do Gemini"` |
| `description` | `text` | yes | — | texto explicativo exibido como tooltip |
| `small_description` | `text` | yes | — | legenda abaixo do campo (não usada nesta chave) |
| `placeholder` | `character varying(191)` | yes | — | `"Digite a chave da API do Gemini"` |
| `key` | `character varying(191)` | not null | — | **identificador funcional** = `gemini_api_key` |
| `value` | `text` | yes | — | **a chave da API em texto claro** (ou vazio) |
| `type` | `character varying(191)` | not null | — | `text` (campo de texto simples) |
| `order` | `integer` | yes | — | `70` na v5 / `71` na v6 — posição na aba "Opções" |
| `max_input` | `integer` | yes | — | `255` — `maxlength` do `<input>` |
| `min_input` | `integer` | yes | — | `0` — `minlength` do `<input>` |
| `category_configurations_id` | `integer` | not null | — | FK → `category_configurations(id)`; valor `1` (`Site`) |
| `notnull` | `boolean` | yes | — | `false` — o campo **não** é obrigatório |
| `status_create` | `boolean` | yes | — | `true` |
| `created_at` | `timestamp(0) without time zone` | yes | — | vazio na linha |
| `updated_at` | `timestamp(0) without time zone` | yes | — | vazio na linha |
| `deleted_at` | `timestamp(0) without time zone` | yes | — | `SoftDeletes` do model |

Índices e constraints:

```text
"configurations_pkey" PRIMARY KEY, btree (id)
"configurations_category_configurations_id_foreign" FOREIGN KEY (category_configurations_id)
    REFERENCES category_configurations(id)
```

> Não há índice único em `configurations.key`. A unicidade é garantida apenas pelo código
> dos seeders (`->where('key', 'gemini_api_key')->exists()`), o que permite linhas
> duplicadas se alguém inserir manualmente.

---

## 2. Tabela `category_configurations` (referenciada)

| Coluna | Tipo | Nullable | Default |
|---|---|---|---|
| `id` | `integer` | not null | `nextval('category_configurations_id_seq')` |
| `name` | `character varying(191)` | not null | — |
| `slug` | `character varying(191)` | not null | — |
| `status_create` | `boolean` | yes | — |
| `created_at` / `updated_at` / `deleted_at` | `timestamp(0)` | yes | — |

Categorias existentes: `1 Site`, `2 Prefeitura`, `3 Temas`, `4 Outros`, `5 Layout`.
`gemini_api_key` aponta para `id = 1` (**Site**).

---

## 3. Linha `gemini_api_key` — metadados (sem expor o valor)

Consulta executada em modo **READ-ONLY** na base v5:

```sql
SELECT id, "order", title, placeholder, key,
       length(value) AS value_len, max_input, min_input,
       "notnull", type, status_create, category_configurations_id,
       left(description, 70) AS descr, created_at, updated_at
  FROM configurations
 WHERE key = 'gemini_api_key';
```

| Campo | v5 (`prefeituras_v5`) | v6 (base `prefeituras_v5` do container v6) |
|---|---|---|
| `id` | 77 | 74 |
| `order` | 70 | 71 |
| `title` | Chave da API do Gemini | Chave da API do Gemini |
| `placeholder` | Digite a chave da API do Gemini | idem |
| `key` | `gemini_api_key` | `gemini_api_key` |
| `length(value)` | **0** (vazia) | **0** (vazia) |
| `max_input` / `min_input` | 255 / 0 | 255 / 0 |
| `notnull` | false | false |
| `type` | text | text |
| `status_create` | true | true |
| `category_configurations_id` | 1 (Site) | 1 (Site) |
| `description` | "Chave de acesso para a API do Gemini, necessária para utilizar a IA para preenchimento de formulário." | idem |
| `created_at` / `updated_at` | nulos | nulos |

> **O valor da chave nunca é reproduzido neste dossiê.** O campo `value` é texto claro;
> nas bases inspecionadas está vazio (`length = 0`), porque o valor é inserido
> manualmente pelo operador ou pelo seeder (ver item 5).

---

## 4. Tabelas consumidoras (saídas producedas pelo caminho B)

O mascaramento não cria registros novos; ele **preenche colunas** de duas tabelas.

### 4.1 `informations_requests` (e-SIC)

| Coluna | Tipo | Uso |
|---|---|---|
| `id` | `integer` PK | — |
| `request` | `text` not null | texto **original** do cidadão (LGPD: fica no banco) |
| `mask_request` | `text` | **saída do `maskTextWithAi()`** — texto mascarado exibido nos relatórios |
| `protocol` | `character varying(191)` not null | — |
| `persons_id` | `integer` | FK → `persons(id)` |
| `status_id` | `integer` | FK → `statuses(id)` |
| `ip` / `browser` | `character varying(191)` not null | — |
| `created_at` / `updated_at` / `deleted_at` | `timestamp(0)` | — |

Gravado em `v5/app/Http/Controllers/Portal/InformationController.php:112` (formulário
clássico) e `:320` (envio Ajax).

### 4.2 `ombudsman_requests` (Ouvidoria)

| Coluna | Tipo | Uso |
|---|---|---|
| `id` | `integer` PK | — |
| `description` | `text` not null | texto **original** |
| `mask_description` | `text` | **saída do `maskTextWithAi()`** |
| `protocol` | `character varying(191)` not null, **UNIQUE** | — |
| `cpf_cnpj`, `gender`, `education_level`, `birth_date`, `address`, `number`, `neighborhood`, `complement`, `reference_point` |various| dados estruturados do pedido (não passam pela IA) |
| `anonymous` | `boolean` not null default `false` | quando `true`, os campos pessoais são gravados como `null` |
| `modality` | `character varying(191)` | `"Anônimo"` / `"Sigiloso"` — tratado por `maskName()` |

Gravado em `v5/app/Http/Controllers/Portal/OmbudsmanController.php:180`.

### 4.3 `activity_log` — exposição colateral da chave

| Coluna | Tipo | Risco |
|---|---|---|
| `properties` | `text` | **contém o valor da configuração em texto claro** |

`v5/app/Http/Controllers/Prefecture/ConfigurationController.php:200` e `:315` gravam:

```php
->withProperty('Detalhe', 'Foi atualizada uma configuração: Id:' . $configurationModel->id . ',Chave:' . $configurationModel->key . ',Valor:' . $configurationModel->value . ...)
```

Ou seja, **cada edição da tela de Configurações grava a chave da API do Gemini no log de
atividades em texto claro**. Tabela e colunas confirmadas via `\d activity_log`.

---

## 5. Seeds que criam/populam a chave

| Seeder (v5) | O que faz | Condição |
|---|---|---|
| `v5/database/seeds/ConfigurationOrderSeeder.php:504-518` | `INSERT` da linha com `order = 70`, `value = ''` | apenas se `where('key','gemini_api_key')->first() == null` |
| `v5/database/seeds/UpdateConfigurationSeeder.php:15-31` | `updateOrInsert` com os mesmos campos (`order = 70`, `value = ''`) | apenas se `!exists()` |
| `v5/database/seeds/AddToConfigurationsGeminiApiKeyValue.php` | `UPDATE` de `configurations.value` com uma **chave literal hardcoded no repositório** | **incondicional** — sobrescreve sempre o valor cadastrado pelo operador |
| `v5/database/seeds/ExtrasSeeder.php:35` | `$this->call(AddToConfigurationsGeminiApiKeyValue::class);` | sempre executado dentro do `run()` |

Trecho real de `v5/database/seeds/AddToConfigurationsGeminiApiKeyValue.php` (o **valor** da
chave foi **omitido** neste dossiê — ele existe no arquivo, em texto claro, e **não deve
ser reproduzido**):

```php
DB::table('configurations')
    ->where('key', 'gemini_api_key')
    ->update([
        'value' => /* literal omitido por segurança — ver o arquivo-fonte */
    ]);
```

> **Bug:** rodar `ExtrasSeeder` **sobrescreve** a chave que o operador configurou na tela
> de Configurações. A v6 corrigiu isso (ver `08-GAPS-E-DIVIDAS.md`).

### 5.1 Seeds na v6 (referência para a migração)

| Seeder (v6) | Diferença para a v5 |
|---|---|
| `v6/database/seeds/AddToConfigurationsSeederGeminiKey.php` | **novo**; `INSERT` se ausente, `UPDATE` dos metadados se presente; `order = 71` |
| `v6/database/seeds/AddToConfigurationsGeminiApiKeyValue.php` | agora só grava o valor **se `empty($config->value)`** |
| `v6/database/seeds/ConfigurationsTableSeeder.php:1108-1120` | `Configuration::updateOrCreate(['key' => 'gemini_api_key'], [...])` |
| `v6/database/seeds/ExtrasSeeder.php:34-35` | chama os dois seeders acima |

---

## 6. Model Eloquent

`v5/app/Http/Model/Prefecture/Configuration.php`:

```php
namespace App\Prefecture;

class Configuration extends Model
{
    use SoftDeletes;
    protected $table = 'configurations';

    protected $fillable = [
        'key', 'value', 'status_create', 'type', 'title',
        'category_configurations_id', 'max_input', 'min_input',
        'notnull', 'order', 'description', 'placeholder', 'small_description'
    ];

    protected $primaryKey = 'id';

    public function categoryConfiguration()
    {
        return $this->hasOne(CategoryConfiguration::class, 'id', 'category_configurations_id');
    }
}
```

Observações:

- O namespace `App\Prefecture` vem do **classmap** `"app/"` do `v5/composer.json`
  (o `psr-4` `App\ → app/` não cobriria `app/Http/Model/Prefecture`).
- **Não há casts** — `value`, `notnull` e `status_create` são lidos como string crua.
  `notnull = 'false'` (string) e `notnull = false` (boolean) coexistem no banco.
- **Não há `hidden`/`$casts` para `value`** → o valor da chave é serializado em qualquer
  `toArray()`/`toJson()` do model, e aparece no `<input type="text">` da tela de
  Configurações em claro.

---

## 7. Views SQL

**Nenhuma.** O módulo não cria nem consome `CREATE VIEW`. Todo o acesso é Eloquent
(`Configuration::where('key', ...)`) sobre a tabela base.

---

## 8. Migrations

**Nenhuma migration cria ou altera estrutura** para este módulo — `configurations` já
existia e é reutilizada. As mudanças são feitas por *seeders* (ver item 5).

> Observação: a tabela `modulos` (que existe **apenas na v6** —
> `v6/database/migrations/2024_11_01_150206_create_modulos_table.php`) **não tem**
> equivalente na v5. Portanto o slug `gemini` **não existe** em `modulos` em nenhuma
> das versões, e o módulo não aparece na listagem de módulos do CMS.

---

## 9. Referências

- `v5/app/Http/Model/Prefecture/Configuration.php`
- `v5/database/seeds/ConfigurationOrderSeeder.php:504-518`
- `v5/database/seeds/UpdateConfigurationSeeder.php:15-31`
- `v5/database/seeds/AddToConfigurationsGeminiApiKeyValue.php`
- `v5/database/seeds/ExtrasSeeder.php:35`
- `v6/database/seeds/AddToConfigurationsSeederGeminiKey.php`
- `v6/database/seeds/AddToConfigurationsGeminiApiKeyValue.php`
