# Módulo: Associados (GAP)

## 1. O que é o módulo

Gerencia a listagem pública de empresas associadas à SUCESU Nacional. Cada associado é exibido em formato de cards lado a lado (2 colunas no REFERENCE) com logo, nome, descrição, "associado desde" e links para redes sociais (LinkedIn, site, YouTube, Instagram, Facebook/X). A página possui paginação.

No CMS, deve permitir CRUD completo dos associados: cadastro com upload de logo, campos de texto, links de redes sociais, ordenação e status de exibição.

## 2. Estrutura de dados

**Associado (`associado`)**
- `id` (UUID, PK)
- `name` (string, 255) — nome da empresa
- `slug` (string, 255, unique) — slug para URL amigável
- `description` (text) — descrição da empresa
- `logo` (string, 255) — caminho da imagem do logo
- `url` (string, 255, nullable) — site da empresa
- `linkedin_url` (string, 255, nullable)
- `youtube_url` (string, 255, nullable)
- `instagram_url` (string, 255, nullable)
- `facebook_url` (string, 255, nullable)
- `x_url` (string, 255, nullable) — Twitter/X
- `associate_since` (date, nullable) — data de associação (ex: "11/2025")
- `sort_order` (integer, default 0) — ordenação manual
- `is_active` (boolean, default true)
- `created_at` (timestamp)
- `updated_at` (timestamp)
- `deleted_at` (timestamp, nullable) — soft delete

## 3. Dependências

- **Módulos:** Nenhum específico além do core V2
- **Serviços:** `ActivityLoggerService` (para log de auditoria)
- **Pacotes:** Nenhum adicional
- **Storage:** `public/upload/associados/` para logos

## 4. Tabelas necessárias

### `v2_associados`

| Coluna | Tipo | Restrições |
|---|---|---|
| id | uuid | PK |
| name | varchar(255) | NOT NULL |
| slug | varchar(255) | UNIQUE, NOT NULL |
| description | text | NULL |
| logo | varchar(255) | NULL |
| url | varchar(255) | NULL |
| linkedin_url | varchar(255) | NULL |
| youtube_url | varchar(255) | NULL |
| instagram_url | varchar(255) | NULL |
| facebook_url | varchar(255) | NULL |
| x_url | varchar(255) | NULL |
| associate_since | date | NULL |
| sort_order | integer | DEFAULT 0 |
| is_active | boolean | DEFAULT true |
| created_at | timestamp | NULL |
| updated_at | timestamp | NULL |
| deleted_at | timestamp | NULL |

## 5. Arquivos necessários

```
V2/Modules/Publicacoes/Associados/
├── Controllers/
│   ├── AssociadosCmsController.php      # CRUD administrativo
│   └── AssociadosPortalController.php   # Listagem pública
├── Models/
│   └── Associado.php                    # Eloquent model
├── Requests/
│   ├── AssociadoStoreRequest.php        # Validação de criação
│   └── AssociadoUpdateRequest.php       # Validação de atualização
├── Services/
│   └── AssociadoService.php             # Lógica de negócio (opcional)
├── Views/
│   ├── cms/
│   │   ├── index.blade.php              # Listagem CMS
│   │   ├── create.blade.php             # Formulário de criação
│   │   ├── edit.blade.php               # Formulário de edição
│   │   └── _form.blade.php              # Partial do formulário
│   └── portal/
│       └── index.blade.php              # Página pública de associados
├── Migrations/
│   └── 2024_01_01_000001_create_v2_associados_table.php
├── Routes/
│   ├── cms.php                          # Rotas CMS (prefix: cms/associados)
│   └── web.php                          # Rotas públicas (prefix: /associados)
└── ModuleProvider.php                   # Implementa ModuleProviderInterface
```

**Total estimado: 15 arquivos**

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

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

```php
Schema::create('v2_associados', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->string('name', 255);
    $table->string('slug', 255)->unique();
    $table->text('description')->nullable();
    $table->string('logo', 255)->nullable();
    $table->string('url', 255)->nullable();
    $table->string('linkedin_url', 255)->nullable();
    $table->string('youtube_url', 255)->nullable();
    $table->string('instagram_url', 255)->nullable();
    $table->string('facebook_url', 255)->nullable();
    $table->string('x_url', 255)->nullable();
    $table->date('associate_since')->nullable();
    $table->integer('sort_order')->default(0);
    $table->boolean('is_active')->default(true);
    $table->timestamps();
    $table->softDeletes();
});

// down(): Schema::rename('v2_associados', 'backup_v2_associados');
```

### Model

```php
class Associado extends Model
{
    public $incrementing = false;
    protected $keyType = 'string';
    protected $fillable = [
        'name', 'slug', 'description', 'logo', 'url',
        'linkedin_url', 'youtube_url', 'instagram_url',
        'facebook_url', 'x_url', 'associate_since',
        'sort_order', 'is_active',
    ];
    protected $casts = [
        'is_active' => 'boolean',
        'associate_since' => 'date',
    ];
}
```

### Controller (Portal)

Extrair os associados ativos ordenados por `sort_order` e paginados (ex: 12 por página). Retornar para a view `v2-associados::portal.index`.

### Controller (CMS)

CRUD padrão com:
- `index()` — listagem com paginação, filtro por ativo/inativo
- `create()` + `store()` — formulário com upload de logo (usar `$request->file('logo')->store('public/upload/associados')`)
- `edit()` + `update()` — atualização com replace opcional de logo
- `destroy()` — soft delete com flash message `->success()`

### ModuleProvider

```php
return [
    'title'          => 'Associados',
    'description'    => 'Gerenciamento de empresas associadas.',
    'icon'           => 'bi bi-building',
    'position'       => 3,
    'active_pattern' => ['cms/associados*'],
    'route'          => 'v2.associados.cms.index',
    'subModulesLevel1' => [
        [
            'title'    => 'Todos os Associados',
            'icon'     => 'bi bi-list-ul',
            'route'    => 'v2.associados.cms.index',
            'position' => 1,
        ],
        [
            'title'    => 'Novo Associado',
            'icon'     => 'bi bi-plus-circle',
            'route'    => 'v2.associados.cms.create',
            'position' => 2,
        ],
    ],
];
```

### Rotas públicas (web.php)

```php
Route::get('/associados', [
    'as'   => 'v2.associados.portal.index',
    'uses' => 'AssociadosPortalController@index',
]);
```

### Rotas CMS (cms.php)

Prefix `cms/associados` com middleware `CmsAuthenticate`. CRUD completo: GET /, GET /novo, POST /novo, GET /editar/{id}, PUT /editar/{id}, DELETE /{id}.

### Observações

- Usar `Str::slug($request->name)` para gerar slug automaticamente no store/update
- Logar atividade com `$this->activityLogger->log($associado, 'created', ['type' => 'module_action', 'description' => 'Associado criado/atualizado/removido'])`
- As URLs de redes sociais devem ser validadas como URLs válidas nos Requests
- Para exibir o logo na view pública, usar `Storage::url($associado->logo)`

## 7. Detalhamento de Implementação V2

### a) ModuleProvider.php

```php
<?php

namespace V2\Modules\Publicacoes\Associados;

use V2\Core\Contracts\ModuleProviderInterface;

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

    public function getConfig(): array
    {
        return [
            'title'          => 'Associados',
            'description'    => 'Gerenciamento de empresas associadas.',
            'icon'           => 'bi bi-building',
            'position'       => 3,
            'active_pattern' => ['cms/associados*'],
            'route'          => 'v2.associados.cms.index',
            'subModulesLevel1' => [
                [
                    'title'    => 'Todos os Associados',
                    'icon'     => 'bi bi-list-ul',
                    'route'    => 'v2.associados.cms.index',
                    'position' => 1,
                ],
                [
                    'title'    => 'Novo Associado',
                    'icon'     => 'bi bi-plus-circle',
                    'route'    => 'v2.associados.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 CreateV2AssociadosTable extends Migration
{
    public function up()
    {
        DB::beginTransaction();
        DB::statement("SET lock_timeout = '3s';");

        try {
            Schema::create('v2_associados', function (Blueprint $table) {
                $table->uuid('id')->primary();
                $table->string('name', 255);
                $table->string('slug', 255)->unique();
                $table->text('description')->nullable();
                $table->string('logo', 255)->nullable();
                $table->string('url', 255)->nullable();
                $table->string('linkedin_url', 255)->nullable();
                $table->string('youtube_url', 255)->nullable();
                $table->string('instagram_url', 255)->nullable();
                $table->string('facebook_url', 255)->nullable();
                $table->string('x_url', 255)->nullable();
                $table->date('associate_since')->nullable();
                $table->integer('sort_order')->default(0);
                $table->boolean('is_active')->default(true);
                $table->string('created_by', 36)->nullable();
                $table->timestamps();
                $table->softDeletes();
                $table->index('is_active', 'idx_v2_associados_active');
                $table->index('sort_order', 'idx_v2_associados_sort');
            });

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

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

### c) Model (Associado.php)

```php
<?php

namespace V2\Modules\Publicacoes\Associados\Models;

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

class Associado extends Model
{
    use SoftDeletes;

    protected $table = 'v2_associados';

    public $incrementing = false;

    protected $keyType = 'string';

    protected $fillable = [
        'name', 'slug', 'description', 'logo', 'url',
        'linkedin_url', 'youtube_url', 'instagram_url',
        'facebook_url', 'x_url', 'associate_since',
        'sort_order', 'is_active',
    ];

    protected $casts = [
        'is_active'      => 'boolean',
        'associate_since' => 'date',
        '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();
            }
            if (empty($model->slug) && !empty($model->name)) {
                $model->slug = str_slug($model->name);
            }
        });

        static::saving(function ($model) {
            // Garantir unicidade do slug
            $baseSlug = $model->slug ?: str_slug($model->name);
            $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,
                        'Associados',
                        'associado',
                        $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('associado', $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 scopeOrdered($query)
    {
        return $query->orderBy('sort_order')->orderBy('name');
    }
}
```

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

```php
<?php

namespace V2\Modules\Publicacoes\Associados\Controllers;

use V2\Core\Controllers\BaseController;
use Illuminate\Http\Request;
use V2\Modules\Publicacoes\Associados\Models\Associado;
use V2\Services\ActivityLogger\ActivityLoggerService;
use V2\Services\SEO\Services\SEOService;
use Sentinel;
use Str;

class AssociadosCmsController 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 = Associado::query();

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

        if ($request->has('filter_active')) {
            $query->where('is_active', $request->input('filter_active') === '1');
        }

        $items = $query->orderBy('sort_order')->orderBy('name')->paginate(15);

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

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

    public function store(Request $request)
    {
        $data = $request->validate([
            'name'          => 'required|string|max:255',
            'slug'          => 'nullable|string|max:255',
            'description'   => 'nullable|string',
            'logo'          => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048',
            'url'           => 'nullable|url|max:255',
            'linkedin_url'  => 'nullable|url|max:255',
            'youtube_url'   => 'nullable|url|max:255',
            'instagram_url' => 'nullable|url|max:255',
            'facebook_url'  => 'nullable|url|max:255',
            'x_url'         => 'nullable|url|max:255',
            'associate_since' => 'nullable|date',
            'sort_order'    => 'nullable|integer|min:0',
            'is_active'     => 'nullable|boolean',
        ]);

        $model = new Associado();
        $model->id = v2_uuid();
        $model->name = $data['name'];
        $model->slug = !empty($data['slug']) ? $data['slug'] : Str::slug($data['name']);
        $model->description = $data['description'] ?? null;
        $model->url = $data['url'] ?? null;
        $model->linkedin_url = $data['linkedin_url'] ?? null;
        $model->youtube_url = $data['youtube_url'] ?? null;
        $model->instagram_url = $data['instagram_url'] ?? null;
        $model->facebook_url = $data['facebook_url'] ?? null;
        $model->x_url = $data['x_url'] ?? null;
        $model->associate_since = $data['associate_since'] ?? null;
        $model->sort_order = $data['sort_order'] ?? 0;
        $model->is_active = $data['is_active'] ?? true;
        $model->author_id = Sentinel::getUser()->id ?? null;

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

        $model->save();

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

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

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

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

        $data = $request->validate([
            'name'          => 'required|string|max:255',
            'slug'          => 'nullable|string|max:255|unique:v2_associados,slug,' . $id . ',id',
            'description'   => 'nullable|string',
            'logo'          => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048',
            'url'           => 'nullable|url|max:255',
            'linkedin_url'  => 'nullable|url|max:255',
            'youtube_url'   => 'nullable|url|max:255',
            'instagram_url' => 'nullable|url|max:255',
            'facebook_url'  => 'nullable|url|max:255',
            'x_url'         => 'nullable|url|max:255',
            'associate_since' => 'nullable|date',
            'sort_order'    => 'nullable|integer|min:0',
            'is_active'     => 'nullable|boolean',
        ]);

        $model->name = $data['name'];
        if (!empty($data['slug'])) {
            $model->slug = $data['slug'];
        }
        $model->description = $data['description'] ?? null;
        $model->url = $data['url'] ?? null;
        $model->linkedin_url = $data['linkedin_url'] ?? null;
        $model->youtube_url = $data['youtube_url'] ?? null;
        $model->instagram_url = $data['instagram_url'] ?? null;
        $model->facebook_url = $data['facebook_url'] ?? null;
        $model->x_url = $data['x_url'] ?? null;
        $model->associate_since = $data['associate_since'] ?? null;
        $model->sort_order = $data['sort_order'] ?? 0;
        $model->is_active = $data['is_active'] ?? true;

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

        $model->save();

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

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

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

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

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

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

```php
<?php

namespace V2\Modules\Publicacoes\Associados\Controllers;

use V2\Core\Controllers\BaseController;
use V2\Modules\Publicacoes\Associados\Models\Associado;
use V2\Services\SEO\Services\SEOService;

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

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

    public function index()
    {
        $items = Associado::active()->ordered()->paginate(12);

        $this->seo->setTitle('Associados — ' . config('app.name'));
        $this->seo->setDescription('Empresas associadas à SUCESU Nacional.');

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

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

```php
<?php

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

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

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

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

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

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

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

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

```php
<?php

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

    Route::get('/associados', [
        'as'   => 'v2.associados.portal.index',
        'uses' => 'AssociadosPortalController@index',
    ]);
});
```

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

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

@section('title', 'Associados')
@section('header-title', 'Associados')
@section('header-description', 'Gerenciamento de empresas associadas')

@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">
                <input type="text" name="search" class="form-control form-control-sm"
                       placeholder="Buscar..." value="{{ request('search') }}">
                <button type="submit" class="btn btn-sm btn-outline-secondary">
                    <i class="bi bi-search"></i>
                </button>
            </form>
            @v2can('Associados', 'write')
                <a href="{{ route('v2.associados.cms.create') }}" class="btn btn-sm btn-primary">
                    <i class="bi bi-plus-circle"></i> Novo
                </a>
            @endv2can
        </div>
        <div class="card-body">
            @if(count($items) > 0)
                <table class="table table-hover">
                    <thead>
                        <tr>
                            <th>Logo</th>
                            <th>Nome</th>
                            <th>Ordem</th>
                            <th>Ativo</th>
                            <th>Ações</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach($items as $item)
                        <tr>
                            <td>
                                @if($item->logo)
                                    <img src="{{ Storage::url($item->logo) }}" alt="{{ $item->name }}"
                                         style="max-height: 40px;">
                                @endif
                            </td>
                            <td>{{ $item->name }}</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.associados.cms.edit', ['id' => $item->id]) }}"
                                   class="btn btn-sm btn-outline-primary">
                                    <i class="bi bi-pencil"></i>
                                </a>
                                <form action="{{ route('v2.associados.cms.destroy', ['id' => $item->id]) }}"
                                      method="POST" class="d-inline"
                                      onsubmit="return confirm('Arquivar este associado?');">
                                    {{ 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 associado 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="associados-section section-padding fix">
        <div class="container">
            <div class="row g-4">
                @foreach($items as $item)
                <div class="col-xl-6 col-lg-6 col-md-6 wow fadeInUp">
                    <div class="associado-card">
                        <div class="row align-items-center">
                            <div class="col-md-4 text-center">
                                @if($item->logo)
                                    <img src="{{ Storage::url($item->logo) }}" alt="{{ $item->name }}"
                                         class="img-fluid associado-logo">
                                @endif
                            </div>
                            <div class="col-md-8">
                                <h3>{{ $item->name }}</h3>
                                <p>{{ Str::limit($item->description, 200) }}</p>
                                @if($item->associate_since)
                                    <small class="text-muted">Associado desde {{ $item->associate_since->format('m/Y') }}</small>
                                @endif
                                <div class="social-links mt-2">
                                    @if($item->url)
                                        <a href="{{ $item->url }}" target="_blank" class="me-2" title="Site">
                                            <i class="bi bi-globe"></i>
                                        </a>
                                    @endif
                                    @if($item->linkedin_url)
                                        <a href="{{ $item->linkedin_url }}" target="_blank" class="me-2" title="LinkedIn">
                                            <i class="bi bi-linkedin"></i>
                                        </a>
                                    @endif
                                    @if($item->instagram_url)
                                        <a href="{{ $item->instagram_url }}" target="_blank" class="me-2" title="Instagram">
                                            <i class="bi bi-instagram"></i>
                                        </a>
                                    @endif
                                    @if($item->facebook_url)
                                        <a href="{{ $item->facebook_url }}" target="_blank" class="me-2" title="Facebook">
                                            <i class="bi bi-facebook"></i>
                                        </a>
                                    @endif
                                    @if($item->youtube_url)
                                        <a href="{{ $item->youtube_url }}" target="_blank" class="me-2" title="YouTube">
                                            <i class="bi bi-youtube"></i>
                                        </a>
                                    @endif
                                    @if($item->x_url)
                                        <a href="{{ $item->x_url }}" target="_blank" class="me-2" title="Twitter/X">
                                            <i class="bi bi-twitter-x"></i>
                                        </a>
                                    @endif
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                @endforeach
            </div>
            {{ $items->links() }}
        </div>
    </section>
@endsection
```

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

- **ActivityLoggerService**: Injetado no `AssociadosCmsController` para auditoria em create/update/destroy.
- **SEOService**: Injetado em ambos os controllers para metadados de página.
- **Storage**: Upload de logos para `public/upload/associados/`. Usar `Storage::url($model->logo)` na exibição pública.
- **Permissões**: `@v2can('Associados', 'write')` nas views CMS para controle de acesso a ações de escrita.
- **Slug**: Gerado automaticamente via `Str::slug($data['name'])` no boot do model com uniqueness enforcement.
