# PLATAFORMA — Regras, Acoplamentos e Portabilidade

> Todas as contagens deste arquivo foram obtidas por `grep` real sobre o
> repositório. O comando e o escopo estão indicados em cada tabela.

---

## 1. Escopo das contagens

```bash
grep -rn -- "<padrão>" v6/app v6/database v6/routes v6/resources/views --include='*.php' | wc -l
```

Escopo: `app/`, `database/` (migrations + seeds + factories), `routes/` e
`resources/views/`. O `vendor/` foi excluído. Quando a métrica é
específica de `app/`, isso é indicado.

---

## 2. Inventário de padrões problemáticos (números reais)

### 2.1 SQL cru e expressões raw

| Padrão | v5 | v6 | Onde se concentra (v6) |
|---|---|---|---|
| `whereRaw(` | **35** | **35** | `Swagger/V1/Portal/NewController` (4), `OmbudsmanRequest` (3), `InformationsRequest` (3), `Portal/LegislationController` (3), `Portal/BuscaDiariasController` (3), `Swagger/V1/Portal/SecretariaController` (2), `Swagger/V1/Portal/PublicationController` (2), `Swagger/V1/Portal/OrgaoController` (2), `Swagger/V1/Portal/LegislationController` (2), `Portal/PublicationController` (2), `Http/Filter/FilterBiddings` (1), `Http/Filter/FilterSearchAllSite` (1), `Portal/ServiceLetterController` (1), `Portal/RequerimentoController` (1), `DiarioOficialRepository` (1) |
| `selectRaw(` | 5 | **8** | — |
| `orderByRaw(` | 7 | **2** | — |
| `havingRaw(` | 1 | **0** | — |
| `groupByRaw(` | 0 | **0** | — |
| `DB::raw(` | 18 | **9** | `Prefecture/ContractController` (3), `Prefecture/ProposalController` (2), `Dashboard/Widgets/OuvidoriaWidget` (4) |
| `DB::select(` | 14 | **17** | — |
| `->join(` | 4 | **6** | — |
| `->leftJoin(` | 2 | **0** | — |

**Leitura:** o volume de SQL cru **não cresceu** da v5 para a v6 — o
legado é antigo, não uma regressão. Mas a **localização** é o problema:
quase todo o `whereRaw` está na **camada de API/portal** (Swagger e
controllers de leitura), ou seja, em código que a v7 precisa reescrever do
zero. O SQL em `DB::raw` está concentrado em **3 arquivos** (licitações/
contratos/propostas) e no dashboard.

Exemplo real de `DB::raw` (`v6/app/Http/Controllers/Prefecture/ContractController.php:67`):

```php
p.id, concat('Lic.: ', b.number, ' | Data Public.: ', " . DB::raw("to_char(b.publication_edital_date, 'DD/MM/YYYY')") . ", ' | Valor: ', 'R$', p.value, ' - ', case when p2.name is null then p2.social_name else p2.name end) as name
```

Este é **SQL com concatenação e CASE dentro do PHP** — inoportável de
portar para outra engine e imposible de testear.

### 2.2 SQL inline em Blade

| Padrão | v5 | v6 |
|---|---|---|
| SQL real (`select … from`, `insert into`, `delete from`) em `.blade.php` | **0** | **0** |

A busca inicial por `select ` / `insert into` / `update ` / `delete from`
em Blades retornou **277 falsos positivos** em ambas as versões — todos
caso de `<select class="form-control">` do HTML, não SQL.

**Conclusão:** não há SQL inline em Blade. Este é um **ponto positivo** do
legado. O acoplamento de apresentação está no *modelo de dados* (views SQL),
não na camada de view.

### 2.3 Depuração esquecida

| Padrão | v5 | v6 | Observação |
|---|---|---|---|
| `dd(` (chamada real, regex com limite de palavra) | **14** | **12** | 6 ocorrências em Blades são **comentadas** (`{{-- {{ dd(…) }} --}}`) em `portal/layouts/modelo{1..6}/noticias/show.blade.php:39` |

Ocorrências ativas em `v6/app/`:

| Arquivo | Linha |
|---|---|
| `v6/app/Http/Controllers/Prefecture/ContentController.php` | 1294, 1303 |
| `v6/app/Http/Controllers/Prefecture/OmbudsmanAnswerController.php` | 230, 234 |
| `v6/app/Http/Controllers/Prefecture/OmbudsmanRequestController.php` | 220 |
| `v6/app/Repositories/Repository.php` | 67 |

O caso de `v6/app/Repositories/Repository.php:67` é o mais grave: está dentro
do `catch` de `upInsert()` — **qualquer falha ao salvar qualquer conteúdo do
CMS drena a requisição** e exibe a exceção crua ao operador.

```php
public function upInsert($request, int $id = null)
{
    try {
        $data = DB::transaction(function () use ($request, $id) {
            return $this->structureUpInsert($this->dataTrait($request));
        });
        return $this->getModel()->updateOrCreate(['id' => $id], $data);
    } catch (\Throwable $th) {
        dd($th);
    }
    return false;
}
```

### 2.4 N+1 / query dentro de loop

| Métrica | v5 | v6 |
|---|---|---|
| Chamadas a `->get()/all()/first()/count()/find()/pluck()` em até 3 linhas após um `foreach` | **22** | **23** |

(Métrica por heurística: `grep -rn "foreach" v6/app --include='*.php' -A3 | grep -cE '\->(get|all|first|count|find|pluck)\('`.)

Exemplo real em `v6/app/Http/Controllers/Prefecture/OmbudsmanRequestController.php`
e em `v6/app/Http/Controllers/Portal/OmbudsmanController.php:346-367`:

```php
->select('t.title', DB::raw('count(*) as total'))
```

### 2.5 Rotas dentro de controller

| Métrica | v5 | v6 |
|---|---|---|
| `Route::` em `app/` (fora de `routes/`) | **28** | **23** |
| `Route::` em `routes/` (declarações) | 578 | **620** |

Das 23 ocorrências em `app/` (v6), **19** são `Route::getCurrentRoute()` /
`Route::currentRouteName()` — usadas para decidir regras de validação:

| Arquivo | Linha | Uso |
|---|---|---|
| `v6/app/Http/Controllers/Admin/RoleController.php` | 213 | `unique:roles` condicional |
| `v6/app/Http/Controllers/Admin/UserController.php` | 358 | `unique:users` condicional |
| `v6/app/Http/Controllers/Portal/BiddingsController.php` | 101 | detecção de rota de exportação |
| `v6/app/Http/Middleware/CheckRoutePermission.php` | 59 | **autorização pelo nome da rota** |
| `v6/app/Http/Requests/BiddingRequest.php` | 33, 98 | índice `create`/`update` |
| `v6/app/Http/Requests/ConfigurationRequest.php` | 50, 103 | regra condicional |
| `v6/app/Http/Requests/ContentRequest.php` | 73, 100, 131, 159, 174, 281 | regras condicionais |
| `v6/app/Http/Requests/ManagementUnitRequest.php` | 40 | índice `create`/`update` |
| `v6/app/Http/Requests/MenuItemRequest.php` | 46, 59, 71 | limite de 6 itens |
| `v6/app/Http/Requests/ProponentRequest.php` | 74 | índice `create`/`update` |
| `v6/app/Http/Requests/UserRequest.php` | 41 | `hasAnyAccess` |

**Nenhuma** dessas chamadas cria rota dinamicamente — todas leem a rota
atual. Ainda assim, isso é **acoplamento de controller a naming de rota** e
precisa ser eliminado na v7 (usar `authorize()`, `Gate`, ou injetar a
"intenção" via FormRequest distinto para create/update).

### 2.6 Superglobais

| Padrão | v5 | v6 |
|---|---|---|
| `$_SESSION` | **0** | **0** |
| `$_GET` | 15 | 16 |
| `$_POST` | 2 | 0 |

**Boa notícia:** não há uso de `$_SESSION` em nenhuma das versões. O
`session()` helper do Laravel é usado 7× (v5) e 9× (v6) vezes em `app/`.

Os `$_GET` restantes estão em: `v6/app/Helpers/functions.php:959`
(`$_SERVER['HTTP_HOST']`, `$_SERVER['REQUEST_URI']`),
`v6/app/Repositories/Eloquent/Prefecture/StaticPage/StaticPageRepository.php:36`
e `v6/app/Rules/ConditionalRequiredSlug.php:39` (`$_SERVER['REQUEST_URI']`).
São acessos a `$_SERVER`, não a `$_GET` — o padrão "superglobal" do Laravel
não é praticado.

### 2.7 SQL em migrations

| Métrica | v5 | v6 |
|---|---|---|
| `DB::statement(` em `database/` | **339** | **382** |
| `DB::statement(` em `app/` | 0 | 0 |

Todo o SQL bruto do projeto está nas migrations — e é **exclusivamente** para
criar/alterar **views SQL** (382 ocorrências ≈ 126 migrations de view × ~3
statement cada: `CREATE`, `DROP`, `ALTER`).

### 2.8 Outros marcadores

| Padrão | v5 | v6 |
|---|---|---|
| `TODO` | 2 | **4** |
| `FIXME` | 0 | 0 |
| `dump(` / `var_dump(` / `die(` | 0 | 0 |
| `HTML::` em Blades (facade abandonada) | 37 | **37** |
| `csrf_field` / `@csrf` em Blades | — | **61** |

---
