# Módulo: Diretoria

## 1. O que é o módulo

Gerencia a página de Diretoria Executiva da SUCESU (rota `/cms/institucional/diretoria`, portal `/diretoria`). O REFERENCE (`sobre-nos.html` seção `#diretoria`) exibe cards dos membros da diretoria com nome, cargo, foto, descrição e link do LinkedIn, identificando o biênio vigente (ex.: "Biênio 2026–2027").

Permite ao CMS:
- CRUD de membros da diretoria
- Ordenação por posição (Presidente, Vice, Diretores, etc.)
- Upload de foto
- Gerenciar biênios (criar novo biênio, ativar/desativar)
- Associar membros a um biênio

## 2. Estrutura de dados

### `v2_board_members` — Membros da Diretoria
| Campo | Tipo | Descrição |
|-------|------|-----------|
| id | uuid (string, PK) | UUID primário |
| biennium_id | uuid (FK → v2_bienniums.id) | Biênio ao qual pertence |
| name | string(255) | Nome completo |
| position | string(255) | Cargo (ex.: Presidente Executivo, Vice-Presidente Executivo) |
| description | text | Biografia / descrição |
| photo | string(255) | Caminho da foto (storage) |
| linkedin_url | string(255) | URL do LinkedIn |
| email | string(255) | E-mail de contato (opcional) |
| sort_order | integer | Ordem de exibição |
| is_active | boolean | Ativo/inativo |
| created_by | uuid (FK → users.id) | Quem criou |
| created_at | timestamp | |
| updated_at | timestamp | |
| deleted_at | timestamp (nullable) | Soft delete |

### `v2_bienniums` — Biênios
| Campo | Tipo | Descrição |
|-------|------|-----------|
| id | uuid (string, PK) | UUID primário |
| label | string(255) | Ex.: "2026–2027" |
| start_year | integer | Ano inicial |
| end_year | integer | Ano final |
| is_current | boolean | Biênio ativo (exibido no portal) |
| created_at | timestamp | |
| updated_at | timestamp | |
| deleted_at | timestamp (nullable) | Soft delete |

## 3. Dependências

- **Core V2** (`V2ServiceProvider`) — auto-descoberta do módulo
- **Cartalyst Sentinel** — autenticação e permissões
- **Flash messages** (`laracasts/flash`) — feedback no CMS
- **Activity Logger Service** (`ActivityLoggerService`) — auditoria
- **UUID genérico** (`v2_uuid()`) — chaves primárias
- **Storage (Laravel Filesystem)** — upload de fotos
- **Módulo BasicPage** — template de página CMS (padrão)

## 4. Tabelas necessárias

```sql
-- Biênios
CREATE TABLE v2_bienniums (
    id           VARCHAR(36) PRIMARY KEY,
    label        VARCHAR(255) NOT NULL,
    start_year   INTEGER NOT NULL,
    end_year     INTEGER NOT NULL,
    is_current   BOOLEAN DEFAULT FALSE,
    created_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    deleted_at   TIMESTAMP NULL
);

-- Membros da Diretoria
CREATE TABLE v2_board_members (
    id           VARCHAR(36) PRIMARY KEY,
    biennium_id  VARCHAR(36) NOT NULL REFERENCES v2_bienniums(id),
    name         VARCHAR(255) NOT NULL,
    position     VARCHAR(255) NOT NULL,
    description  TEXT,
    photo        VARCHAR(255),
    linkedin_url VARCHAR(255),
    email        VARCHAR(255),
    sort_order   INTEGER DEFAULT 0,
    is_active    BOOLEAN DEFAULT TRUE,
    created_by   VARCHAR(36) REFERENCES users(id),
    created_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    deleted_at   TIMESTAMP NULL
);
```

## 5. Arquivos necessários

```
V2/Modules/Institucional/Diretoria/
├── Controllers/
│   ├── Cms/
│   │   └── BienniumController.php
│   │   └── BoardMemberController.php
│   └── Portal/
│       └── DiretoriaController.php
├── Models/
│   ├── Biennium.php
│   └── BoardMember.php
├── Requests/
│   ├── Cms/
│   │   ├── StoreBienniumRequest.php
│   │   ├── UpdateBienniumRequest.php
│   │   ├── StoreBoardMemberRequest.php
│   │   └── UpdateBoardMemberRequest.php
├── Services/
│   └── DiretoriaService.php
├── Views/
│   ├── cms/
│   │   ├── bienniums/
│   │   │   ├── index.blade.php
│   │   │   ├── create.blade.php
│   │   │   └── edit.blade.php
│   │   └── members/
│   │       ├── index.blade.php
│   │       ├── create.blade.php
│   │       └── edit.blade.php
│   └── portal/
│       └── index.blade.php
├── Migrations/
│   ├── 2025_01_01_000001_create_v2_bienniums_table.php
│   └── 2025_01_01_000002_create_v2_board_members_table.php
├── Routes/
│   ├── cms.php
│   └── portal.php
└── ModuleProvider.php
```

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

1. **Migration `v2_bienniums`**: criar tabela com safe-rollback (no `down()`, renomear para `backup_v2_bienniums`). Seed do biênio 2026-2027 como `is_current = true`.

2. **Migration `v2_board_members`**: criar tabela com FK para `v2_bienniums` e `users`. Safe-rollback.

3. **Model `Biennium`**: UUID primary key (uso de `$incrementing = false`, `$keyType = 'string'`), soft deletes, fillable, relação `hasMany(BoardMember::class)`.

4. **Model `BoardMember`**: UUID PK, soft deletes, fillable, casts (`photo` como string), relação `belongsTo(Biennium::class)`, `belongsTo(User::class, 'created_by')`.

5. **Controllers CMS**:
   - `BienniumController`: CRUD simples com paginação, flash messages, activity log.
   - `BoardMemberController`: CRUD, upload de foto com `Storage::disk('public')`, ordenação por `sort_order`, filtro por biênio.

6. **Controller Portal**: `DiretoriaController` com método `index()` que busca `Biennium::where('is_current', true)->with('members')->first()` e retorna a view.

7. **Views CMS**: Bootstrap 5, inclusive com foto preview. Flash messages.

8. **View Portal**: Renderiza cards conforme REFERENCE — imagem, nome, cargo, descrição, LinkedIn.

9. **ModuleProvider**: implementa `ModuleProviderInterface`, registra sidebar com subníveis (Biênios, Membros).

10. **Permissions**: automáticas via `V2PermissionMiddleware` — `Diretoria` (GET→read, POST/PUT/DELETE→write).

## 7. Detalhamento de Implementação V2

### a) ModuleProvider.php

```php
<?php

namespace V2\Modules\Institucional\Diretoria;

use V2\Core\Contracts\ModuleProviderInterface;

class ModuleProvider implements ModuleProviderInterface
{
    public function __construct()
    {
        $app = app();
    }

    public function getConfig(): array
    {
        return [
            'title'          => 'Diretoria',
            'description'    => 'Gerenciamento da Diretoria Executiva.',
            'icon'           => 'bi bi-people',
            'position'       => 5,
            'active_pattern' => ['cms/institucional/diretoria*'],
            'route'          => 'v2.diretoria.cms.bienniums',

            'subModulesLevel1' => [
                [
                    'title'    => 'Biênios',
                    'icon'     => 'bi bi-calendar-range',
                    'route'    => 'v2.diretoria.cms.bienniums',
                    'position' => 1,
                ],
                [
                    'title'    => 'Membros',
                    'icon'     => 'bi bi-person-badge',
                    'route'    => 'v2.diretoria.cms.members',
                    'position' => 2,
                ],
            ],
        ];
    }
}
```

### b) Migrations

**Migration 1 — `v2_bienniums`:**

```php
<?php

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

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

        try {
            Schema::create('v2_bienniums', function (Blueprint $table) {
                $table->uuid('id')->primary();
                $table->string('label', 255);
                $table->integer('start_year');
                $table->integer('end_year');
                $table->boolean('is_current')->default(false);
                $table->timestamps();
                $table->softDeletes();

                $table->index('is_current', 'idx_v2_bienniums_current');
                $table->index(['start_year', 'end_year'], 'idx_v2_bienniums_years');
            });

            DB::commit();
            Log::info("[Migration] Tabela 'v2_bienniums' criada com sucesso.");
        } catch (\Exception $e) {
            DB::rollBack();
            Log::error("[Migration] ERRO AO CRIAR v2_bienniums: " . $e->getMessage());
            throw $e;
        }
    }

    public function down()
    {
        DB::beginTransaction();
        DB::statement("SET lock_timeout = '3s';");

        try {
            if (Schema::hasTable('v2_bienniums')) {
                $backupTable = 'backup_v2_bienniums_' . date('Y_m_d_His');
                Schema::rename('v2_bienniums', $backupTable);
                Log::alert("[Migration] Tabela 'v2_bienniums' preservada como '{$backupTable}'.");
            }
            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            Log::error("[Migration] ERRO AO REVERTER v2_bienniums: " . $e->getMessage());
            throw $e;
        }
    }
}
```

**Migration 2 — `v2_board_members`:**

```php
<?php

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

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

        try {
            Schema::create('v2_board_members', function (Blueprint $table) {
                $table->uuid('id')->primary();
                $table->uuid('biennium_id');
                $table->string('name', 255);
                $table->string('position', 255);
                $table->text('description')->nullable();
                $table->string('photo', 255)->nullable();
                $table->string('linkedin_url', 255)->nullable();
                $table->string('email', 255)->nullable();
                $table->integer('sort_order')->default(0);
                $table->boolean('is_active')->default(true);
                $table->string('created_by', 36)->nullable();
                $table->timestamps();
                $table->softDeletes();

                $table->foreign('biennium_id')->references('id')->on('v2_bienniums');
                $table->index('biennium_id', 'idx_v2_board_members_biennium');
                $table->index('is_active', 'idx_v2_board_members_active');
                $table->index('sort_order', 'idx_v2_board_members_order');
            });

            DB::commit();
            Log::info("[Migration] Tabela 'v2_board_members' criada com sucesso.");
        } catch (\Exception $e) {
            DB::rollBack();
            Log::error("[Migration] ERRO AO CRIAR v2_board_members: " . $e->getMessage());
            throw $e;
        }
    }

    public function down()
    {
        DB::beginTransaction();
        DB::statement("SET lock_timeout = '3s';");

        try {
            if (Schema::hasTable('v2_board_members')) {
                Schema::table('v2_board_members', function (Blueprint $table) {
                    $table->dropForeign(['biennium_id']);
                });
                $backupTable = 'backup_v2_board_members_' . date('Y_m_d_His');
                Schema::rename('v2_board_members', $backupTable);
                Log::alert("[Migration] Tabela 'v2_board_members' preservada como '{$backupTable}'.");
            }
            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            Log::error("[Migration] ERRO AO REVERTER v2_board_members: " . $e->getMessage());
            throw $e;
        }
    }
}
```

### c) Models

**Model `Biennium`:**

```php
<?php

namespace V2\Modules\Institucional\Diretoria\Models;

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

class Biennium extends Model
{
    use SoftDeletes;

    protected $table = 'v2_bienniums';

    public $incrementing = false;

    protected $keyType = 'string';

    protected $fillable = [
        'id', 'label', 'start_year', 'end_year', 'is_current',
    ];

    protected $casts = [
        'is_current' => 'boolean',
        'start_year' => 'integer',
        'end_year'   => 'integer',
        'metadata'   => 'array',
    ];

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

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

        static::creating(function ($model) {
            if (empty($model->id)) {
                $model->id = v2_uuid();
            }
        });
    }

    public function members()
    {
        return $this->hasMany(BoardMember::class, 'biennium_id', 'id');
    }

    public function scopeCurrent($query)
    {
        return $query->where('is_current', true);
    }
}
```

**Model `BoardMember`:**

```php
<?php

namespace V2\Modules\Institucional\Diretoria\Models;

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

class BoardMember extends Model
{
    use SoftDeletes;

    protected $table = 'v2_board_members';

    public $incrementing = false;

    protected $keyType = 'string';

    protected $fillable = [
        'id', 'biennium_id', 'name', 'position', 'description',
        'photo', 'linkedin_url', 'email', 'sort_order', 'is_active', 'created_by',
    ];

    protected $casts = [
        'is_active'  => 'boolean',
        'sort_order' => 'integer',
        'metadata'   => 'array',
    ];

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

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

        static::creating(function ($model) {
            if (empty($model->id)) {
                $model->id = v2_uuid();
            }
        });

        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 ?? $model->id,
                        'Diretoria',
                        'board_member',
                        $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('board_member', $model->id);
            } catch (\Exception $e) {
                \Log::error('[Module] Falha ao remover da busca: ' . $e->getMessage());
            }
        });
    }

    public function biennium()
    {
        return $this->belongsTo(Biennium::class, 'biennium_id', 'id');
    }

    public function creator()
    {
        return $this->belongsTo(\App\Admin\User::class, 'created_by', 'id');
    }

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

    public function scopeOrdered($query)
    {
        return $query->orderBy('sort_order');
    }
}
```

### d) CMS Controllers

**`BienniumController`:**

```php
<?php

namespace V2\Modules\Institucional\Diretoria\Controllers;

use V2\Core\Controllers\BaseController;
use Illuminate\Http\Request;
use V2\Modules\Institucional\Diretoria\Models\Biennium;
use V2\Services\ActivityLogger\ActivityLoggerService;
use V2\Services\SEO\Services\SEOService;
use Sentinel;

class BienniumController 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 = Biennium::query();

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

        $items = $query->orderBy('start_year', 'desc')->paginate(15);
        return view('v2-diretoria::cms.bienniums.index', compact('items'));
    }

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

    public function store(Request $request)
    {
        $this->validate($request, [
            'label'      => 'required|string|max:255',
            'start_year' => 'required|integer|min:1900|max:2100',
            'end_year'   => 'required|integer|min:1900|max:2100|gt:start_year',
            'is_current' => 'boolean',
        ]);

        if ($request->boolean('is_current')) {
            Biennium::where('is_current', true)->update(['is_current' => false]);
        }

        $model = new Biennium();
        $model->id = v2_uuid();
        $model->fill($request->all());
        $model->save();

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

        flash('Biênio criado com sucesso!')->success();
        return redirect()->route('v2.diretoria.cms.bienniums.edit', ['id' => $model->id]);
    }

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

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

        $this->validate($request, [
            'label'      => 'required|string|max:255',
            'start_year' => 'required|integer|min:1900|max:2100',
            'end_year'   => 'required|integer|min:1900|max:2100|gt:start_year',
            'is_current' => 'boolean',
        ]);

        if ($request->boolean('is_current') && !$model->is_current) {
            Biennium::where('is_current', true)->update(['is_current' => false]);
        }

        $model->update($request->all());

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

        flash('Biênio atualizado com sucesso!')->success();
        return redirect()->route('v2.diretoria.cms.bienniums.edit', ['id' => $model->id]);
    }

    public function destroy($id)
    {
        $model = Biennium::findOrFail($id);
        $label = $model->label;
        $model->delete();

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

        flash('Biênio "' . $label . '" arquivado com sucesso!')->success();
        return redirect()->route('v2.diretoria.cms.bienniums');
    }
}
```

**`BoardMemberController`:**

```php
<?php

namespace V2\Modules\Institucional\Diretoria\Controllers;

use V2\Core\Controllers\BaseController;
use Illuminate\Http\Request;
use V2\Modules\Institucional\Diretoria\Models\BoardMember;
use V2\Modules\Institucional\Diretoria\Models\Biennium;
use V2\Services\ActivityLogger\ActivityLoggerService;
use V2\Services\SEO\Services\SEOService;
use Sentinel;
use Storage;

class BoardMemberController 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 = BoardMember::with('biennium');

        if ($request->has('biennium_id')) {
            $query->where('biennium_id', $request->input('biennium_id'));
        }

        if ($request->has('search')) {
            $searchTerm = $request->input('search');
            $query->where(function ($q) use ($searchTerm) {
                $q->where('name', 'ilike', '%' . $searchTerm . '%')
                  ->orWhere('position', 'ilike', '%' . $searchTerm . '%');
            });
        }

        $items = $query->orderBy('sort_order')->paginate(15);
        $bienniums = Biennium::all();

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

    public function create()
    {
        $bienniums = Biennium::all();
        return view('v2-diretoria::cms.members.create', compact('bienniums'));
    }

    public function store(Request $request)
    {
        $this->validate($request, [
            'biennium_id' => 'required|string|exists:v2_bienniums,id',
            'name'        => 'required|string|max:255',
            'position'    => 'required|string|max:255',
            'description' => 'nullable|string',
            'photo'       => 'nullable|image|mimes:jpeg,png,webp|max:2048',
            'linkedin_url' => 'nullable|url|max:255',
            'email'       => 'nullable|email|max:255',
            'sort_order'  => 'integer|min:0',
            'is_active'   => 'boolean',
        ]);

        $model = new BoardMember();
        $model->id = v2_uuid();
        $model->fill($request->except('photo'));
        $model->created_by = Sentinel::getUser()->id ?? null;

        if ($request->hasFile('photo')) {
            $path = $request->file('photo')->store('diretoria', 'public');
            $model->photo = $path;
        }

        $model->save();

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

        flash('Membro cadastrado com sucesso!')->success();
        return redirect()->route('v2.diretoria.cms.members.edit', ['id' => $model->id]);
    }

    public function edit($id)
    {
        $model = BoardMember::findOrFail($id);
        $bienniums = Biennium::all();
        return view('v2-diretoria::cms.members.edit', compact('model', 'bienniums'));
    }

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

        $this->validate($request, [
            'biennium_id' => 'required|string|exists:v2_bienniums,id',
            'name'        => 'required|string|max:255',
            'position'    => 'required|string|max:255',
            'description' => 'nullable|string',
            'photo'       => 'nullable|image|mimes:jpeg,png,webp|max:2048',
            'linkedin_url' => 'nullable|url|max:255',
            'email'       => 'nullable|email|max:255',
            'sort_order'  => 'integer|min:0',
            'is_active'   => 'boolean',
        ]);

        $model->fill($request->except('photo'));

        if ($request->hasFile('photo')) {
            if ($model->photo) {
                Storage::disk('public')->delete($model->photo);
            }
            $path = $request->file('photo')->store('diretoria', 'public');
            $model->photo = $path;
        }

        $model->save();

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

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

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

        if ($model->photo) {
            Storage::disk('public')->delete($model->photo);
        }

        $model->delete();

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

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

### e) Portal Controller

```php
<?php

namespace V2\Modules\Institucional\Diretoria\Controllers;

use V2\Core\Controllers\BaseController;
use V2\Modules\Institucional\Diretoria\Models\Biennium;
use V2\Services\SEO\Services\SEOService;

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

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

    public function index()
    {
        $biennium = Biennium::with(['members' => function ($q) {
            $q->active()->ordered();
        }])->current()->first();

        $this->seo->setTitle('Diretoria Executiva — ' . config('app.name'));
        $this->seo->setDescription('Conheça a diretoria executiva da SUCESU.');

        return view('v2-diretoria::portal.index', compact('biennium'));
    }
}
```

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

```php
<?php

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

    // Biênios
    Route::get('/bienniums', [
        'as'   => 'v2.diretoria.cms.bienniums',
        'uses' => 'BienniumController@index',
    ]);

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

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

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

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

    Route::delete('/bienniums/{id}', [
        'as'   => 'v2.diretoria.cms.bienniums.destroy',
        'uses' => 'BienniumController@destroy',
    ]);

    // Membros
    Route::get('/members', [
        'as'   => 'v2.diretoria.cms.members',
        'uses' => 'BoardMemberController@index',
    ]);

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

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

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

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

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

### g) Rotas Portal (`Routes/web.php`)

```php
<?php

Route::group(['middleware' => ['web']], function () {
    Route::get('/diretoria', [
        'as'   => 'v2.diretoria.portal.index',
        'uses' => 'DiretoriaController@index',
    ]);
});
```

### h) View CMS — members/index.blade.php

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

@section('title', 'Membros da Diretoria')
@section('header-title', 'Membros da Diretoria')
@section('header-description', 'Gerenciamento dos membros da diretoria executiva.')

@section('content')
    <div class="d-flex justify-content-between align-items-center mb-3">
        <form method="GET" class="d-flex gap-2">
            <select name="biennium_id" class="form-select" onchange="this.form.submit()">
                <option value="">Todos os biênios</option>
                @foreach($bienniums as $b)
                    <option value="{{ $b->id }}" {{ request('biennium_id') == $b->id ? 'selected' : '' }}>
                        {{ $b->label }}
                    </option>
                @endforeach
            </select>
            <input type="text" name="search" class="form-control" placeholder="Buscar..." value="{{ request('search') }}">
            <button class="btn btn-outline-secondary"><i class="bi bi-search"></i></button>
        </form>
        @v2can('Diretoria', 'write')
            <a href="{{ route('v2.diretoria.cms.members.create') }}" class="btn btn-primary">
                <i class="bi bi-plus-circle"></i> Novo Membro
            </a>
        @endv2can
    </div>

    <div class="card">
        <div class="card-body">
            @if(count($items) > 0)
                <table class="table">
                    <thead>
                        <tr>
                            <th>Foto</th>
                            <th>Nome</th>
                            <th>Cargo</th>
                            <th>Biênio</th>
                            <th>Ordem</th>
                            <th>Ativo</th>
                            <th>Ações</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach($items as $item)
                        <tr>
                            <td>
                                @if($item->photo)
                                    <img src="{{ asset('storage/' . $item->photo) }}" width="40" height="40" class="rounded-circle">
                                @else
                                    <i class="bi bi-person-circle fs-4"></i>
                                @endif
                            </td>
                            <td>{{ $item->name }}</td>
                            <td>{{ $item->position }}</td>
                            <td>{{ $item->biennium->label ?? '-' }}</td>
                            <td>{{ $item->sort_order }}</td>
                            <td>
                                <span class="badge bg-{{ $item->is_active ? 'success' : 'secondary' }}">
                                    {{ $item->is_active ? 'Ativo' : 'Inativo' }}
                                </span>
                            </td>
                            <td>
                                <a href="{{ route('v2.diretoria.cms.members.edit', ['id' => $item->id]) }}"
                                   class="btn btn-sm btn-outline-primary">
                                    <i class="bi bi-pencil"></i>
                                </a>
                            </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 membro encontrado.</p>
                </div>
            @endif
        </div>
    </div>
@endsection
```

### i) View Portal — index.blade.php

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

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

@section('content')
    <section class="board-section section-padding fix">
        <div class="container">
            @if($biennium)
                <div class="text-center mb-5">
                    <h2>Diretoria Executiva</h2>
                    <p class="text-muted">Biênio {{ $biennium->label }}</p>
                </div>
                <div class="row g-4">
                    @foreach($biennium->members as $member)
                    <div class="col-xl-3 col-lg-4 col-md-6 wow fadeInUp">
                        <div class="board-card text-center">
                            <div class="board-photo mb-3">
                                @if($member->photo)
                                    <img src="{{ asset('storage/' . $member->photo) }}"
                                         alt="{{ $member->name }}" class="rounded-circle" width="150" height="150">
                                @else
                                    <i class="bi bi-person-circle display-1 text-muted"></i>
                                @endif
                            </div>
                            <h5>{{ $member->name }}</h5>
                            <p class="text-primary fw-bold">{{ $member->position }}</p>
                            <p class="text-muted small">{{ Str::limit($member->description, 120) }}</p>
                            @if($member->linkedin_url)
                                <a href="{{ $member->linkedin_url }}" target="_blank" class="btn btn-sm btn-outline-primary">
                                    <i class="bi bi-linkedin"></i> LinkedIn
                                </a>
                            @endif
                        </div>
                    </div>
                    @endforeach
                </div>
            @else
                <div class="text-center py-5">
                    <i class="bi bi-people fs-1 text-muted"></i>
                    <p class="mt-3">Em breve — diretoria em definição.</p>
                </div>
            @endif
        </div>
    </section>
@endsection
```

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

| Serviço | Como usar |
|---------|-----------|
| **ActivityLoggerService** | Injetado via construtor nos controllers CMS. Log em create, update, destroy. |
| **SEOService** | Injetado no PortalController. `setTitle()`, `setDescription()` na página pública. |
| **Sentinel** | `Sentinel::getUser()` para `created_by` e activity log. |
| **Storage (public)** | Upload de fotos para `diretoria/`, acesso via `asset('storage/...')`. |
| **v2_uuid()** | Geração de UUIDs nos models (boot) e controllers. |
| **flash()** | Mensagens de sucesso/erro em todas as operações de escrita. |
| **v2_can()** | Controle de botões de ação nas views CMS. |
| **V2PermissionMiddleware** | Automático: GET → read, POST/PUT/DELETE → write para permissão `Diretoria`. |
| **Super Admin** | Email em `config('v2.support.email')` ou role `super-admin` bypassa tudo. |
