# Obras — Gaps, Dívidas e Regressão v5 → v6

> Versão analisada: `v5`. Este módulo **existe apenas na v5**.

---

## 1. Bugs e erros conhecidos (presentes na v5)

### 1.1 Críticos

| # | Local | Descrição | Efeito |
|---|---|---|---|
| B1 | `Prefecture\Works\SaveRequest::prepareForValidation()` | `'id' => $this->route('obra')` — o parâmetro de rota do CMS é `{id}`, não `obra`. O `merge()` sobrescreve o `id` do formulário com `null`. | A regra `exists:works,id` **nunca** é exercitada. O `id` do formulário é descartado. |
| B2 | `Prefecture\Works\DeleteRequest` | Mesmo problema de `route('obra')` + `failedValidation` devolvendo 422. | O request **sempre** reprovaria. Por sorte **não é usado**: `WorkController@destroy` usa `Illuminate\Http\Request` cru. Código morto. |
| B3 | `WorkController@update()` | `Work::query()->updateOrCreate(['id' => $id], $this->attributes($request))` — se o `id` da URL não existir, **cria uma obra nova** com outro `id` (o `id` do array de busca não é aplicado ao `create`). | `PUT /cms/obras/999999` cria uma obra em vez de dar 404. |
| B4 | `WorkController@destroy()` | Usa `Request` cru, ignorando o `DeleteRequest` importado. | Sem validação de existência: id inválido cai no `catch (\Throwable)` e devolve 500 em vez de 404. |
| B5 | `WorkCategoryController@store()` | `dd($e);` dentro do `catch`. | Erro na criação de categoria **dumpa a exceção** em vez de exibir a mensagem de erro. |
| B6 | `Portal\WorksController::getNonAccentedILikeSearch()` | Interpola `$search` direto no SQL (`like '%$search%'`), sem binding nem escape; `prepareStringToSearch()` não remove aspas simples. | **SQL injection** via `?contract=` / `?edital=` no portal público. |
| B7 | `SaveRequest::prepareForValidation()` | `Carbon::createFromFormat('d/m/Y', ...)` sem try/catch. | Data em formato inesperado gera **500** em vez de mensagem de validação. |
| B8 | `WorkCategoryController@update()` | `updateOrCreate(['id' => $id], ['name' => ...])`. | Id inexistente **cria** uma categoria nova com outro id. |

### 1.2 Funcionais

| # | Local | Descrição | Efeito |
|---|---|---|---|
| B9 | `SaveRequest` / `form.blade.php` | A obrigatoriedade da **justificativa** para "Suspensa"/"Cancelada" existe **apenas no JavaScript** (`toggleJustification()`). | POST/PUT direto grava obra suspensa **sem justificativa**. A salvaguarda de transparência é contornável. |
| B10 | `SaveRequest` (`archive.*.file` → `max:2048`) × `form.blade.php` ("Tamanho máximo: 128MB") | Limite do servidor (2 MB) diverge do anunciado (128 MB). | Operador recebe erro de validação inesperado ao anexar arquivo entre 2 MB e 128 MB. |
| B11 | `Portal\WorksController::index()` | `paginate(10)` **antes** da verificação `Route::currentRouteName() == 'portal-obras.exportacao'`. | A exportação entrega **só os 10 registros da página 1**, não o conjunto filtrado. |
| B12 | `gerar-pdf-obras.js` | `element.paid_value` não existe (o PHP produz `value_paid`). | Coluna "Valor Pago" **vazia** no PDF. |
| B13 | `gerar-pdf-obras.js` | `doc.save('licitações.pdf')`. | Arquivo baixado com o nome de outro módulo. |
| B14 | `v5/resources/views/portal/layouts/modelo1/obras/show.blade.php` (e modelo2..modelo6) | Compara `status.name` com `'aberta'` / `'em andamento'` (minúsculas) enquanto o banco grava `Aberta` / `Em Andamento`. | As cores de situação **nunca** são aplicadas; tudo fica `text-secondary`. |
| B15 | `v5/resources/views/portal/components/obras/arquivos.blade.php` | Itera `$work->archives` — que **não** filtra por `work_archives.type`. | As **fotos da galeria** aparecem também na lista de "Arquivos para Download" (e de novo na galeria). |
| B16 | `script.js` (geocoding) | Em caso de falha, escreve a string `"não encontrado"` em `latitude`/`longitude`. | A string é **persistida** no banco e entra no `$forMap` do mapa (lat/long inválidos). |
| B17 | `WorkController@saveArchivesFromGallery()` | Grava o arquivo **antes** de criar `archives`/`work_archives`; o `DB::rollback()` não apaga o arquivo. | Imagem órfã no storage. |
| B18 | `WorkController@store()` / `@update()` | `storeArchives()` grava o arquivo dentro do bloco transacional, mas o disco não é transacional. | Arquivo órfão se o commit falhar. |
| B19 | `WorkController@edit()` | `Work::query()->find($id)` sem tratar `null`. | `GET /cms/obras/999999/edit` → erro 500. |
| B20 | `Portal\WorksController::indexShow()` | `validator()` sem `->fails()`/abort; `$work->archives` em objeto nulo. | `GET /obras/999999` → erro 500 (deveria ser 404). |
| B21 | `WorkCategoryController` (resource) | A rota `categorias-obras.show` existe, mas **`show()` não está implementado**. | GET na rota → erro 500. |
| B22 | `WorkCategoryController::create()` | Corpo vazio. | GET `/cms/categorias-obras/create` devolve resposta em branco. |
| B23 | `WorkCategoryController::store()` | `flash('Obra cadastrada com sucesso')` ao criar uma **categoria**. | Mensagem errada na tela. |
| B24 | `routes/basepro.php:538` | Grupo `galeria-obras.` sem `->name()` nas rotas; prefixo de URI `galerias-obras` ≠ prefixo de nome `galeria-obras.`. | Nomes de rota `galeria-obras.` (inválido) — `route()` não funciona com eles e o `CheckRoutePermission` perde a derivação por nome. |
| B25 | `WorkController@show($id)` | Método vazio e **sem rota** (o `Route::resource("obras", ...)` está comentado na linha 525). | Código morto. |
| B26 | `SaveRequest::prepareForValidation()` | `merge(['gallery' => ... ? 1 : 0])` **sempre** sobrescreve. | Um `PUT` sem o campo `gallery` (ou com valor ≠ `on`) **desativa** a galeria da obra. |
| B27 | `Portal\WorksController::index()` | `$forMap = (clone $result)->...->get()` **antes** do `paginate(10)`. | Carrega **todas** as obras com coordenadas em memória a cada visita ao portal. |
| B28 | Portal (tabela, `$forMap`, export, TXT) | `data_get($res,'status.name')` / `$model->workCategory->name` sem `with()`. | N+1 em listagem, mapa e exportações. |
| B29 | `resources/views/exports/works.blade.php` | `{{ $obra->value }}` sem formatação; `R$ {{ $obra->value }}` imprime o `varchar` cru. | Planilha com valores sem separador de milhar/decimal. |
| B30 | `gerarTxt()` | Não inclui descrição, previsão de conclusão, percentual nem justificativa; datas em `Y-m-d`. | TXT é um subconjunto do Excel. |
| B31 | `v5/resources/views/portal/components/obras/filtro.blade.php` | `toCleanFields()` não limpa o campo `edital`. | Filtro "limpar" é incompleto. |
| B32 | `v5/resources/views/portal/layouts/modelo5/obras/index.blade.php:17` | Referência a `$oldBidding`, variável **nunca enviada**. | Código morto (protegido por `isset()`). |
| B33 | `geo.js` | Camada de satélite do Google em **HTTP** (`http://{s}.google.com/vt/lyrs=s`). | Mixed content em páginas HTTPS; tiles podem ser bloqueados. |
| B34 | `view listagem_obras` | `INNER JOIN` em `work_categories` e `work_statuses`. | Obras com FK nula (o `ON DELETE SET NULL` permite) **desaparecem** da listagem do CMS. |
| B35 | `view listagem_obras` | Não filtra `wc.deleted_at` / `ws.deleted_at`. | Categorias/situações **soft-deleted** continuam visíveis na listagem. |
| B36 | `view listagem_obras` | `row_number() OVER (ORDER BY w.created_at DESC) AS value`. | A coluna "ID" exibida é a **posição**, não o `id` real. |
| B37 | `routes/basepro.php:515` | `Route::group(['prefix' => 'cms', 'before' => 'csrf'], ...)` — `before` não é um alias de middleware no Laravel 5.5. | Atributo enganoso; o CSRF vem do grupo `web`. |
| B38 | `WorkController::attributes()` | `'status_id' => $request->get('status_id')` — nome do formulário e do banco coincidem, mas a **view SQL** e o `show.blade.php` referem-se a "situação". | Nomenclatura divergente entre "status" (código) e "situação" (tela). |
| B39 | `works.value` / `value_paid` / `latitude` / `longitude` | `varchar(191)` | Sem validação numérica; formatação pt-BR quebrada com separador de milhar. |
| B40 | `WorkController@destroy()` | O soft delete **não** remove `work_archives` nem `archives`. | Arquivos órfãos no banco e no storage. |
| B41 | `App\Library\Helper\appHelpers.php` / `CheckRoutePermission.php` | `str_contains()` / `str_starts_with()` (PHP 8) em runtime PHP 7.2, funcionando só por polyfill. | Quebra em PHP 7.2 sem `symfony/polyfill-php80`. |
| B42 | `routes/web.php:206` | `Route::any('/obras', ...)` — aceita POST. | Superfície de escrita desnecessária no portal público. |
| B43 | `GeocodingService` | `Configuration('geocoding_api_key')` está **vazia** na base v5. | O geocoding **sempre falha** com erro do Google (chave inválida). |

### 1.3 Riscos de dados

| # | Risco | Consequência |
|---|---|---|
| D1 | `latitude`/`longitude` com a string `"não encontrado"` | Registros corrompidos no mapa e no `$forMap`. |
| D2 | `value`/`value_paid` como texto com formatos misturados | Cálculos e ordenações por valor ficam inválidos. |
| D3 | `work_archives` com `type` inválido (sem CHECK) | As imagens/arquivos somem silenciosamente de um dos lados. |
| D4 | `ON DELETE CASCADE` de `work_archives.archive_id` | Exclusão física de um `archives` (de outro módulo!) remove o vínculo da obra. |
| D5 | `archives` compartilhada entre 10+ módulos | Exclusão/alteração feita por outro módulo afeta as obras. |
| D6 | Soft delete de `archives` **não** remove o pivot `work_archives` | O arquivo continua listado na obra, mas some do módulo que o originou. |
| D7 | Base sem índices em `status_id`, `work_category_id`, `start_date`, `contract`, `edital` | Degradação com volume. |
| D8 | `works` sem índice único em `contract`/`edital` | Contratos repetidos são permitidos (pode ser correto). |

---

## 2. Funcionalidades incompletas ou mortas

| Item | Situação |
|---|---|
| `WorkController@show($id)` | Método vazio, sem rota. |
| `WorkCategoryController@show()` | Rota existe, método não. |
| `WorkCategoryController::create()` | Método vazio. |
| `Prefecture\Works\DeleteRequest` | Importado no controller, nunca aplicado. |
| `Prefecture\Works\SaveRequest` → regra `'id' => exists:works,id` | Inerte (o `id` é sobrescrito por `null`). |
| `work_statuses.requires_justification` | **Não existe** — a regra da justificativa está hard-coded no Blade/JS por **nome** da situação. |
| `Work::archives()` `withPivot('type')` | Ausente — impede distinguir arquivo de galeria pela relação. |
| Ordenação da galeria | O `prefecture.contents.components.galerias.index` tem CSS `sortable.css`, mas o módulo de obras **não** expõe UI de reordenação (as rotas `/order` e `/cms/drive/atualizar-ordem` são de outros módulos). |
| PDF do portal | Gerado no cliente, com nome de arquivo e coluna errados; exclui obras sem coordenadas. |
| Exportação | Só exporta a página corrente. |
| Galeria no cadastro | Não existe na tela de create — exige duas etapas. |
| Justificativa obrigatória | Não existe no servidor. |
| Motivo/Justificativa no portal | Exibida na ficha, mas **não** exportada. |
| Descrição no portal | Exibida na ficha e no Excel, ausente no TXT e no PDF. |

---

## 3. Regressão v5 -> v6

A v6 **removeu integralmente** o módulo Obras. Não há "divergência de
comportamento": há ausência total de tudo que compõe a funcionalidade.

### 3.1 O que sumiu

| Camada | Item removido | Caminho na v5 | Equivalente na v6 |
|---|---|---|---|
| **Models** | `Work` | `v5/app/Http/Model/Prefecture/Works/Work.php` | **inexistente** |
| | `WorkCategory` | `v5/app/Http/Model/Prefecture/Works/WorkCategory.php` | **inexistente** |
| | `WorkStatus` | `v5/app/Http/Model/Prefecture/Works/WorkStatus.php` | **inexistente** |
| | `WorkArchive` | `v5/app/Http/Model/Prefecture/Works/WorkArchive.php` | **inexistente** |
| **Controllers (CMS)** | `WorkController` | `v5/app/Http/Controllers/Prefecture/Works/WorkController.php` | **inexistente** |
| | `WorkCategoryController` | `v5/app/Http/Controllers/Prefecture/Works/WorkCategoryController.php` | **inexistente** |
| **Controller (Portal)** | `WorksController` | `v5/app/Http/Controllers/Portal/WorksController.php` | **inexistente** |
| **Requests** | `SaveRequest` (obras) | `v5/app/Http/Requests/Prefecture/Works/SaveRequest.php` | **inexistente** |
| | `DeleteRequest` (obras) | `v5/app/Http/Requests/Prefecture/Works/DeleteRequest.php` | **inexistente** |
| | `SaveRequest` (categorias) | `v5/app/Http/Requests/Prefecture/WorkCategories/SaveRequest.php` | **inexistente** |
| | `DeleteRequest` (categorias) | `v5/app/Http/Requests/Prefecture/WorkCategories/DeleteRequest.php` | **inexistente** |
| **Enum** | `ArchiveTypesEnum` | `v5/app/Enum/Prefecture/Works/ArchiveTypesEnum.php` | **inexistente** |
| **Service** | `GeocodingService` | `v5/app/Services/GeocodingService.php` | **inexistente** |
| **Export** | `WorksExport` | `v5/app/Exports/WorksExport.php` | **inexistente** |
| | view de exportação | `v5/resources/views/exports/works.blade.php` | **inexistente** |
| **Views (CMS)** | listagem | `v5/resources/views/prefecture/works/index.blade.php` | **inexistente** |
| | cadastro | `v5/resources/views/prefecture/works/create.blade.php` | **inexistente** |
| | edição | `v5/resources/views/prefecture/works/edit.blade.php` | **inexistente** |
| | formulário | `v5/resources/views/prefecture/works/form.blade.php` | **inexistente** |
| | categorias (listagem) | `v5/resources/views/prefecture/works/categories/index.blade.php` | **inexistente** |
| | categorias (form) | `v5/resources/views/prefecture/works/categories/form.blade.php` | **inexistente** |
| | categorias (edição) | `v5/resources/views/prefecture/works/categories/edit.blade.php` | **inexistente** |
| **Views (Portal)** | listagem × 6 modelos | `v5/resources/views/portal/layouts/modelo1/obras/index.blade.php` (idem para modelo2..modelo6 — 6 arquivos) | **inexistente** |
| | ficha × 6 modelos | `v5/resources/views/portal/layouts/modelo1/obras/show.blade.php` (idem para modelo2..modelo6 — 6 arquivos) | **inexistente** |
| | filtro × 4 modelos | `v5/resources/views/portal/layouts/modelo3/obras/filtro.blade.php` (idem para modelo4..modelo6 — 4 arquivos) | **inexistente** |
| | componentes do portal | `v5/resources/views/portal/components/obras/filtro.blade.php`, `tabela.blade.php` e `arquivos.blade.php` | **inexistente** |
| **JS** | DataTable de obras | `v5/public/assets/js/custom/contents/obras/datatable.js` | **inexistente** |
| | DataTable de categorias | `v5/public/assets/js/custom/contents/obras/categorias-datatable.js` | **inexistente** |
| | script do formulário | `v5/public/assets/js/custom/contents/obras/script.js` | **inexistente** |
| | PDF do portal | `v5/public/assets/js/modulos/jspdf/gerar-pdf-obras.js` | **inexistente** |
| | mapa | `v5/public/assets/js/custom/portal/work/geo.js` | **inexistente** |
| **Rotas (CMS)** | `obras.index`, `.create`, `.store`, `.edit`, `.update`, `.destroy`, `.getGeolocationData` | `v5/routes/basepro.php:526-535` | **inexistentes** |
| | `galeria-obras.*` (2 rotas) | `v5/routes/basepro.php:538-541` | **inexistentes** |
| | `categorias-obras.*` (7 rotas) | `v5/routes/basepro.php:543` | **inexistentes** |
| | `datatable/listagem_obras`, `datatable/listagem_categorias_obras` | `v5/routes/basepro.php:472-473` | **inexistentes** |
| **Rotas (Portal)** | `portal-obras.index`, `.exportacao`, `.show` | `v5/routes/web.php:206-208` | **inexistentes** |
| **Views SQL** | `listagem_obras` | migration `2025_04_01_025035_create_listagem_obras_view.php` | **inexistente** |
| | `listagem_categorias_obras` | migration `2025_04_01_045658_create_listagem_categorias_obras_view.php` | **inexistente** |
| **Migrations** | 9 migrations do módulo | `v5/database/migrations/2025_04_01_014508` … `2026_05_27_140000` | **inexistentes** |
| **Tabelas** | `works`, `work_categories`, `work_statuses`, `work_archives` | banco `prefeituras_v5` (v5) | **inexistentes** no banco v6 (confirmado por `\dt works` → "Did not find any relation") |
| **Resource / permissão** | `resources.id = 13`, `slug = 'obras'`, nome "Obras", descrição "Formulários de Obras" | banco v5 | **inexistente** no banco v6 |
| **Menu** | `menu_orders.slug = 'obras'` (id 33) | banco v5 | **inexistente** |
| **Menu lateral** | `case 'obras':` no submenu "Obras" | `v5/app/Providers/MenuServiceProvider.php:362-368` | **removido** |

### 3.2 Permissão `obras` (id 13) — o que se perdeu

Na v5, o banco registra:

```
 id | slug   | name  | description
----+--------+-------+------------------
 13 | obras  | Obras | Formulários de Obras
```

Esse resource é a **âncora** de todas as permissões `obras.*`
(`obras.index`, `obras.create`, `obras.edit`, `obras.delete`, `obras.show`,
`obras.restore`, `obras.inactivate`) e do item de menu. Na v6 **não existe
nenhum resource com esse slug** — a funcionalidade de permissão e o item de
menu desapareceram junto com o módulo.

> Observação: o resource `obras` na v5 é **compartilhado** com o módulo
> "Obras Paralisadas" (que também só existe na v5). A remoção na v6 afetou
> os dois. A reconstrução do módulo Obras deve recriar o resource **com
> `modulo_id` apontando para o `modulo` "Obras"** na tabela `modulos`
> (que só existe na v6).

### 3.3 Dados

| Tabela | Volume na v5 | Volume na v6 |
|---|---|---|
| `works` | 0 linhas | tabela inexistente |
| `work_categories` | 1 linha ("Infraestrutura") | tabela inexistente |
| `work_statuses` | 5 linhas | tabela inexistente |
| `work_archives` | 0 linhas | tabela inexistente |

> Como a base v5 do módulo está **vazia**, a reconstrução **não exige
> backfill de dados de obras**. O que se perde é a **funcionalidade**, os
> **algoritmos** (geocodificação, mapa, exportação, filtro sem acento) e as
> **decisões de negócio** (justificativa para suspensão/cancelamento).

### 3.4 O que **não** se perdeu (regressão inversa)

O módulo **Obras Paralisadas** também é exclusivo da v5, e a v6 perdeu os dois.
Já a tabela `archives` (compartilhada) **existe** na v6, mas **sem** as colunas
usadas pelo módulo de obras para distinguir arquivos e galeria — o `type` da v6 não
tem o papel `file`/`gallery` do pivot `work_archives`, porque o pivot não
existe.

---

## 4. Perguntas em aberto para o negócio

| # | Pergunta | Impacto na reconstrução |
|---|---|---|
| Q1 | A **justificativa** deve ser **obrigatória** para "Suspensa" e "Cancelada" no servidor? | Define se a regra sai do JS (v5) e passa a ser `Rule::requiredIf`. **Recomendação: sim** — é a principal salvaguarda de transparência do módulo. |
| Q2 | "Suspensa" e "Cancelada" são os **únicos** status que exigem justificativa? | Se sim, adicionar `requires_justification` (boolean) em `work_statuses` permite alterar a regra sem deploy. |
| Q3 | A **categoria** deve ter herança ou ordenação própria? | Na v5 é só `name`; o portal filtra por id. |
| Q4 | Os **valores** (`value`, `value_paid`) devem ser `decimal` ou permanecer texto? | Afeta o backfill (vazio hoje) e a exportação. **Recomendação: `decimal(14,2)`.** |
| Q5 | A exportação deve trazer **todas** as obras filtradas ou só a página corrente? | Hoje traz só 10. **Recomendação: todas** — com limite máximo configurável. |
| Q6 | O PDF deve ser gerado no servidor ou manter no cliente? | Manter no cliente exclui obras sem coordenadas. **Recomendação: servidor.** |
| Q7 | As categorias de obra devem ter permissão **própria** (`categorias-obras.*`) ou herdar de `obras.*`? | Hoje herdam por alias. Separar quebra perfis existentes; manter preserva. |
| Q8 | O mapa deve mostrar **todas** as obras filtradas ou só as da página atual? | Hoje mostra todas (com carregamento integral em memória). |
| Q9 | Deve existir **histórico** de alterações da obra (valor pago, percentual, situação ao longo do tempo)? | A v5 não tem — só o registro atual e o log de atividades spatie (que registra a ação, não o valor anterior). |
| Q10 | A exclusão da obra deve remover os arquivos do storage? | Hoje não remove (soft delete preserva). |
| Q11 | Qual o limite de **upload** real (2 MB vs 128 MB)? | Precisa ser definido antes de escrever a validação. |
| Q12 | Deve haver **notificação** ao responsável quando o percentual ou a situação mudam? | Não existe na v5. |

---

## 5. Riscos de migração de schema

| # | Risco | Mitigação |
|---|---|---|
| M1 | Tabelas não existem na v6 — a migração é **criação pura**, sem backfill | Migrations `create` com a estrutura definida em `06-MIGRACAO-MODERNA.md` |
| M2 | `archives` existe na v6 mas é de outro formato | Criar `work_files` própria (decisão D5) |
| M3 | Recriar o resource `obras` na v6 exige `modulo_id` (coluna que **não existe** na tabela `resources` da v5) | Seeder do módulo + do resource, ligado por `modulo_id` |
| M4 | Perfis da v6 não têm `obras.*` | Seeder de permissões + papel `super-admin` já cobre; demais papéis precisam ser reatribuídos |
| M5 | `modulos` só existe na v6 (`v6/database/migrations/2024_11_01_150206_create_modulos_table.php`) | Registrar o módulo "Obras" na tabela `modulos` antes do resource |
| M6 | Nomes de permissão usados por outros módulos da v5 (`obra-paralisada`, `obras-paralisadas`, `categorias-obras`) | Manter o helper de alias para não quebrar "Obras Paralisadas" |
| M7 | Templates do portal da v6 (`modelo1..modelo6`, `default`) podem não ter pasta `obras` | Componentizar as views (ver `06-MIGRACAO-MODERNA.md` §6.2) |
| M8 | Colunas `value`/`value_paid` como `decimal` exigem backfill se houver dados | Base v5 está vazia → sem backfill |

---

## 6. Mapa v5 → v6 (resumo visual)

```
v5 (Laravel 5.5 / PHP 7.2)              v6 (Laravel 5.5 / PHP 7.2)
──────────────────────────              ──────────────────────────
app/Http/Model/Prefecture/Works/       ── não existe ──
  Work.php  WorkCategory.php
  WorkStatus.php  WorkArchive.php

app/Http/Controllers/Prefecture/Works/  ── não existe ──
  WorkController.php
  WorkCategoryController.php
app/Http/Controllers/Portal/
  WorksController.php                   ── não existe ──

app/Http/Requests/Prefecture/
  Works/{Save,Delete}Request.php        ── não existe ──
  WorkCategories/{Save,Delete}Request   ── não existe ──

app/Enum/Prefecture/Works/
  ArchiveTypesEnum.php                  ── não existe ──
app/Services/
  GeocodingService.php                  ── não existe ──
app/Exports/
  WorksExport.php                       ── não existe ──

resources/views/prefecture/works/**     ── não existe ──
resources/views/portal/layouts/
  modelo1 a modelo6/obras/**                   ── não existe ──
resources/views/portal/components/obras/── não existe ──
resources/views/exports/works.blade.php ── não existe ──

public/assets/js/custom/contents/obras/── não existe ──
public/assets/js/modulos/jspdf/
  gerar-pdf-obras.js                    ── não existe ──
public/assets/js/custom/portal/work/
  geo.js                                ── não existe ──

routes/basepro.php:526-543              ── não existe ──
routes/web.php:206-208                  ── não existe ──

database/migrations (9 do módulo)       ── não existem ──
tabelas works/work_*/                   ── não existem ──
views SQL listagem_obras /
  listagem_categorias_obras             ── não existem ──
resources.id=13 slug='obras'            ── não existe ──
menu_orders.slug='obras'               ── não existe ──
```

**Conclusão da regressão:** a v6 perdeu **100%** do módulo Obras — código,
schema, permissões, menu, dados e rotas. A reconstrução deve ser tratada
como **funcionalidade nova**, aproveitando o dossiê como especificação, e não
como "portar" de uma base existente.
