# Módulo: Agenda de Eventos (GAP)

## 1. O que é o módulo

Gerencia a agenda de eventos da SUCESU. Possui duas páginas no portal:

1. **Listagem** (`/agenda`) — grade de cards 4 colunas com imagem, título, data, local, regional organizadora (ex: "SUCESU PA") e link "Saiba mais".
2. **Detalhe** (`/agenda/{slug}`) — página de detalhe do evento com breadcrumb, imagem grande, organização, data/hora, descrição completa, lista de temas abordados, público-alvo, e botão "Mais agenda".

No CMS, deve permitir CRUD completo com upload de imagem, campos de data, local, regional, grade de programação e controle de publicação.

## 2. Estrutura de dados

### Evento (`evento`)
- `id` (UUID, PK)
- `title` (string, 255) — título do evento
- `slug` (string, 255, unique)
- `image` (string, 255) — imagem principal do card/detalhe
- `description` (text) — descrição completa do evento
- `event_date_start` (datetime) — data/hora de início
- `event_date_end` (datetime, nullable) — data/hora de fim
- `location` (string, 255) — local do evento (ex: "Centro de Eventos da PUCPA")
- `organizer` (string, 255) — regional organizadora (ex: "SUCESU PA")
- `target_audience` (text, nullable) — público-alvo
- `topics_list` (text, nullable) — lista de temas abordados (separados por quebra de linha)
- `schedule` (json, nullable) — grade de programação (array de {time, activity, speaker})
- `is_active` (boolean, default true)
- `published_at` (timestamp, nullable) — data de publicação
- `created_at`, `updated_at`, `deleted_at`

## 3. Dependências

- **Módulos:** Nenhum específico
- **Serviços:** `ActivityLoggerService`
- **Pacotes:** Nenhum adicional
- **Storage:** `public/upload/agenda/` para imagens

## 4. Tabelas necessárias

### `v2_eventos`

| Coluna | Tipo | Restrições |
|---|---|---|
| id | uuid | PK |
| title | varchar(255) | NOT NULL |
| slug | varchar(255) | UNIQUE |
| image | varchar(255) | NULL |
| description | text | NULL |
| event_date_start | datetime | NOT NULL |
| event_date_end | datetime | NULL |
| location | varchar(255) | NULL |
| organizer | varchar(255) | NULL |
| target_audience | text | NULL |
| topics_list | text | NULL |
| schedule | jsonb | NULL |
| is_active | boolean | DEFAULT true |
| published_at | timestamp | NULL |
| created_at | timestamp | NULL |
| updated_at | timestamp | NULL |
| deleted_at | timestamp | NULL |

## 5. Arquivos necessários

```
V2/Modules/Agenda/Eventos/
├── Controllers/
│   ├── EventosCmsController.php        # CRUD administrativo
│   └── EventosPortalController.php     # Páginas públicas
├── Models/
│   └── Evento.php                      # Eloquent model
├── Requests/
│   ├── EventoStoreRequest.php
│   └── EventoUpdateRequest.php
├── Views/
│   ├── cms/
│   │   ├── index.blade.php
│   │   ├── create.blade.php
│   │   ├── edit.blade.php
│   │   └── _form.blade.php
│   └── portal/
│       ├── index.blade.php             # Grade de eventos (/agenda)
│       └── show.blade.php              # Detalhe do evento (/agenda/{slug})
├── Migrations/
│   └── 2024_01_01_000006_create_v2_eventos_table.php
├── Routes/
│   ├── cms.php
│   └── web.php
└── ModuleProvider.php
```

**Total estimado: 14 arquivos**

## 6. Sugestão de implementação

### Migração (safe-rollback)

```php
Schema::create('v2_eventos', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->string('title', 255);
    $table->string('slug', 255)->unique();
    $table->string('image', 255)->nullable();
    $table->text('description')->nullable();
    $table->dateTime('event_date_start');
    $table->dateTime('event_date_end')->nullable();
    $table->string('location', 255)->nullable();
    $table->string('organizer', 255)->nullable();
    $table->text('target_audience')->nullable();
    $table->text('topics_list')->nullable();
    $table->jsonb('schedule')->nullable();
    $table->boolean('is_active')->default(true);
    $table->timestamp('published_at')->nullable();
    $table->timestamps();
    $table->softDeletes();
});

// down(): Schema::rename('v2_eventos', 'backup_v2_eventos');
```

### Model

```php
class Evento extends Model
{
    public $incrementing = false;
    protected $keyType = 'string';
    protected $fillable = [
        'title', 'slug', 'image', 'description',
        'event_date_start', 'event_date_end', 'location',
        'organizer', 'target_audience', 'topics_list',
        'schedule', 'is_active', 'published_at',
    ];
    protected $casts = [
        'is_active' => 'boolean',
        'event_date_start' => 'datetime',
        'event_date_end' => 'datetime',
        'published_at' => 'datetime',
        'schedule' => 'array',
    ];
    protected $dates = [
        'event_date_start', 'event_date_end', 'published_at',
    ];
}
```

### Controller (Portal)

- `index()`: `Evento::where('is_active', true)->where('event_date_start', '>=', now())->orWhereNull('event_date_end')->orderBy('event_date_start')->paginate(12)` — apenas eventos futuros/ativos
- `show($slug)`: `Evento::where('slug', $slug)->where('is_active', true)->firstOrFail()`

### Controller (CMS)

CRUD padrão:
- Schedule gerenciado como JSON no formulário (via JavaScript para adicionar/remover linhas de grade)
- Na view de form, usar textarea para `topics_list` (uma linha = um tópico)
- Upload de imagem: `$request->file('image')->store('public/upload/agenda')`
- Flash messages e ActivityLog conforme padrão

### ModuleProvider

```php
return [
    'title'          => 'Agenda',
    'description'    => 'Gerenciamento de eventos da agenda SUCESU.',
    'icon'           => 'bi bi-calendar-event',
    'position'       => 6,
    'active_pattern' => ['cms/agenda*'],
    'route'          => 'v2.eventos.cms.index',
    'subModulesLevel1' => [
        [
            'title'    => 'Todos os Eventos',
            'icon'     => 'bi bi-list-ul',
            'route'    => 'v2.eventos.cms.index',
            'position' => 1,
        ],
        [
            'title'    => 'Novo Evento',
            'icon'     => 'bi bi-plus-circle',
            'route'    => 'v2.eventos.cms.create',
            'position' => 2,
        ],
    ],
];
```

### Rotas

- **web.php:** `GET /agenda` (index), `GET /agenda/{slug}` (show)
- **cms.php:** prefix `cms/agenda`, CRUD completo

### Formatação na view portal

- Data formatada: `@datetime($evento->event_date_start)` ou manualmente "14 e 15 de maio"
- Local e organizador exibidos abaixo da data
- No detalhe, exibir topics_list como `<ul>`, schedule como tabela
- Usar `$evento->schedule` como array e iterar no Blade

## 7. Detalhamento de Implementação V2

### a) ModuleProvider.php

```php
<?php

namespace V2\Modules\Agenda\Eventos;

use V2\Core\Contracts\ModuleProviderInterface;

class ModuleProvider implements ModuleProviderInterface
{
    public function __construct()
    {
    }

    public function getConfig(): array
    {
        return [
            'title'          => 'Agenda',
            'description'    => 'Gerenciamento de eventos da agenda SUCESU.',
            'icon'           => 'bi bi-calendar-event',
            'position'       => 6,
            'active_pattern' => ['cms/agenda*'],
            'route'          => 'v2.eventos.cms.index',
            'subModulesLevel1' => [
                [
                    'title'    => 'Todos os Eventos',
                    'icon'     => 'bi bi-list-ul',
                    'route'    => 'v2.eventos.cms.index',
                    'position' => 1,
                ],
                [
                    'title'    => 'Novo Evento',
                    'icon'     => 'bi bi-plus-circle',
                    'route'    => 'v2.eventos.cms.create',
                    'position' => 2,
                ],
            ],
        ];
    }
}
```

### b) Migration (safe-rollback)

```php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;

class CreateV2EventosTable extends Migration
{
    public function up()
    {
        DB::beginTransaction();
        DB::statement("SET lock_timeout = '3s';");

        try {
            Schema::create('v2_eventos', function (Blueprint $table) {
                $table->uuid('id')->primary();
                $table->string('title', 255);
                $table->string('slug', 255)->unique();
                $table->string('image', 255)->nullable();
                $table->text('description')->nullable();
                $table->dateTime('event_date_start');
                $table->dateTime('event_date_end')->nullable();
                $table->string('location', 255)->nullable();
                $table->string('organizer', 255)->nullable();
                $table->text('target_audience')->nullable();
                $table->text('topics_list')->nullable();
                $table->jsonb('schedule')->nullable();
                $table->boolean('is_active')->default(true);
                $table->timestamp('published_at')->nullable();
                $table->string('created_by', 36)->nullable();
                $table->timestamps();
                $table->softDeletes();
                $table->index('is_active', 'idx_v2_eventos_active');
                $table->index('event_date_start', 'idx_v2_eventos_date');
                $table->index('published_at', 'idx_v2_eventos_published');
            });

            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }

    public function down()
    {
        DB::beginTransaction();
        try {
            $backupTable = 'backup_v2_eventos_' . date('Ymd_His');
            Schema::rename('v2_eventos', $backupTable);
            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }
}
```

### c) Model (Evento.php)

```php
<?php

namespace V2\Modules\Agenda\Eventos\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Evento extends Model
{
    use SoftDeletes;

    protected $table = 'v2_eventos';

    public $incrementing = false;

    protected $keyType = 'string';

    protected $fillable = [
        'title', 'slug', 'image', 'description',
        'event_date_start', 'event_date_end', 'location',
        'organizer', 'target_audience', 'topics_list',
        'schedule', 'is_active', 'published_at',
    ];

    protected $casts = [
        'is_active'       => 'boolean',
        'event_date_start' => 'datetime',
        'event_date_end'  => 'datetime',
        'published_at'    => 'datetime',
        'schedule'        => 'array',
        'metadata'        => 'array',
    ];

    protected $dates = [
        'created_at', 'updated_at', 'deleted_at',
        'event_date_start', 'event_date_end', 'published_at',
    ];

    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            if (empty($model->id)) {
                $model->id = v2_uuid();
            }
            if (empty($model->slug) && !empty($model->title)) {
                $model->slug = str_slug($model->title);
            }
        });

        static::saving(function ($model) {
            // Garantir unicidade do slug
            $baseSlug = $model->slug ?: str_slug($model->title);
            $slug = $baseSlug;
            $counter = 1;
            if (!empty($model->id)) {
                while (static::where('slug', $slug)->where('id', '!=', $model->id)->exists()) {
                    $slug = $baseSlug . '-' . $counter;
                    $counter++;
                }
            } else {
                while (static::where('slug', $slug)->exists()) {
                    $slug = $baseSlug . '-' . $counter;
                    $counter++;
                }
            }
            $model->slug = $slug;
        });

        static::saved(function ($model) {
            try {
                app(\V2\Services\Search\Services\PublicIndexerService::class)->index(
                    new \V2\Services\Search\DTOs\PublicSearchDTO(
                        $model->title ?? $model->name,
                        $model->slug,
                        'Eventos',
                        'evento',
                        $model->id,
                        $model->description ?? '',
                        null,
                        ['tags' => []]
                    )
                );
            } catch (\Exception $e) {
                \Log::error('[Module] Falha ao sincronizar busca: ' . $e->getMessage());
            }
        });

        static::deleted(function ($model) {
            try {
                app(\V2\Services\Search\Services\PublicIndexerService::class)->remove('evento', $model->id);
            } catch (\Exception $e) {
                \Log::error('[Module] Falha ao remover da busca: ' . $e->getMessage());
            }
        });
    }

    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }

    public function scopePublished($query)
    {
        return $query->where('is_active', true)
                     ->whereNotNull('published_at')
                     ->where('published_at', '<=', now());
    }

    public function scopeUpcoming($query)
    {
        return $query->where('event_date_start', '>=', now())
                     ->orWhere(function ($q) {
                         $q->whereNotNull('event_date_end')
                           ->where('event_date_end', '>=', now());
                     });
    }

    public function scopeRecent($query)
    {
        return $query->orderBy('event_date_start', 'desc');
    }

    public function scopeUpcomingOrdered($query)
    {
        return $query->orderBy('event_date_start', 'asc');
    }
}
```

### d) CMS Controller (EventosCmsController.php)

```php
<?php

namespace V2\Modules\Agenda\Eventos\Controllers;

use V2\Core\Controllers\BaseController;
use Illuminate\Http\Request;
use V2\Modules\Agenda\Eventos\Models\Evento;
use V2\Services\ActivityLogger\ActivityLoggerService;
use V2\Services\SEO\Services\SEOService;
use Sentinel;
use Str;

class EventosCmsController extends BaseController
{
    /** @var ActivityLoggerService */
    protected $activityLogger;

    /** @var SEOService */
    protected $seo;

    public function __construct(
        ActivityLoggerService $activityLogger,
        SEOService $seo
    ) {
        $this->activityLogger = $activityLogger;
        $this->seo = $seo;
    }

    public function index(Request $request)
    {
        $query = Evento::query();

        if ($request->has('search') && !empty($request->input('search'))) {
            $searchTerm = $request->input('search');
            $query->where(function ($q) use ($searchTerm) {
                $q->where('title', 'ilike', '%' . $searchTerm . '%');
            });
        }

        if ($request->has('filter_status')) {
            $filter = $request->input('filter_status');
            if ($filter === 'published') {
                $query->published();
            } elseif ($filter === 'draft') {
                $query->whereNull('published_at');
            }
        }

        $items = $query->recent()->paginate(15);

        return view('v2-eventos::cms.index', compact('items'));
    }

    public function create()
    {
        return view('v2-eventos::cms.create');
    }

    public function store(Request $request)
    {
        $data = $request->validate([
            'title'           => 'required|string|max:255',
            'slug'            => 'nullable|string|max:255',
            'image'           => 'nullable|image|mimes:jpeg,png,jpg,webp|max:4096',
            'description'     => 'nullable|string',
            'event_date_start' => 'required|date',
            'event_date_end'  => 'nullable|date|after_or_equal:event_date_start',
            'location'        => 'nullable|string|max:255',
            'organizer'       => 'nullable|string|max:255',
            'target_audience' => 'nullable|string',
            'topics_list'     => 'nullable|string',
            'schedule'        => 'nullable|array',
            'is_active'       => 'nullable|boolean',
            'published_at'    => 'nullable|date',
        ]);

        $model = new Evento();
        $model->id = v2_uuid();
        $model->title = $data['title'];
        $model->slug = !empty($data['slug']) ? $data['slug'] : Str::slug($data['title']);
        $model->description = $data['description'] ?? null;
        $model->event_date_start = $data['event_date_start'];
        $model->event_date_end = $data['event_date_end'] ?? null;
        $model->location = $data['location'] ?? null;
        $model->organizer = $data['organizer'] ?? null;
        $model->target_audience = $data['target_audience'] ?? null;
        $model->topics_list = $data['topics_list'] ?? null;
        $model->schedule = $data['schedule'] ?? null;
        $model->is_active = $data['is_active'] ?? true;
        $model->published_at = $data['published_at'] ?? now();
        $model->author_id = Sentinel::getUser()->id ?? null;

        if ($request->hasFile('image')) {
            $model->image = $request->file('image')->store('public/upload/agenda');
        }

        $model->save();

        $this->activityLogger->log($model, 'created', ['type' => 'evento_create']);

        flash('Evento criado com sucesso!')->success();
        return redirect()->route('v2.eventos.cms.edit', ['id' => $model->id]);
    }

    public function edit($id)
    {
        $model = Evento::findOrFail($id);
        return view('v2-eventos::cms.edit', compact('model'));
    }

    public function update(Request $request, $id)
    {
        $model = Evento::findOrFail($id);

        $data = $request->validate([
            'title'           => 'required|string|max:255',
            'slug'            => 'nullable|string|max:255|unique:v2_eventos,slug,' . $id . ',id',
            'image'           => 'nullable|image|mimes:jpeg,png,jpg,webp|max:4096',
            'description'     => 'nullable|string',
            'event_date_start' => 'required|date',
            'event_date_end'  => 'nullable|date|after_or_equal:event_date_start',
            'location'        => 'nullable|string|max:255',
            'organizer'       => 'nullable|string|max:255',
            'target_audience' => 'nullable|string',
            'topics_list'     => 'nullable|string',
            'schedule'        => 'nullable|array',
            'is_active'       => 'nullable|boolean',
            'published_at'    => 'nullable|date',
        ]);

        $model->title = $data['title'];
        if (!empty($data['slug'])) {
            $model->slug = $data['slug'];
        }
        $model->description = $data['description'] ?? null;
        $model->event_date_start = $data['event_date_start'];
        $model->event_date_end = $data['event_date_end'] ?? null;
        $model->location = $data['location'] ?? null;
        $model->organizer = $data['organizer'] ?? null;
        $model->target_audience = $data['target_audience'] ?? null;
        $model->topics_list = $data['topics_list'] ?? null;
        $model->schedule = $data['schedule'] ?? null;
        $model->is_active = $data['is_active'] ?? true;
        $model->published_at = $data['published_at'] ?? $model->published_at;

        if ($request->hasFile('image')) {
            $model->image = $request->file('image')->store('public/upload/agenda');
        }

        $model->save();

        $this->activityLogger->log($model, 'updated', ['type' => 'evento_update']);

        flash('Evento atualizado com sucesso!')->success();
        return redirect()->route('v2.eventos.cms.edit', ['id' => $model->id]);
    }

    public function destroy($id)
    {
        $model = Evento::findOrFail($id);
        $name = $model->title;
        $model->delete();

        $this->activityLogger->log($model, 'archived', ['type' => 'evento_archive']);

        flash('"' . $name . '" arquivado com sucesso!')->success();
        return redirect()->route('v2.eventos.cms.index');
    }
}
```

### e) Portal Controller (EventosPortalController.php)

```php
<?php

namespace V2\Modules\Agenda\Eventos\Controllers;

use V2\Core\Controllers\BaseController;
use V2\Modules\Agenda\Eventos\Models\Evento;
use V2\Services\SEO\Services\SEOService;

class EventosPortalController extends BaseController
{
    /** @var SEOService */
    protected $seo;

    public function __construct(SEOService $seo)
    {
        $this->seo = $seo;
    }

    public function index()
    {
        $items = Evento::published()->upcoming()->upcomingOrdered()->paginate(12);

        $this->seo->setTitle('Agenda de Eventos — ' . config('app.name'));
        $this->seo->setDescription('Confira a agenda de eventos da SUCESU Nacional.');

        return view('v2-eventos::portal.index', compact('items'));
    }

    public function show($slug)
    {
        $model = Evento::where('slug', $slug)->published()->firstOrFail();

        $this->seo->setTitle($model->title . ' — Agenda SUCESU');
        $this->seo->setDescription(Str::limit(strip_tags($model->description), 160));
        $this->seo->bindModel($model);

        $topicsList = !empty($model->topics_list)
            ? array_filter(explode("\n", $model->topics_list))
            : [];

        return view('v2-eventos::portal.show', compact('model', 'topicsList'));
    }
}
```

### f) Rotas CMS (Routes/cms.php)

```php
<?php

Route::group([
    'middleware' => [\V2\Core\Middleware\CmsAuthenticate::class],
    'prefix'     => 'cms/agenda',
], function () {

    Route::get('/', [
        'as'   => 'v2.eventos.cms.index',
        'uses' => 'EventosCmsController@index',
    ]);

    Route::get('/novo', [
        'as'   => 'v2.eventos.cms.create',
        'uses' => 'EventosCmsController@create',
    ]);

    Route::post('/novo', [
        'as'   => 'v2.eventos.cms.store',
        'uses' => 'EventosCmsController@store',
    ]);

    Route::get('/editar/{id}', [
        'as'   => 'v2.eventos.cms.edit',
        'uses' => 'EventosCmsController@edit',
    ]);

    Route::match(['PUT', 'POST'], '/editar/{id}', [
        'as'   => 'v2.eventos.cms.update',
        'uses' => 'EventosCmsController@update',
    ]);

    Route::delete('/{id}', [
        'as'   => 'v2.eventos.cms.destroy',
        'uses' => 'EventosCmsController@destroy',
    ]);
});
```

### g) Rotas Web (Routes/web.php)

```php
<?php

Route::group(['middleware' => ['web']], function () {

    Route::get('/agenda', [
        'as'   => 'v2.eventos.portal.index',
        'uses' => 'EventosPortalController@index',
    ]);

    Route::get('/agenda/{slug}', [
        'as'   => 'v2.eventos.portal.show',
        'uses' => 'EventosPortalController@show',
    ]);
});
```

### h) View CMS (index.blade.php)

```blade
@extends('v2-cms::layouts.app')

@section('title', 'Agenda de Eventos')
@section('header-title', 'Agenda')
@section('header-description', 'Gerenciamento de eventos')

@section('content')
    <div class="card">
        <div class="card-header d-flex justify-content-between align-items-center">
            <form method="GET" class="d-flex gap-2 align-items-center">
                <input type="text" name="search" class="form-control form-control-sm"
                       placeholder="Buscar..." value="{{ request('search') }}">
                <select name="filter_status" class="form-select form-select-sm">
                    <option value="">Todos</option>
                    <option value="published" {{ request('filter_status') === 'published' ? 'selected' : '' }}>Publicados</option>
                    <option value="draft" {{ request('filter_status') === 'draft' ? 'selected' : '' }}>Rascunhos</option>
                </select>
                <button type="submit" class="btn btn-sm btn-outline-secondary">
                    <i class="bi bi-search"></i>
                </button>
            </form>
            @v2can('Eventos', 'write')
                <a href="{{ route('v2.eventos.cms.create') }}" class="btn btn-sm btn-primary">
                    <i class="bi bi-plus-circle"></i> Novo Evento
                </a>
            @endv2can
        </div>
        <div class="card-body">
            @if(count($items) > 0)
                <table class="table table-hover">
                    <thead>
                        <tr>
                            <th>Imagem</th>
                            <th>Título</th>
                            <th>Data</th>
                            <th>Local</th>
                            <th>Status</th>
                            <th>Ações</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach($items as $item)
                        <tr>
                            <td>
                                @if($item->image)
                                    <img src="{{ Storage::url($item->image) }}" alt="{{ $item->title }}"
                                         style="max-height: 40px;">
                                @endif
                            </td>
                            <td>{{ $item->title }}</td>
                            <td>{{ $item->event_date_start->format('d/m/Y H:i') }}</td>
                            <td>{{ $item->location }}</td>
                            <td>
                                <span class="badge bg-{{ $item->published_at ? 'success' : 'warning' }}">
                                    {{ $item->published_at ? 'Publicado' : 'Rascunho' }}
                                </span>
                            </td>
                            <td>
                                <a href="{{ route('v2.eventos.cms.edit', ['id' => $item->id]) }}"
                                   class="btn btn-sm btn-outline-primary">
                                    <i class="bi bi-pencil"></i>
                                </a>
                                <form action="{{ route('v2.eventos.cms.destroy', ['id' => $item->id]) }}"
                                      method="POST" class="d-inline"
                                      onsubmit="return confirm('Arquivar este evento?');">
                                    {{ csrf_field() }}
                                    {{ method_field('DELETE') }}
                                    <button type="submit" class="btn btn-sm btn-outline-danger">
                                        <i class="bi bi-archive"></i>
                                    </button>
                                </form>
                            </td>
                        </tr>
                        @endforeach
                    </tbody>
                </table>
                {{ $items->links() }}
            @else
                <div class="text-center py-4 text-muted">
                    <i class="bi bi-inbox fs-1"></i>
                    <p class="mt-2">Nenhum evento encontrado.</p>
                </div>
            @endif
        </div>
    </div>
@endsection
```

### i) Views Portal

**portal/index.blade.php:**

```blade
@extends('v2-portal::layouts.app')

@section('seo')
    @include('v2-portal::partials.seo')
@endsection

@section('content')
    <section class="agenda-section section-padding fix">
        <div class="container">
            <div class="row g-4">
                @foreach($items as $item)
                <div class="col-xl-4 col-lg-6 col-md-6 wow fadeInUp">
                    <div class="evento-card">
                        @if($item->image)
                            <img src="{{ Storage::url($item->image) }}" alt="{{ $item->title }}"
                                 class="img-fluid">
                        @endif
                        <div class="evento-info p-3">
                            <h3>
                                <a href="{{ route('v2.eventos.portal.show', ['slug' => $item->slug]) }}">
                                    {{ $item->title }}
                                </a>
                            </h3>
                            <p class="text-muted">
                                <i class="bi bi-calendar"></i>
                                {{ $item->event_date_start->format('d/m/Y H:i') }}
                                @if($item->event_date_end)
                                    — {{ $item->event_date_end->format('d/m/Y H:i') }}
                                @endif
                            </p>
                            <p class="text-muted">
                                <i class="bi bi-geo-alt"></i> {{ $item->location }}
                                <br>
                                <i class="bi bi-people"></i> {{ $item->organizer }}
                            </p>
                            <a href="{{ route('v2.eventos.portal.show', ['slug' => $item->slug]) }}"
                               class="link-btn">
                                Saiba Mais <i class="fa-light fa-arrow-right-long"></i>
                            </a>
                        </div>
                    </div>
                </div>
                @endforeach
            </div>
            {{ $items->links() }}
        </div>
    </section>
@endsection
```

**portal/show.blade.php:**

```blade
@extends('v2-portal::layouts.app')

@section('seo')
    @include('v2-portal::partials.seo')
@endsection

@section('content')
    <section class="evento-detail-section section-padding fix">
        <div class="container">
            <nav aria-label="breadcrumb">
                <ol class="breadcrumb">
                    <li class="breadcrumb-item"><a href="{{ route('v2.eventos.portal.index') }}">Agenda</a></li>
                    <li class="breadcrumb-item active">{{ $model->title }}</li>
                </ol>
            </nav>

            @if($model->image)
                <img src="{{ Storage::url($model->image) }}" alt="{{ $model->title }}"
                     class="img-fluid mb-4">
            @endif

            <h1>{{ $model->title }}</h1>

            <div class="evento-meta mb-4">
                <p><strong>Data:</strong>
                    {{ $model->event_date_start->format('d/m/Y H:i') }}
                    @if($model->event_date_end)
                        — {{ $model->event_date_end->format('d/m/Y H:i') }}
                    @endif
                </p>
                <p><strong>Local:</strong> {{ $model->location }}</p>
                <p><strong>Organização:</strong> {{ $model->organizer }}</p>
            </div>

            <div class="evento-description mb-4">
                {!! nl2br(e($model->description)) !!}
            </div>

            @if(!empty($topicsList))
                <div class="evento-topics mb-4">
                    <h3>Temas Abordados</h3>
                    <ul>
                        @foreach($topicsList as $topic)
                            <li>{{ $topic }}</li>
                        @endforeach
                    </ul>
                </div>
            @endif

            @if($model->target_audience)
                <div class="evento-audience mb-4">
                    <h3>Público-Alvo</h3>
                    <p>{{ $model->target_audience }}</p>
                </div>
            @endif

            @if($model->schedule && count($model->schedule) > 0)
                <div class="evento-schedule mb-4">
                    <h3>Programação</h3>
                    <table class="table">
                        <thead>
                            <tr>
                                <th>Horário</th>
                                <th>Atividade</th>
                                <th>Palestrante</th>
                            </tr>
                        </thead>
                        <tbody>
                            @foreach($model->schedule as $entry)
                            <tr>
                                <td>{{ $entry['time'] ?? '' }}</td>
                                <td>{{ $entry['activity'] ?? '' }}</td>
                                <td>{{ $entry['speaker'] ?? '' }}</td>
                            </tr>
                            @endforeach
                        </tbody>
                    </table>
                </div>
            @endif

            <a href="{{ route('v2.eventos.portal.index') }}" class="btn btn-outline-primary">
                <i class="bi bi-arrow-left"></i> Mais agenda
            </a>
        </div>
    </section>
@endsection
```

### j) Dependências e Integrações

- **ActivityLoggerService**: Injetado no `EventosCmsController` para auditoria em create/update/destroy.
- **SEOService**: Injetado em ambos os controllers para metadados de página.
- **Storage**: Upload de imagens para `public/upload/agenda/`. Usar `Storage::url($model->image)` na exibição.
- **Schedule JSON**: Campo `schedule` armazena array de objetos `{time, activity, speaker}`. Gerenciado via JavaScript no formulário CMS.
- **Permissões**: `@v2can('Eventos', 'write')` nas views CMS para controle de acesso.
- **Scopes**: `published()` para eventos publicados, `upcoming()` para eventos futuros, `upcomingOrdered()` para ordenação ascendente por data de início.
