# Obras — API, Fila, Riscos e Ordem de Execução (complemento)

> Complemento de `06-MIGRACAO-MODERNA.md` (v5). As seções "Endpoints REST / JSON",
> "Fila, cache e storage", "Riscos e decisões de equivalência funcional" e
> "Ordem sugerida de execução" foram movidas para cá para manter todos os
> arquivos dentro do limite de 400 linhas.

---

## 1. Endpoints REST / JSON

### 1.1 API (nova, para o portal e integrações)

```
GET    /api/obras                    ?dt_inicial&dt_final&status&category&contract&edital&page
GET    /api/obras/{id}
GET    /api/obras/{id}/files
POST   /api/obras                    (auth: obras.create)
PUT    /api/obras/{id}               (auth: obras.edit)
DELETE /api/obras/{id}               (auth: obras.delete)
POST   /api/obras/{id}/files         (auth: obras.edit)   — upload de anexos
POST   /api/obras/{id}/gallery       (auth: obras.edit)   — upload de fotos
GET    /api/obras/{id}/gallery
GET    /api/work-categories
POST   /api/work-categories
PUT    /api/work-categories/{id}
DELETE /api/work-categories/{id}
GET    /api/obras/export?format=xlsx|csv|txt   (auth: obras.index)
```

### 1.2 Rotas web do portal

```
GET /obras                → listagem pública (Blade)
GET /obras/{id}           → ficha pública (Blade)
GET /exportacao-obras     → download (xlsx/csv/txt), preservando a URL atual
```

> Manter `/exportacao-obras` e `/obras` como **nomes distintos** — mas
> **separar em controllers diferentes**, eliminando a bifurcação por
> `Route::currentRouteName()`.

### 1.3 API Resources

```php
class WorkResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'edital' => $this->edital,
            'contract' => $this->contract,
            'category' => $this->whenLoaded('workCategory', fn () => [
                'id' => $this->workCategory->id,
                'name' => $this->workCategory->name,
            ]),
            'status' => $this->whenLoaded('status', fn () => [
                'id' => $this->status->id,
                'name' => $this->status->name,
            ]),
            'value' => (float) $this->value,
            'value_paid' => (float) $this->value_paid,
            'start_date' => $this->start_date?->toDateString(),
            'end_date' => $this->end_date?->toDateString(),
            'estimated_completion_date' => $this->estimated_completion_date?->toDateString(),
            'completion_percentage' => $this->completion_percentage,
            'gallery' => (bool) $this->gallery,
            'justification' => $this->justification,
            'description' => $this->description,
            'location' => [
                'cep' => $this->cep,
                'state' => $this->state,
                'city' => $this->city,
                'neighborhood' => $this->neighborhood,
                'street' => $this->street,
                'house_number' => $this->house_number,
                'complement' => $this->complement,
                'latitude' => $this->latitude !== null ? (float) $this->latitude : null,
                'longitude' => $this->longitude !== null ? (float) $this->longitude : null,
            ],
            'files' => WorkFileResource::collection($this->whenLoaded('files')),
        ];
    }
}
```

A serialização do `value` deve sair como **número** (`float`), resolvendo a
inconsistência entre o `varchar` da v5 e o `decimal` do novo schema.

---

## 2. Fila, cache e storage

| Item | v5 | Proposta |
|---|---|---|
| Upload de imagem + compressão | síncrono, no request | **Job** `CompressWorkImage` (queue `default`), com `OnQueue`/`ShouldQueue`; o upload grava o original e o job gera `_min`/`_med`/`_larg`. Evitar timeout em uploads grandes. |
| Geocodificação | síncrona, chamada do controller | **Job** `GeocodeWork` + cache; ou serviço síncrono com `Cache::remember` por endereço (a chave é o endereço normalizado). |
| Consulta do ViaCEP | direto do navegador | Manter no navegador (o ViaCEP permite CORS) ou mover para o backend com cache de 30 dias (`Cache::remember("cep:{$cep}", now()->addDays(30), ...)`). |
| Geometria do IBGE | `localStorage` do navegador | Manter no front (o cache é por navegador) ou mover para `Cache::store('redis')` com TTL longo. |
| Arquivos | disk `public` com `path` relativo | `Storage::disk('s3')` ou `local` com URL assinada; **não** gravar `storage/...` no banco — gravar a **chave** do objeto e gerar a URL no acesso. |
| Listagem do CMS | view SQL | Query Eloquent com `with()` + `withTrashed` nas relacionadas (decisão D6). |
| Mapa do portal | todas as obras em memória | Endpoint paginado/limitado só com as obras do filtro atual (limite de 500 pontos). |

---

## 3. Riscos e decisões de equivalência funcional

| # | Risco | Impacto | Mitigação |
|---|---|---|---|
| R1 | **Perda da regra da justificativa** ao mover só para o front | Obras suspensas/canceladas sem justificativa | `Rule::requiredIf` no servidor + teste dedicado |
| R2 | **Perda da precondition** "categoria com obras vinculadas não pode ser excluída" | Exclusão acidental de categoria com obras | Checagem no controller **e** na Policy; erro 422 |
| R3 | **Exportar só a página corrente** (bug da v5) passa a ser "exportar tudo" | Mudança de comportamento observável | **Desejado**; documentar na release. Se for preciso manter, exportar a página corrente explicitamente |
| R4 | Migração de `varchar` → `decimal` em `value`/`value_paid`/`latitude`/`longitude` | Dados com separador pt-BR malformado | Backfill com `str_replace` + validação; base vazia na v5, logo sem risco real |
| R5 | Tabela `archives` compartilhada | Regressão em conteúdos/licitações se algo for alterado | Criar `work_files` própria (D5) ou isolar rigorosamente |
| R6 | `ON DELETE CASCADE` do pivot | Perda silenciosa de vínculo | `restrictOnDelete` ou tabela própria (D5) |
| R7 | Mudança do slug de permissão (`obras.*`) | Perfis perdem acesso | Manter os slugs; documentar o alias `categorias-obras` |
| R8 | Templates do portal (`modelo1` a `modelo6`) | O módulo depende do subsistema de templates | Componentizar e registrar a dependência |
| R9 | PDF gerado no cliente | PDF sem obras sem coordenadas | Gerar no servidor |
| R10 | `check.site.manutention` (modo manutenção do portal) | Portão de manutenção não aplicado | Replicar o middleware ou unificar com o subsistema de manutenção |
| R11 | `menuClicks` (contador por menu) e ordenação por cliques | Menu muda de ordem | Replicar ou remover em conjunto com o painel |
| R12 | `str_contains`/`str_starts_with` (PHP 8) no middleware | Quebra em PHP 7.2 sem polyfill | Ir para PHP 8.x — sem risco na v7 |

---

## 4. Ordem sugerida de execução

1. Migrations (`work_statuses`, `work_categories`, `works`, `work_files`).
2. Models, factories e seeders (situações + categoria + resource `obras`).
3. Policies + `WorkPolicy` + `WorkCategoryPolicy`; registrar as permissões.
4. `SaveWorkRequest` / `DeleteWorkRequest` / requests de categoria.
5. Admin: `WorkController` + `WorkCategoryController` (CRUD + upload + galeria).
6. Views do admin (componentes `<x-admin.*>`).
7. Portal: `PublicWorkController` (listagem + ficha) + componentes `<x-portal.*>`.
8. Exportação (Excel/CSV/TXT no servidor + PDF no servidor).
9. Mapa (endpoint JSON + Leaflet).
10. Testes de feature (ver `07-TESTES.md`).
