# Módulo: Associe-se

## 1. O que é o módulo

Gerencia a página de registro de interesse em associação (rota `/cms/institucional/associe-se`, portal `/associe-se`). O REFERENCE (`index.html` header e footer) exibe o link "Associe-se" que leva a um formulário de cadastro de interesse: nome, empresa, email, telefone, estado.

O módulo captura leads de pré-associação e permite que administradores do CMS gerenciem esses leads (visualizar, exportar, alterar status). Possui preparação para futura integração com pagamento (assinatura).

## 2. Estrutura de dados

### `v2_membership_leads` — Leads de associação
| Campo | Tipo | Descrição |
|-------|------|-----------|
| id | uuid (string, PK) | UUID primário |
| name | string(255) | Nome completo |
| company | string(255) | Empresa |
| email | string(255) | E-mail |
| phone | string(50) | Telefone |
| state | varchar(2) | UF |
| city | string(255) | Cidade |
| message | text | Mensagem adicional |
| status | enum('pending','contacted','converted','discarded') | Status do lead |
| notes | text | Observações internas do CMS |
| converted_at | timestamp (nullable) | Data de conversão |
| created_at | timestamp | |
| updated_at | timestamp | |
| deleted_at | timestamp (nullable) | Soft delete |

### `v2_membership_plans` — Planos de associação (preparação futura)
| Campo | Tipo | Descrição |
|-------|------|-----------|
| id | uuid (string, PK) | UUID primário |
| name | string(255) | Nome do plano |
| description | text | Descrição |
| price | decimal(10,2) | Valor |
| billing_cycle | varchar(20) | Mensal, anual |
| is_active | boolean | |
| 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 (CMS)
- **Flash messages** (`laracasts/flash`) — feedback no CMS
- **Activity Logger Service** (`ActivityLoggerService`) — auditoria
- **UUID genérico** (`v2_uuid()`) — chaves primárias
- **Mail (Laravel Mail)** — notificação por e-mail ao admin quando novo lead chega
- **Módulo BasicPage** — template de página CMS (padrão)

## 4. Tabelas necessárias

```sql
CREATE TABLE v2_membership_leads (
    id            VARCHAR(36) PRIMARY KEY,
    name          VARCHAR(255) NOT NULL,
    company       VARCHAR(255),
    email         VARCHAR(255) NOT NULL,
    phone         VARCHAR(50),
    state         VARCHAR(2),
    city          VARCHAR(255),
    message       TEXT,
    status        VARCHAR(20) DEFAULT 'pending'
                  CHECK (status IN ('pending','contacted','converted','discarded')),
    notes         TEXT,
    converted_at  TIMESTAMP NULL,
    created_at    TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at    TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    deleted_at    TIMESTAMP NULL
);

CREATE TABLE v2_membership_plans (
    id             VARCHAR(36) PRIMARY KEY,
    name           VARCHAR(255) NOT NULL,
    description    TEXT,
    price          DECIMAL(10,2) NOT NULL,
    billing_cycle  VARCHAR(20) NOT NULL,
    is_active      BOOLEAN DEFAULT TRUE,
    created_at     TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at     TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    deleted_at     TIMESTAMP NULL
);
```

## 5. Arquivos necessários

```
V2/Modules/Institucional/AssocieSe/
├── Controllers/
│   ├── Cms/
│   │   ├── MembershipLeadController.php
│   │   └── MembershipPlanController.php
│   └── Portal/
│       └── AssocieSeController.php
├── Models/
│   ├── MembershipLead.php
│   └── MembershipPlan.php
├── Requests/
│   ├── Cms/
│   │   ├── UpdateLeadStatusRequest.php
│   │   ├── StoreMembershipPlanRequest.php
│   │   └── UpdateMembershipPlanRequest.php
│   └── Portal/
│       └── StoreLeadRequest.php
├── Mail/
│   └── NewMembershipLeadMail.php
├── Services/
│   └── MembershipService.php
├── Views/
│   ├── cms/
│   │   ├── leads/
│   │   │   ├── index.blade.php
│   │   │   └── show.blade.php
│   │   └── plans/
│   │       ├── index.blade.php
│   │       ├── create.blade.php
│   │       └── edit.blade.php
│   └── portal/
│       ├── index.blade.php
│       └── success.blade.php
├── Migrations/
│   ├── 2025_01_01_000004_create_v2_membership_leads_table.php
│   └── 2025_01_01_000005_create_v2_membership_plans_table.php
├── Routes/
│   ├── cms.php
│   └── portal.php
└── ModuleProvider.php
```

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

1. **Migration `v2_membership_leads`**: criar tabela com safe-rollback. Sem FK (lead pode ser de não-usuário).

2. **Migration `v2_membership_plans`**: criar para preparação futura de pagamento.

3. **Model `MembershipLead`**: UUID PK, soft deletes, fillable, casts (`status` como string), escopo por status.

4. **Controller Portal (`AssocieSeController`)**: métodos `index()` (exibe formulário) e `store()` (valida, salva, dispara e-mail de notificação ao admin, flash de sucesso na sessão, retorna view de sucesso).

5. **Controller CMS (`MembershipLeadController`)**: lista leads com filtros (status, data), exportação CSV, atualização de status (contacted, converted, discarded), campo notes.

6. **Controller CMS (`MembershipPlanController`)**: preparação futura — CRUD de planos.

7. **Requests Portal**: `StoreLeadRequest` com validação: nome obrigatório, email obrigatório + formato, phone opcional, state opcional.

8. **Mail**: `NewMembershipLeadMail` notifica `config('v2.support.email')`.

9. **Views CMS**: Bootstrap 5, tabela com badges de status, filtros, paginação.

10. **View Portal**: formulário responsivo Bootstrap 5 com campos: Nome, Empresa, E-mail, Telefone, Estado (select com UFs), Mensagem. Após envio, exibe tela de sucesso.

11. **ModuleProvider**: implementa `ModuleProviderInterface`, sidebar com leads e planos.

12. **Permissions**: `AssocieSe` (GET→read, POST/PUT/DELETE→write).

## 7. Detalhamento de Implementação V2

### a) ModuleProvider.php

```php
<?php

namespace V2\Modules\Institucional\AssocieSe;

use V2\Core\Contracts\ModuleProviderInterface;

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

    public function getConfig(): array
    {
        return [
            'title'          => 'Associe-se',
            'description'    => 'Gestão de leads de associação.',
            'icon'           => 'bi bi-person-plus',
            'position'       => 7,
            'active_pattern' => ['cms/institucional/associe-se*'],
            'route'          => 'v2.associe-se.cms.leads',

            'subModulesLevel1' => [
                [
                    'title'    => 'Leads',
                    'icon'     => 'bi bi-inbox',
                    'route'    => 'v2.associe-se.cms.leads',
                    'position' => 1,
                ],
                [
                    'title'    => 'Planos',
                    'icon'     => 'bi bi-currency-dollar',
                    'route'    => 'v2.associe-se.cms.plans',
                    'position' => 2,
                ],
            ],
        ];
    }
}
```

### b) Migrations

**Migration 1 — `v2_membership_leads`:**

```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 CreateV2MembershipLeadsTable extends Migration
{
    public function up()
    {
        DB::beginTransaction();
        DB::statement("SET lock_timeout = '3s';");

        try {
            Schema::create('v2_membership_leads', function (Blueprint $table) {
                $table->uuid('id')->primary();
                $table->string('name', 255);
                $table->string('company', 255)->nullable();
                $table->string('email', 255);
                $table->string('phone', 50)->nullable();
                $table->string('state', 2)->nullable();
                $table->string('city', 255)->nullable();
                $table->text('message')->nullable();
                $table->string('status', 20)->default('pending');
                $table->text('notes')->nullable();
                $table->timestamp('converted_at')->nullable();
                $table->timestamps();
                $table->softDeletes();

                $table->index('status', 'idx_v2_membership_leads_status');
                $table->index('email', 'idx_v2_membership_leads_email');
                $table->index('created_at', 'idx_v2_membership_leads_created');
            });

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

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

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

**Migration 2 — `v2_membership_plans`:**

```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 CreateV2MembershipPlansTable extends Migration
{
    public function up()
    {
        DB::beginTransaction();
        DB::statement("SET lock_timeout = '3s';");

        try {
            Schema::create('v2_membership_plans', function (Blueprint $table) {
                $table->uuid('id')->primary();
                $table->string('name', 255);
                $table->text('description')->nullable();
                $table->decimal('price', 10, 2);
                $table->string('billing_cycle', 20);
                $table->boolean('is_active')->default(true);
                $table->timestamps();
                $table->softDeletes();

                $table->index('is_active', 'idx_v2_membership_plans_active');
            });

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

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

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

### c) Models

**Model `MembershipLead`:**

```php
<?php

namespace V2\Modules\Institucional\AssocieSe\Models;

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

class MembershipLead extends Model
{
    use SoftDeletes;

    protected $table = 'v2_membership_leads';

    public $incrementing = false;

    protected $keyType = 'string';

    protected $fillable = [
        'id', 'name', 'company', 'email', 'phone', 'state', 'city',
        'message', 'status', 'notes', 'converted_at',
    ];

    protected $casts = [
        'converted_at' => 'datetime',
        'metadata'     => 'array',
    ];

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

    const STATUS_PENDING   = 'pending';
    const STATUS_CONTACTED = 'contacted';
    const STATUS_CONVERTED = 'converted';
    const STATUS_DISCARDED = 'discarded';

    public static $validStatuses = [
        self::STATUS_PENDING,
        self::STATUS_CONTACTED,
        self::STATUS_CONVERTED,
        self::STATUS_DISCARDED,
    ];

    public static $statusLabels = [
        self::STATUS_PENDING   => 'Pendente',
        self::STATUS_CONTACTED => 'Contactado',
        self::STATUS_CONVERTED => 'Convertido',
        self::STATUS_DISCARDED => 'Descartado',
    ];

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

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

    public function scopeByStatus($query, $status)
    {
        return $query->where('status', $status);
    }

    public function scopePending($query)
    {
        return $query->where('status', self::STATUS_PENDING);
    }
}
```

**Model `MembershipPlan`:**

```php
<?php

namespace V2\Modules\Institucional\AssocieSe\Models;

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

class MembershipPlan extends Model
{
    use SoftDeletes;

    protected $table = 'v2_membership_plans';

    public $incrementing = false;

    protected $keyType = 'string';

    protected $fillable = [
        'id', 'name', 'description', 'price', 'billing_cycle', 'is_active',
    ];

    protected $casts = [
        'price'     => 'decimal:2',
        'is_active' => 'boolean',
        '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 scopeActive($query)
    {
        return $query->where('is_active', true);
    }
}
```

### d) CMS Controllers

**`MembershipLeadController`:**

```php
<?php

namespace V2\Modules\Institucional\AssocieSe\Controllers;

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

class MembershipLeadController 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 = MembershipLead::query();

        if ($request->has('status') && in_array($request->input('status'), MembershipLead::$validStatuses)) {
            $query->where('status', $request->input('status'));
        }

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

        $items = $query->orderBy('created_at', 'desc')->paginate(15);

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

    public function show($id)
    {
        $model = MembershipLead::findOrFail($id);
        return view('v2-associe-se::cms.leads.show', compact('model'));
    }

    public function updateStatus(Request $request, $id)
    {
        $model = MembershipLead::findOrFail($id);

        $this->validate($request, [
            'status' => 'required|string|in:' . implode(',', MembershipLead::$validStatuses),
            'notes'  => 'nullable|string',
        ]);

        $oldStatus = $model->status;
        $model->status = $request->input('status');

        if ($request->has('notes')) {
            $model->notes = $request->input('notes');
        }

        if ($model->status === MembershipLead::STATUS_CONVERTED && $oldStatus !== MembershipLead::STATUS_CONVERTED) {
            $model->converted_at = now();
        }

        $model->save();

        $this->activityLogger->log($model, 'updated', [
            'type'       => 'lead_status_update',
            'old_status' => $oldStatus,
            'new_status' => $model->status,
        ]);

        flash('Status do lead atualizado com sucesso!')->success();
        return redirect()->route('v2.associe-se.cms.leads.show', ['id' => $model->id]);
    }

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

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

        flash('Lead "' . $name . '" arquivado com sucesso!')->success();
        return redirect()->route('v2.associe-se.cms.leads');
    }
}
```

**`MembershipPlanController`:**

```php
<?php

namespace V2\Modules\Institucional\AssocieSe\Controllers;

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

class MembershipPlanController 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)
    {
        $items = MembershipPlan::orderBy('price')->paginate(15);
        return view('v2-associe-se::cms.plans.index', compact('items'));
    }

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

    public function store(Request $request)
    {
        $this->validate($request, [
            'name'          => 'required|string|max:255',
            'description'   => 'nullable|string',
            'price'         => 'required|numeric|min:0',
            'billing_cycle' => 'required|string|in:monthly,yearly',
            'is_active'     => 'boolean',
        ]);

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

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

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

    public function edit($id)
    {
        $model = MembershipPlan::findOrFail($id);
        return view('v2-associe-se::cms.plans.edit', compact('model'));
    }

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

        $this->validate($request, [
            'name'          => 'required|string|max:255',
            'description'   => 'nullable|string',
            'price'         => 'required|numeric|min:0',
            'billing_cycle' => 'required|string|in:monthly,yearly',
            'is_active'     => 'boolean',
        ]);

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

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

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

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

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

        flash('Plano "' . $name . '" arquivado com sucesso!')->success();
        return redirect()->route('v2.associe-se.cms.plans');
    }
}
```

### e) Portal Controller

```php
<?php

namespace V2\Modules\Institucional\AssocieSe\Controllers;

use V2\Core\Controllers\BaseController;
use Illuminate\Http\Request;
use V2\Modules\Institucional\AssocieSe\Models\MembershipLead;
use V2\Services\SEO\Services\SEOService;
use Sentinel;
use Mail;

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

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

    public function index()
    {
        $this->seo->setTitle('Associe-se — ' . config('app.name'));
        $this->seo->setDescription('Preencha o formulário e manifeste seu interesse em se associar à SUCESU.');

        return view('v2-associe-se::portal.index');
    }

    public function store(Request $request)
    {
        $this->validate($request, [
            'name'    => 'required|string|max:255',
            'company' => 'nullable|string|max:255',
            'email'   => 'required|email|max:255',
            'phone'   => 'nullable|string|max:50',
            'state'   => 'nullable|string|size:2',
            'city'    => 'nullable|string|max:255',
            'message' => 'nullable|string|max:2000',
        ]);

        $lead = new MembershipLead();
        $lead->id = v2_uuid();
        $lead->fill($request->all());
        $lead->status = MembershipLead::STATUS_PENDING;
        $lead->save();

        // Notifica admin por email
        try {
            Mail::send('v2-associe-se::emails.new-lead', ['lead' => $lead], function ($m) {
                $m->to(config('v2.support.email'))
                  ->subject('Novo lead de associação — ' . config('app.name'));
            });
        } catch (\Exception $e) {
            \Log::error('[AssocieSe] Falha ao enviar notificação: ' . $e->getMessage());
        }

        return view('v2-associe-se::portal.success');
    }
}
```

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

```php
<?php

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

    // Leads
    Route::get('/leads', [
        'as'   => 'v2.associe-se.cms.leads',
        'uses' => 'MembershipLeadController@index',
    ]);

    Route::get('/leads/{id}', [
        'as'   => 'v2.associe-se.cms.leads.show',
        'uses' => 'MembershipLeadController@show',
    ]);

    Route::match(['PUT', 'POST'], '/leads/{id}/status', [
        'as'   => 'v2.associe-se.cms.leads.status',
        'uses' => 'MembershipLeadController@updateStatus',
    ]);

    Route::delete('/leads/{id}', [
        'as'   => 'v2.associe-se.cms.leads.destroy',
        'uses' => 'MembershipLeadController@destroy',
    ]);

    // Planos
    Route::get('/plans', [
        'as'   => 'v2.associe-se.cms.plans',
        'uses' => 'MembershipPlanController@index',
    ]);

    Route::get('/plans/novo', [
        'as'   => 'v2.associe-se.cms.plans.create',
        'uses' => 'MembershipPlanController@create',
    ]);

    Route::post('/plans/novo', [
        'as'   => 'v2.associe-se.cms.plans.store',
        'uses' => 'MembershipPlanController@store',
    ]);

    Route::get('/plans/editar/{id}', [
        'as'   => 'v2.associe-se.cms.plans.edit',
        'uses' => 'MembershipPlanController@edit',
    ]);

    Route::match(['PUT', 'POST'], '/plans/editar/{id}', [
        'as'   => 'v2.associe-se.cms.plans.update',
        'uses' => 'MembershipPlanController@update',
    ]);

    Route::delete('/plans/{id}', [
        'as'   => 'v2.associe-se.cms.plans.destroy',
        'uses' => 'MembershipPlanController@destroy',
    ]);
});
```

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

```php
<?php

Route::group(['middleware' => ['web']], function () {
    Route::get('/associe-se', [
        'as'   => 'v2.associe-se.portal.index',
        'uses' => 'AssocieSeController@index',
    ]);

    Route::post('/associe-se', [
        'as'   => 'v2.associe-se.portal.store',
        'uses' => 'AssocieSeController@store',
    ]);
});
```

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

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

@section('title', 'Leads de Associação')
@section('header-title', 'Leads de Associação')
@section('header-description', 'Gerencie os leads recebidos pelo formulário de associação.')

@section('content')
    <div class="d-flex justify-content-between align-items-center mb-3">
        <form method="GET" class="d-flex gap-2">
            <select name="status" class="form-select" onchange="this.form.submit()">
                <option value="">Todos os status</option>
                @foreach(\V2\Modules\Institucional\AssocieSe\Models\MembershipLead::$statusLabels as $k => $l)
                    <option value="{{ $k }}" {{ request('status') == $k ? 'selected' : '' }}>{{ $l }}</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>
    </div>

    <div class="card">
        <div class="card-body">
            @if(count($items) > 0)
                <table class="table">
                    <thead>
                        <tr>
                            <th>Nome</th>
                            <th>Email</th>
                            <th>Empresa</th>
                            <th>Status</th>
                            <th>Data</th>
                            <th>Ações</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach($items as $item)
                        <tr>
                            <td>{{ $item->name }}</td>
                            <td>{{ $item->email }}</td>
                            <td>{{ $item->company ?? '-' }}</td>
                            <td>
                                <span class="badge bg-{{ $item->status === 'pending' ? 'warning' : ($item->status === 'contacted' ? 'info' : ($item->status === 'converted' ? 'success' : 'secondary')) }}">
                                    {{ \V2\Modules\Institucional\AssocieSe\Models\MembershipLead::$statusLabels[$item->status] ?? $item->status }}
                                </span>
                            </td>
                            <td>{{ $item->created_at->format('d/m/Y H:i') }}</td>
                            <td>
                                <a href="{{ route('v2.associe-se.cms.leads.show', ['id' => $item->id]) }}"
                                   class="btn btn-sm btn-outline-primary">
                                    <i class="bi bi-eye"></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 lead 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="membership-section section-padding fix">
        <div class="container">
            <div class="row justify-content-center">
                <div class="col-lg-8">
                    <div class="text-center mb-5">
                        <h2>Associe-se à SUCESU</h2>
                        <p class="text-muted">Preencha o formulário abaixo para manifestar seu interesse em se associar.</p>
                    </div>
                    <div class="card">
                        <div class="card-body p-4">
                            <form method="POST" action="{{ route('v2.associe-se.portal.store') }}">
                                {{ csrf_field() }}
                                <div class="row g-3">
                                    <div class="col-md-6">
                                        <label class="form-label">Nome *</label>
                                        <input type="text" name="name" class="form-control" required>
                                    </div>
                                    <div class="col-md-6">
                                        <label class="form-label">Empresa</label>
                                        <input type="text" name="company" class="form-control">
                                    </div>
                                    <div class="col-md-6">
                                        <label class="form-label">E-mail *</label>
                                        <input type="email" name="email" class="form-control" required>
                                    </div>
                                    <div class="col-md-6">
                                        <label class="form-label">Telefone</label>
                                        <input type="text" name="phone" class="form-control">
                                    </div>
                                    <div class="col-md-6">
                                        <label class="form-label">Estado</label>
                                        <select name="state" class="form-select">
                                            <option value="">Selecione</option>
                                            @foreach(['AC','AL','AP','AM','BA','CE','DF','ES','GO','MA','MT','MS','MG','PA','PB','PR','PE','PI','RJ','RN','RS','RO','RR','SC','SP','SE','TO'] as $uf)
                                                <option value="{{ $uf }}">{{ $uf }}</option>
                                            @endforeach
                                        </select>
                                    </div>
                                    <div class="col-md-6">
                                        <label class="form-label">Cidade</label>
                                        <input type="text" name="city" class="form-control">
                                    </div>
                                    <div class="col-12">
                                        <label class="form-label">Mensagem</label>
                                        <textarea name="message" class="form-control" rows="4"></textarea>
                                    </div>
                                    <div class="col-12 text-center">
                                        <button type="submit" class="btn btn-primary btn-lg">
                                            Enviar Solicitação
                                        </button>
                                    </div>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </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()` usado internamente nos controllers CMS. |
| **Mail (Laravel)** | Notificação por email ao admin quando novo lead chega via `Mail::send()`. |
| **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 `AssocieSe`. |
| **Super Admin** | Email em `config('v2.support.email')` ou role `super-admin` bypassa tudo. |
| **config('v2.support.email')** | Destinatário das notificações de novo lead. |
