# Módulo: Regionais

## 1. O que é o módulo

Gerencia as regionais da SUCESU (rota `/cms/institucional/regionais`, portal `/regionais`). O REFERENCE (`index.html` seção "Regionais SUCESU") exibe um mapa do Brasil com as regionais: BA, CE, MG, MS, MT, PA, PB, PE, PR, RS, SP. Cada regional contém nome, sigla, descrição, site e contato. Aparece também no footer (link "Regionais").

Permite ao CMS:
- CRUD de regionais
- Upload de logotipo/bandeira
- Ordenação por posição
- Ativar/desativar regionais

## 2. Estrutura de dados

### `v2_chapters` — Regionais
| Campo | Tipo | Descrição |
|-------|------|-----------|
| id | uuid (string, PK) | UUID primário |
| name | string(255) | Nome completo (ex.: "SUCESU Bahia") |
| slug | string(100) | Slug único para URL |
| acronym | string(10) | Sigla (ex.: BA, SP) |
| description | text | Descrição da regional |
| site_url | string(255) | URL do site da regional |
| email | string(255) | E-mail de contato |
| phone | string(50) | Telefone |
| address | text | Endereço |
| city | string(255) | Cidade-sede |
| state | string(2) | UF (ex.: BA, SP) |
| logo | string(255) | Caminho do logotipo |
| map_coordinates | string(255) | Coordenadas para o mapa (ex.: "-12.97,-38.50") |
| 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 |

## 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 logotipos
- **Módulo BasicPage** — template de página CMS (padrão)

## 4. Tabelas necessárias

```sql
CREATE TABLE v2_chapters (
    id               VARCHAR(36) PRIMARY KEY,
    name             VARCHAR(255) NOT NULL,
    slug             VARCHAR(100) NOT NULL UNIQUE,
    acronym          VARCHAR(10) NOT NULL,
    description      TEXT,
    site_url         VARCHAR(255),
    email            VARCHAR(255),
    phone            VARCHAR(50),
    address          TEXT,
    city             VARCHAR(255),
    state            VARCHAR(2) NOT NULL,
    logo             VARCHAR(255),
    map_coordinates  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/Regionais/
├── Controllers/
│   ├── Cms/
│   │   └── ChapterController.php
│   └── Portal/
│       └── RegionalController.php
├── Models/
│   └── Chapter.php
├── Requests/
│   ├── Cms/
│   │   ├── StoreChapterRequest.php
│   │   └── UpdateChapterRequest.php
├── Services/
│   └── RegionalService.php
├── Views/
│   ├── cms/
│   │   ├── index.blade.php
│   │   ├── create.blade.php
│   │   └── edit.blade.php
│   └── portal/
│       └── index.blade.php
├── Migrations/
│   └── 2025_01_01_000003_create_v2_chapters_table.php
├── Routes/
│   ├── cms.php
│   └── portal.php
└── ModuleProvider.php
```

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

1. **Migration `v2_chapters`**: criar tabela com unique slug, safe-rollback. Seed com todas as regionais (BA, CE, MG, MS, MT, PA, PB, PE, PR, RS, SP) usando dados do REFERENCE.

2. **Model `Chapter`**: UUID PK, soft deletes, fillable, casts, método `getStateAttribute()` para sigla, scope `active()`.

3. **Controller CMS (`ChapterController`)**: CRUD completo com upload de logo, busca/filtro por UF, paginação.

4. **Controller Portal (`RegionalController`)**: método `index()` que retorna `Chapter::where('is_active', true)->orderBy('sort_order')->get()` para a view.

5. **Views CMS**: Bootstrap 5, listagem em tabela, formulário com upload de imagem, flash messages.

6. **View Portal**: Renderiza o mapa do Brasil com as regionais (SVG ou lista), cards com nome, sigla, descrição, site, contato. Conforme REFERENCE (`index.html`): "BA, CE, MG, MS, MT, PA, PB, PE, PR, RS, SP e mais".

7. **ModuleProvider**: implementa `ModuleProviderInterface`, sidebar com link para listagem.

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

9. **Dica**: o mapa interativo pode ser implementado com um SVG do Brasil onde cada estado é um link; as coordenadas (`map_coordinates`) servem para posicionar pins ou tooltips.

## 7. Detalhamento de Implementação V2

### a) ModuleProvider.php

```php
<?php

namespace V2\Modules\Institucional\Regionais;

use V2\Core\Contracts\ModuleProviderInterface;

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

    public function getConfig(): array
    {
        return [
            'title'          => 'Regionais',
            'description'    => 'Gerenciamento das regionais SUCESU.',
            'icon'           => 'bi bi-geo-alt',
            'position'       => 6,
            'active_pattern' => ['cms/institucional/regionais*'],
            'route'          => 'v2.regionais.cms.index',

            'subModulesLevel1' => [
                [
                    'title'    => 'Todas as Regionais',
                    'icon'     => 'bi bi-list-ul',
                    'route'    => 'v2.regionais.cms.index',
                    'position' => 1,
                ],
                [
                    'title'    => 'Nova Regional',
                    'icon'     => 'bi bi-plus-circle',
                    'route'    => 'v2.regionais.cms.create',
                    'position' => 2,
                ],
            ],
        ];
    }
}
```

### b) Migration

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

        try {
            Schema::create('v2_chapters', function (Blueprint $table) {
                $table->uuid('id')->primary();
                $table->string('name', 255);
                $table->string('slug', 100)->unique();
                $table->string('acronym', 10);
                $table->text('description')->nullable();
                $table->string('site_url', 255)->nullable();
                $table->string('email', 255)->nullable();
                $table->string('phone', 50)->nullable();
                $table->text('address')->nullable();
                $table->string('city', 255)->nullable();
                $table->string('state', 2);
                $table->string('logo', 255)->nullable();
                $table->string('map_coordinates', 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->index('slug', 'idx_v2_chapters_slug');
                $table->index('is_active', 'idx_v2_chapters_active');
                $table->index('state', 'idx_v2_chapters_state');
                $table->index('sort_order', 'idx_v2_chapters_order');
            });

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

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

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

### c) Model

```php
<?php

namespace V2\Modules\Institucional\Regionais\Models;

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

class Chapter extends Model
{
    use SoftDeletes;

    protected $table = 'v2_chapters';

    public $incrementing = false;

    protected $keyType = 'string';

    protected $fillable = [
        'id', 'name', 'slug', 'acronym', 'description', 'site_url',
        'email', 'phone', 'address', 'city', 'state', 'logo',
        'map_coordinates', '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();
            }
            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,
                        'Regionais',
                        'chapter',
                        $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('chapter', $model->id);
            } catch (\Exception $e) {
                \Log::error('[Module] Falha ao remover da busca: ' . $e->getMessage());
            }
        });
    }

    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');
    }

    public function scopeByState($query, $state)
    {
        return $query->where('state', $state);
    }
}
```

### d) CMS Controller

```php
<?php

namespace V2\Modules\Institucional\Regionais\Controllers;

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

class ChapterController 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 = Chapter::query();

        if ($request->has('state') && !empty($request->input('state'))) {
            $query->where('state', $request->input('state'));
        }

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

        $items = $query->orderBy('sort_order')->paginate(15);
        $states = Chapter::select('state')->distinct()->orderBy('state')->pluck('state');

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

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

    public function store(Request $request)
    {
        $this->validate($request, [
            'name'            => 'required|string|max:255',
            'slug'            => 'nullable|string|max:100|unique:v2_chapters,slug',
            'acronym'         => 'required|string|max:10',
            'description'     => 'nullable|string',
            'site_url'        => 'nullable|url|max:255',
            'email'           => 'nullable|email|max:255',
            'phone'           => 'nullable|string|max:50',
            'address'         => 'nullable|string',
            'city'            => 'nullable|string|max:255',
            'state'           => 'required|string|size:2',
            'logo'            => 'nullable|image|mimes:jpeg,png,webp,svg|max:2048',
            'map_coordinates' => 'nullable|string|max:255',
            'sort_order'      => 'integer|min:0',
            'is_active'       => 'boolean',
        ]);

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

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

        $model->save();

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

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

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

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

        $this->validate($request, [
            'name'            => 'required|string|max:255',
            'slug'            => 'nullable|string|max:100|unique:v2_chapters,slug,' . $id . ',id',
            'acronym'         => 'required|string|max:10',
            'description'     => 'nullable|string',
            'site_url'        => 'nullable|url|max:255',
            'email'           => 'nullable|email|max:255',
            'phone'           => 'nullable|string|max:50',
            'address'         => 'nullable|string',
            'city'            => 'nullable|string|max:255',
            'state'           => 'required|string|size:2',
            'logo'            => 'nullable|image|mimes:jpeg,png,webp,svg|max:2048',
            'map_coordinates' => 'nullable|string|max:255',
            'sort_order'      => 'integer|min:0',
            'is_active'       => 'boolean',
        ]);

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

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

        $model->save();

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

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

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

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

        $model->delete();

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

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

### e) Portal Controller

```php
<?php

namespace V2\Modules\Institucional\Regionais\Controllers;

use V2\Core\Controllers\BaseController;
use V2\Modules\Institucional\Regionais\Models\Chapter;
use V2\Services\SEO\Services\SEOService;

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

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

    public function index()
    {
        $chapters = Chapter::active()->ordered()->get();

        $this->seo->setTitle('Regionais — ' . config('app.name'));
        $this->seo->setDescription('Conheça as regionais da SUCESU em todo o Brasil.');

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

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

```php
<?php

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

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

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

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

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

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

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

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

```php
<?php

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

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

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

@section('title', 'Regionais')
@section('header-title', 'Regionais')
@section('header-description', 'Gerenciamento das regionais SUCESU.')

@section('content')
    <div class="d-flex justify-content-between align-items-center mb-3">
        <form method="GET" class="d-flex gap-2">
            <select name="state" class="form-select" onchange="this.form.submit()">
                <option value="">Todos os estados</option>
                @foreach($states as $s)
                    <option value="{{ $s }}" {{ request('state') == $s ? 'selected' : '' }}>{{ $s }}</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('Regionais', 'write')
            <a href="{{ route('v2.regionais.cms.create') }}" class="btn btn-primary">
                <i class="bi bi-plus-circle"></i> Nova Regional
            </a>
        @endv2can
    </div>

    <div class="card">
        <div class="card-body">
            @if(count($items) > 0)
                <table class="table">
                    <thead>
                        <tr>
                            <th>Logo</th>
                            <th>Nome</th>
                            <th>Sigla</th>
                            <th>UF</th>
                            <th>Cidade</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="{{ asset('storage/' . $item->logo) }}" width="30" height="30">
                                @else
                                    <i class="bi bi-building fs-5"></i>
                                @endif
                            </td>
                            <td>{{ $item->name }}</td>
                            <td>{{ $item->acronym }}</td>
                            <td>{{ $item->state }}</td>
                            <td>{{ $item->city }}</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.regionais.cms.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">Nenhuma regional encontrada.</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="chapters-section section-padding fix">
        <div class="container">
            <div class="text-center mb-5">
                <h2>Regionais SUCESU</h2>
                <p class="text-muted">Conheça nossas regionais espalhadas por todo o Brasil.</p>
            </div>
            <div class="row g-4">
                @foreach($chapters as $chapter)
                <div class="col-xl-3 col-lg-4 col-md-6 wow fadeInUp">
                    <div class="chapter-card">
                        @if($chapter->logo)
                            <img src="{{ asset('storage/' . $chapter->logo) }}" alt="{{ $chapter->name }}" class="mb-3" height="60">
                        @endif
                        <h4>{{ $chapter->name }}</h4>
                        <span class="badge bg-primary mb-2">{{ $chapter->acronym }} — {{ $chapter->state }}</span>
                        <p class="small">{{ Str::limit($chapter->description, 100) }}</p>
                        @if($chapter->site_url)
                            <a href="{{ $chapter->site_url }}" target="_blank" class="btn btn-sm btn-outline-primary">
                                <i class="bi bi-globe"></i> Site
                            </a>
                        @endif
                        @if($chapter->email)
                            <a href="mailto:{{ $chapter->email }}" class="btn btn-sm btn-outline-secondary">
                                <i class="bi bi-envelope"></i> Contato
                            </a>
                        @endif
                    </div>
                </div>
                @endforeach
            </div>
        </div>
    </section>
@endsection
```

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

| Serviço | Como usar |
|---------|-----------|
| **ActivityLoggerService** | Injetado via construtor no ChapterController. Log em create, update, destroy. |
| **SEOService** | Injetado no RegionalController. `setTitle()`, `setDescription()` na página pública. |
| **Sentinel** | `Sentinel::getUser()` para `created_by` e activity log. |
| **Storage (public)** | Upload de logotipos para `regionais/`, 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 `Regionais`. |
| **Super Admin** | Email em `config('v2.support.email')` ou role `super-admin` bypassa tudo. |
