# Obras — Resumo Executivo

> **Versão analisada:** `v5` (Laravel 5.5.50, PHP 7.2.34, PostgreSQL 10 + PostGIS)
> **Slug do módulo:** `obras` — **existe** na tabela `resources` da v5 (id 13, nome "Obras", descrição "Formulários de Obras") e **não existe** na v6
> **Natureza:** CMS (painel administrativo) + Portal (público)
> **Estado:** **apenas v5** — toda a funcionalidade (models, controllers, requests, views, export, views SQL, rotas) foi removida na v6

---

## 1. Objetivo

O módulo **Obras** é o cadastro-mestre das **obras públicas** da prefeitura:
cada obra tem identificação (título, edital, contrato), classificação
(categoria), situação, valores (total e pago), datas (início, fim, previsão de
conclusão), percentual de execução, endereço completo com coordenadas, responsáveis
(empresa e prefeitura), justificativa, descrição, arquivos para download e
galeria de fotos.

Serve ao **servidor municipal** da Secretaria de Obras, que mantém o
andamento das obras e publica os documentos; e serve ao **cidadão**, que
consulta as obras no portal, filtra por período/situação/categoria/contrato/edital,
vê a localização no mapa, baixa os arquivos e exporta a relação.

**Ponto central:** este módulo **não** é o módulo "Obras Paralisadas"
(`obras_paralisadas`), que é uma entidade independente e mais simples. As duas
funcionalidades aparecem lado a lado no menu "Obras" do CMS, mas não
compartilham nenhuma tabela.

## 2. Efeito / Impacto

| Faixa | Efeito |
|---|---|
| Cidadão | Lista pública paginada (10 por página) com filtros por período, situação, categoria, contrato e edital; mapa Leaflet com todas as obras georreferenciadas; ficha individual com dados, arquivos para download e galeria de fotos; exportação em xls, pdf, csv e txt. |
| Prefeitura (CMS) | CRUD de obras no AdminLTE (listagem em DataTable sobre view SQL), cadastro de **categorias de obra** em resource separado, upload múltiplo de arquivos por obra, galeria de fotos com upload em lote, geocodificação automática por CEP/endereço e log de atividades em todas as ações. |
| Transparência e legalidade | Expõe valorescontratados e valores pagos, responsáveis, prazos, percentual de conclusão e a **justificativa obrigatória quando a situação é "Suspensa" ou "Cancelada"** — é a principal salvaguarda do módulo. |
| Operação técnica | Depende da tabela `archives` (compartilhada com conteúdo, licitações e destaques) via pivot `work_archives`; usa duas views SQL (`listagem_obras`, `listagem_categorias_obras`); consome a API do Google Geocoding e a API de CEP do ViaCEP; compartilha o recurso de permissão `obras` com "Obras Paralisadas". |

## 3. Comportamento

- O FormRequest valida **29 campos**; **16 são obrigatórios**: título, edital,
  contrato, categoria, situação, valor, valor pago, data de início, CEP, estado,
  cidade, bairro, rua, número, empresa responsável e responsável da prefeitura.
- Datas chegam em `d/m/Y` e são convertidas para `Y-m-d` no
  `prepareForValidation()` do FormRequest; a data de fim, quando presente, tem de
  ser **igual ou posterior** à data de início.
- **Justificativa**: o campo só aparece (e só é enviado) quando a situação
  escolhida é **Suspensa** ou **Cancelada** — decisão tomada no JavaScript do
  formulário e no Blade, **não** validada no servidor.
- **Percentual de conclusão** é inteiro entre 0 e 100; **previsão de conclusão**
  é data; **descrição** é texto livre (opcional).
- A **galeria** é um flag booleano (`works.gallery`). Ao ativar a galeria em uma
  obra já existente, o controller redireciona de volta para a aba "Galeria"
  da tela de edição (`?goto=galeria`) para que o operador envie as fotos.
- Arquivos anexados são gravados em `archives` (tabela compartilhada) e
  vinculados por `work_archives` com `type = 'file'`; as imagens da galeria usam
  `type = 'gallery'`.
- A exclusão da obra é **lógica** (`works.deleted_at`); os arquivos em `archives`
  **não** são apagados, e o pivot `work_archives` só é removido em cascata se a
  obra for removida fisicamente.
- A exclusão de uma **categoria** com obras vinculadas é **bloqueada** (HTTP 400
  com mensagem "Não é possível excluir esta categoria de obra...").
- O portal ordena por `start_date` descendente com `NULL` no fim; a listagem do
  CMS usa a view `listagem_obras`, que faz **INNER JOIN** com categorias e
  situações (ou seja, obras com categoria ou situação nula **não** aparecem).
- Busca textual no portal por "contrato" e "edital" é **case-insensitive e
  insensível a acentos** via `TRANSLATE` no PostgreSQL.
- Exportação no portal: `xls`, `csv` e `txt` gerados no servidor; `pdf` gerado
  **no navegador** com jsPDF a partir do JSON embutido no HTML.

## 4. Como o Usuário Interage

**Operador (CMS — `/cms/obras`):** DataTable alimentada por
`GET /cms/datatable/listagem_obras` (view SQL `listagem_obras`), com colunas
ID, Título, Categoria, Situação, Valor Total, Valor Pago e Ações. Cada linha
tem link para a obra no portal, botão de editar e botão de excluir (o botão de
excluir só aparece para quem tem `obras.delete`). Os botões "Categorias de
Obras" e "Criar Obra" só aparecem para quem tem `obras.create`.

**Formulário (CMS):** abas "Obra" e (quando `gallery = 1`) "Galeria". O CEP é
consultado no **ViaCEP** ao perder o foco, preenchendo estado, cidade, bairro e
rua; em seguida o endereço é enviado ao endpoint próprio
`GET /cms/obras/get-geolocation-data`, que consulta o **Google Geocoding** e
devolve `lat`/`lng` para os campos de latitude e longitude. Os anexos são
adicionados dinamicamente (par *input file* + *input de descrição*) e só sobem
junto com o `POST`/`PUT` do formulário.

**Cidadão (portal — `/obras`):** caixa de filtros (De/Até, Status, Contrato,
Categoria, Edital), mapa Leaflet, botões "Salvar como: xls / pdf / csv / txt" e
tabela com Situação, Título, Contrato, Categoria, % Concluído, Edital, Valor,
Valor Pago, Data Início, Data Fim e um botão de lupa que abre a ficha
`/obras/{id}`.

## 5. Detalhes e Particularidades Técnicas

- Camadas: `Route` → `Prefecture\Controller` (aplica `check.route.permission` a
  todas as actions) → `SaveRequest`/`DeleteRequest` → model Eloquent
  (`Work`, `WorkCategory`, `WorkStatus`, `WorkArchive`) → tabelas `works`,
  `work_categories`, `work_statuses`, `work_archives` e `archives`.
- Não há repository nem service de domínio; o único service é
  `App\Services\GeocodingService` (acesso externo, sem cache).
- O portal (`Portal\WorksController`) **não** passa pelo `Prefecture\Controller`:
  não há `check.route.permission` nem `check.logged`; apenas
  `check.site.manutention` (grupo de `v5/routes/web.php`).
- O controller do portal monta a lista de geolocalização **antes** da
  paginação (`clone $result->get()`), carregando **todas** as obras com
  latitude/longitude em memória a cada requisição — inclusive na listagem
  normal, não só na exportação.
- `getNonAccentedILikeSearch()` monta SQL com **interpolação direta** da
  variável de busca (sem binding nem escape) — SQL injection mitigada apenas
  pelo fato de o valor ser normalizado por `prepareStringToSearch()`, que
  **não** remove aspas simples.
- `storeArchives()` grava o arquivo **fora** da transação SQL e com o campo
  `path` montado à mão (`storage/obras/{id}/arquivos/{nome}`), enquanto a
  galeria usa `FileTrait::saveUpload()` (disk `public`, pasta `/works`).
- Dependências: `yajra/laravel-datatables` (listagem do CMS),
  `maatwebsite/excel` (xls/csv), `laracasts/flash` (mensagens),
  `spatie/laravel-activitylog` (`activity()`), `cartalyst/sentinel`
  (sessão/permissões), `intervention/image` (compressão de imagens via
  `FileTrait`), `guzzlehttp/guzzle` (Google Geocoding), jsPDF +
  jspdf-autotable (PDF do portal), Leaflet/LeafletGeo + IBGE (mapa).

## 6. Estrutura de Dados

| Item | Detalhe |
|---|---|
| Tabelas | `works` (principal), `work_categories`, `work_statuses`, `work_archives` (pivot) + `archives` (compartilhada) |
| Colunas de `works` | 29 colunas: id, title, edital, contract, work_category_id, status_id, value, value_paid, start_date, end_date, cep, state, city, neighborhood, street, house_number, complement, latitude, longitude, company_holder, holder, gallery, created_at, updated_at, deleted_at, justification, description, estimated_completion_date, completion_percentage |
| Chaves estrangeiras | `works_status_id_foreign` (ON DELETE SET NULL), `works_work_category_id_foreign` (ON DELETE SET NULL), `work_archives_work_id_foreign` (ON DELETE CASCADE), `work_archives_archive_id_foreign` (ON DELETE CASCADE) |
| Soft delete | `works`, `work_categories`, `work_statuses` e `work_archives` têm `deleted_at` |
| Valor monetário | `value` e `value_paid` são `varchar(191)` — **não** são numéricos no banco |
| Pivot | `work_archives.type` distingue `file` (`ArchiveTypesEnum::FILE`) de `gallery` (`ArchiveTypesEnum::GALLERY`) |
| Views SQL | `listagem_obras` (obra + categoria + situação, com `row_number()` como coluna `value`) e `listagem_categorias_obras` (categorias com `row_number()`) |
| Volume | `works`: **0 linhas**; `work_statuses`: 5 (Aberta, Em Andamento, Finalizada, Cancelada, Suspensa); `work_categories`: 1 (Infraestrutura); `work_archives`: 0 |
| Migrations | 9 migrations em `v5/database/migrations/` (2025-04-01 × 7, 2026-05-21, 2026-05-27) |
| Seeds | nenhum seeder dedicado; o seeding inicial é feito **dentro da migration** `2025_04_01_022047_set_up_works_resources.php` (5 situações + 1 categoria + resource + menu_order) |

## 7. Como Testar

Cenário de aceite completo em `07-TESTES.md`: criação de obra com arquivos,
edição, ativação de galeria com upload de fotos, exclusão de obra, exclusão de
categoria com e sem obras vinculadas, listagem do portal com cada filtro,
acesso à ficha, download de arquivo, mapa com marcador, e as quatro exportações.

## 8. Migração para Laravel Moderno

Reconstruir com: models `Work`, `WorkCategory`, `WorkStatus`, `WorkArchive` com
`SoftDeletes`, `HasFactory` e casts explícitos (`gallery` → bool, datas →
`date`, `value`/`value_paid` → `decimal:2`); migration com as mesmas 27 colunas
e as mesmas FKs (decisão a registrar: manter `varchar` ou migrar para
`decimal`); `enum` PHP para `work_archives.type`; `FormRequest` com as mesmas
regras e mensagens e a **regra ausente na v5** de justificativa obrigatória
para Suspensa/Cancelada; `Policy` no lugar do par `check.logged` +
`check.route.permission`; `API Resource` para o portal; `Route::apiResource`
em `/api/obras`; `Enum`/DTO para o geocoding com cache; fila para a
compressão de imagens; e a decisão de manter ou substituir as **views SQL**
`listagem_obras` / `listagem_categorias_obras` por queries Eloquent com
`withCount`.

## 9. Resumo Geral

Obras é o cadastro-mestre das obras públicas da prefeitura: título, edital,
contrato, categoria, situação, valores, prazos, percentual de execução,
endereço com coordenadas, responsáveis, justificativa, descrição, arquivos e
galeria de fotos. No CMS, o operador cria e edita obras em um formulário que
busca o CEP no ViaCEP e as coordenadas no Google Geocoding, anexa documentos e
sobe fotos de progresso; tudo é registrado em log de atividades e cada visita
incrementa o contador de cliques do menu. No portal, o cidadão filtra as obras
por período, situação, categoria, contrato e edital, vê o mapa, abre a ficha,
baixa os arquivos e exporta em xls, pdf, csv ou txt. O módulo se apoia na
tabela `archives`, compartilhada com o resto do sistema, e em duas views SQL
que alimentam as listagens do CMS. Na v6 **nada disso existe**: nem models,
nem controllers, nem requests, nem rotas, nem views de CMS ou portal, nem
export, nem views SQL, nem o recurso de permissão `obras` (id 13) — a
funcionalidade precisa ser reconstruída do zero.

---

## Í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 (tabelas, relações, casts, migrations, seeds) |
| `03-ROTAS-E-CONTROLADORES.md` | Rotas, controllers, requests, permissões |
| `04-VIEWS-E-UX.md` | Telas do CMS e do portal, componentes e JS |
| `05-REGRAS-E-PORTABILIDADE.md` | Acoplamentos, armadilhas, código duplicado, checklist de equivalência |
| `06-MIGRACAO-MODERNA.md` | Schema, models, Form Requests, policies e views Blade modernas |
| `07-TESTES.md` | Estratégia e casos de teste |
| `08-GAPS-E-DIVIDAS.md` | Pendências, riscos, bugs e comparativo **v5 → v6** |
| `09-VIEWS-SQL-E-PERMISSOES.md` | Views SQL (`listagem_obras`, `listagem_categorias_obras`) e permissões do resource `obras` |
| `10-EXPORTACOES-E-ENDPOINTS.md` | Endpoints consumidos pelo front e detalhamento das exportações (xls/csv/txt/pdf) |
| `11-REGRAS-DE-NEGOCIO.md` | Regras de negócio não óbvias (justificativa, galeria, exclusão de categoria, normalização de datas) |
| `12-API-E-EXECUCAO.md` | Endpoints REST/JSON propostos, API Resource, fila/cache, riscos e ordem de execução |
