# Licitantes Sancionados — Arquitetura

> Escopo: **apenas v5**. A v6 não possui nenhum componente deste módulo.

---

## 1. Visão geral das camadas

O módulo não usa repository, service ou API. São dois fluxos independentes que
convergem na mesma tabela:

```
CMS (autenticado)                                 Portal (público)
─────────────────────────────────────            ─────────────────────────────────────
Route /cms/licitantes-sancionados                Route /licitantes-sancionados
        │                                                │
        ▼                                                ▼
check.logged + check.route.permission            check.site.manutention
        │                                                │
        ▼                                                ▼
Prefecture\LicitanteSancionadoController         Portal\LicitantesSancionadosController
        │                                                │
        ├─ index  ─► view CMS (datatable.js)             ├─ index ─► query Eloquent paginada
        ├─ create ─► form                                │              + filtros
        ├─ store ─► LicitanteSancionadoRequest           │              + view do template ativo
        ├─ edit / update                                  └─ show  ─► LicitanteSancionado::where('slug')
        └─ destroy / destroyArquivo                               │
                 │                                                 │
                 ▼                                                 ▼
        App\Http\Model\Prefecture\LicitanteSancionado ───► tabela licitantes_sancionados
                 │                                                 ▲
                 │                                                 │
                 └──► DB::table('licitantes_sancionados_view') ────┘
                        (somente o DataTable do CMS)
```

## 2. Classes envolvidas

| Classe | Caminho | Papel |
|---|---|---|
| `LicitanteSancionadoController` | `v5/app/Http/Controllers/Prefecture/LicitanteSancionadoController.php` | CRUD do CMS; valida, grava anexos, redireciona |
| `LicitantesSancionadosController` | `v5/app/Http/Controllers/Portal/LicitantesSancionadosController.php` | Listagem pública com filtros e ficha por `slug` |
| `Admin\Controller` (base do controller do portal) | `v5/app/Http/Controllers/Admin/Controller.php` | Fornece `getTemplateActived()` (escolhe `modelo1`..`modelo6`) |
| `Prefecture\Controller` (base do controller do CMS) | `v5/app/Http/Controllers/Prefecture/Controller.php` | Aplica `check.route.permission` no construtor |
| `DatatableController` | `v5/app/Http/Controllers/Prefecture/DatatableController.php` | Serve o JSON do DataTable do CMS a partir da view SQL |
| `LicitanteSancionadoRequest` | `v5/app/Http/Requests/LicitanteSancionadoRequest.php` | Validação e mensagens do formulário |
| `LicitanteSancionado` (model) | `v5/app/Http/Model/Prefecture/LicitanteSancionado.php` | Eloquent + `SoftDeletes` + geração de `slug` |
| `FileTrait` | `v5/app/Http/Traits/FileTrait.php` | `saveUpload()` para gravar PDFs em `storage/app/public/docs` |
| `Configuration` (model) | `v5/app/Http/Model/Prefecture/Configuration.php` | Lido por `getTemplateActived()` para escolher o template do portal |

## 3. Diagrama de fluxo (portal)

```
GET /licitantes-sancionados?ano=2026&busca=joao&dt_inicio=01/01/2026
        │
        ▼
Portal\LicitantesSancionadosController@index
        │
        ├─ $query = LicitanteSancionado::query()
        │      ->orderBy('ano','desc')->orderBy('nome_razao_social')
        │
        ├─ when($request->ano)      → where('ano', $request->ano)
        ├─ when($request->busca)     → whereRaw(translate(lower(nome_razao_social)) ILIKE '%termo%')
        │                                OR whereRaw(translate(lower(cpf_cnpj))      ILIKE '%termo%')
        ├─ when($request->dt_inicio) → whereDate('inicio_sancao','>=', ...)
        └─ when($request->dt_fim)    → whereDate('inicio_sancao','<=', ...)
        │
        ├─ $anos   = selectRaw('ano')->distinct()->orderBy('ano','desc')->pluck('ano')
        ├─ $result = $query->paginate(15)
        │
        ▼
view: portal.layouts.{configurations.layout}.licitacao.sancionados.index
        ├─ @include(...licitacao.sancionados.filtro)   → form de filtros
        └─ @include(portal.components.sancionados.table) → tabela + paginação
```

## 4. Diagrama de fluxo (CMS)

```
GET /cms/licitantes-sancionados
        │  check.logged  (rota)
        │  check.route.permission (construtor de Prefecture\Controller)
        ▼
LicitanteSancionadoController@index
        └─ view('prefecture.licitantes-sancionados.index')
                 └─ <table id="tabela_licitante-sancionado">  (tbody preenchido por JS)

GET /cms/datatable/licitante-sancionado   (basepro.php:422)
        │  nome: datatable.licitante-sancionado
        │  ⚠ SEM middleware check.logged nem check.route.permission
        ▼
DatatableController@get_licitante_sancionado
        ├─ $sancionados = DB::table('licitantes_sancionados_view')->get()
        ├─ struct_views($sancionados, 'licitante-sancionado')
        │     └─ injeta edit/delete/active/inactivate por has_access()
        └─ DataTables::of(...)->make(true)   → JSON

POST /cms/licitantes-sancionados  (store)
        ▼
LicitanteSancionadoRequest  → rules() + messages()
        ▼
prepareData($request)  → Y-m-d nas datas
        ▼
saveUpload($file,'docs',null,null,false)  → storage/app/public/docs/<aleatório>.pdf
        ▼
LicitanteSancionado::create($data)  → boot(): created → slug = str_slug(nome-id) → save()
        ▼
flash('Registro cadastrado com sucesso.')->success()
redirect()->route('licitante-sancionado.index')
```

## 5. Middleware aplicado

| Middleware | Onde | Efeito |
|---|---|---|
| `check.site.manutention` | grupo de `v5/routes/web.php:5` (abrangendo a linha 158) | Se `pagina_em_manutencao == 1` e o visitante não está logado, devolve `portal.manutencao.index` |
| `check.logged` | `v5/routes/basepro.php:347-349` (index, create, edit) | Redireciona para `dashboard.index` se não houver sessão Sentinel |
| `check.route.permission` | construtor de `Prefecture\Controller` (aplicado a **todas** as ações do CMS, inclusive `store`, `update`, `destroy`) | Para usuário não `super-admin`, resolve a permissão a partir do **nome da rota** e do segmento da URL; sem permissão → `flash(...)->warning()` + `redirect()->back()` |

Rota do portal (`web.php:158-159`) **não** usa `check.logged` nem
`check.route.permission`: é pública.

## 6. Dependências externas (composer)

Todas em `v5/composer.json`:

| Pacote | Versão | Uso no módulo |
|---|---|---|
| `laravel/framework` | `5.5.*` | Eloquent, validação, roteamento, `str_slug`, `str_random` |
| `cartalyst/sentinel` | `2.0.*` | `Sentinel::check()`, `hasAnyAccess()`, `inRole()` (via `has_access()`) |
| `yajra/laravel-datatables` | `^1.5` | `DataTables::of(...)->make(true)` no `DatatableController` |
| `laracasts/flash` | `3.0.*` | `flash('...')->success()/->error()` nas respostas do CMS |
| `laravelcollective/html` | `^5.5` | `Form::open()`, `Form::model()`, `Form::close()` nos formulários |
| `diglactic/laravel-breadcrumbs` | `^4.0` | `Breadcrumbs::view(...)` nas views do portal; registro em `v5/routes/breadcrumbs.php` |
| `jeroennoten/laravel-adminlte` | `1.22` | Layout do CMS (`admin.layouts.app` → `adminlte::page`) |
| `intervention/image` | `^2.7` | Indireto: `FileTrait` usa `compress()` (desativado neste módulo, pois `saveUpload` é chamado com `$compress = false`) |

## 7. Onde o módulo encosta em outros módulos

| Dependência | Onde | Observação |
|---|---|---|
| `MenuServiceProvider` | `v5/app/Providers/MenuServiceProvider.php:106-108` | Cria o item "Sancionados" dentro do submenu "Licitações" (case `licitacao`), condicionado a `has_access(['licitante-sancionado.index'])` |
| `check.route.permission` | `v5/app/Http/Middleware/CheckRoutePermission.php` | Converte o **nome da rota** e o **segmento da URL** em slugs de permissão |
| `resolve_permission_aliases()` | `v5/app/Library/Helper/appHelpers.php:44` | Mapa de aliases: só cobre `obra-paralisada`/`obras-paralisadas`/`categorias-obras`; **não** cobre `licitante-sancionado` |
| `configurations.layout` | `v5/app/Http/Controllers/Admin/Controller.php:39-48` | Define qual template do portal renderiza as views |
| `resources` (tabela) | `v5/database/migrations/2018_03_16_190404_create_resources_table.php` | Deveria conter o slug `licitante-sancionado`; **não contém** |

## 8. Ausências relevantes da arquitetura

- **Sem API** — não há rota correspondente em `v5/routes/api.php` nem controller de
  API para este módulo (o diretório `v5/app/Http/Controllers/` contém apenas
  `Admin`, `Portal`, `Prefecture` e `Swagger`).
- **Sem service/repository** — a lógica de filtros está inline no controller do
  portal; a listagem do CMS está inline no `DatatableController`.
- **Sem export no portal** — ao contrário de `TransparenciaController` (que aceita
  `?type=`), o módulo de licitantes sancionados não gera XLSX/PDF/CSV.
- **Sem log de atividade** — nenhum `activity()->...->log(...)` no controller do CMS.
- **Sem menu no portal** — não há entrada em `menu_items`/`links` para
  `/licitantes-sancionados` no banco v5; o acesso público é por URL direta.

## 9. Referências de código

```php
// v5/app/Http/Controllers/Portal/LicitantesSancionadosController.php:11-49
public function index(Request $request)
{
    $title = 'Licitantes Sancionados';

    $query = LicitanteSancionado::query()->orderBy('ano', 'desc')->orderBy('nome_razao_social');

    $query->when($request->ano, function ($q) use ($request) {
        $q->where('ano', $request->ano);
    });
    // ... demais filtros ...
    $result = $query->paginate(15);

    return view(
        'portal.layouts.' . $this->getTemplateActived() . '.licitacao.sancionados.index',
        compact('title', 'result', 'anos', 'anoSelecionado', 'busca', 'dtInicio', 'dtFim')
    );
}
```

```php
// v5/app/Http/Model/Prefecture/LicitanteSancionado.php:30-40
protected static function boot()
{
    parent::boot();

    static::created(function ($model) {
        $slug = str_slug($model->nome_razao_social . '-' . $model->id);
        $model->slug = $slug;
        $model->save();
    });

    static::updating(function ($model) {
        $model->slug = str_slug($model->nome_razao_social . '-' . $model->id);
    });
}
```
