# Agenda de Eventos — Arquitetura

> Escopo: `v6` (com notas de diferença em relação à `v5` no arquivo `08-GAPS-E-DIVIDAS.md`).

---

## 1. Visão geral das camadas

O módulo segue o MVC "clássico" da plataforma, com **duas implementações divergentes**:

| Lado | Onde a regra de negócio mora | Acesso a dados |
|---|---|---|
| CMS (`/cms/...`) | Controllers em `App\Http\Controllers\Prefecture\CalendarEvent` | Repository `App\Repositories\Eloquent\Prefecture\CalendarEvent\CalendarEventRepositoryDefaultImpl` para escrita; `DB::table('agenda_de_eventos')` (view SQL) para a listagem |
| Portal (público) | Controller `App\Http\Controllers\Portal\CalendarEventController` | **Eloquent direto** no controller, sem repository |

Não há API JSON para este módulo: o portal é renderizado por Blade e o CMS usa
DataTables (Yajra) sobre uma view SQL.

---

## 2. Classes envolvidas

### 2.1 Controllers (CMS)

| Classe | Caminho | Papel |
|---|---|---|
| `Prefecture\CalendarEvent\CalendarEventController` | `v6/app/Http/Controllers/Prefecture/CalendarEvent/CalendarEventController.php` | CRUD de eventos, upload de arquivos, exclusão em lote, log de auditoria |
| `Prefecture\CalendarEvent\CategoryCalendarEventController` | `v6/app/Http/Controllers/Prefecture/CalendarEvent/CategoryCalendarEventController.php` | CRUD de categorias (slug automático, listagem embutida na tela de cadastro) |
| `Prefecture\DatatableController` | `v6/app/Http/Controllers/Prefecture/DatatableController.php` | Servidor da DataTable `get_agenda_eventos` (linha 153) |
| `Prefecture\ArchiveController` | `v6/app/Http/Controllers/Prefecture/ArchiveController.php` | Endpoint genérico de galeria (`saveArchivesFromGalleryOrDriver`, `getArchivesFromGalleryOrDriver`, `deleteArchives`) usado pela aba Galeria do evento |

### 2.2 Controller (Portal)

| Classe | Caminho | Papel |
|---|---|---|
| `Portal\CalendarEventController` | `v6/app/Http/Controllers/Portal/CalendarEventController.php` | `index` (lista), `categoryIndex` (filtro por categoria), `show` (detalhe) |

Herda de `App\Http\Controllers\Admin\Controller`, que fornece
`getTemplateActived()` (`v6/app/Http/Controllers/Admin/Controller.php:39-49`): lê a
configuração `layout` e devolve `modelo1`..`modelo5` (ou `default`), abortando 404 se a
view do template não existir.

### 2.3 Models

| Classe | Caminho | Observações |
|---|---|---|
| `App\Prefecture\CalendarEvent` | `v6/app/Http/Model/Prefecture/CalendarEvent.php` | `SoftDeletes`, tabela `calendar_events` |
| `App\Prefecture\CategoryCalendarEvent` | `v6/app/Http/Model/Prefecture/CategoryCalendarEvent.php` | `SoftDeletes`, tabela `category_calendar_events` |
| `App\Prefecture\Archive` | `v6/app/Http/Model/Prefecture/Archive.php` | Tabela `archives` compartilhada entre módulos |

Relações declaradas em `CalendarEvent`:

| Método | Tipo | Colunas |
|---|---|---|
| `archives()` | hasMany | `calendar_events_id` -> `archives.id` |
| `archiveGallery()` | hasMany + `scopeByHighLight(null)` | `calendar_events_id` |
| `categoryCalendarEvents()` | **hasOne** | `category_calendar_events_id` -> `category_calendar_events.id` |
| `agendaEventosSecretarias()` | hasMany | `secretarias_id` -> `calendar_events.id` (auto-relacionamento, uso questionável) |
| `agendaEventosOrgaos()` | hasMany | `orgaos_id` -> `calendar_events.id` (idem) |

Relações declaradas em `CategoryCalendarEvent`:

| Método | Tipo | Observação |
|---|---|---|
| `calendarEvents()` | belongsTo | `category_calendar_events_id` -> `calendar_events.id` (FK invertida: **não** representa a coleção inversa) |
| `contents()` | belongsTo | `id` -> `contents.category_contents_id` (FK invertida, e a coluna não existe em `category_calendar_events`) |

### 2.4 Repository

| Item | Caminho |
|---|---|
| Contrato | `v6/app/Repositories/Contracts/ICalendarEventRepository.php` (interface **vazia**, sem métodos) |
| Implementação | `v6/app/Repositories/Eloquent/Prefecture/CalendarEvent/CalendarEventRepositoryDefaultImpl.php` |
| Base | `v6/app/Repositories/Repository.php` (`upInsert`, `destroy`, `destroyAll`, `dataTrait`) |

`CalendarEventRepositoryDefaultImpl::structureUpInsert()` é onde o slug é gerado
(`slugfy(str_limit($request['title'], 50))` + desambiguação `-N`) e onde as datas são
convertidas de `dd/mm/aaaa hh:mm` para `aaaa-mm-dd hh:mm`
(`toUSDataHourFormat`).

### 2.5 Requests (Form Requests)

| Classe | Caminho | Regras |
|---|---|---|
| `App\Http\Requests\CalendarEventRequest` | `v6/app/Http/Requests/CalendarEventRequest.php` | `category_calendar_events_id` (SelectRule), `title` required|max:120, `initial_date` required, `final_date` nullable, `source`/`local` max:120, `text` required, `archive_highlight` max:20000\|mimes:jpg,jpeg,png,gif,webp |
| `App\Http\Requests\CategoryCalendarEventRequest` | `v6/app/Http/Requests/CategoryCalendarEventRequest.php` | `name` nullable\|max:190 |

Regra auxiliar: `App\Rules\SelectRule` (`v6/app/Rules/SelectRule.php`) — reprova o valor
`'0'` (placeholder "Selecione").

### 2.6 Traits e helpers usados

| Item | Caminho | Uso |
|---|---|---|
| `FileTrait` | `v6/app/Http/Traits/FileTrait.php` | `validFile`, `saveUpload`, `compress` (gera `_min`, `_med`, `_larg`) |
| `AppTrait` | `v6/app/Http/Traits/AppTrait.php` | `slugfy`, `isTrue`, `toUSDataHourFormat`, `traitRequest`, `tError` |
| `GetInformationsPrefectureTrait` | `v6/app/Http/Traits/GetInformationsPrefectureTrait.php` | `getContentByCategoryWith('secretarias'|'orgaos')` — lista os conteúdos das categorias "secretarias" e "orgaos" |
| `functions.php` | `v6/app/Helpers/functions.php` | `addHash`, `slug_fix`, `menuClicks('agenda-evento')`, `getMinFile`, `getLargFile`, `flash()` |
| `appHelpers.php` | `v6/app/Library/Helper/appHelpers.php` | `has_access()` (Sentinel) |

### 2.7 Registro no menu/recursos

| Item | Onde | Valor |
|---|---|---|
| Módulo | `v6/database/migrations/2024_11_01_150206_create_modulos_table.php` + seed | `modulos.id = 39`, `slug = 'agenda-de-eventos'` |
| Recurso (permissão) | `v6/database/seeds/ResourcesTableSeeder.php:468` | `resources.slug = 'agenda-evento'`, `modulo_id = 39` |
| Menu do CMS | `v6/database/seeds/MenuOrderSeeder.php:33` | `menu_orders.slug = 'agenda-evento'`, `modulo_id = 39` |
| Menu do portal | `v6/database/seeds/CategoryContentTableSeeder.php:222` e `v6/database/seeds/MenuItemsTableSeeder.php:173` | Categoria de conteúdo `agenda-eventos` (id 40) e item de menu "Agenda" (ordem 9) |
| Permissões Sentinel | `v6/database/seeds/RolesTableSeeder.php:28` | `agenda-evento.index/.create/.edit/.delete/.inactivate/.restore/.show` |

---

## 3. Fluxo de requisição

### 3.1 CMS — criação/edição de evento

```
POST /cms/agenda-evento            (ou PUT /cms/agenda-evento/{id})
  └─ grupo Route::prefix('cms') + 'before' => 'csrf'   (v6/routes/basepro.php:34)
  └─ middleware check.logged        (resource 'agenda-evento', v6/routes/basepro.php:202)
  └─ CalendarEventRequest            (validações + mensagens em pt-BR)
  └─ CalendarEventController@store / @update
       ├─ DB::beginTransaction()
       ├─ CalendarEventRepositoryDefaultImpl@upInsert
       │    ├─ isTrue(galery) / isTrue(status)          (checkbox 'on' => true)
       │    ├─ secretarias_id/orgaos_id == 0 => null
       │    ├─ structureUpInsert(): slug, datas, categoria
       │    └─ Model::updateOrCreate(['id' => $id], $data)
       ├─ extractedToArchive(): grava imagem de destaque em `archives` (highlight = 1)
       ├─ log(): activity()->performedOn(...)->log('Cadastro de conteúdo')
       └─ DB::commit() + flash('Evento atualizado com sucesso!')->success()
            └─ redirect route('agenda-evento.index')
               ou route('editar-agenda', [id . '#galeria']) quando a galeria foi ativada
```

### 3.2 CMS — listagem (DataTable)

```
GET /cms/datatable/agenda_evento
  └─ DatatableController@get_agenda_eventos            (v6/app/Http/Controllers/Prefecture/DatatableController.php:153)
  └─ DB::table('agenda_de_eventos')->get()              (view SQL)
  └─ struct_views($data, 'agenda-evento')               (adiciona edit/delete/active/inactivate via has_access)
  └─ DataTables::of(...)->make(true)                    (Yajra, resposta JSON)
```

### 3.3 CMS — exclusão em lote

```
POST /cms/agenda-evento/delete-por-lote   { toDelete: [ids] }
  └─ CalendarEventController@deleteByLote
       ├─ activity(): log "Exclusão de eventos" com os ids
       └─ Repository::destroyAll($request->toDelete)  -> DELETE ... WHERE id IN (...)
  └─ JSON { success: 'Eventos deletados com sucesso.' } | { erro: ... } 500
```

### 3.4 Portal — lista / filtro / detalhe

```
GET /agenda-eventos
  └─ middleware check.site.manutention                 (v6/routes/web.php:6)
  └─ Portal\CalendarEventController@index
       ├─ CalendarEvent::with('categoryCalendarEvents','archives')->where('status','1')->paginate(12)
       ├─ CategoryCalendarEvent::all()
       ├─ getTemplateActived()  -> modelo1..modelo5
       └─ view('portal.layouts.calendar-events.index')

GET /agenda-eventos/{category}
  └─ @categoryIndex -> whereHas('categoryCalendarEvents', slug = $slug) + status = 1 + paginate(12)

GET /agenda-eventos/{category}/{slug}
  └─ @show
       ├─ CalendarEvent::where('slug',$slug)->first()
       ├─ Archive::where('calendar_events_id',$content->id)->where('highlight',null)->get()
       ├─ CategoryCalendarEvent::find($content->category_calendar_events_id)
       └─ view('portal.layouts.calendar-events.show')
```

---

## 4. Diagrama do fluxo

```
            +-------------------------+            +----------------------------+
            |   CMS  /cms/agenda-evento|            |  PORTAL /agenda-eventos    |
            +-------------------------+            +----------------------------+
                      |                                        |
              check.logged (Sentinel)               check.site.manutention
                      |                                        |
     +----------------v-----------------+          +-----------v-------------+
     | CalendarEventController          |          | Portal\CalendarEvent   |
     | CategoryCalendarEventController  |          | Controller             |
     +----------------+-----------------+          +-----------+-------------+
                      |                                        |
        CalendarEventRequest /                   (sem repository;
        CategoryCalendarEventRequest)             Eloquent direto)
                      |                                        |
     +----------------v-----------------+          +-----------v-------------+
     | CalendarEventRepositoryDefaultImpl|          | CalendarEvent /         |
     | (upInsert, destroy, destroyAll)  |          | CategoryCalendarEvent / |
     +----------------+-----------------+          | Archive                |
                      |                                        |
     +----------------v-----------------+          +-----------v-------------+
     | Model: CalendarEvent            |          | PostgreSQL              |
     | Model: CategoryCalendarEvent    |          | calendar_events         |
     | Model: Archive (compartilhada)  |          | category_calendar_events|
     +----------------+-----------------+          | archives               |
                      |                             +-----------+-------------+
     +----------------v-----------------+                        |
     | archives / storage/app/public    |          +-----------v-------------+
     | calendar-events/<cat>/<id>/...   |          | Blade portal.layouts.    |
     +----------------------------------+          | calendar-events.*       |
                                                 +-------------------------+
```

## 5. Dependências externas

| Pacote / asset | Origem | Uso no módulo |
|---|---|---|
| `cartalyst/sentinel` | composer | `Sentinel::check()`, `Sentinel::getUser()`, `has_access()` |
| `yajra/laravel-datatables` | composer | `DataTables::of()` na listagem do CMS |
| `spatie/laravel-activitylog` (via `activity()`) | composer | Trilha de auditoria de listagem/cadastro/edição/exclusão |
| Summernote | `v6/public/assets/plugins/summernote/` | Editor WYSIWYG do campo `text` |
| `jquery-upload-files` | `v6/public/assets/plugins/jquery-upload-files/jquery.uploadfile.min.js` | Upload múltiplo da galeria |
| lightgallery | `v6/public/assets/plugins/lightgallery/` | Galeria do portal |
| Intervention Image | composer (via `FileTrait::compress`) | Miniaturas `_min`/`_med`/`_larg` |
| AdminLTE 1.22 | `v6/public/assets/` | Layout do CMS (`admin.layouts.app`) |
| Bootstrap 4 | CDN/vendor | Layout do portal |
