# Licitações Antigas — Regras de Negócio e Portabilidade

---

## 1. Regras de negócio (observáveis)

| # | Regra | Onde está implementada |
|---|---|---|
| R1 | O acervo legado vive **exclusivamente** na tabela `biddings_legacys`; nenhuma query do módulo toca `biddings`. | `v6/app/Http/Controllers/Portal/BiddingOldController.php` |
| R2 | Não há recorte temporal "até 2018" no código: registros com `data` posterior a 2018, se existissem, apareceriam normalmente. O rótulo é apenas de navegação. | ausência de filtro no controller |
| R3 | A ordenação padrão é por `data` decrescente (na v5 era por número do edital, extração via regex). | `BiddingOldController.php:46-47` |
| R4 | A paginação é fixa em 10 registros por página. | `BiddingOldController.php:77` |
| R5 | Todos os filtros são opcionais e se combinam por AND. | `BiddingOldController.php:62-75` |
| R6 | O filtro de ano usa `whereYear('data', ...)` — extração do ano da coluna `date`. | `BiddingOldController.php:72-74` |
| R7 | A busca textual é acento-insensível e case-insensível: normaliza o termo com `transliterator_transliterate('Any-Latin; Latin-ASCII', ...)` e normaliza o banco com `translate(lower(informacoes), ...)` seguido de `ilike`. | `BiddingOldController.php:68-70` |
| R8 | A lista de anos disponíveis vem de uma agregação SQL crua sobre `biddings_legacys` (agrupando pelo alias `ano`). | `BiddingOldController.php:57` |
| R9 | Modalidade e secretaria são selecionadas por **ID**; o model legado não define relações para os catálogos. | `BiddingOldController.php:54-55,62-70` |
| R10 | Detalhe inexistente devolve a view de erro `admin.errors.conteudo-nao-encontrado` (não é um 404 HTTP). | `BiddingOldController.php:93-96` |
| R11 | Os anexos só aparecem como link se `file_exist(url(...))` for verdadeiro; caso contrário o bloco imprime "Arquivos inexistentes". | `.../licitacao-antiga/show.blade.php` |
| R12 | O corpo do aviso é HTML renderizado sem escape (`{!! !!}`) — o conteúdo é HTML por design (editais antigos). | `.../licitacao-antiga/show.blade.php` |
| R13 | O módulo é desativável no CMS apenas como chave da tabela `modulos` (`status`); o status **não** é lido pelo controller do portal. | `v6/app/Providers/ModulosServiceProvider.php:22-24` |

## 2. Acoplamentos

### 2.1 Dependências de saída (o módulo depende de)

| Alvo | Tipo | Observação |
|---|---|---|
| `configurations.key = 'layout'` | banco | define qual template renderiza as views (`Admin\Controller::getTemplateActived()`). |
| `configurations.key = 'licitacoes_old'` | banco (leitura no módulo **novo**) | `v6/app/Http/Controllers/Portal/BiddingsController.php:72` — a chave não existe na base de referência (só `licitacoes_url`), então `$oldBidding` é `null`. |
| `modulos.id = 20` | banco | habilitação do slug; `menu_orders` não possui linhas para o módulo. |
| `biddings_legacys_modalities` / `biddings_legacys_jurisdictional` | banco | catálogos usados nos filtros — **inexistentes** na base de referência. |
| `v6/app/Helpers/functions.php` | código | `translateMonth()` (linha 93) e `file_exist()` (linha 267). |
| Módulo `licitacoes` | navegação | botões cruzados entre as duas listagens. |

### 2.2 Dependências de entrada (outros módulos dependem deste)

- O widget de licitações do Dashboard (`v6/app/Services/Dashboard/Widgets/LicitacoesWidget.php`)
  **não** consulta o acervo legado (busca por `Legacy` no arquivo não retorna
  ocorrências).
- `v6/routes/breadcrumbs.php` não tem entrada específica do legado.

## 3. Armadilhas técnicas

| # | Armadilha | Impacto |
|---|---|---|
| A1 | **Tabelas de catálogo ausentes.** `ModalitiesBiddingsLegacys` e `JurisdictionalBiddingsLegacys` apontam para tabelas que nenhuma migration cria. | `pluck()` lança `QueryException` → listagem quebra (HTTP 500) em qualquer base sem essas tabelas. |
| A2 | **`DB::select` cru com `GROUP BY` sobre alias.** `GROUP BY ano` só é válido no PostgreSQL. | Quebra em MySQL/SQLite; migração v7 precisa reescrever. |
| A3 | **`whereRaw` com `translate()` e literal de acentuação.** Depende do `translate` do PostgreSQL e de `ext-intl` no PHP. | Não portável; a string de tradução é duplicada em forma e tem tamanho fixo — se a lista de caracteres de origem/destino divergirem em tamanho, o Postgres trunca silenciosamente. |
| A4 | **`file_exist(url(...))`.** `url()` devolve uma URL (`http://host/...`), e `file_exists()` sobre uma URL é sempre `false`. | O bloco de anexos sempre mostra "Arquivos inexistentes" (a menos que o servidor sirva URLs como caminhos locais). |
| A5 | **`{!! $bidding->informacoes !!}`.** HTML do banco renderizado sem escape. | XSS armazenado: qualquer conteúdo HTML injetado na base é executado no navegador do visitante. |
| A6 | **Tipo do parâmetro `{id}`.** `show(int $id)` sem `where('id','[0-9]+')` na rota. | `/licitacoes-ate-2018/abc` gera erro de coerção (500) em vez de 404. |
| A7 | **Ressolução dinâmica de view por layout.** `portal.layouts.{layout}.licitacao.licitacao-antiga.*` só existe em `modelo1..modelo6`. | Layout `default` ou `newsbit` → "View not found" (500). |
| A8 | **`$links` sempre `true`.** A variável é passada à view mas nunca pode ser `false`. | Código morto na view. |
| A9 | **Coluna `arquivo` no `$fillable` sem existir na tabela.** | `BiddingsLegacys` declara `arquivo` no fillable; inserções com esse campo falham (columna inexistente). |
| A10 | **`title` no `$fillable` de `ArchivesBiddingsLegacys` sem coluna.** | `$archive->title` é sempre `null` → a view cai no `or $archive->name`. |
| A11 | **Duplicação de view por layout.** 18 arquivos Blade idênticos para o mesmo conteúdo (6 modelos × 3 arquivos). | Correção precisa ser replicada 6× (é o que aconteceu na v6 com outros módulos). |
| A12 | **Duplicação de código entre v5 e v6.** Controller, models e views legadas são praticamente idênticos nas duas versões. | Custo de manutenção duplicado. |
| A13 | **Sem índice secundário em `biddings_legacys.data`.** A listagem ordena por `data` e o filtro de ano usa `whereYear`. | Varredura sequencial da tabela em acervos grandes. |
| A14 | **`with('archives')` + `@foreach` de anexos na listagem.** | A listagem carrega os anexos de todos os registros da página, embora só a listagem de títulos seja usada (eager loading excessivo). |
| A15 | **Herança cruzada portal↔CMS.** `Portal\BiddingOldController extends Admin\Controller`. | Controller público herda de base administrativa; mistura de responsabilidades. |
| A16 | **Erro 404 devolvido como view.** `show()` não retorna `abort(404)`. | Resposta HTTP 200 com página de erro (ruim para SEO e para testes). |

## 4. Código duplicado / copiado entre v5 e v6

| Arquivo | Situação |
|---|---|
| `v5/app/Http/Controllers/Portal/BiddingOldController.php` vs `v6/app/Http/Controllers/Portal/BiddingOldController.php` | **Idênticos exceto a ordenação**: v5 usa `orderByRaw` com extração de números do campo `numero`; v6 usa `orderBy('data','desc')`. |
| `v5/app/Http/Model/Prefecture/{BiddingsLegacys,ArchivesBiddingsLegacys,JurisdictionalBiddingsLegacys,ModalitiesBiddingsLegacys}.php` | **Byte a byte idênticos** aos da v6 (diff vazio). |
| `v5/database/migrations/2018_11_21_121507_create_biddings_legacys_table.php` e `2018_11_21_121854_create_archives_biddings_legacy_table.php` | **Byte a byte idênticos** aos da v6. |
| `v5/resources/views/portal/layouts/modelo1..6/licitacao/licitacao-antiga/*` | **Idênticos** aos da v6 (diff recursivo vazio em modelo1..modelo6). |
| Rotas | v5: `v5/routes/web.php:145-146`; v6: `v6/routes/web.php:179-180` — mesma declaração, apenas deslocada. |

Motivo provável da mudança de ordenação: o `orderByRaw` da v5 dependia de
regex sobre `numero` (campo `varchar` livre, com formatos heterogêneos) e foi
simplificado para ordenar por data — mais previsível, mas perde a ordenação
"por número de edital" que o usuário esperava em arquivos históricos.

## 5. Onde a v6 divergiu da v5 (resumo)

| Aspecto | v5 | v6 |
|---|---|---|
| Ordenação da listagem | `orderByRaw` por número extraído via regex | `orderBy('data','desc')` |
| Demais arquivos do módulo | — | idênticos |
| Rotas | `v5/routes/web.php:145-146` | `v6/routes/web.php:179-180` |

## 6. Regras de UI observadas

| # | Regra | Verificado em |
|---|---|---|
| U1 | A listagem mostra apenas `titulo` e `data` de cada registro — o número do edital, o CNPJ, a modalidade e a secretaria **não** são exibidos nos resultados. | `v6/resources/views/portal/layouts/modelo1/licitacao/licitacao-antiga/index.blade.php` |
| U2 | O cabeçalho da listagem é "Licitações" (não "Licitações Antigas"), diferenciando-se apenas pelo botão de navegação. | `v6/app/Http/Controllers/Portal/BiddingOldController.php:53` |
| U3 | O detalhe exibe a data por extenso em português (`translateMonth`). | `v6/resources/views/portal/layouts/modelo1/licitacao/licitacao-antiga/show.blade.php` |
| U4 | A mensagem de lista vazia é "Nenhuma informação encontrada!". | `v6/resources/views/portal/layouts/modelo1/licitacao/licitacao-antiga/index.blade.php` |
| U5 | A mensagem para anexo indisponível é "Arquivos inexistentes" (exibida dentro do laço, portanto pode se repetir por anexo). | `v6/resources/views/portal/layouts/modelo1/licitacao/licitacao-antiga/show.blade.php` |
| U6 | A paginação preserva os filtros aplicados (`Request::except('page')`). | `v6/resources/views/portal/layouts/modelo1/licitacao/licitacao-antiga/index.blade.php` |

## 7. Recomendações de portabilidade (priorizadas)

| Prioridade | Ação | Onde age |
|---|---|---|
| P1 | Eliminar a dependência de `DB::select` cru e de `translate()`/`whereRaw`: usar query builder + índice `unaccent`/`pg_trgm`. | `v6/app/Http/Controllers/Portal/BiddingOldController.php:57,68-70` |
| P1 | Sanitizar o HTML do aviso na renderização. | `v6/resources/views/portal/layouts/modelo1/licitacao/licitacao-antiga/show.blade.php` |
| P1 | Criar as tabelas de catálogo ou derivar modalidade/secretaria de tabelas existentes (o módulo novo usa `bidding_modalities` e `contents` da categoria `secretarias`). | `v6/app/Http/Controllers/Portal/BiddingOldController.php:54-55` |
| P2 | Trocar a verificação de arquivo por `Storage::exists()`. | `v6/resources/views/portal/layouts/modelo1/licitacao/licitacao-antiga/show.blade.php` |
| P2 | Introduzir route model binding + `whereNumber` para eliminar o 500 em id inválido. | `v6/routes/web.php:180` |
| P2 | Componentizar as 18 views duplicadas em 6 modelos. | `v6/resources/views/portal/layouts/modelo1/licitacao/licitacao-antiga/` |
| P3 | Definir se `cnpj`/`numero` devem ser exibidos (dado de terceiro). | `v6/resources/views/portal/layouts/modelo1/licitacao/licitacao-antiga/show.blade.php` |
| P3 | Introduzir cache de listagem e índice em `data`. | tabela `biddings_legacys` |
