# PLATAFORMA — Arquitetura Geral

> Escopo: arquitetura transversal que sustenta todos os módulos.
> Todas as afirmações citam arquivo real do repositório.

---

## 1. Visão das 3 camadas

A aplicação é **monolítica** (um único deploy, um único banco) e se divide
logicamente em três camadas de entrada:

| Camada | Arquivo de rotas | Prefixo | Middleware | Nº de rotas |
|---|---|---|---|---|
| Portal público | `v6/routes/web.php` | `/` | `web`, `check.site.manutention` | 228 |
| CMS | `v6/routes/basepro.php` | `/cms` | `web`, `check.logged`, `check.route.permission` | 695 |
| API dados abertos | `v6/routes/api.php` | `/api/v1` | `api` (throttle 60/1) | 5 |

Total registrado por `php artisan route:list`: **928 rotas**.
Fonte: contagem direta sobre a saída do artisan no container `v6_6f22e1bd-app-1`.

Arquivos de rota auxiliares:

- `v6/routes/service_letter.php` — **não** é carregado pelo `RouteServiceProvider`;
  é `require`d dentro do grupo `cms` em `v6/routes/basepro.php:512`.
- `v6/routes/prefecture.php` — arquivo **inteiramente comentado** (só há blocos
  `//Route::group(...)` comentados). É resíduo.
- `v6/routes/breadcrumbs.php` — consumido pelo pacote
  `diglactic/laravel-breadcrumbs` via `v6/config/breadcrumbs.php:35`
  (`'files' => base_path('routes/breadcrumbs.php')`), **não** pelo Laravel.
- `v6/routes/channels.php` — carregado pelo `BroadcastServiceProvider`.
- `v6/routes/console.php` — Closure de console, padrão Laravel.

---

## 2. Fluxo da requisição

### 2.1 Portal

```mermaid
graph TD
    U[Visitante] --> MW1[web group: EncryptCookies, StartSession, VerifyCsrfToken, SubstituteBindings]
    MW1 --> MW2[check.site.manutention<br/>v6/app/Http/Middleware/CheckSiteManutention.php]
    MW2 --> R{route em web.php}
    R --> CTRL[App\Http\Controllers\Portal\*]
    CTRL --> TRAIT[GetInformationsPrefectureTrait::getTemplateActived()<br/>resolve portal.layouts.&lt;modelo&gt;]
    TRAIT --> MODEL[App\Prefecture\Content / CategoryContent / Configuration]
    MODEL --> VIEW[view portal.layouts.&lt;modelo&gt;.*.blade.php]
    VIEW --> RESP[HTML]
    CTRL --> RESP2[JSON / Excel / PDF / redirect]
```

### 2.2 CMS

```mermaid
graph TD
    OP[Operador] --> MW1[web group]
    MW1 --> MW2[check.logged<br/>CheckLogged.php → redireciona a dashboard.index]
    MW2 --> MW3[check.route.permission<br/>CheckRoutePermission.php → Sentinel hasAnyAccess]
    MW3 --> R{route em basepro.php}
    R --> CTRL[App\Http\Controllers\Prefecture\* ou Admin\*]
    CTRL --> CTOR[Prefecture\Controller::__construct<br/>$this->middleware('check.route.permission')]
    CTOR --> FR[App\Http\Requests\*<br/>FormRequest: rules() + validation()]
    FR --> REPO[App\Repositories\Eloquent\Prefecture\*<br/>bind em RepositoryServiceProvider]
    REPO --> MODEL[App\Prefecture\* ou App\Http\Model\Prefecture\*]
    MODEL --> VIEW[view prefecture.*.blade.php sob admin.layouts.app]
```

### 2.3 API

```mermaid
graph TD
    D[Consumidor] --> TH[throttle:60,1]
    TH --> R{route em api.php}
    R --> CTRL[App\Http\Controllers\Swagger\V1\Portal\*]
    CTRL --> MODEL[App\Prefecture\Content / CategoryContent]
    MODEL --> RES[App\Http\Resources\Swagger\V1\Portal\*Resource]
    RES --> JSON[JSON + OpenAPI annotations]
```

### 2.4 Ordem real das camadas (o que existe e o que não)

| Camada | Existe? | Evidência |
|---|---|---|
| Route | Sim | `v6/routes/*.php` (7 arquivos) |
| Middleware | Sim | `v6/app/Http/Middleware/` (9 classes), 4 registradas em `v6/app/Http/Kernel.php:57-63` |
| Controller | Sim | 158 controllers em `v6/app/Http/Controllers/` (Admin 20, Portal 48, Prefecture 83, Swagger 7) |
| FormRequest | Parcial | 78 classes em `v6/app/Http/Requests/`; o restante valida com `Validator::make()` inline no controller |
| Repository/Service | Parcial | 66 classes em `v6/app/Repositories/` (35 contratos + 31 implementações Eloquent); o `RepositoryServiceProvider` faz bind de ~35 interfaces. Services: 28 arquivos em `v6/app/Services/` (dashboard, PDF, transparência) |
| Model | Sim | 131 models em `v6/app/Http/Model/` |
| View/Resource | Sim | 1.659 Blades em `v6/resources/views/`; 21 API Resources em `v6/app/Http/Resources/` |

**Antipadrão estrutural:** o `RepositoryServiceProvider` liga apenas os
repositórios "novos". A maioria dos controllers do CMS resolve o model
diretamente (ex.: `v6/app/Http/Controllers/Prefecture/ContentController.php`
usa `App\Prefecture\Content` em vez de `IContentRepository`). Isso significa que
a camada de repositório é **parcial e opcional** — um divisor de água para a
reconstrução.

---

## 3. Mapa de diretórios

```
v6/app/
├── Console/Commands/        7 comandos (swagger:generate, CompressImages, MakeRepository…)
├── Enum/Prefecture/         2 enums (DynamicForms: AskStylesEnum, AskTypesEnum)
├── Exceptions/              Handler.php (redireciona TokenMismatch → logout, InvalidArgument → error.404)
├── Exports/                 8 exportadores Maatwebsite Excel (FromView) + DynamicForms/
├── Helpers/functions.php    1.132 linhas, 27 funções globais (mask, slug_fix, getActualLayout…)
├── Http/
│   ├── Components/Prefecture/
│   ├── Controllers/         Admin | Portal | Prefecture | Swagger
│   ├── Filter/              FilterBiddings, FilterSearchAllSite
│   ├── Middleware/          9 classes
│   ├── Model/               Admin (24) | Prefecture (92) — namespace NÃO segue o diretório
│   ├── Presenters/          10 presenters (Laracasts)
│   ├── Requests/            78 Form Requests
│   ├── Resources/           21 API Resources
│   ├── Traits/              7 traits (AppTrait, FileTrait, NewsTrait, GetInformationsPrefectureTrait…)
│   └── Views/Composers/
├── Library/
│   ├── Helper/appHelpers.php   função global has_access()
│   └── Menu/Filters/SentinelFilter.php
├── Observers/ActivityObserver.php  (spatie/laravel-activitylog)
├── Providers/              13 providers
├── Repositories/
│   ├── Contracts/          35 interfaces (IContentRepository, IBiddingRepository…)
│   └── Eloquent/Prefecture/ 31 implementações
├── Rules/                   7 regras (ConditionalRequiredSlug, Validate_cpf, Validate_cnpj…) + Rules/Swagger/V1
├── Services/                Dashboard/ (widgets), Pdf/ (PdfConverter, ImageProcessor), FPdfService, PortalTransparenciaService
└── Validators/CustomValidator.php
```

**Tamanhos (arquivos .php) por pasta — v6:**

| Pasta | Arquivos |
|---|---|
| `v6/app/Http` | 419 |
| `v6/app/Repositories` | 66 |
| `v6/app/Services` | 28 |
| `v6/app/Providers` | 13 |
| `v6/app/Exports` | 8 |
| `v6/app/Rules` | 7 |
| `v6/app/Console` | 7 |
| `v6/app/Library` | 2 |
| `v6/app/Enum` | 2 |
| `v6/app/Helpers` | 1 |
| `v6/app/Observers` | 1 |
| `v6/app/Validators` | 1 |

Total de arquivos `.php` em `app/`, `database/`, `routes/`: **1.034** (v5: 998).

---

## 4. Autoload — o ponto mais crítico

`v6/composer.json` (bloco `autoload`):

```json
"autoload": {
    "classmap": [
        "database/seeds",
        "database/factories",
        "app/"
    ],
    "psr-4": {
        "App\\": "app/"
    }
}
```

Consequência prática: **o PSR-4 resolve `App\Http\Model\Prefecture\Content` a partir
de `app/Http/Model/Prefecture/Content.php`, mas o classmap sobrepõe isso e faz o
PHP aceitar qualquer namespace dentro de `app/`.** Por isso os models existem
com **namespacesincoerentes** com o diretório físico:

| Namespace declarado | Nº de arquivos | Diretório físico |
|---|---|---|
| `App\Prefecture` | 41 | `v6/app/Http/Model/Prefecture/` |
| `App\Http\Model\Prefecture` | 40 | `v6/app/Http/Model/Prefecture/` |
| `App\Admin` | 22 | `v6/app/Http/Model/Admin/` |
| `App\Prefecture\ServiceLetter` | 10 | `v6/app/Http/Model/Prefecture/ServiceLetter/` |
| `App\Models` | 8 | `v6/app/Http/Model/` |
| `App\Prefecture\DynamicForms` | 7 | `v6/app/Http/Model/Prefecture/DynamicForms/` |
| `App\Http\Model\Admin` | 2 | `v6/app/Http/Model/Admin/` |

Não há colisão de classe (nenhum FQCN duplicado foi encontrado em
`v6/app/Http/Model/`), mas o mesmo **diretório** hospeda 7 namespaces
diferentes. Caso mais extremo: os 8 models de ouvidoria
(`OmbudsmanRequest`, `OmbudsmanAnswer`, `OmbudsmanOrigin`, …) usam
`namespace App\Models;` apesar de estarem em
`v6/app/Http/Model/Prefecture/`.

O import no topo de `v6/app/Http/Controllers/Portal/PortalController.php`
mostra o sintoma:

```php
use App\Prefecture\Answer;
use App\Http\Model\Prefecture\Layout;
use App\Prefecture\Archive;
use App\Prefecture\Bidding;
use App\Prefecture\CategoryContent;
use App\Prefecture\Configuration;
```

Ou seja, `Configuration` e `CategoryContent` vêm de `App\Prefecture`, enquanto
`Layout` vem de `App\Http\Model\Prefecture` — **duas classes do mesmo diretório**.

**Impacto na v7:** é obrigatório remover o classmap e passar a 100% PSR-4
(`App\Portal\Models\…`, `App\Prefecture\Models\…`), senão o classmap gerado
automaticamente pelo `composer dump-autoload` continua mascarando erros de
namespace.

`v6/composer.json` também declara, em `autoload-dev`:

```json
"files": [
    "app/Helpers/functions.php"
]
```

**Cuidado:** as funções globais (`mask()`, `getActualLayout()`, `has_access()`)
só são carregadas em ambiente de **desenvolvimento/testes** (`autoload-dev`),
não em produção (`--no-dev`). Isso é um **bug latente**: em produção, qualquer
chamada a `getActualLayout()` (ex.: `v6/resources/views/components/weather-box.blade.php:111`)
estoura *undefined function*.

---

## 5. Providers

`v6/app/Providers/` — 13 classes. Registradas em `v6/config/app.php:193-202`
e `v6/config/app.php:176-177`.

| Provider | Papel |
|---|---|
| `AppServiceProvider` | Registra `ActivityObserver` no model `Activity`; `Schema::defaultStringLength(191)`; instala `CustomValidator` (CNPJ alfanumérico) como resolver global do `Validator`. |
| `AuthServiceProvider` | Declara `$policies = ['App\Model' => 'App\Policies\ModelPolicy']` — **policy fictícia**, `App\Policies\ModelPolicy` não existe no repositório. |
| `RouteServiceProvider` | Carrega `api.php` (prefixo `api`), `web.php` e `basepro.php` (grupo `web`, sem prefixo — o `/cms` está dentro do arquivo). `$namespace = 'App\Http\Controllers'`. |
| `EventServiceProvider` | `$listen` com o placeholder `'App\Events\Event' => ['App\Listeners\EventListener']` — **placeholder padrão do skeleton, sem listeners reais**. |
| `BroadcastServiceProvider` | `require base_path('routes/channels.php')`. |
| `RepositoryServiceProvider` | Bind das ~35 interfaces `App\Repositories\Contracts\*` para as implementações `App\Repositories\Eloquent\Prefecture\*`. |
| `ModulosServiceProvider` | `Cache::rememberForever('modulos', …)` → injeta o mapa `status` por slug em 23 views (home + sidebar dos 6 modelos, `prefecture.contents.noticias.form`, `admin.layouts.dashboard`). |
| `MenuServiceProvider` | Escuta `BuildingMenu` do AdminLTE e monta o menu lateral a partir de `menu_orders` + `modulos` + `has_access()`. |
| `BannerServiceProvider` / `FooterServiceProvider` / `ConfigHeaderServiceProvider` / `NewsMorePopularServiceProvider` | Injeção de dados compartilhados em views (banners, rodapé, cabeçalho, "mais lidas"). |
| `UniqueKeyDupleServiceProvider` | Constraint de chave única (par duplicado) — ver `v6/app/Rules/ConditionalRequiredSlug.php`. |

### 5.1 Nota crítica sobre `RouteServiceProvider`

`v6/app/Providers/RouteServiceProvider.php:80-89` mapeia `basepro.php` **sem
prefixo**, mas o próprio arquivo `basepro.php:35` abre o grupo
`Route::group(['prefix' => 'cms', 'before' => 'csrf'], …)`. Consequência: o
prefixo `/cms` está **hardcoded dentro do arquivo de rotas**, e não na
configuração do provider. A v7 deve mover esse prefixo para o provider.

---

## 6. Dependências de composer (arquitetura)

`v6/composer.json` (35 pacotes em `require`, 6 em `require-dev`).

**Infraestrutura obrigatória:**

| Pacote | Versão travada | Uso na arquitetura |
|---|---|---|
| `laravel/framework` | `5.5.*` | Core |
| `cartalyst/sentinel` | `2.0.*` | Autenticação + permissões (`Sentinel::check()`, `hasAnyAccess`, roles) |
| `jeroennoten/laravel-adminlte` | `1.22` | Layout do CMS + evento `BuildingMenu` |
| `yajra/laravel-datatables` | `^1.5` | Tabelas do CMS (`Prefecture\DatatableController`) |
| `maatwebsite/excel` | `^3.1` | Exportação XLSX (`App\Exports\*`, `FromView`) |
| `diglactic/laravel-breadcrumbs` | `^4.0` | Breadcrumbs (fork do pacote abandonado — ver `03-ROTAS-E-CONTROLADORES.md`) |
| `laracasts/presenter` | `^0.2.2` | 10 presenters (`ContentPresenter`, `LayoutPresenter`…) |
| `spatie/laravel-activitylog` | `^2.8` | Log de atividades (`activity_log` + `ActivityObserver`) |
| `phaza/laravel-postgis` | `3.3.*` | Driver PostGIS (declarado, mas **nenhuma coluna `geometry` foi encontrada** — ver `02-MODELAGEM.md`) |
| `laravelcollective/html` | `^5.5` | Facade `HTML::` (37 usos em Blades) — **abandonado** |
| `jadjoubran/laravel-angular` | `^0.3.0` | AngularJS 1.x no CMS (`v6/resources/views/admin/layouts/app.blade.php` carrega `angular.min.js`) |
| `intervention/image` | `^2.7` | Manipulação de imagem (galeria, banners) |
| `barryvdh/laravel-dompdf` + `dompdf/dompdf` | `^0.8.7` / `^0.8.3` | PDF (diário oficial, carta de serviços) |
| `tecnickcom/tcpdf` + `setasign/fpdi` | `^6.3` / `^2.3` | FPDF/TCPDF para os fluxos de PDF do diário |
| `simplesoftwareio/simple-qrcode` | `^2.0` | QR Code (selos) |
| `blueimp/jquery-file-upload` | `^9.22` | Upload múltiplo (galeria) |
| `intervention/image` + `rosell-dk/webp-convert` | `^2.7` / `^1.2` | Conversão WebP |
| `swiftmailer/swiftmailer` | `^6.0` | E-mail (contato, LAI, ouvidoria) — **abandonado** |
| `guzzlehttp/guzzle` | `^5.3` | HTTP client — **5.x EOL desde 2015** |
| `greggilbert/recaptcha` | `^2.2` | reCAPTCHA (formulários) |
| `laravellegends/pt-br-validator` | `^5.1` | Validações em pt-BR |
| `canducci/zipcode` | `1.*` | Busca de CEP |
| `laracasts/flash` | `3.0.*` | Mensagens flash (`flash('…')->success()`) |
| `laralabs/toaster` | `^2.0` | Notificações toast |
| `fideloper/proxy` | `~3.3` | Trusted proxies |
| `barryvdh/laravel-debugbar` | `^3.1` | dev |

---

## 7. Referências de código (trechos reais)

### 7.1 `v6/app/Http/Traits/GetInformationsPrefectureTrait.php:92-102`

```php
public function getTemplateActived()
{
    $templateActived = Configuration::where('key', 'layout')->first()->value;

    if (!View::exists('portal.layouts.' . $templateActived . '.home.index')) {
        DB::table('configurations')->where('key', 'pagina_em_manutencao')->update(['value' => 1]);
        abort(404, 'view_inexistente');
    }

    return $templateActived;
}
```

### 7.2 `v6/app/Http/Middleware/CheckRoutePermission.php:59-96` (trecho)

```php
$access = Route::getCurrentRoute()->getName();
$accessArr = explode('.', $access);
…
$open = array_where($accessArr, function ($value, $key) use ($openRoutes, $resourceRoutes) {
    if (in_array($value, $openRoutes) || in_array($value, $resourceRoutes)) {
        return true;
    }
});
```

**Armadilha:** a autorização é resolvida por **nome da rota**, não por
política. Renomear uma rota no CMS muda a permissão efetiva sem aviso.

### 7.3 `v6/app/Http/Kernel.php:57-63`

```php
'check.route.permission' => \App\Http\Middleware\CheckRoutePermission::class,
'check.site.manutention' => \App\Http\Middleware\CheckSiteManutention::class,
'active.landing'         => \App\Http\Middleware\ActiveLanding::class,
'check.logged'           => \App\Http\Middleware\CheckLogged::class,
```

Nenhuma dessas quatro é aplicada globalmente: todas são aplicadas por rota
ou por construtor de controller (36 controllers do CMS aplicam
`check.route.permission` no construtor — ver `03-ROTAS-E-CONTROLADORES.md`).
