# Obras Paralisadas — Resumo Executivo

> **Versão analisada:** `v5` (Laravel 5.5.50, PHP 7.2.34, PostgreSQL 10 + PostGIS)
> **Slug do módulo:** `obras-paralisadas` — **não existe** na tabela `resources` da v5 nem na da v6
> **Natureza:** CMS (painel administrativo) + Portal (público)
> **Estado:** **apenas v5** — a funcionalidade foi removida na v6

---

## 1. Objetivo

O módulo **Obras Paralisadas** publica e mantém o registro das obras da
prefeitura que estão **paralisadas** (travadas), expondo ao cidadão o motivo da
paralisação, o responsável pela obra e a previsão de reinício, além dos PDFs
que documentam o caso.

Serve ao **servidor municipal** que responde pela obra (Secretaria de Obras,
Departamento de Engenharia) e que precisa registrar e publicizar o
paralisamento; e serve ao **cidadão** que quer saber por que uma obra que ele
acompanha está parada e quando ela será retomada.

**Ponto central e frequentemente mal entendido:** este módulo **não é** o
módulo "Obras" (`works`, `work_categories`, `work_statuses`, `work_archives`).
Ele é uma entidade independente e mais simples: uma tabela só
(`obras_paralisadas`), sem geolocalização, sem percentual de execução, sem
fotos de progresso, sem categorias e **sem vínculo com a tabela `archives`**.
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 (15 por página) com busca por identificação ou motivo, ficha individual por obra e download dos PDFs anexos. Exportação em xls, pdf, csv e txt. |
| Prefeitura (CMS) | CRUD de obras paralisadas no AdminLTE, com listagem em DataTable, anexo de múltiplos PDFs com descrição e exclusão individual de anexo. |
| Transparência e legalidade | Dá publicidade ao motivo do travamento e à previsão de reinício — mas o módulo é declarativo: **não registra** datas de início, valores, prazos efetivos, responsável jurídico ou responsabilização. A tabela do banco está **vazia** na v5 (0 registros). |
| Operação técnica | Baixo acoplamento: uma tabela, um model, um request, dois controllers, sem view SQL e sem permissões próprias no banco (usa o recurso `obras`). |

## 3. Comportamento

- Cadastro exige quatro campos: **Identificação da Obra**, **Motivo da
  Paralisação**, **Responsável** e **Previsão de Reinício**. Arquivos são
  opcionais.
- Anexos aceitam **apenas PDF**, até **10 MB** cada, múltiplos por registro,
  cada um com uma descrição livre. Ficam gravados como **JSON em uma coluna
  `text`** da própria linha (`obras_paralisadas.arquivo`), não em `archives`.
- A exclusão do registro é **lógica** (`deleted_at`), preservando o histórico.
- A exclusão de um anexo é feita **por posição (índice)** dentro do array
  JSON; o arquivo físico **não** é apagado do storage.
- O portal lista as obras com ordenação alfabética por `identificacao`,
  paginação de 15 e busca por texto em `identificacao` e `motivo_paralisacao`.
- A URL da ficha pública usa um **slug** gerado a partir de
  `identificacao + id`; ao renomear a identificação, o slug muda e o link
  publicado deixa de funcionar.
- Exportações do portal: `xls`, `csv` e `txt` gerados no servidor; `pdf`
  gerado **no navegador** com jsPDF a partir de todos os registros enviados
  em JSON no HTML.
- Não existe workflow de estados: uma obra não tem "situação" (paralisada /
  em andamento / concluída). O registro sai da lista pública apenas por soft
  delete.

## 4. Como o Usuário Interage

**Operador (CMS — `/cms/obras-paralisadas`):** vê a listagem em DataTable com
as colunas ID, Identificação da Obra, Responsável, Previsão de Reinício e
Ações. O botão "Cadastrar" só aparece para quem tem a permissão
`obra-paralisada.create` (mapeada para `obras.create`). Editar e Excluir são
habilitados por linha conforme `obra-paralisada.edit` / `obra-paralisada.delete`.
O botão "Excluir" dispara um `DELETE` via `fetch` e recarrega a tabela; o botão
"Editar" abre `/cms/obras-paralisadas/editar/{id}`.

**Formulário (CMS):** campos de identificação, motivo (textarea), responsável e
previsão de reinício (input `date`). A cada clique em "Adicionar Arquivo" é
inserido um par *input file* + *input de descrição* via jQuery, sem upload
paralelo: os arquivos só sobem junto com o `POST`/`PUT` do formulário. Os
arquivos já enviados aparecem listados com botões de download, visualizar e
excluir (exclusão por `DELETE` na rota `obra-paralisada.destroy-arquivo`).

**Cidadão (portal — `/obras-paralisadas`):** caixa de filtro com um único
campo "Buscar por Identificação ou Motivo", botão Consultar e botão Limpar
campos. Abaixo, botões "Salvar como: xls / pdf / csv / txt" (só aparecem se
houver registros) e a tabela com as quatro colunas de dados e um botão de
lupa que abre a ficha `/obra-paralisada/{slug}`. Na ficha, os anexos aparecem
como lista de download.

## 5. Detalhes e Particularidades Técnicas

- Camadas: `Route` → `Prefecture\Controller` (aplica `check.route.permission`
  a todas as actions) → `ObraParalisadaRequest` → `ObraParalisada` (Eloquent)
  → `obras_paralisadas`. Não há repository, service ou job.
- O model calcula o slug em `created` e em `updating`, gravando com um
  `save()` extra dentro do próprio evento.
- As rotas de escrita (`store`, `update`, `destroy`, `destroy-arquivo`) **não**
  declaram `middleware('check.logged')`, ao contrário das de leitura — mas
  herdam `check.route.permission` do controller base, que redireciona para
  `login` quando não há sessão. Detalhado em `03-ROTAS-E-CONTROLADORES.md`.
- Anexos são gravados com `FileTrait::saveUpload($file, 'docs', null, null, false)`
  no disk `public`, e lidos com `url('storage/' . $path)`.
- Dependências: `yajra/laravel-datatables` (listagem do CMS),
  `laracasts/flash` (mensagens), `laralabs/toaster` (toasts), jsPDF +
  jspdf-autotable (PDF do portal), `cartalyst/sentinel` (sessão/permissões).
- O controller do portal **carrega todos os registros em memória** em toda
  requisição (`(clone $query)->get()`), mesmo sem exportar, porque o PDF é
  montado no cliente.

## 6. Estrutura de Dados

| Item | Detalhe |
|---|---|
| Tabela | `obras_paralisadas` (PostgreSQL 10) |
| Colunas | `id` (serial, PK), `identificacao` varchar(255) NOT NULL, `motivo_paralisacao` text NOT NULL, `responsavel` varchar(255) NOT NULL, `previsao_reinicio` varchar(255) NOT NULL, `arquivo` text NULL (JSON), `slug` varchar(191) NULL, `created_at`, `updated_at`, `deleted_at` |
| Índices | apenas `obras_paralisadas_pkey` (btree id) — **sem índice em `slug`** |
| Volume | 0 linhas na base `prefeituras_v5` (id 13–17 do recurso `obras` continua em uso pelo módulo "Obras") |
| Views SQL | nenhuma VIEW para este módulo (o `DatatableController` consulta a tabela direto) |
| Migrations | `v5/database/migrations/2026_05_26_000003_create_obras_paralisadas_table.php` |
| Seeds | nenhum |
| Anexos | coluna `text` com JSON `[{path, description}]` — **não** usa `archives` |

## 7. Como Testar

1. Cadastrar uma obra sem anexos e conferir a mensagem "Registro cadastrado com
   sucesso." e o redirecionamento para a listagem do CMS.
2. Cadastrar com 2 PDFs (um deles com descrição) e verificar os dois itens na
   listagem de arquivos enviados.
3. Editar a obra, anexar mais um PDF e confirmar que os anteriores permanecem.
4. Excluir o 2º anexo pelo botão da lixeira e confirmar que só ele some da
   lista (o arquivo continua no storage).
5. Recarregar a página do portal e localizar a obra na listagem; abrir a ficha
   pelo botão de lupa e baixar os anexos.
6. Buscar por trecho com acento e sem acento; conferir o comportamento
   descrito em `05-REGRAS-E-PORTABILIDADE.md`.
7. Exportar em xls, csv, txt (servidor) e pdf (navegador).
8. Excluir a obra e confirmar que ela some do CMS e do portal, mas permanece
   no banco com `deleted_at` preenchido.

## 8. Migração para Laravel Moderno

Reconstruir com: model `ObraParalisada` com `SoftDeletes` e `HasFactory`;
migration com `string('identificacao', 255)`, `text('motivo_paralisacao')`,
`string('responsavel', 255)`, `date('previsao_reinicio')` — **decisão de
equivalência a avaliar**, pois na v5 é `varchar` e o formulário é `date`;
`json('arquivo')` no lugar da coluna `text` com JSON; índice único em `slug`;
`HasFactory` e `Observer`/`saving` para gerar o slug; `FormRequest` com as
mesmas regras e mensagens; `Policy` no lugar do par
`check.logged` + `check.route.permission`; rotas REST sob `Route::resource`
com `auth` e `can:`; `Archive`/`Media` dedicado (a coluna JSON não é
indexável, não tem FK e não suporta ordenação); `API Resource` para o portal e
um endpoint JSON paginado no lugar do `(clone $query)->get()` sem limite.

## 9. Resumo Geral

Obras Paralisadas é um módulo pequeno, autocontido e **existente apenas na
v5**. Ele cadastra, no AdminLTE, obras travadas com identificação, motivo,
responsável e previsão de reinício, anexa PDFs com descrição e publica tudo
no portal, onde o cidadão filtra por texto e baixa os documentos. A tabela
`obras_paralisadas` guarda também, dentro de uma coluna `text`, o JSON com os
anexos — nenhum registro na tabela `archives` é criado, ao contrário do módulo
"Obras". O slug público é derivado de `identificacao + id` e recalculado a
cada edição, o que quebra links publicados quando a identificação muda. As
rotas de escrita não declaram `check.logged` e dependem do middleware de
permissão herdado do controller base para bloquear acesso anônimo. Não há
workflow, não há histórico de alterações, não há registro em log de atividades
e não há view SQL. Na v6 **nada disso existe**: nem model, nem controller,
nem request, nem rotas, nem views, nem recurso de permissão, nem dados — 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 |
| `03-ROTAS-E-CONTROLADORES.md` | Rotas, controllers, requests, permissões |
| `04-VIEWS-E-UX.md` | Telas, componentes, JS, assets |
| `05-REGRAS-E-PORTABILIDADE.md` | Particularidades e armadilhas |
| `06-MIGRACAO-MODERNA.md` | Plano de reconstrução em Laravel moderno |
| `07-TESTES.md` | Estratégia e casos de teste |
| `08-GAPS-E-DIVIDAS.md` | Pendências, riscos, comparativo v5 vs v6 |
| `09-PORTAL-EXPORTACOES-E-JS.md` | Exportações (xls/csv/txt/pdf), endpoints JS, assets e checklist de UX |
