# Licitantes Sancionados — Resumo Executivo

> **Versão analisada:** `v5` (módulo **ausente** na `v6`)
> **Slug do módulo:** `licitantes-sancionados` (rota do portal) / `licitante-sancionado` (prefixo de permissões do CMS)
> **Natureza:** Ambos (CMS `/cms/licitantes-sancionados` + Portal `/licitantes-sancionados`)
> **Estado:** apenas v5 — a v6 não possui model, controller, request, migrations, views, JS nem rotas deste módulo

---

## 1. Objetivo

O módulo **Licitantes Sancionados** publica a relação de empresas e pessoas
fornecedoras da prefeitura que estão **sancionadas** (impedidas de participar de
licitação) por um órgão controlador. O operador do CMS cadastra, para cada registro,
o **ano** da sanção, o **número do processo/sanção**, a **razão social ou nome**,
o **CPF/CNPJ**, o **período de vigência** (início e fim) e os **documentos** que
comprovam a sanção (PDFs). O cidadão acessa a página pública `/licitantes-sancionados`,
filtra por ano, texto livre (nome ou CPF/CNPJ) e intervalo de datas, e abre a ficha
de cada sancionado para baixar os documentos anexos. Serve ao município como
instrumento de transparência e de defesa do contraditório em processos licitatórios,
permitindo que qualquer licitante consulte antecipadamente se está ou não impedido
de participar.

## 2. Efeito / Impacto

| Faixa | Efeito |
|---|---|
| Cidadão | Página pública com lista paginada (15 por página), filtros por ano, busca por nome/CPF-CNPJ, intervalo de datas de início da sanção, ficha individual e download dos PDFs anexos. |
| Prefeitura (CMS) | CRUD completo com anexo múltiplo de PDFs (limite de 10 MB por arquivo), exclusão individual (soft delete), permissões por prefixo `licitante-sancionado.*` e item de menu "Sancionados" dentro do submenu "Licitações". |
| Transparência e legalidade | Publica sanções vigentes e encerradas, permitindo a qualquer licitante impugnar a sua inclusão antes de participar de um certame. |
| Operação técnica | Tabela `licitantes_sancionados` com `SoftDeletes`; view SQL `licitantes_sancionados_view` usada pelo DataTable do CMS; anexos gravados em `storage/app/public/docs/` e referenciados por JSON na coluna `arquivo`. |

## 3. Comportamento

- Cada registro é **obrigatório** em todos os campos textuais: `ano` (inteiro entre
  1900 e 2100), `numero_sancao` (até 50 caracteres), `nome_razao_social` (até 255),
  `cpf_cnpj` (até 18) e as datas `inicio_sancao` e `fim_sancao` (obrigatórias, formato
  `dd/mm/aaaa` na entrada).
- O **período de vigência** é sempre preenchido: não existe sanção "indeterminada" nem
  bloqueio por data de expiração — o filtro do portal é por **data de início** da
  sanção, e registros expirados continuam publicados.
- Os **anexos são opcionais** no modelo (`arquivo` é `nullable`), apesar de a mensagem
  de erro `arquivo.required` existir no request (ver `08-GAPS-E-DIVIDAS.md`, G3).
  Quando presentes, são vários PDFs, cada um com descrição livre (`arquivo_desc`).
- A coluna `arquivo` guarda um **JSON** no formato
  `[{"path":"docs/xxxx.pdf","description":"..."}]`; o download usa `url('storage/' . $path)`.
- Na edição, arquivos novos são **somados** aos existentes; a remoção de um anexo
  individual (`DELETE .../{id}/arquivo/{index}`) reescreve o JSON, mas **não apaga o
  arquivo físico** do storage.
- A exclusão do registro é *soft delete* (coluna `deleted_at`); tanto a view SQL do
  CMS quanto a query Eloquent do portal filtram `deleted_at IS NULL`.
- O `slug` público é gerado automaticamente pelo model: `str_slug(nome_razao_social . '-' . id)`
  no evento `created` e **recalculado** no evento `updating` — renomear o sancionado
  **muda a URL pública** do registro.
- O portal **não** possui exportação (XLSX/PDF/CSV) nem rota de API; apenas paginação.
- O CMS **não** registra log de atividade (`activity()`) para este módulo, ao contrário
  de `ContentController`, `ContractController` e outros.

## 4. Como o Usuário Interage

**Operador (CMS, `/cms/licitantes-sancionados`)**

1. Menu AdminLTE "Licitações" → "Sancionados" (visível apenas para quem tem
   `licitante-sancionado.index`). A listagem é um **DataTable carregado por AJAX** em
   `/cms/datatable/licitante-sancionado`, alimentado pela view SQL
   `licitantes_sancionados_view` (colunas: Ano, Nº Sanção, Nome/Razão Social, CPF/CNPJ,
   Início, Fim). Botões de Excel, PDF, CSV e TXT são habilitados pela função global
   `runDatatables`.
2. Botão "Cadastrar" (visível com `licitante-sancionado.create`) abre o formulário com
   máscara de CPF/CNPJ (numérico ou alfanumérico) e máscara de data `br-data`.
3. O bloco "Adicionar Arquivo" insere dinamicamente inputs `arquivo[]` (accept `.pdf`)
   e `arquivo_desc[]`; já enviados aparecem listados com download, visualizar e excluir
   (chamada AJAX `DELETE`).
4. A coluna "Ações" traz editar/excluir por linha, condicionados às permissões
   `licitante-sancionado.edit` e `licitante-sancionado.delete`.

**Cidadão (Portal, `/licitantes-sancionados`)**

1. Cabeçalho "Licitantes Sancionados", card "Filtros" (Ano, Buscar por Nome ou
   CPF/CNPJ, Início da Sanção — De / Até) e botões "Consultar" / "Limpar campos".
2. Tabela com os registros e link "Detalhes" por linha → ficha com Ano, Número da
   Sanção, Nome/Razão Social, CPF/CNPJ, Início, Fim e o card "Arquivos para Download".
3. Paginação com total de registros e links de paginação.

## 5. Detalhes e Particularidades Técnicas

- Camadas: `Route` → `check.site.manutention` (portal) / `check.logged` +
  `check.route.permission` (CMS) → `Portal\LicitantesSancionadosController` ou
  `Prefecture\LicitanteSancionadoController` → `LicitanteSancionadoRequest` →
  `App\Http\Model\Prefecture\LicitanteSancionado` → tabela `licitantes_sancionados`.
- O CMS **não** usa o model para listar: o DataTable lê a **view SQL**
  `licitantes_sancionados_view` diretamente via `DB::table()` em
  `Prefecture\DatatableController@get_licitante_sancionado`.
- A view SQL **não expõe a coluna `slug`** (apenas `value` = `row_number()`), o que é
  coerente com o CMS, mas significa que a listagem pública vem do model, não da view.
- O portal monta a view dinamicamente: `portal.layouts.{template}.licitacao.sancionados.*`,
  onde `{template}` é o valor de `configurations.layout` (`modelo1`..`modelo6` no banco
  v5). As views foram replicadas **uma a uma** em cada template.
- A busca do portal usa `whereRaw` com `translate()` do PostgreSQL e o helper
  `clean_slug()` (`v5/app/Helpers/functions.php:780`) — os dois lados da comparação
  usam tabelas de transliteração **diferentes** (ver `08-GAPS-E-DIVIDAS.md`, G2).
- Anexos: `FileTrait::saveUpload($file, 'docs', ...)` grava em `storage/app/public/docs/`
  com nome aleatório (`uniqid` + `str_random(5)`), e o valor persistido é
  `"docs/{$path}"`.
- Dependências: `laracasts/flash` (mensagens), `yajra/laravel-datatables` (DataTable do
  CMS), `laravelcollective/html` (`Form::open`/`Form::model`), `cartalyst/sentinel`
  (permissões), `diglactic/laravel-breadcrumbs` (migalhas do portal).

## 6. Estrutura de Dados

| Objeto | Tipo | Observação |
|---|---|---|
| `licitantes_sancionados` | tabela | 11 colunas; PK `id`; `slug` nullable, **sem índice único** |
| `licitantes_sancionados_view` | view SQL | `row_number()` + colunas de negócio; filtra `deleted_at IS NULL` |
| `resources` | tabela (v5) | **não existe** registro com slug `licitante-sancionado` — o módulo não é administrável por perfil |

## 7. Como Testar

Cenários manuais: criar sanção com e sem anexo, editar mantendo/acrescentando anexos,
remover anexo pela lista, excluir registro e confirmar que some do CMS e do portal,
filtrar o portal por ano/texto/datas e abrir a ficha por `slug`. Detalhamento em
`07-TESTES.md`.

## 8. Migração para Laravel Moderno

Eloquent enxuto com `casts` para `date`, Form Request com as mesmas regras (inclusive a
conversão `dd/mm/aaaa` → `Y-m-d`), Policy no lugar do middleware `check.route.permission`,
índice único em `slug`, query Eloquent paginada no lugar da view SQL e um
`SanctionedBidderResource` para a API. Detalhamento em `06-MIGRACAO-MODERNA.md`.

## 9. Resumo Geral

Licitantes Sancionados é um módulo pequeno e de escopo bem delimitado, existente
**apenas na v5**: um CRUD de sanções a fornecedores com anexo de PDFs, publicado no
portal com filtros e paginação. Não há API, não há exportação no portal, não há log de
atividade e não há entrada de menu no portal — o acesso público é por URL direta ou
por link editorial. O banco v5 está com a tabela **vazia** e o slug é reconstruído por
duas rotinas **divergentes** (helper `str_slug` no model e `translate()` do PostgreSQL
na migration), o que produz slugs diferentes para o mesmo nome conforme a origem do
registro. A permissão `licitante-sancionado` **não está cadastrada** na tabela
`resources`, então, na prática, o módulo só é utilizável por `super-admin`. A v6
removeu o módulo inteiro: nem tabela, nem view, nem controller, nem rota, nem
permissão. Ao reconstruir, priorize corrigir o slug (índice único + gerador único),
normalizar a busca do portal e criar a entrada de `resources`/modulo correspondente.

---

## Índice do dossiê

| Arquivo | Conteúdo |
|---|---|
| `00-RESUMO.md` | Este documento |
| `01-ARQUITETURA.md` | Camadas, classes e o diagrama de fluxo |
| `02-MODELAGEM.md` | Estrutura de dados detalhada (tabela, view SQL, migrations) |
| `03-ROTAS-E-CONTROLADORES.md` | Rotas, controllers, request, permissões |
| `04-VIEWS-E-UX.md` | Telas CMS/portal, JS, assets, templates |
| `05-REGRAS-E-PORTABILIDADE.md` | Regras não óbvias, armadilhas e acoplamentos |
| `06-MIGRACAO-MODERNA.md` | Plano de reconstrução em Laravel moderno (schema, model, request, policy, endpoints, resources) |
| `07-TESTES.md` | Estratégia e casos de teste |
| `08-GAPS-E-DIVIDAS.md` | Pendências, riscos e comparativo v5 → v6 |
| `09-MIGRACAO-FRONTEND-E-DADOS.md` | Complemento da 06: views Blade modernas, fila/cache/storage, riscos de equivalência e checklist de migração de dados |
